├── .rustfmt.toml ├── libzmq-book ├── .gitignore ├── src │ ├── examples │ │ ├── README.md │ │ ├── basic_req_rep.md │ │ ├── reliable_req_rep.md │ │ └── secure_req_rep.md │ ├── advanced │ │ ├── README.md │ │ └── protocols.md │ ├── basics │ │ ├── README.md │ │ ├── socket.md │ │ ├── patterns.md │ │ └── methods.md │ ├── SUMMARY.md │ ├── about.md │ └── glossary.md └── book.toml ├── libzmq-sys ├── .rustfmt.toml ├── .gitignore ├── src │ ├── lib.rs │ └── errno.rs ├── Cargo.toml ├── LICENSE ├── LICENSE-MIT ├── README.md ├── build.rs └── LICENSE-APACHE ├── libzmq ├── .rustfmt.toml ├── .gitignore ├── examples │ ├── gen_curve_cert.rs │ ├── secure_req_rep.yml │ ├── basic_req_rep.rs │ ├── reliable_req_rep.rs │ └── secure_req_rep.rs ├── benches │ ├── bench_main.rs │ ├── msg.rs │ ├── curve.rs │ └── socket.rs ├── README.md ├── LICENSE-MIT ├── Cargo.toml ├── src │ ├── lib.rs │ ├── socket │ │ ├── mod.rs │ │ ├── scatter.rs │ │ ├── gather.rs │ │ ├── client.rs │ │ └── radio.rs │ ├── utils.rs │ ├── old.rs │ ├── auth │ │ ├── mod.rs │ │ └── mechanism.rs │ ├── core │ │ ├── heartbeat.rs │ │ ├── recv.rs │ │ ├── send.rs │ │ └── sockopt.rs │ ├── error.rs │ └── group.rs └── LICENSE-APACHE ├── .gitignore ├── Cargo.toml ├── .gitmodules ├── .github └── ISSUE_TEMPLATE │ ├── question.md │ ├── feedback.md │ ├── feature_request.md │ └── bug_report.md ├── .travis.yml ├── CONTRIBUTING.md ├── LICENSE-MIT ├── FAQ.md ├── README.md └── LICENSE-APACHE /.rustfmt.toml: -------------------------------------------------------------------------------- 1 | max_width = 80 2 | -------------------------------------------------------------------------------- /libzmq-book/.gitignore: -------------------------------------------------------------------------------- 1 | book 2 | -------------------------------------------------------------------------------- /libzmq-sys/.rustfmt.toml: -------------------------------------------------------------------------------- 1 | max_width = 80 2 | -------------------------------------------------------------------------------- /libzmq/.rustfmt.toml: -------------------------------------------------------------------------------- 1 | max_width = 80 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | Cargo.lock 4 | -------------------------------------------------------------------------------- /libzmq-sys/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | Cargo.lock 4 | -------------------------------------------------------------------------------- /libzmq/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | Cargo.lock 4 | perf.data* 5 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "libzmq", 4 | "libzmq-sys", 5 | ] 6 | -------------------------------------------------------------------------------- /libzmq-book/src/examples/README.md: -------------------------------------------------------------------------------- 1 | # Examples 2 | 3 | Here are a few examples usage of varying complexity. 4 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "libzmq-sys/vendor"] 2 | path = libzmq-sys/vendor 3 | url = https://github.com/zeromq/libzmq 4 | -------------------------------------------------------------------------------- /libzmq-book/src/advanced/README.md: -------------------------------------------------------------------------------- 1 | # Advanced 2 | 3 | Now that the basic stuff is taken care off, lets dig deeper. 4 | -------------------------------------------------------------------------------- /libzmq-book/book.toml: -------------------------------------------------------------------------------- 1 | [book] 2 | authors = ["jean-airoldie"] 3 | multilingual = false 4 | src = "src" 5 | title = "ZeroMQ Guide" 6 | -------------------------------------------------------------------------------- /libzmq-book/src/basics/README.md: -------------------------------------------------------------------------------- 1 | # Basics 2 | 3 | This is the minimal set of concepts required to get a decent grasp of `libzmq`. 4 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/question.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Question 3 | about: This is the right place to ask. 4 | title: '' 5 | labels: question 6 | assignees: '' 7 | 8 | --- 9 | 10 | 11 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feedback.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feedback 3 | about: Provide feedback or a critique of the API. 4 | title: '' 5 | labels: feedback 6 | assignees: '' 7 | 8 | --- 9 | 10 | 11 | -------------------------------------------------------------------------------- /libzmq/examples/gen_curve_cert.rs: -------------------------------------------------------------------------------- 1 | use libzmq::auth::CurveCert; 2 | 3 | // Used to generate `CURVE` certificates. 4 | fn main() { 5 | let cert = CurveCert::new_unique(); 6 | println!("public: \"{}\"", cert.public().as_str()); 7 | println!("secret: \"{}\"", cert.secret().as_str()); 8 | } 9 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project. 4 | title: '' 5 | labels: feature request 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the feature you'd like** 11 | A informal and concise description the feature. 12 | 13 | **Describe the use case of such feature** 14 | Add a minimal code example if possible. 15 | -------------------------------------------------------------------------------- /libzmq/benches/bench_main.rs: -------------------------------------------------------------------------------- 1 | mod curve; 2 | mod msg; 3 | mod socket; 4 | 5 | use criterion::{criterion_group, criterion_main}; 6 | 7 | use std::time::Duration; 8 | 9 | const MSG_AMOUNT: usize = 50; 10 | const MSG_SIZE: usize = 500_000; // 0.5MB messages 11 | const HWM: i32 = 10; 12 | const SAMPLE_SIZE: usize = 10; 13 | const MEASUREMENT_TIME: Duration = Duration::from_secs(30); 14 | 15 | criterion_group!(benches, socket::bench, curve::bench, msg::bench); 16 | criterion_main!(benches); 17 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Something is wrong. 4 | title: '' 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Give a minimal reproduction example of the bug, if applicable. 15 | 16 | **Expected behavior** 17 | A clear and concise description of what you expected to happen. 18 | 19 | **Platform:** 20 | - OS: [e.g. fedora29] 21 | - API Version 22 | -------------------------------------------------------------------------------- /libzmq-book/src/examples/basic_req_rep.md: -------------------------------------------------------------------------------- 1 | # Basic Request Reply 2 | 3 | This is as simple as it gets. We have a [Server] that does some request-reply 4 | work in a dedicated thread. We have a [Client] that sends a "ping" and gets 5 | a "pong" back. There is no attempt at security and no attempt at error handling. 6 | For a `INPROC` server, that might be enough. 7 | 8 | {{#playpen ../../../libzmq/examples/basic_req_rep.rs}} 9 | 10 | [Server]: https://docs.rs/libzmq/0.1/libzmq/struct.Server.html 11 | [Client]: https://docs.rs/libzmq/0.1/libzmq/struct.Client.html 12 | -------------------------------------------------------------------------------- /libzmq-book/src/SUMMARY.md: -------------------------------------------------------------------------------- 1 | # Summary 2 | 3 | [About](./about.md) 4 | 5 | * [Basics](./basics/README.md) 6 | * [Socket](./basics/socket.md) 7 | * [Methods](./basics/methods.md) 8 | * [Patterns](./basics/patterns.md) 9 | * [Advanced](./advanced/README.md) 10 | * [Custom Protocols](./advanced/protocols.md) 11 | * [Examples](./examples/README.md) 12 | * [Basic Request Reply](./examples/basic_req_rep.md) 13 | * [Reliable Request Reply](./examples/reliable_req_rep.md) 14 | * [Secure Request Reply](./examples/secure_req_rep.md) 15 | 16 | [Glossary](./glossary.md) 17 | -------------------------------------------------------------------------------- /libzmq-sys/src/lib.rs: -------------------------------------------------------------------------------- 1 | // Ignore generated code 2 | #![allow(clippy::all)] 3 | #![doc(html_root_url = "https://docs.rs/libzmq-sys/0.1")] 4 | 5 | //! libzmq-sys - Raw cFFI bindings to [libzmq](https://github.com/zeromq/libzmq). 6 | 7 | #![allow(non_upper_case_globals)] 8 | #![allow(non_camel_case_types)] 9 | #![allow(non_snake_case)] 10 | 11 | mod bindings; 12 | pub mod errno; 13 | 14 | pub use bindings::*; 15 | 16 | #[cfg(test)] 17 | mod test { 18 | #[test] 19 | fn test_readme_deps() { 20 | version_sync::assert_markdown_deps_updated!("README.md"); 21 | } 22 | 23 | #[test] 24 | fn test_html_root_url() { 25 | version_sync::assert_html_root_url_updated!("src/lib.rs"); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /libzmq/README.md: -------------------------------------------------------------------------------- 1 | [![](https://img.shields.io/crates/v/libzmq.svg)][crates-io] 2 | [![](https://docs.rs/libzmq/badge.svg)][api-docs] 3 | [![Apache 2.0 licensed](https://img.shields.io/badge/license-Apache2.0-blue.svg)](./LICENSE-APACHE) 4 | [![MIT licensed](https://img.shields.io/badge/license-MIT-blue.svg)](./LICENSE-MIT) 5 | 6 | # libzmq 7 | This is the core crate of the project which hold all the logic. 8 | 9 | 10 | # License 11 | This project is licensed under either of 12 | 13 | * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or 14 | http://www.apache.org/licenses/LICENSE-2.0) 15 | * MIT license ([LICENSE-MIT](LICENSE-MIT) or 16 | http://opensource.org/licenses/MIT) 17 | 18 | at your option. 19 | 20 | [crates-io]: https://crates.io/crates/libzmq 21 | [api-docs]: https://docs.rs/libzmq 22 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: rust 2 | rust: 3 | - stable 4 | compiler: 5 | - clang 6 | env: 7 | - RUST_BACKTRACE=1 RUST_LOG=error 8 | cache: 9 | directories: 10 | - /home/travis/.cargo 11 | before_cache: 12 | - cargo cache -r registry 13 | before_script: 14 | - rustup component add rustfmt clippy 15 | - (test -x $HOME/.cargo/bin/cargo-cache || cargo install cargo-cache) 16 | - (test -x $HOME/.cargo/bin/mdbook || cargo install --vers "^0.3" mdbook) 17 | script: 18 | - cargo test --all-targets --all-features --no-run 19 | - cargo test --all --all-features 20 | - cargo test --examples --all-features 21 | - cargo fmt --all -- --check 22 | - cargo clippy --all-targets -- -D warnings 23 | - mdbook build libzmq-book 24 | deploy: 25 | provider: pages 26 | skip-cleanup: true 27 | github-token: $GITHUB_TOKEN 28 | local-dir: libzmq-book/book/ 29 | keep-history: false 30 | on: 31 | branch: master 32 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | To contribute to this project first fork the repository and then create PR against the master branch. 4 | 5 | Explain the problem your PR solves and solve only for that problem. 6 | If you find more problems while writing your PR, open another PR. 7 | 8 | Your PR will be squashed so please write proper commit messages that document the problem that you fix. 9 | 10 | Please avoid sending a pull request with recursive merge nodes, as they are impossible to fix once merged. 11 | Please rebase your branch on master instead of merging it. 12 | ``` 13 | git remote add upstream git@github.com:jean-airoldie/libzmq-rs.git 14 | git fetch upstream 15 | git rebase upstream/master 16 | git push -f 17 | ``` 18 | 19 | If you extend the public API, make sure to document it as well as test it. 20 | It is recommanded to write your test as a documentation test, this way you both test and document 21 | at the same time. 22 | -------------------------------------------------------------------------------- /libzmq-book/src/basics/socket.md: -------------------------------------------------------------------------------- 1 | # Socket 2 | 3 | The concept of a `socket` in `ZeroMQ` is completely novel. A `ZeroMQ` socket 4 | differs from a traditional `TCP` socket in the following ways (but not limited to): 5 | 6 | * A socket sends and receives atomic messages; messages are guaranteed to 7 | either be transmitted in their entirety, or not transmitted at all. 8 | * A socket send and receive messages asynchronously. 9 | * A socket can transmit messages over many supported transports, including `TCP`. 10 | * Incoming and outgoing messages can be queued and transmitted asynchronously 11 | by a background I/O thread. 12 | * A socket can be connected to zero or more peers at any time. 13 | * A socket can be bound to zero or more endpoints at any time. Each bound 14 | endpoint can listen to zero or more peers. 15 | * Peer reconnection and disconnection is handled in the background. 16 | * Support for many authentication and encryption strategies via [Mechanism]. 17 | 18 | [Mechanism]: https://docs.rs/libzmq/0.1/libzmq/auth/enum.Mechanism.html 19 | -------------------------------------------------------------------------------- /libzmq-sys/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "libzmq-sys" 3 | version = "0.1.8+4.3.2" 4 | authors = ["jean-airoldie "] 5 | edition = "2018" 6 | license = "MIT OR Apache-2.0" 7 | links = "zmq" 8 | build = "build.rs" 9 | description = """ 10 | Raw CFFI bindings for libzmq 11 | """ 12 | repository = "https://github.com/jean-airoldie/libzmq-rs" 13 | readme = "README.md" 14 | keywords = ["libzmq", "zmq", "zeromq", "bindings", "cffi"] 15 | categories = ["external-ffi-bindings"] 16 | 17 | [badges] 18 | maintenance = { status = "passively-maintained" } 19 | 20 | [features] 21 | default = ['curve'] 22 | curve = [] 23 | # Renew the pre-generated `libzmq` bindings. 24 | renew-bindings = ['bindgen'] 25 | libsodium = ['libsodium-sys'] 26 | 27 | [dependencies] 28 | libc = "0.2" 29 | libsodium-sys = { version = "0.2.3", optional = true } 30 | 31 | [dev-dependencies] 32 | version-sync = "0.9" 33 | 34 | [build-dependencies] 35 | cmake = "0.1" 36 | bindgen = { version = "0.53.2", optional = true } 37 | # libzmq 4.3.2 38 | zeromq-src = "0.1.10" 39 | -------------------------------------------------------------------------------- /libzmq-book/src/examples/reliable_req_rep.md: -------------------------------------------------------------------------------- 1 | # Reliable Request Reply 2 | 3 | This is a basic example when using the `TCP` transport adapting the code 4 | from the previous `Basic Request Reply` example. 5 | 6 | Note that this example does not make any attempt at security. 7 | 8 | Since `TCP` is connection oriented transport, we have to take in account that 9 | the connection might fail at any time. We use heartbeating to detect failure 10 | but also `send` and `recv` timeouts to prevent blocking forever. 11 | 12 | In this example, the server is protected against failures since it will drop 13 | messages if it is unable to route them before `send_timeout` expires (`WouldBlock`), 14 | or it detects that the peer disconnected via the heartbeats (`HostUnreachable`). 15 | 16 | The client in this case will simply fail if it unable to send a request before the 17 | `send_timeout` or unable to receive a reply before the `recv_timeout` (`WouldBlock`). 18 | The client might choose to retry later or connect to another server etc. 19 | 20 | {{#playpen ../../../libzmq/examples/reliable_req_rep.rs}} 21 | -------------------------------------------------------------------------------- /libzmq-book/src/about.md: -------------------------------------------------------------------------------- 1 | # About 2 | 3 | ## This is a WIP guide 4 | 5 | I believe that `ZeroMQ` is a diamond in the rough that is still a in the early 6 | adoption stage despite being a mature project. In my opinon, the lack of traction 7 | for the library is due to the community rather than the technology itself. 8 | 9 | `ZeroMQ` suffers from a significant learning curve due to its foreign concepts. 10 | It requires the programmer to think differently about messaging. When this is 11 | combined with a lacking documentation, it results in very significant time 12 | requirement to properly understand how the library works and how it can be used. 13 | 14 | I want this guide to reduce this time requirement by explaining the key concepts of 15 | `ZeroMQ`, give real world usage examples as well as a general vision of how it 16 | could be use. To do so, we will use [libzmq-rs] which is a library aimed at 17 | making `ZeroMQ` dead simple to use. 18 | 19 | ## Reading Tips 20 | * There is a search-bar friendly glossary at the end of the guide. 21 | 22 | [libzmq-rs]: https://github.com/jean-airoldie/libzmq-rs 23 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Permission is hereby granted, free of charge, to any 2 | person obtaining a copy of this software and associated 3 | documentation files (the "Software"), to deal in the 4 | Software without restriction, including without 5 | limitation the rights to use, copy, modify, merge, 6 | publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software 8 | is furnished to do so, subject to the following 9 | conditions: 10 | 11 | The above copyright notice and this permission notice 12 | shall be included in all copies or substantial portions 13 | of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 16 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 17 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 18 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 19 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 22 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /libzmq-sys/LICENSE: -------------------------------------------------------------------------------- 1 | Permission is hereby granted, free of charge, to any 2 | person obtaining a copy of this software and associated 3 | documentation files (the "Software"), to deal in the 4 | Software without restriction, including without 5 | limitation the rights to use, copy, modify, merge, 6 | publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software 8 | is furnished to do so, subject to the following 9 | conditions: 10 | 11 | The above copyright notice and this permission notice 12 | shall be included in all copies or substantial portions 13 | of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 16 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 17 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 18 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 19 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 22 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /libzmq/LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Permission is hereby granted, free of charge, to any 2 | person obtaining a copy of this software and associated 3 | documentation files (the "Software"), to deal in the 4 | Software without restriction, including without 5 | limitation the rights to use, copy, modify, merge, 6 | publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software 8 | is furnished to do so, subject to the following 9 | conditions: 10 | 11 | The above copyright notice and this permission notice 12 | shall be included in all copies or substantial portions 13 | of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 16 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 17 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 18 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 19 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 22 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /libzmq-sys/LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Permission is hereby granted, free of charge, to any 2 | person obtaining a copy of this software and associated 3 | documentation files (the "Software"), to deal in the 4 | Software without restriction, including without 5 | limitation the rights to use, copy, modify, merge, 6 | publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software 8 | is furnished to do so, subject to the following 9 | conditions: 10 | 11 | The above copyright notice and this permission notice 12 | shall be included in all copies or substantial portions 13 | of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 16 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 17 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 18 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 19 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 22 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /libzmq-book/src/examples/secure_req_rep.md: -------------------------------------------------------------------------------- 1 | # Secure Request Reply 2 | 3 | The previous example did not offer neither authentication nor encryption. 4 | For a public `TCP` connection, its a must. Let's fix that by adapting the 5 | previous example. 6 | 7 | This time we will the `CURVE` mechanism, which is a public-key crypto. 8 | To enable the usage of the `CURVE` mechanism, the feature flag 'curve' 9 | must be enabled. 10 | 11 | However, this time we will use an external configuration file to get 12 | rid of all the boilerplate. This will also allows our application 13 | to run indepently of the socket configuration. 14 | 15 | Based on some basic benchmarks, the `CURVE` mechanism 16 | might reduce the throughtput of I/O heavy applications by half due 17 | to the overhead of the `salsa20` encryption. 18 | 19 | ## Config File 20 | 21 | In this case we used `yaml` configuration file, but any file format 22 | supported by `Serde` will work (as long as it supports typed enums). 23 | ```yml 24 | {{#include ../../../libzmq/examples/secure_req_rep.yml}} 25 | ``` 26 | 27 | ## The code 28 | 29 | Aside from the additionnal logic for reading the config file, 30 | the code is now simpler than before. 31 | 32 | {{#playpen ../../../libzmq/examples/secure_req_rep.rs}} 33 | -------------------------------------------------------------------------------- /libzmq/examples/secure_req_rep.yml: -------------------------------------------------------------------------------- 1 | # The curve keys where generated by running: 2 | # `$ cargo run --example gen_curve_cert` 3 | 4 | auth: 5 | # The public keys allowed to authenticate. Note that this is 6 | # the client's public key. 7 | curve_registry: 8 | - "n%3)5@(3pzp)v8yt6RW3eQVq5OQYb#TEodD^6oA^" 9 | 10 | client: 11 | # In a real life scenario the server would have a known addr. 12 | #connect: 13 | # - tcp: "127.0.0.1:3000" 14 | heartbeat: 15 | interval: 1s 16 | timeout: 3s 17 | ttl: 3s 18 | send_high_water_mark: 10 19 | send_timeout: 300ms 20 | recv_high_water_mark: 100 21 | recv_timeout: 300ms 22 | mechanism: 23 | curve_client: 24 | client: 25 | public: "n%3)5@(3pzp)v8yt6RW3eQVq5OQYb#TEodD^6oA^" 26 | secret: "JiUDa>>owH1+mPTWs=>Jcyt%h.C1E4Js>)(g{geY" 27 | # This is the server's public key. 28 | server: "et189NB9uJC7?J+XU8JRhCbF?gOP9+o%kli=y2b8" 29 | 30 | server: 31 | # Here we use a system defined port so as to not conflict with the host 32 | # machine. In a real life scenario we would have a port available. 33 | bind: 34 | - tcp: "127.0.0.1:*" 35 | heartbeat: 36 | interval: 1s 37 | timeout: 3s 38 | ttl: 3s 39 | mechanism: 40 | curve_server: 41 | secret: "iaoRiIVA^VgV:f4a<@{8K{cP62cE:dh=4:oY+^l(" 42 | 43 | -------------------------------------------------------------------------------- /libzmq/benches/msg.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | use criterion::{black_box, Benchmark, Criterion}; 4 | use libzmq::Msg; 5 | 6 | pub(crate) fn bench(c: &mut Criterion) { 7 | c.bench( 8 | &"Msg::from".to_owned(), 9 | Benchmark::new("String", move |b| { 10 | b.iter(|| { 11 | let string = "some string".to_owned(); 12 | let msg: Msg = string.into(); 13 | black_box(msg); 14 | }); 15 | }) 16 | .with_function("&str", move |b| { 17 | b.iter(|| { 18 | let msg: Msg = "some str".into(); 19 | black_box(msg); 20 | }); 21 | }) 22 | .with_function("Vec", move |b| { 23 | b.iter(|| { 24 | let bytes = b"some bytes".to_owned(); 25 | let msg: Msg = bytes.into(); 26 | black_box(msg); 27 | }); 28 | }) 29 | .with_function("&[u8; X]", move |b| { 30 | b.iter(|| { 31 | let msg: Msg = b"some bytes".into(); 32 | black_box(msg); 33 | }); 34 | }) 35 | .with_function("&[u8]", move |b| { 36 | b.iter(|| { 37 | let bytes: &[u8] = b"some bytes"; 38 | let msg: Msg = bytes.into(); 39 | black_box(msg); 40 | }); 41 | }) 42 | .measurement_time(MEASUREMENT_TIME), 43 | ); 44 | } 45 | -------------------------------------------------------------------------------- /libzmq/examples/basic_req_rep.rs: -------------------------------------------------------------------------------- 1 | use libzmq::{prelude::*, *}; 2 | 3 | use std::thread; 4 | 5 | fn main() -> Result<(), anyhow::Error> { 6 | let addr: InprocAddr = InprocAddr::new_unique(); 7 | 8 | let server = ServerBuilder::new().bind(&addr).build()?; 9 | 10 | // Spawn the server thread. 11 | let handle = thread::spawn(move || -> Result<(), Error> { 12 | loop { 13 | let request = server.recv_msg()?; 14 | assert_eq!(request.to_str(), Ok("ping")); 15 | 16 | // Retrieve the routing_id to route the reply to the client. 17 | let id = request.routing_id().unwrap(); 18 | // We cast the Error to Error<()>. This drops the Msg. 19 | server.route("pong", id).map_err(Error::cast)?; 20 | } 21 | }); 22 | 23 | let client = ClientBuilder::new().connect(addr).build()?; 24 | 25 | // Do some request-reply work. 26 | client.send("ping")?; 27 | let msg = client.recv_msg()?; 28 | assert_eq!(msg.to_str(), Ok("pong")); 29 | 30 | // This will cause the server to fail with `InvalidCtx`. 31 | Ctx::global().shutdown(); 32 | 33 | // Join with the thread. 34 | let err = handle.join().unwrap().unwrap_err(); 35 | assert_eq!(err.kind(), ErrorKind::InvalidCtx); 36 | 37 | Ok(()) 38 | } 39 | 40 | #[cfg(test)] 41 | mod tests { 42 | use super::main; 43 | 44 | #[test] 45 | fn main_runs() { 46 | main().unwrap(); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /libzmq/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "libzmq" 3 | version = "0.2.5" 4 | authors = ["jean-airoldie "] 5 | edition = "2018" 6 | license = "MIT OR Apache-2.0" 7 | description = """ 8 | A strict subset of ØMQ with a high level API. 9 | """ 10 | repository = "https://github.com/jean-airoldie/libzmq-rs" 11 | readme = "../README.md" 12 | keywords = ["libzmq", "zmq", "zeromq", "bindings"] 13 | categories = ["api-bindings", "asynchronous"] 14 | autobenches = false 15 | homepage = "https://jean-airoldie.github.io/libzmq-rs/" 16 | 17 | [badges] 18 | maintenance = { status = "passively-maintained" } 19 | 20 | [features] 21 | curve = ['libzmq-sys/curve', 'libzmq-sys/libsodium'] 22 | 23 | [dependencies] 24 | libc = "0.2" 25 | serde = { version = "1.0", features = ["derive"] } 26 | humantime-serde = "1.0" 27 | serde_with = "1.3.1" 28 | lazy_static = "1.3.0" 29 | thiserror = "1" 30 | libzmq-sys = { path = "../libzmq-sys", version = "0.1.8" } 31 | bitflags = "1.0" 32 | log = "0.4" 33 | uuid = { version = "0.8", features = ["v4"] } 34 | bincode = "1.1" 35 | byteorder = "1.3.1" 36 | 37 | [dev-dependencies] 38 | anyhow = "1" 39 | rand = "0.7" 40 | rand_isaac = "0.2" 41 | rand_core = "0.5" 42 | criterion = "0.3" 43 | version-sync = "0.9" 44 | quickcheck = "0.9" 45 | serde_yaml = "0.8" 46 | 47 | [build-dependencies] 48 | flatc-rust = "0.1" 49 | 50 | [[example]] 51 | name = "secure_req_rep" 52 | required-features = ['curve'] 53 | 54 | [[bench]] 55 | required-features = ['curve'] 56 | name = "bench_main" 57 | harness = false 58 | -------------------------------------------------------------------------------- /FAQ.md: -------------------------------------------------------------------------------- 1 | # Frequently Asked Questions 2 | 3 | ## Why are so many socket types currently not supported? 4 | Currently only a fraction of all available ØMQ sockets are supported. 5 | This because I determined that most of the sockets were deprecated. 6 | 7 | To do so, I implemented every socket type (except `REQ` and `REP`) 8 | and tested their functionality and their performance. A socket type 9 | had to either offer exceptional performance in some domain or offer 10 | a unique feature. 11 | 12 | Turns out that you can replace all those deprecated 13 | socket types in terms of functionality using only `Client`, 14 | `Server`, `Dish` and `Radio`. 15 | 16 | To test performance I wrote a benchmark using criterion that tested every basic 17 | socket pattern (PUSH-PULL, EXCLUSIVE PAIR, etc.). I used a sample size of 18 | 50m messages per messages size of 10, 50 and 100 bytes, per pattern. 19 | I determined that the thread safe type had equal or better single thread 20 | performance in almost ALL cases. The only exception was the EXCLUSIVE PAIR 21 | pattern which is used exclusively for inter-thread communication, where 22 | you should probably be be using something like [`crossbeam_channel`], which offers 23 | significantly better performance. 24 | 25 | Lastly these deprecated socket types had a ton of footguns that were 26 | kept to maintain backward compatibility. 27 | 28 | If you still think your favorite socket type should be supported, feel 29 | free to open and issue as this is only my subjective opinion. 30 | 31 | [`crossbeam_channel`]: https://docs.rs/crossbeam-channel/0.3.8/crossbeam_channel/ 32 | -------------------------------------------------------------------------------- /libzmq-sys/README.md: -------------------------------------------------------------------------------- 1 | [![](https://img.shields.io/crates/v/libzmq-sys.svg)][crates-io] 2 | [![](https://docs.rs/libzmq-sys/badge.svg)][api-docs] 3 | [![Apache 2.0 licensed](https://img.shields.io/badge/license-Apache2.0-blue.svg)](./LICENSE-APACHE) 4 | [![MIT licensed](https://img.shields.io/badge/license-MIT-blue.svg)](./LICENSE-MIT) 5 | 6 | > libzmq-sys - Rust raw cffi bindings to libzmq 7 | 8 | Based on this [`guide`](https://kornel.ski/rust-sys-crate) as well as [`zmq-sys`]. 9 | 10 | # Dependencies 11 | * [CMake 2.8.12+ (or 3.0.2+ on Darwin)](https://github.com/zeromq/libzmq/blob/de4d69f59788fed86bcb0f610723c5acd486a7da/CMakeLists.txt#L7) 12 | 13 | This crate uses pre-generated bindings to `libzmq`. To generate your own 14 | bindings, use the `renew-bindings` feature. This requires [`Clang 3.9+`]. 15 | 16 | # Build and Linking. 17 | The lib is built and linked statically. 18 | 19 | # Build Type 20 | The lib is built depending on the profile (either release or debug). 21 | 22 | # OUTPUT ENV Variables 23 | These are the output ENV variables of the cargo build script: 24 | * `DEP_ZMQ_INCLUDE` is the directory which contains the `zmq.h` header. 25 | * `DEP_ZMQ_ROOT` is the root of the `OUT_DIR`. 26 | * `DEP_ZMQ_PKG_CONFIG_PATH` is the path to the directory 27 | containing the `libzmq.pc` package config fileo 28 | 29 | [`guide`]: https://kornel.ski/rust-sys-crate 30 | [`zmq-sys`]: https://github.com/erickt/rust-zmq/tree/master/zmq-sys 31 | [crates-io]: https://crates.io/crates/libzmq-sys 32 | [api-docs]: https://docs.rs/libzmq-sys 33 | [`Clang 3.9+`]: https://rust-lang.github.io/rust-bindgen/requirements.html 34 | -------------------------------------------------------------------------------- /libzmq-book/src/advanced/protocols.md: -------------------------------------------------------------------------------- 1 | # Custom Protocols 2 | 3 | For two peers to be able to communicate, they must share a contract. In the 4 | world of communication, these are called protocols. `ZeroMQ` enables 5 | programmer to create protocols that suit their needs by removing most of the 6 | boilerplate. 7 | 8 | You might have realized by now that there is no strict concept of request-reply 9 | as a socket operation. Indeed the library does not enforce a client socket 10 | to follow a `send` call by a `recv` call. This does't mean however that this 11 | strict type of request-reply could not be achieved. To do so, a programmer could 12 | easily write the following code: 13 | 14 | ```rust 15 | // Client side 16 | fn request_reply(&mut self, msg: Msg) -> Result { 17 | self.client.send(msg)?; 18 | self.client.recv_msg()? 19 | } 20 | 21 | // Server side 22 | fn run(&mut self) -> Result<(), Error> { 23 | loop { 24 | let request = self.server.recv_msg()?; 25 | let reply = self.on_request(request)?; 26 | self.server.send(reply) 27 | } 28 | } 29 | ``` 30 | 31 | This creates an implicit contract between the client and the server. 32 | We will disregard the error handling and timeouts for simplicity. 33 | * The client must send one request at a time and wait for one reply. 34 | * The server must wait for a request and send one reply. 35 | 36 | Since contract must be ensured at the application level, it must be properly 37 | documentated for developpers to be able to respect it. 38 | 39 | `ZeroMQ` does not enforce a particular messaging protocol, instead 40 | it offers all the tools to build one. 41 | -------------------------------------------------------------------------------- /libzmq-sys/src/errno.rs: -------------------------------------------------------------------------------- 1 | // taken from github.com/erickt/rust-zmq/blob/master/zmq-sys/src/errno.rs 2 | 3 | #[cfg(unix)] 4 | use libc as errno; 5 | #[cfg(windows)] 6 | use windows::errno; 7 | 8 | const ZMQ_HAUSNUMERO: i32 = 156_384_712; 9 | 10 | pub const EBADF: i32 = errno::EBADF; 11 | pub const EACCES: i32 = errno::EACCES; 12 | pub const EADDRINUSE: i32 = errno::EADDRINUSE; 13 | pub const EAGAIN: i32 = errno::EAGAIN; 14 | pub const EBUSY: i32 = errno::EBUSY; 15 | pub const ECONNREFUSED: i32 = errno::ECONNREFUSED; 16 | pub const EFAULT: i32 = errno::EFAULT; 17 | pub const EINTR: i32 = errno::EINTR; 18 | pub const EHOSTUNREACH: i32 = errno::EHOSTUNREACH; 19 | pub const EINPROGRESS: i32 = errno::EINPROGRESS; 20 | pub const EINVAL: i32 = errno::EINVAL; 21 | pub const EMFILE: i32 = errno::EMFILE; 22 | pub const EMSGSIZE: i32 = errno::EMSGSIZE; 23 | pub const ENAMETOOLONG: i32 = errno::ENAMETOOLONG; 24 | pub const ENODEV: i32 = errno::ENODEV; 25 | pub const ENOENT: i32 = errno::ENOENT; 26 | pub const ENOMEM: i32 = errno::ENOMEM; 27 | pub const ENOTCONN: i32 = errno::ENOTCONN; 28 | pub const ENOTSOCK: i32 = errno::ENOTSOCK; 29 | #[cfg(not(target_os = "openbsd"))] 30 | pub const EPROTO: i32 = errno::EPROTO; 31 | #[cfg(target_os = "openbsd")] 32 | pub const EPROTO: i32 = errno::EOPNOTSUPP; 33 | pub const EPROTONOSUPPORT: i32 = errno::EPROTONOSUPPORT; 34 | 35 | #[cfg(not(target_os = "windows"))] 36 | pub const ENOTSUP: i32 = ZMQ_HAUSNUMERO + 1; 37 | #[cfg(target_os = "windows")] 38 | pub const ENOTSUP: i32 = errno::ENOTSUP; 39 | 40 | pub const ENOBUFS: i32 = errno::ENOBUFS; 41 | pub const ENETDOWN: i32 = errno::ENETDOWN; 42 | pub const EADDRNOTAVAIL: i32 = errno::EADDRNOTAVAIL; 43 | 44 | // native zmq error codes 45 | pub const EFSM: i32 = ZMQ_HAUSNUMERO + 51; 46 | pub const ENOCOMPATPROTO: i32 = ZMQ_HAUSNUMERO + 52; 47 | pub const ETERM: i32 = ZMQ_HAUSNUMERO + 53; 48 | pub const EMTHREAD: i32 = ZMQ_HAUSNUMERO + 54; 49 | -------------------------------------------------------------------------------- /libzmq-book/src/glossary.md: -------------------------------------------------------------------------------- 1 | # Glossary 2 | Some high level definitions. 3 | 4 | ## Endpoint 5 | A endpoint is a rendez-vous address for a specified transport. The syntax 6 | of the address depends on the nature of the transport. For instance 7 | a `TcpAddr` is an endpoint over the `TCP` transport. 8 | 9 | ## Transport 10 | A protocol to transfert data. Could be a network protocol, such as `TCP`, 11 | could be a inter-thread protocol, such as `INPROC`, etc. 12 | 13 | ## Connection 14 | Connections in `ZeroMQ` are different from traditional connections is the 15 | sense that they automatically handle failure. For instance, if a connection 16 | fails because of a network error, it will be automatically reconnected if 17 | possible. Thus sockets should not worry about the state of a given connection. 18 | 19 | ## Message 20 | An atomic arbitrary set of bytes owned by the `ZeroMQ` engine. `ZeroMQ` does 21 | not know how to interpret these bytes, only the user does. Messages are 22 | the units that are transferred between sockets. 23 | 24 | ## Socket 25 | A `ZeroMQ` construct used to send and receive messages using connections 26 | accros endpoints. The specific behavior of the socket depends on its type. 27 | 28 | ## High Water Mark 29 | The message limit in the incoming or outgoing buffer. If the incoming 30 | buffer has reached this limit, the socket will stop receiving messages 31 | in the background. If the outgoing buffer has reached this limit, attempting 32 | to queue a message will block the calling thread. Conceptually, this is a 33 | socket's back throttling mechanism. 34 | 35 | ## Context 36 | A `ZeroMQ` context is a session that keeps track of all the sockets, 37 | the messages, the async threads and the internal queries. 38 | 39 | ## Mechanism 40 | A specific protocol used by sockets to authenticate and encrypt traffic. 41 | 42 | ## Mute State 43 | A socket that is in mute state is unable to queue and receive messages. 44 | This is likely because it has no peers. The condition for the mute state to 45 | occur depends on the socket type. 46 | -------------------------------------------------------------------------------- /libzmq-sys/build.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | #[cfg(feature = "renew-bindings")] 3 | use std::path::{Path, PathBuf}; 4 | 5 | #[cfg(feature = "renew-bindings")] 6 | fn gen_bindings(include_dir: &Path) { 7 | let args = vec!["-DZMQ_BUILD_DRAFT_API=1"]; 8 | 9 | let bindings = bindgen::Builder::default() 10 | .header(include_dir.join("zmq.h").to_string_lossy()) 11 | .size_t_is_usize(true) 12 | .derive_default(true) 13 | .derive_eq(true) 14 | .derive_partialeq(true) 15 | .derive_debug(true) 16 | .derive_hash(true) 17 | .whitelist_function("^zmq_.*") 18 | .whitelist_type("^zmq_.*") 19 | .whitelist_var("^ZMQ_.*") 20 | .clang_args(args) 21 | .generate() 22 | .expect("Unable to generate bindings"); 23 | 24 | let out_path = PathBuf::from("./src").join("bindings.rs"); 25 | bindings 26 | .write_to_file(out_path) 27 | .expect("Couldn't write bindings!"); 28 | } 29 | 30 | fn main() { 31 | println!("cargo:rerun-if-changed=build.rs"); 32 | println!("cargo:rerun-if-env-changed=PROFILE"); 33 | 34 | let enable_curve = cfg!(feature = "curve"); 35 | 36 | let wants_debug = env::var_os("PROFILE").unwrap() == "debug"; 37 | 38 | let maybe_libsodium = if cfg!(feature = "libsodium") { 39 | let lib_dir = env::var("DEP_SODIUM_LIB") 40 | .expect("build metadata `DEP_SODIUM_LIB` required"); 41 | let include_dir = env::var("DEP_SODIUM_INCLUDE") 42 | .expect("build metadata `DEP_SODIUM_INCLUDE` required"); 43 | 44 | Some(zeromq_src::LibLocation::new(lib_dir, include_dir)) 45 | } else { 46 | None 47 | }; 48 | 49 | let artifacts = zeromq_src::Build::new() 50 | .link_static(true) 51 | .enable_draft(true) 52 | .enable_curve(enable_curve) 53 | .build_debug(wants_debug) 54 | .with_libsodium(maybe_libsodium) 55 | .build(); 56 | 57 | artifacts.print_cargo_metadata(); 58 | 59 | #[cfg(feature = "renew-bindings")] 60 | gen_bindings(artifacts.include_dir()); 61 | } 62 | -------------------------------------------------------------------------------- /libzmq-book/src/basics/patterns.md: -------------------------------------------------------------------------------- 1 | # Patterns 2 | 3 | These are the most basic socket patterns in `libzmq`. 4 | 5 | ## Client-Server 6 | 7 | The `Client-Server` pattern is a advanced asynchronous request-reply pattern. 8 | 9 | The [Server] receives messages with a unique [RoutingId] associated with a 10 | [Client]. This [RoutingId] can be used by the [Server] to route replies to the 11 | [Client]. 12 | ``` 13 | <───> client 14 | server <───> client 15 | <───> client 16 | 17 | ``` 18 | 19 | The [Client] socket receives upstream messages in a fair-queued fashion 20 | ``` 21 | server ─┐ 22 | server ────> client 23 | server ─┘ 24 | ``` 25 | 26 | ## Radio-Dish 27 | 28 | The `Radio-Dish` pattern is an asynchronous publish-subscribe pattern. 29 | 30 | The [Radio] socket send messages in a fan-out fashion to all [Dish] 31 | that [joined] the message's [Group]. 32 | ``` 33 | ────> dish 34 | radio ────> dish 35 | ────> dish 36 | ``` 37 | 38 | The [Dish] socket receive messages from [Group] it has [joined] in a 39 | fair-queued fashion. 40 | ``` 41 | radio ─┐ 42 | radio ────> dish 43 | radio ─┘ 44 | ``` 45 | 46 | ## Scatter-Gather 47 | 48 | The `Scatter-Gather` pattern is an asynchronous pipeline pattern. 49 | 50 | The [Scatter] socket send messages downstream in a round-robin fashion 51 | ``` 52 | ┌──> gather 53 | scatter ────> gather 54 | └──> gather 55 | ``` 56 | 57 | The [Gather] socket receives upstream messages in a fair-queued fashion 58 | ``` 59 | scatter ─┐ 60 | scatter ───> gather 61 | scatter ─┘ 62 | ``` 63 | 64 | [Server]: https://docs.rs/libzmq/0.1/libzmq/struct.Server.html 65 | [RoutingId]: https://docs.rs/libzmq/0.1/libzmq/struct.RoutingId.html 66 | [Client]: https://docs.rs/libzmq/0.1/libzmq/struct.Client.html 67 | [Radio]: https://docs.rs/libzmq/0.1/libzmq/struct.Radio.html 68 | [Dish]: https://docs.rs/libzmq/0.1/libzmq/struct.Dish.html 69 | [Group]: https://docs.rs/libzmq/0.1/libzmq/struct.Group.html 70 | [Scatter]: https://docs.rs/libzmq/0.1/libzmq/struct.Scatter.html 71 | [Gather]: https://docs.rs/libzmq/0.1/libzmq/struct.Gather.html 72 | [joined]: https://docs.rs/libzmq/0.1/libzmq/struct.Dish.html#method.join 73 | -------------------------------------------------------------------------------- /libzmq/examples/reliable_req_rep.rs: -------------------------------------------------------------------------------- 1 | use libzmq::{prelude::*, *}; 2 | 3 | use std::{thread, time::Duration}; 4 | 5 | fn main() -> Result<(), anyhow::Error> { 6 | // We use a system assigned port here. 7 | let addr: TcpAddr = "127.0.0.1:*".try_into()?; 8 | let duration = Duration::from_millis(300); 9 | 10 | let hb = Heartbeat::new(duration) 11 | .add_timeout(3 * duration) 12 | .add_ttl(3 * duration); 13 | 14 | let server = ServerBuilder::new() 15 | .bind(addr) 16 | .send_timeout(duration) 17 | .heartbeat(&hb) 18 | .build()?; 19 | 20 | // Retrieve the assigned port. 21 | let bound = server.last_endpoint()?; 22 | 23 | // Spawn the server thread. In a real application, this 24 | // would be on another node. 25 | let handle = thread::spawn(move || -> Result<(), Error> { 26 | use ErrorKind::*; 27 | loop { 28 | let request = server.recv_msg()?; 29 | assert_eq!(request.to_str(), Ok("ping")); 30 | 31 | // Retrieve the routing_id to route the reply to the client. 32 | let id = request.routing_id().unwrap(); 33 | if let Err(err) = server.route("pong", id) { 34 | match err.kind() { 35 | // Cannot route msg, drop it. 36 | WouldBlock | HostUnreachable => (), 37 | _ => return Err(err.cast()), 38 | } 39 | } 40 | } 41 | }); 42 | 43 | let client = ClientBuilder::new() 44 | .connect(bound) 45 | .recv_timeout(duration) 46 | .send_timeout(duration) 47 | .heartbeat(hb) 48 | .build()?; 49 | 50 | // Do some request-reply work. 51 | client.send("ping")?; 52 | let msg = client.recv_msg()?; 53 | assert_eq!(msg.to_str(), Ok("pong")); 54 | 55 | // This will cause the server to fail with `InvalidCtx`. 56 | Ctx::global().shutdown(); 57 | 58 | // Join with the thread. 59 | let err = handle.join().unwrap().unwrap_err(); 60 | assert_eq!(err.kind(), ErrorKind::InvalidCtx); 61 | 62 | Ok(()) 63 | } 64 | 65 | #[cfg(test)] 66 | mod tests { 67 | use super::main; 68 | 69 | #[test] 70 | fn main_runs() { 71 | main().unwrap(); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /libzmq/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![doc(html_root_url = "https://docs.rs/libzmq/0.2")] 2 | 3 | //! *libzmq* - A strict subset of ØMQ with a high level API. 4 | 5 | #[macro_use] 6 | mod core; 7 | pub mod auth; 8 | mod ctx; 9 | mod endpoint; 10 | mod error; 11 | mod group; 12 | mod msg; 13 | mod old; 14 | pub mod poll; 15 | mod socket; 16 | mod utils; 17 | 18 | pub use crate::core::{Heartbeat, Period}; 19 | pub use ctx::{Ctx, CtxBuilder, CtxHandle}; 20 | pub use endpoint::{ 21 | EpgmAddr, InprocAddr, PgmAddr, TcpAddr, UdpAddr, INPROC_MAX_SIZE, 22 | }; 23 | pub use error::{Error, ErrorKind}; 24 | pub use group::*; 25 | pub use msg::*; 26 | pub use socket::{ 27 | Client, ClientBuilder, Dish, DishBuilder, Gather, GatherBuilder, Radio, 28 | RadioBuilder, Scatter, ScatterBuilder, Server, ServerBuilder, SocketType, 29 | }; 30 | pub use utils::*; 31 | /// Configurations for *libzmq* types. 32 | pub mod config { 33 | pub use crate::auth::client::AuthConfig; 34 | pub use crate::ctx::CtxConfig; 35 | pub use crate::socket::{ 36 | ClientConfig, ConfigType, DishConfig, GatherConfig, RadioConfig, 37 | ScatterConfig, ServerConfig, 38 | }; 39 | } 40 | 41 | /// Address related types. 42 | pub mod addr { 43 | pub use crate::endpoint::{ 44 | AddrParseError, Endpoint, Hostname, Interface, IntoIpAddrs, Port, 45 | SocketAddr, SrcAddr, 46 | }; 47 | } 48 | 49 | /// A "prelude" for users of the *libzmq* crate. 50 | /// 51 | /// This prelude is similar to the standard library's prelude in that you'll 52 | /// almost always want to import its entire contents, but unlike the standard 53 | /// library's prelude you'll have to do so manually: 54 | /// 55 | /// ``` 56 | /// use libzmq::prelude::*; 57 | /// ``` 58 | /// 59 | /// The prelude may grow over time as additional items see ubiquitous use. 60 | pub mod prelude { 61 | pub use crate::core::{ 62 | BuildHeartbeating, BuildRecv, BuildSend, BuildSocket, 63 | ConfigureHeartbeating, ConfigureRecv, ConfigureSend, ConfigureSocket, 64 | Heartbeating, RecvMsg, SendMsg, Socket, 65 | }; 66 | // These should be in the standard prelude anyway. 67 | pub use std::convert::{TryFrom, TryInto}; 68 | } 69 | 70 | #[cfg(test)] 71 | mod test { 72 | #[test] 73 | fn test_readme_deps() { 74 | version_sync::assert_markdown_deps_updated!("../README.md"); 75 | } 76 | 77 | #[test] 78 | fn test_html_root_url() { 79 | version_sync::assert_html_root_url_updated!("src/lib.rs"); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /libzmq/examples/secure_req_rep.rs: -------------------------------------------------------------------------------- 1 | use libzmq::{config::*, prelude::*, *}; 2 | 3 | use serde::{Deserialize, Serialize}; 4 | 5 | use std::{ 6 | fs::File, 7 | io::Read, 8 | path::{Path, PathBuf}, 9 | thread, 10 | }; 11 | 12 | const CONFIG_FILE: &str = "secure_req_rep.yml"; 13 | 14 | #[derive(Debug, Clone, Serialize, Deserialize)] 15 | pub struct Config { 16 | auth: AuthConfig, 17 | client: ClientConfig, 18 | server: ServerConfig, 19 | } 20 | 21 | fn read_file(name: &Path) -> std::io::Result> { 22 | let mut file = File::open(name)?; 23 | let mut buf = Vec::new(); 24 | file.read_to_end(&mut buf)?; 25 | Ok(buf) 26 | } 27 | 28 | fn main() -> Result<(), anyhow::Error> { 29 | let path = PathBuf::from("examples").join(CONFIG_FILE); 30 | 31 | let config: Config = 32 | serde_yaml::from_slice(&read_file(&path).unwrap()).unwrap(); 33 | 34 | // Configure the `AuthServer`. We won't need the returned `AuthClient`. 35 | let _ = config.auth.build()?; 36 | 37 | // Configure our two sockets. 38 | let server = config.server.build()?; 39 | let client = config.client.build()?; 40 | 41 | // Once again we used a system assigned port for our server. 42 | let bound = server.last_endpoint()?; 43 | client.connect(bound)?; 44 | 45 | // Spawn the server thread. In a real application, this 46 | // would be on another node. 47 | let handle = thread::spawn(move || -> Result<(), Error> { 48 | use ErrorKind::*; 49 | loop { 50 | let request = server.recv_msg()?; 51 | assert_eq!(request.to_str(), Ok("ping")); 52 | 53 | // Retrieve the routing_id to route the reply to the client. 54 | let id = request.routing_id().unwrap(); 55 | if let Err(err) = server.route("pong", id) { 56 | match err.kind() { 57 | // Cannot route msg, drop it. 58 | WouldBlock | HostUnreachable => (), 59 | _ => return Err(err.cast()), 60 | } 61 | } 62 | } 63 | }); 64 | 65 | // Do some request-reply work. 66 | client.send("ping")?; 67 | let msg = client.recv_msg()?; 68 | assert_eq!(msg.to_str(), Ok("pong")); 69 | 70 | // This will cause the server to fail with `InvalidCtx`. 71 | Ctx::global().shutdown(); 72 | 73 | // Join with the thread. 74 | let err = handle.join().unwrap().unwrap_err(); 75 | assert_eq!(err.kind(), ErrorKind::InvalidCtx); 76 | 77 | Ok(()) 78 | } 79 | 80 | #[cfg(test)] 81 | mod tests { 82 | use super::main; 83 | 84 | #[test] 85 | fn main_runs() { 86 | main().unwrap(); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /libzmq/src/socket/mod.rs: -------------------------------------------------------------------------------- 1 | //! The ØMQ socket types. 2 | 3 | mod client; 4 | mod dish; 5 | mod gather; 6 | mod radio; 7 | mod scatter; 8 | mod server; 9 | 10 | pub use client::*; 11 | pub use dish::*; 12 | pub use gather::*; 13 | pub use radio::*; 14 | pub use scatter::*; 15 | pub use server::*; 16 | 17 | use crate::{ 18 | core::{GetRawSocket, RawSocket}, 19 | Error, 20 | }; 21 | 22 | use serde::{Deserialize, Serialize}; 23 | 24 | /// An enum containing all the socket types. 25 | /// 26 | /// # Note 27 | /// This error type is non-exhaustive and could have additional variants 28 | /// added in future. Therefore, when matching against variants of 29 | /// non-exhaustive enums, an extra wildcard arm must be added to account 30 | /// for any future variants. 31 | #[derive(Debug, Clone, PartialEq, Eq)] 32 | pub enum SocketType { 33 | Client(Client), 34 | Server(Server), 35 | Radio(Radio), 36 | Dish(Dish), 37 | Gather(Gather), 38 | Scatter(Scatter), 39 | } 40 | 41 | impl GetRawSocket for SocketType { 42 | fn raw_socket(&self) -> &RawSocket { 43 | match self { 44 | SocketType::Client(client) => client.raw_socket(), 45 | SocketType::Server(server) => server.raw_socket(), 46 | SocketType::Radio(radio) => radio.raw_socket(), 47 | SocketType::Dish(dish) => dish.raw_socket(), 48 | SocketType::Gather(dish) => dish.raw_socket(), 49 | SocketType::Scatter(dish) => dish.raw_socket(), 50 | } 51 | } 52 | } 53 | 54 | /// An enum containing all the socket config types. 55 | /// 56 | /// # Note 57 | /// This error type is non-exhaustive and could have additional variants 58 | /// added in future. Therefore, when matching against variants of 59 | /// non-exhaustive enums, an extra wildcard arm must be added to account 60 | /// for any future variants. 61 | #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] 62 | #[serde(rename_all = "snake_case")] 63 | pub enum ConfigType { 64 | Client(ClientConfig), 65 | Server(ServerConfig), 66 | Radio(RadioConfig), 67 | Dish(DishConfig), 68 | Gather(GatherConfig), 69 | Scatter(ScatterConfig), 70 | } 71 | 72 | impl ConfigType { 73 | pub fn build(&self) -> Result { 74 | match self { 75 | ConfigType::Client(config) => { 76 | let client = config.build()?; 77 | Ok(SocketType::Client(client)) 78 | } 79 | ConfigType::Server(config) => { 80 | let server = config.build()?; 81 | Ok(SocketType::Server(server)) 82 | } 83 | ConfigType::Radio(config) => { 84 | let radio = config.build()?; 85 | Ok(SocketType::Radio(radio)) 86 | } 87 | ConfigType::Dish(config) => { 88 | let dish = config.build()?; 89 | Ok(SocketType::Dish(dish)) 90 | } 91 | ConfigType::Gather(config) => { 92 | let dish = config.build()?; 93 | Ok(SocketType::Gather(dish)) 94 | } 95 | ConfigType::Scatter(config) => { 96 | let dish = config.build()?; 97 | Ok(SocketType::Scatter(dish)) 98 | } 99 | } 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /libzmq/src/utils.rs: -------------------------------------------------------------------------------- 1 | use crate::{core::GetRawSocket, error::*}; 2 | use libzmq_sys as sys; 3 | use sys::errno; 4 | 5 | use std::{os::raw::*, ptr}; 6 | 7 | /// Reports the ØMQ library version. 8 | /// 9 | /// Returns a tuple in the format `(Major, Minor, Patch)`. 10 | /// 11 | /// See [`zmq_version`]. 12 | /// 13 | /// [`zmq_version`]: http://api.zeromq.org/4-2:zmq-version 14 | /// 15 | /// ``` 16 | /// use libzmq::version; 17 | /// 18 | /// assert_eq!(version(), (4, 3, 2)); 19 | /// ``` 20 | // This test acts as a canary when upgrading the *libzmq* 21 | // version. 22 | pub fn version() -> (i32, i32, i32) { 23 | let mut major = 0; 24 | let mut minor = 0; 25 | let mut patch = 0; 26 | unsafe { 27 | sys::zmq_version( 28 | &mut major as *mut c_int, 29 | &mut minor as *mut c_int, 30 | &mut patch as *mut c_int, 31 | ); 32 | } 33 | (major, minor, patch) 34 | } 35 | 36 | /// Start a built-in ØMQ proxy between a frontend and a backend socket. 37 | /// 38 | /// The two sockets must be configured before creating the proxy. 39 | /// 40 | /// The proxy connects a frontend socket to a backend socket. Conceptually, data 41 | /// flows from frontend to backend. Depending on the socket types, replies may 42 | /// flow in the opposite direction. The direction is conceptual only; the proxy 43 | /// is fully symmetric and there is no technical difference between frontend and 44 | /// backend. 45 | /// 46 | /// # Returned Errors 47 | /// * [`InvalidCtx`] 48 | /// 49 | /// # Example 50 | /// ``` 51 | /// # fn main() -> Result<(), anyhow::Error> { 52 | /// use libzmq::{prelude::*, *}; 53 | /// use std::thread; 54 | /// 55 | /// let radio_addr: InprocAddr = "frontend".try_into()?; 56 | /// let dish_addr: InprocAddr = "backend".try_into()?; 57 | /// let group: Group = "some group".try_into()?; 58 | /// 59 | /// let radio = RadioBuilder::new() 60 | /// .bind(&radio_addr) 61 | /// .build()?; 62 | /// 63 | /// let frontend = DishBuilder::new() 64 | /// .connect(&radio_addr) 65 | /// .join(&group) 66 | /// .build()?; 67 | /// 68 | /// let backend = RadioBuilder::new() 69 | /// .bind(&dish_addr) 70 | /// .build()?; 71 | /// 72 | /// let dish = DishBuilder::new() 73 | /// .connect(&dish_addr) 74 | /// .join(&group) 75 | /// .build()?; 76 | /// 77 | /// let proxy_handle = thread::spawn(move || proxy(frontend, backend)); 78 | /// 79 | /// let mut msg = Msg::new(); 80 | /// msg.set_group(&group); 81 | /// radio.send(msg)?; 82 | /// 83 | /// let msg = dish.recv_msg()?; 84 | /// assert!(msg.is_empty()); 85 | /// # 86 | /// # Ok(()) 87 | /// # } 88 | /// ``` 89 | /// 90 | /// [`InvalidCtx`]: ../enum.ErrorKind.html#variant.InvalidCtx 91 | pub fn proxy(frontend: F, backend: B) -> Result<(), Error> 92 | where 93 | F: GetRawSocket, 94 | B: GetRawSocket, 95 | { 96 | let frontend_socket_ptr = frontend.raw_socket().as_mut_ptr(); 97 | let backend_socket_ptr = backend.raw_socket().as_mut_ptr(); 98 | let rc = unsafe { 99 | sys::zmq_proxy(frontend_socket_ptr, backend_socket_ptr, ptr::null_mut()) 100 | }; 101 | 102 | assert_eq!(rc, -1); 103 | 104 | let errno = unsafe { sys::zmq_errno() }; 105 | let err = match errno { 106 | errno::ETERM => Error::new(ErrorKind::InvalidCtx), 107 | _ => panic!(msg_from_errno(errno)), 108 | }; 109 | 110 | Err(err) 111 | } 112 | -------------------------------------------------------------------------------- /libzmq/benches/curve.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | use criterion::{black_box, Benchmark, Criterion, Throughput}; 4 | use lazy_static::lazy_static; 5 | use libzmq::{auth::*, prelude::*, *}; 6 | use rand::distributions::{Distribution, Standard}; 7 | use rand_core::SeedableRng; 8 | use rand_isaac::Isaac64Rng; 9 | 10 | lazy_static! { 11 | static ref ADDR: TcpAddr = "127.0.0.1:*".try_into().unwrap(); 12 | } 13 | 14 | fn gen_dataset(dataset_size: usize, msg_size: usize) -> Vec> { 15 | let mut rng: Isaac64Rng = SeedableRng::seed_from_u64(123_490_814_327); 16 | (0..dataset_size) 17 | .map(|_| Standard.sample_iter(&mut rng).take(msg_size).collect()) 18 | .collect() 19 | } 20 | 21 | pub(crate) fn bench(c: &mut Criterion) { 22 | c.bench( 23 | &"client-server 50u8 msg on TCP".to_owned(), 24 | Benchmark::new("dataset alloc (control)", move |b| { 25 | b.iter(|| { 26 | black_box(gen_dataset(MSG_AMOUNT, MSG_SIZE)); 27 | }); 28 | }) 29 | .with_function("without CURVE encryption", move |b| { 30 | let producer = ServerBuilder::new() 31 | .bind(&*ADDR) 32 | .send_hwm(HWM) 33 | .build() 34 | .unwrap(); 35 | 36 | let bound = producer.last_endpoint().unwrap(); 37 | let consumer = ClientBuilder::new() 38 | .connect(bound) 39 | .recv_hwm(HWM) 40 | .build() 41 | .unwrap(); 42 | 43 | consumer.send("").unwrap(); 44 | let mut msg = producer.recv_msg().unwrap(); 45 | let id = msg.routing_id().unwrap(); 46 | 47 | b.iter(|| { 48 | let dataset = gen_dataset(MSG_AMOUNT, MSG_SIZE); 49 | for data in dataset { 50 | let data: Msg = data.into(); 51 | 52 | producer.route(data, id).unwrap(); 53 | let _ = consumer.try_recv(&mut msg); 54 | } 55 | }); 56 | }) 57 | .with_function("with CURVE encryption", move |b| { 58 | let _ = AuthBuilder::new().no_curve_auth().build().unwrap(); 59 | 60 | let server_cert = CurveCert::new_unique(); 61 | 62 | let creds = CurveServerCreds::new(server_cert.secret()); 63 | 64 | let producer = ServerBuilder::new() 65 | .bind(&*ADDR) 66 | .send_hwm(HWM) 67 | .mechanism(creds) 68 | .build() 69 | .unwrap(); 70 | 71 | let bound = producer.last_endpoint().unwrap(); 72 | 73 | let creds = CurveClientCreds::new(server_cert.public()); 74 | 75 | let consumer = ClientBuilder::new() 76 | .connect(bound) 77 | .recv_hwm(HWM) 78 | .mechanism(creds) 79 | .build() 80 | .unwrap(); 81 | 82 | consumer.send("").unwrap(); 83 | let mut msg = producer.recv_msg().unwrap(); 84 | let id = msg.routing_id().unwrap(); 85 | 86 | b.iter(|| { 87 | let dataset = gen_dataset(MSG_AMOUNT, MSG_SIZE); 88 | for data in dataset { 89 | let data: Msg = data.into(); 90 | 91 | producer.route(data, id).unwrap(); 92 | let _ = consumer.try_recv(&mut msg); 93 | } 94 | }); 95 | }) 96 | .throughput(Throughput::Bytes((MSG_AMOUNT * MSG_SIZE) as u64)) 97 | .sample_size(SAMPLE_SIZE) 98 | .measurement_time(MEASUREMENT_TIME), 99 | ); 100 | } 101 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | > :warning: I wouldn't recommand using ZeroMQ or any ZeroMq bindings library (including `libzmq-rs`), unless **you absolutely have to**. 2 | If you do, then `libzmq-rs` might fit your use case since it basically makes ZeroMQ not a complete footgun. However, just because this library hides the unmaintainable mess that is ZeroMQ, doesn't mean the mess does not exist. See [this comment for more context](https://github.com/jean-airoldie/libzmq-rs/issues/125#issuecomment-570551319). 3 | 4 | [![](https://img.shields.io/crates/v/libzmq.svg)][crates-io] 5 | [![](https://docs.rs/libzmq/badge.svg)][api-docs] 6 | [![Apache 2.0 licensed](https://img.shields.io/badge/license-Apache2.0-blue.svg)](./LICENSE-APACHE) 7 | [![MIT licensed](https://img.shields.io/badge/license-MIT-blue.svg)](./LICENSE-MIT) 8 | [![](https://img.shields.io/website/https/jean-airoldie.github.io/libzmq-rs.svg)][website] 9 | 10 | > libzmq-rs 11 | 12 | A strict subset of ØMQ with an ergonomic API. 13 | 14 | ```toml 15 | [dependencies] 16 | libzmq = "0.2" 17 | ``` 18 | 19 | # Dead Simple Sample 20 | ```rust 21 | use libzmq::{prelude::*, *}; 22 | use std::convert::TryInto; 23 | 24 | // Use a system assigned port. 25 | let addr: TcpAddr = "127.0.0.1:*".try_into()?; 26 | 27 | let server = ServerBuilder::new() 28 | .bind(addr) 29 | .build()?; 30 | 31 | // Retrieve the addr that was assigned. 32 | let bound = server.last_endpoint()?; 33 | 34 | let client = ClientBuilder::new() 35 | .connect(bound) 36 | .build()?; 37 | 38 | // Send a string request. 39 | client.send("tell me something")?; 40 | 41 | // Receive the client request. 42 | let msg = server.recv_msg()?; 43 | let id = msg.routing_id().unwrap(); 44 | 45 | // Reply to the client. 46 | server.route("it takes 224 bits to store a i32 in java", id)?; 47 | 48 | // We can reply as much as we want. 49 | server.route("also don't talk to me", id)?; 50 | 51 | // Retreive the first reply. 52 | let mut msg = client.recv_msg()?; 53 | // And the second. 54 | client.recv(&mut msg)?; 55 | ``` 56 | 57 | # Installation 58 | This crate builds and generates bindings from source. This means that you 59 | do not need to install [`libzmq`]. However building from source requires: 60 | * [CMake 2.8.12+ (or 3.0.2+ on Darwin)](https://github.com/zeromq/libzmq/blob/de4d69f59788fed86bcb0f610723c5acd486a7da/CMakeLists.txt#L7) 61 | * A c++ compiler 62 | 63 | # General Goals 64 | * Conform to these [`API guidelines`]. 65 | * Provide an ergonomic API 66 | * Prevent footguns (which are plentifull in [`libzmq`]) 67 | * Minimize the learning curve 68 | * Don't sacrifice any performance 69 | * Extensively document 70 | 71 | To do so we will only use a subset of [`libzmq`]. If you'd rather have a complete 72 | port, check out [`rust-zmq`]. 73 | 74 | # Frequently Asked Questions 75 | See the [`FAQ`](./FAQ.md). 76 | 77 | # Acknowledgements 78 | * Based on [`rust-zmq`] and [`czmq`]. 79 | 80 | # License 81 | This project is licensed under either of 82 | 83 | * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or 84 | http://www.apache.org/licenses/LICENSE-2.0) 85 | * MIT license ([LICENSE-MIT](LICENSE-MIT) or 86 | http://opensource.org/licenses/MIT) 87 | 88 | at your option. 89 | 90 | ### Contribution 91 | Unless you explicitly state otherwise, any contribution intentionally submitted 92 | for inclusion in `libzmq` by you, as defined in the Apache-2.0 license, shall be 93 | dual licensed as above, without any additional terms or conditions. 94 | 95 | [`rust-zmq`]: https://github.com/erickt/rust-zmq 96 | [`czmq`]: https://github.com/zeromq/czmq 97 | [`API guidelines`]: https://rust-lang-nursery.github.io/api-guidelines/checklist.html 98 | [`libzmq`]: https://github.com/zeromq/libzmq 99 | [crates-io]: https://crates.io/crates/libzmq 100 | [api-docs]: https://docs.rs/libzmq 101 | [website]: https://jean-airoldie.github.io/libzmq-rs/ 102 | -------------------------------------------------------------------------------- /libzmq/benches/socket.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | use criterion::{black_box, Benchmark, Criterion, Throughput}; 4 | use lazy_static::lazy_static; 5 | use libzmq::{prelude::*, *}; 6 | use rand::distributions::{Distribution, Standard}; 7 | use rand_core::SeedableRng; 8 | use rand_isaac::Isaac64Rng; 9 | 10 | lazy_static! { 11 | static ref ADDR: TcpAddr = "127.0.0.1:*".try_into().unwrap(); 12 | static ref GROUP: Group = "group".try_into().unwrap(); 13 | } 14 | 15 | fn gen_dataset(dataset_size: usize, msg_size: usize) -> Vec> { 16 | let mut rng: Isaac64Rng = SeedableRng::seed_from_u64(123_490_814_327); 17 | (0..dataset_size) 18 | .map(|_| Standard.sample_iter(&mut rng).take(msg_size).collect()) 19 | .collect() 20 | } 21 | 22 | pub(crate) fn bench(c: &mut Criterion) { 23 | c.bench( 24 | &"50u8 msg on TCP".to_owned(), 25 | Benchmark::new("dataset alloc (control)", move |b| { 26 | b.iter(|| { 27 | black_box(gen_dataset(MSG_AMOUNT, MSG_SIZE)); 28 | }); 29 | }) 30 | .with_function("server-client", move |b| { 31 | let producer = ServerBuilder::new() 32 | .bind(&*ADDR) 33 | .send_hwm(HWM) 34 | .build() 35 | .unwrap(); 36 | 37 | let bound = producer.last_endpoint().unwrap(); 38 | let consumer = ClientBuilder::new() 39 | .connect(bound) 40 | .recv_hwm(HWM) 41 | .build() 42 | .unwrap(); 43 | 44 | consumer.send("").unwrap(); 45 | let mut msg = producer.recv_msg().unwrap(); 46 | let id = msg.routing_id().unwrap(); 47 | 48 | b.iter(|| { 49 | let dataset = gen_dataset(MSG_AMOUNT, MSG_SIZE); 50 | for data in dataset { 51 | let data: Msg = data.into(); 52 | 53 | producer.route(data, id).unwrap(); 54 | let _ = consumer.try_recv(&mut msg); 55 | } 56 | }); 57 | }) 58 | .with_function("radio-dish", move |b| { 59 | let producer = RadioBuilder::new() 60 | .bind(&*ADDR) 61 | .send_hwm(HWM) 62 | .build() 63 | .unwrap(); 64 | 65 | let bound = producer.last_endpoint().unwrap(); 66 | let consumer = DishBuilder::new() 67 | .connect(bound) 68 | .recv_hwm(HWM) 69 | .build() 70 | .unwrap(); 71 | 72 | let mut msg = Msg::new(); 73 | 74 | b.iter(|| { 75 | let dataset = gen_dataset(MSG_AMOUNT, MSG_SIZE); 76 | for data in dataset { 77 | let data: Msg = data.into(); 78 | 79 | producer.transmit(data, &*GROUP).unwrap(); 80 | let _ = consumer.try_recv(&mut msg); 81 | } 82 | }); 83 | }) 84 | .with_function("scatter-gather", move |b| { 85 | let producer = ScatterBuilder::new() 86 | .bind(&*ADDR) 87 | .send_hwm(HWM) 88 | .build() 89 | .unwrap(); 90 | 91 | let bound = producer.last_endpoint().unwrap(); 92 | let consumer = GatherBuilder::new() 93 | .connect(bound) 94 | .recv_hwm(HWM) 95 | .build() 96 | .unwrap(); 97 | 98 | let mut msg = Msg::new(); 99 | 100 | b.iter(|| { 101 | let dataset = gen_dataset(MSG_AMOUNT, MSG_SIZE); 102 | for data in dataset { 103 | let data: Msg = data.into(); 104 | 105 | producer.send(data).unwrap(); 106 | let _ = consumer.try_recv(&mut msg); 107 | } 108 | }); 109 | }) 110 | .throughput(Throughput::Bytes((MSG_AMOUNT * MSG_SIZE) as u64)) 111 | .sample_size(SAMPLE_SIZE) 112 | .measurement_time(Duration::from_secs(30)), 113 | ); 114 | } 115 | -------------------------------------------------------------------------------- /libzmq/src/old.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | addr::Endpoint, 3 | core::{GetRawSocket, RawSocket, RawSocketType}, 4 | error::*, 5 | CtxHandle, Msg, 6 | }; 7 | use libzmq_sys as sys; 8 | use sys::errno; 9 | 10 | use libc::c_int; 11 | 12 | use std::os::raw::c_void; 13 | 14 | fn send( 15 | mut_sock_ptr: *mut c_void, 16 | mut msg: Msg, 17 | more: bool, 18 | ) -> Result<(), Error> { 19 | let flags = if more { sys::ZMQ_SNDMORE } else { 0 }; 20 | let rc = unsafe { 21 | sys::zmq_msg_send(msg.as_mut_ptr(), mut_sock_ptr, flags as c_int) 22 | }; 23 | 24 | if rc == -1 { 25 | let errno = unsafe { sys::zmq_errno() }; 26 | let err = match errno { 27 | errno::ETERM => Error::new(ErrorKind::InvalidCtx), 28 | errno::EINTR => Error::new(ErrorKind::Interrupted), 29 | errno::EAGAIN => Error::new(ErrorKind::WouldBlock), 30 | _ => panic!(msg_from_errno(errno)), 31 | }; 32 | 33 | Err(err) 34 | } else { 35 | Ok(()) 36 | } 37 | } 38 | 39 | fn recv(mut_sock_ptr: *mut c_void, msg: &mut Msg) -> Result<(), Error> { 40 | let rc = unsafe { sys::zmq_msg_recv(msg.as_mut_ptr(), mut_sock_ptr, 0) }; 41 | 42 | if rc == -1 { 43 | let errno = unsafe { sys::zmq_errno() }; 44 | let err = match errno { 45 | errno::ETERM => Error::new(ErrorKind::InvalidCtx), 46 | errno::EINTR => Error::new(ErrorKind::Interrupted), 47 | errno::EAGAIN => Error::new(ErrorKind::WouldBlock), 48 | _ => panic!(msg_from_errno(errno)), 49 | }; 50 | 51 | Err(err) 52 | } else { 53 | Ok(()) 54 | } 55 | } 56 | 57 | #[allow(dead_code)] 58 | pub(crate) enum OldSocketType { 59 | Router, 60 | Dealer, 61 | Pair, 62 | Sub, 63 | } 64 | 65 | impl From for RawSocketType { 66 | fn from(socket: OldSocketType) -> Self { 67 | match socket { 68 | OldSocketType::Router => RawSocketType::Router, 69 | OldSocketType::Dealer => RawSocketType::Dealer, 70 | OldSocketType::Pair => RawSocketType::Pair, 71 | OldSocketType::Sub => RawSocketType::Sub, 72 | } 73 | } 74 | } 75 | 76 | #[derive(Debug, PartialEq, Eq)] 77 | pub(crate) struct OldSocket { 78 | inner: RawSocket, 79 | } 80 | 81 | impl OldSocket { 82 | pub(crate) fn with_ctx( 83 | socket: OldSocketType, 84 | handle: CtxHandle, 85 | ) -> Result { 86 | let inner = RawSocket::with_ctx(socket.into(), handle)?; 87 | 88 | Ok(Self { inner }) 89 | } 90 | 91 | pub(crate) fn bind(&mut self, endpoint: E) -> Result<(), Error> 92 | where 93 | E: Into, 94 | { 95 | let endpoint = endpoint.into(); 96 | self.inner.bind(&endpoint) 97 | } 98 | 99 | pub(crate) fn send(&mut self, msg: M, more: bool) -> Result<(), Error> 100 | where 101 | M: Into, 102 | { 103 | send(self.inner.as_mut_ptr(), msg.into(), more) 104 | } 105 | 106 | pub(crate) fn send_multipart(&mut self, iter: I) -> Result<(), Error> 107 | where 108 | I: IntoIterator, 109 | M: Into, 110 | { 111 | let mut last = None; 112 | 113 | for msg in iter.into_iter().map(M::into) { 114 | if last == None { 115 | last = Some(msg); 116 | } else { 117 | self.send(last.take().unwrap(), true)?; 118 | last = Some(msg); 119 | } 120 | } 121 | if let Some(msg) = last { 122 | self.send(msg, false)?; 123 | } 124 | Ok(()) 125 | } 126 | 127 | pub(crate) fn recv_msg_multipart(&mut self) -> Result, Error> { 128 | let mut vec = Vec::new(); 129 | loop { 130 | let mut msg = Msg::new(); 131 | recv(self.inner.as_mut_ptr(), &mut msg)?; 132 | let has_more = msg.has_more(); 133 | vec.push(msg); 134 | if !has_more { 135 | break; 136 | } 137 | } 138 | Ok(vec) 139 | } 140 | } 141 | 142 | unsafe impl Send for OldSocket {} 143 | 144 | impl GetRawSocket for OldSocket { 145 | fn raw_socket(&self) -> &RawSocket { 146 | &self.inner 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /libzmq-book/src/basics/methods.md: -------------------------------------------------------------------------------- 1 | # Basics 2 | 3 | These are the basic methods required to use a socket. 4 | 5 | ## Connect 6 | 7 | The socket [connect] method is use to connect to a peer socket bound 8 | at an endpoint to communicate with a peer. Usually a client socket will connect 9 | to a server socket, but it could be the other way around. 10 | 11 | ```rust 12 | let addr: TcpAddr = "8.8.8.8:420".try_into()?; 13 | client.connect(addr)?; 14 | ``` 15 | 16 | Calling [connect] on a socket is not guaranteed to connect to the peer right 17 | away. Usually, the actual connect call will be delayed until it is needed 18 | (e.g. when sending a message). 19 | 20 | Connections in `ZeroMQ` are different from traditional connections is the 21 | sense that they automatically handle failure. For instance, if a connection 22 | fails because of a network error, it will be automatically reconnected if 23 | possible. 24 | 25 | Furthermore, to successfully connect to a peer, the handshake corresponding to 26 | the mechanism used must succeed. This handshake is also done in the background 27 | and might fail for various reasons. 28 | 29 | ## Bind 30 | 31 | The socket [bind] method is used to bind a local endpoint to accept connections 32 | from peers. Usually a server socket will bind a known endpoint so that other socket 33 | can connect to it. 34 | 35 | ```rust 36 | let addr: TcpAddr = "127.0.0.1:*".try_into()?; 37 | server.bind(addr)?; 38 | ``` 39 | 40 | Contrairy to [connect], [bind] will attempt to bind to the endpoint straight 41 | away. If the bind call succeeds, the socket will start to accept connections 42 | attempts to this endpoint. 43 | 44 | ## Send 45 | 46 | This a fundamental operation of a socket used to transfert messages to another 47 | socket. To be able to send messages, a socket must implement the [SendMsg] trait. 48 | 49 | ```rust 50 | client.send(msg)?; 51 | ``` 52 | 53 | When [send] is called on a socket, it will attempt to queue the message 54 | to its outgoing buffer. If the buffer is full, meaning it has reached the 55 | high water mark, the operation will block. If the [send_timeout] is set 56 | to `Period::Infinite`, the operation will block until the buffer can accomodate for 57 | the message. Otherwise if the timeout is set to `Period::Finite(Duration)`, it 58 | will attempt to queue the message for that duration and if it fails, 59 | return [WouldBlock]. 60 | 61 | There is also the [try_send] method which will return with [WouldBlock] immediately 62 | if it cannot queue the message. 63 | 64 | Queued messages are send by a background I/O thread to the peer socket. 65 | For the messages to be actually sent two conditions must be met: 66 | * The connection with the peer socket is up. 67 | * The peer socket can receive messages (its incoming buffer is not full). 68 | 69 | Conceptually, a full outgoing buffer can mean many things: 70 | * The connection has crashed temporarily (network error etc.) 71 | * The peer socket has crashed and is restarting. 72 | * The peer socket receives message slower than we can send 73 | them (thus this is a back throttling mechanism) 74 | * Etc. 75 | 76 | Many of these scenarios are conceptually indistinguishable. Therefore 77 | the user has to decide what to do depending on the context. 78 | 79 | ## Recv 80 | 81 | You guessed it, [recv] is a socket operation used to receive messages from 82 | another socket. To be able to receive messages, a socket must implement 83 | the [RecvMsg] trait. 84 | 85 | ```rust 86 | let msg = client.recv_msg()?; 87 | ``` 88 | 89 | Calling [recv] on a socket will attempt to extract a message from its 90 | incoming buffer. If the incoming buffer is empty, the operation will 91 | block until a mesage is received in the buffer. If the [recv_timeout] 92 | is specified, it will try to extract a message from the buffer for the 93 | given duration and return [WouldBlock] if it failed. 94 | 95 | There is also the [try_recv] method which, similarly to [try_send], will return 96 | with [WouldBlock] immediately if it cannot queue the message. 97 | 98 | The incoming buffer receives message from the background I/O thread from the 99 | peer socket. For the messages to be actually received two conditions must be met: 100 | * The connection with the peer socket is up. 101 | * The incoming buffer is not full. 102 | 103 | Conceptually, an empty incoming buffer can mean many things: 104 | * The socket can receive messages faster than what the peer can send. 105 | * The peer has no messages to send. 106 | * The connection has a network error. 107 | * The peer has crashed 108 | * Etc. 109 | 110 | Like before, many of these scenarios are conceptually indistinguishable. 111 | We have to decide what to do depending on the context. 112 | 113 | [send]: https://docs.rs/libzmq/0.1/libzmq/prelude/trait.SendMsg.html#method.send 114 | [try_send]: https://docs.rs/libzmq/0.1/libzmq/prelude/trait.SendMsg.html#method.try_send 115 | [SendMsg]: https://docs.rs/libzmq/0.1/libzmq/prelude/trait.SendMsg.html 116 | [recv]: https://docs.rs/libzmq/0.1/libzmq/prelude/trait.RecvMsg.html#method.recv 117 | [try_recv]: https://docs.rs/libzmq/0.1/libzmq/prelude/trait.RecvMsg.html#method.try_recv 118 | [RecvMsg]: https://docs.rs/libzmq/0.1/libzmq/prelude/trait.RecvMsg.html 119 | [connect]: https://docs.rs/libzmq/0.1/libzmq/prelude/trait.Socket.html#method.connect 120 | [bind]: https://docs.rs/libzmq/0.1/libzmq/prelude/trait.Socket.html#method.bind 121 | [send_timeout]: https://docs.rs/libzmq/0.1/libzmq/prelude/trait.SendMsg.html#method.send_timeout 122 | [recv_timeout]: https://docs.rs/libzmq/0.1/libzmq/prelude/trait.RecvMsg.html#method.recv_timeout 123 | -------------------------------------------------------------------------------- /libzmq/src/auth/mod.rs: -------------------------------------------------------------------------------- 1 | //! Socket authentication and encryption. 2 | //! 3 | //! In *libzmq* each `Ctx` as a dedicated background 4 | //! `AuthHandler` thread which will handle authentication and encryption 5 | //! for all sockets within the same context. 6 | //! 7 | //! For two sockets to connect to 8 | //! each other, they must have matching `Mechanism`. Then authentication is 9 | //! performed depending on the configuration of the `AuthHandler`. This 10 | //! configuration can be modified by using a `AuthClient` which send commands 11 | //! to the handler. 12 | 13 | pub(crate) mod client; 14 | mod curve; 15 | pub(crate) mod server; 16 | 17 | pub use client::{AuthBuilder, AuthClient}; 18 | pub use curve::*; 19 | pub use server::{StatusCode, StatusCodeParseError}; 20 | 21 | use crate::prelude::TryFrom; 22 | 23 | use serde::{Deserialize, Serialize}; 24 | use thiserror::Error; 25 | 26 | use std::option; 27 | 28 | /// Credentials for a `PLAIN` client. 29 | /// # Example 30 | /// ``` 31 | /// use libzmq::auth::*; 32 | /// 33 | /// let creds = PlainClientCreds::new("user", "pass"); 34 | /// ``` 35 | #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] 36 | pub struct PlainClientCreds { 37 | pub(crate) username: String, 38 | pub(crate) password: String, 39 | } 40 | 41 | impl PlainClientCreds { 42 | /// Create a new `PlainClientCreds` from a username and password. 43 | pub fn new(username: U, password: P) -> Self 44 | where 45 | U: Into, 46 | P: Into, 47 | { 48 | Self { 49 | username: username.into(), 50 | password: password.into(), 51 | } 52 | } 53 | 54 | /// Returns a reference to the username. 55 | pub fn username(&self) -> &str { 56 | &self.username 57 | } 58 | 59 | /// Returns a reference to the password. 60 | pub fn password(&self) -> &str { 61 | &self.password 62 | } 63 | } 64 | 65 | impl<'a> From<&'a PlainClientCreds> for PlainClientCreds { 66 | fn from(creds: &'a PlainClientCreds) -> Self { 67 | creds.to_owned() 68 | } 69 | } 70 | 71 | impl<'a> From<&'a PlainClientCreds> for Mechanism { 72 | fn from(creds: &'a PlainClientCreds) -> Self { 73 | Self::from(creds.to_owned()) 74 | } 75 | } 76 | 77 | impl From for Mechanism { 78 | fn from(creds: PlainClientCreds) -> Self { 79 | Mechanism::PlainClient(creds) 80 | } 81 | } 82 | 83 | impl IntoIterator for PlainClientCreds { 84 | type Item = Self; 85 | type IntoIter = option::IntoIter; 86 | 87 | fn into_iter(self) -> Self::IntoIter { 88 | Some(self).into_iter() 89 | } 90 | } 91 | 92 | impl<'a> IntoIterator for &'a PlainClientCreds { 93 | type Item = Self; 94 | type IntoIter = option::IntoIter; 95 | 96 | fn into_iter(self) -> Self::IntoIter { 97 | Some(self).into_iter() 98 | } 99 | } 100 | 101 | /// A socket's `Mechanism`. 102 | /// 103 | /// The `Mechanism` is used to configure the authentication and encryption 104 | /// strategy to use between two connected sockets. 105 | /// 106 | /// By default the `Null` 107 | /// mechanism is used, meaning there is no attempt authentication nor encryption. 108 | #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] 109 | #[serde(rename_all = "snake_case")] 110 | pub enum Mechanism { 111 | /// No encryption or authentication. 112 | /// 113 | /// A socket using the `Null` mechanism connect or accept connections from 114 | /// sockets also using the `Null` mechanism. 115 | Null, 116 | /// Plain text authentication with no encryption. 117 | /// 118 | /// A socket using the `PlainClient` mechanism connects to sockets using 119 | /// the `PlainServer` mechanism. 120 | PlainClient(PlainClientCreds), 121 | /// Plain text authentication with no encryption. 122 | /// 123 | /// A socket using the `PlainServer` mechanism accept connections from 124 | /// sockets using the `PlainClient` mechanism. 125 | PlainServer, 126 | /// Secure authentication and encryption using the `Curve` public-key 127 | /// mechanism. 128 | /// 129 | /// By default authentication is done using a whitelist of public keys. 130 | /// However, authentication can be disabled. 131 | /// 132 | /// A socket using the `CurveClient` mechanism connects to socket using the 133 | /// `CurveServer` mechanism. 134 | CurveClient(CurveClientCreds), 135 | /// Secure authentication and encryption using the `Curve` public-key 136 | /// mechanism. 137 | /// 138 | /// A socket using the `CurveServer` mechanism accepts connections from 139 | /// sockets using the `CurveClient` mechanism. 140 | CurveServer(CurveServerCreds), 141 | } 142 | 143 | impl<'a> From<&'a Mechanism> for Mechanism { 144 | fn from(mechanism: &'a Mechanism) -> Self { 145 | mechanism.to_owned() 146 | } 147 | } 148 | 149 | impl Default for Mechanism { 150 | fn default() -> Self { 151 | Mechanism::Null 152 | } 153 | } 154 | 155 | #[derive(Debug, Copy, Clone, PartialEq, Eq)] 156 | pub(crate) enum MechanismName { 157 | Null, 158 | Plain, 159 | Curve, 160 | } 161 | 162 | #[derive(Debug, Error)] 163 | #[error("unsupported mechanism")] 164 | pub(crate) struct InvalidMechanismName; 165 | 166 | impl<'a> TryFrom<&'a str> for MechanismName { 167 | type Error = InvalidMechanismName; 168 | 169 | fn try_from(s: &'a str) -> Result { 170 | match s { 171 | "NULL" => Ok(MechanismName::Null), 172 | "PLAIN" => Ok(MechanismName::Plain), 173 | "CURVE" => Ok(MechanismName::Curve), 174 | _ => Err(InvalidMechanismName), 175 | } 176 | } 177 | } 178 | -------------------------------------------------------------------------------- /libzmq/src/core/heartbeat.rs: -------------------------------------------------------------------------------- 1 | use super::{private, GetRawSocket, Period, RawSocket}; 2 | use crate::error::Error; 3 | use Period::*; 4 | 5 | use serde::{Deserialize, Serialize}; 6 | 7 | use std::{sync::MutexGuard, time::Duration}; 8 | 9 | fn set_heartbeat( 10 | raw_socket: &RawSocket, 11 | maybe: Option, 12 | mut mutex: MutexGuard>, 13 | ) -> Result<(), Error> { 14 | if *mutex == maybe { 15 | return Ok(()); 16 | } 17 | 18 | if let Some(heartbeat) = &maybe { 19 | raw_socket.set_heartbeat_interval(heartbeat.interval)?; 20 | if let Finite(timeout) = heartbeat.timeout { 21 | raw_socket.set_heartbeat_timeout(timeout)?; 22 | } 23 | if let Finite(ttl) = heartbeat.ttl { 24 | raw_socket.set_heartbeat_timeout(ttl)?; 25 | } 26 | } else { 27 | raw_socket.set_heartbeat_interval(Duration::from_millis(0))?; 28 | raw_socket.set_heartbeat_timeout(Duration::from_millis(0))?; 29 | raw_socket.set_heartbeat_ttl(Duration::from_millis(0))?; 30 | } 31 | 32 | *mutex = maybe; 33 | Ok(()) 34 | } 35 | 36 | /// Socket heartbeating configuration. 37 | /// 38 | /// # Example 39 | /// ``` 40 | /// use libzmq::Heartbeat; 41 | /// use std::time::Duration; 42 | /// 43 | /// let duration = Duration::from_millis(300); 44 | /// 45 | /// let hb = Heartbeat::new(duration) 46 | /// .add_timeout(2 * duration); 47 | /// ``` 48 | #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] 49 | pub struct Heartbeat { 50 | #[serde(with = "humantime_serde")] 51 | pub(crate) interval: Duration, 52 | pub(crate) timeout: Period, 53 | pub(crate) ttl: Period, 54 | } 55 | 56 | impl Heartbeat { 57 | /// Create a new `Heartbeat` from the given interval. 58 | /// 59 | /// This interval specifies the duration between each heartbeat. 60 | pub fn new(interval: D) -> Self 61 | where 62 | D: Into, 63 | { 64 | Self { 65 | interval: interval.into(), 66 | timeout: Infinite, 67 | ttl: Infinite, 68 | } 69 | } 70 | 71 | /// Returns the interval between each heartbeat. 72 | pub fn interval(&self) -> Duration { 73 | self.interval 74 | } 75 | 76 | /// Set a timeout for the `Heartbeat`. 77 | /// 78 | /// This timeout specifies how long to wait before timing out a connection 79 | /// with a peer for not receiving any traffic. 80 | pub fn add_timeout(mut self, timeout: D) -> Self 81 | where 82 | D: Into, 83 | { 84 | self.timeout = Finite(timeout.into()); 85 | self 86 | } 87 | 88 | /// Returns the heartbeat timeout. 89 | pub fn timeout(&self) -> Period { 90 | self.timeout 91 | } 92 | 93 | /// Set a ttl for the `Heartbeat` 94 | /// 95 | /// This ttl is equivalent to a `heartbeat_timeout` for the remote 96 | /// side for this specific connection. 97 | pub fn add_ttl(mut self, ttl: D) -> Self 98 | where 99 | D: Into, 100 | { 101 | self.ttl = Finite(ttl.into()); 102 | self 103 | } 104 | 105 | /// Returns the heartbeat ttl. 106 | pub fn ttl(&self) -> Period { 107 | self.ttl 108 | } 109 | } 110 | 111 | impl<'a> From<&'a Heartbeat> for Heartbeat { 112 | fn from(hb: &'a Heartbeat) -> Self { 113 | hb.to_owned() 114 | } 115 | } 116 | 117 | /// A trait that indicates that the socket supports configurable 118 | /// heartbeating. 119 | /// 120 | /// The actual heartbeating will be done by the engine in the background. 121 | /// 122 | /// Only applies to connection based transports such as `TCP`. 123 | /// 124 | /// # Contract 125 | /// * timeout and interval duration in ms cannot exceed i32::MAX 126 | /// * ttl duration in ms cannot exceed 6553599 127 | /// 128 | /// # Default value 129 | /// `None` (no heartbeating) 130 | /// 131 | /// # Return Errors 132 | /// * [`InvalidInput`]: (if contract is not respected) 133 | /// 134 | /// # Example 135 | /// ``` 136 | /// # fn main() -> Result<(), anyhow::Error> { 137 | /// use libzmq::{prelude::*, Client, Heartbeat, auth::*}; 138 | /// use std::time::Duration; 139 | /// 140 | /// let client = Client::new()?; 141 | /// assert_eq!(client.heartbeat(), None); 142 | /// 143 | /// let duration = Duration::from_millis(300); 144 | /// let hb = Heartbeat::new(duration) 145 | /// .add_timeout(2 * duration); 146 | /// let expected = hb.clone(); 147 | /// 148 | /// client.set_heartbeat(Some(hb))?; 149 | /// assert_eq!(client.heartbeat(), Some(expected)); 150 | /// # 151 | /// # Ok(()) 152 | /// # } 153 | /// ``` 154 | /// 155 | /// [`Mechanism`]: ../auth/enum.Mechanism.html 156 | pub trait Heartbeating: GetRawSocket { 157 | /// Returns a the socket's heartbeating configuration. 158 | fn heartbeat(&self) -> Option { 159 | self.raw_socket().heartbeat().lock().unwrap().to_owned() 160 | } 161 | 162 | /// Sets the socket's heartbeating configuration. 163 | fn set_heartbeat(&self, maybe: Option) -> Result<(), Error> { 164 | let raw_socket = self.raw_socket(); 165 | let mutex = raw_socket.heartbeat().lock().unwrap(); 166 | 167 | set_heartbeat(raw_socket, maybe, mutex) 168 | } 169 | } 170 | 171 | #[derive(Debug, Default, Clone, PartialEq, Eq, Hash)] 172 | #[doc(hidden)] 173 | pub struct HeartbeatingConfig { 174 | pub(crate) heartbeat: Option, 175 | } 176 | 177 | impl HeartbeatingConfig { 178 | pub(crate) fn apply( 179 | &self, 180 | socket: &S, 181 | ) -> Result<(), Error> { 182 | socket.set_heartbeat(self.heartbeat.clone()) 183 | } 184 | } 185 | 186 | #[doc(hidden)] 187 | pub trait GetHeartbeatingConfig: private::Sealed { 188 | fn heartbeat_config(&self) -> &HeartbeatingConfig; 189 | 190 | fn heartbeat_config_mut(&mut self) -> &mut HeartbeatingConfig; 191 | } 192 | 193 | impl GetHeartbeatingConfig for HeartbeatingConfig { 194 | fn heartbeat_config(&self) -> &HeartbeatingConfig { 195 | self 196 | } 197 | 198 | fn heartbeat_config_mut(&mut self) -> &mut HeartbeatingConfig { 199 | self 200 | } 201 | } 202 | 203 | /// A set of provided methods for a socket configuration. 204 | pub trait ConfigureHeartbeating: GetHeartbeatingConfig { 205 | fn heartbeat(&self) -> Option<&Heartbeat> { 206 | self.heartbeat_config().heartbeat.as_ref() 207 | } 208 | 209 | fn set_heartbeat(&mut self, maybe: Option) { 210 | self.heartbeat_config_mut().heartbeat = maybe; 211 | } 212 | } 213 | 214 | impl ConfigureHeartbeating for HeartbeatingConfig {} 215 | 216 | /// A set of provided methods for a socket builder. 217 | pub trait BuildHeartbeating: GetHeartbeatingConfig + Sized { 218 | fn heartbeat(&mut self, heartbeat: H) -> &mut Self 219 | where 220 | H: Into, 221 | { 222 | self.heartbeat_config_mut() 223 | .set_heartbeat(Some(heartbeat.into())); 224 | self 225 | } 226 | } 227 | -------------------------------------------------------------------------------- /libzmq/src/error.rs: -------------------------------------------------------------------------------- 1 | use crate::{addr::AddrParseError, group::GroupParseError}; 2 | use libzmq_sys as sys; 3 | use thiserror::Error; 4 | 5 | use std::{convert::Infallible, ffi, fmt, io, str}; 6 | 7 | /// An error with a kind and a msg. 8 | /// 9 | /// An `Error` contains a [`ErrorKind`] which gives context on the error cause, 10 | /// as well as an `Option` which may be used to prevent the loss of data 11 | /// in case of a failed `send` function call. When no `T` is specified, it 12 | /// defaults to `()`. 13 | /// 14 | /// # Usage example 15 | /// ``` 16 | /// # fn main() -> Result<(), anyhow::Error> { 17 | /// use libzmq::{prelude::*, *, ErrorKind::*}; 18 | /// 19 | /// // This client has no peer and is therefore in mute state. 20 | /// let client = Client::new()?; 21 | /// 22 | /// // This means that the following call would block. 23 | /// if let Err(mut err) = client.try_send("msg") { 24 | /// match err.kind() { 25 | /// // This covers all the possible error scenarios for this socket type. 26 | /// // Normally we would process each error differently. 27 | /// WouldBlock | InvalidCtx | Interrupted => { 28 | /// // Here we get back the message we tried to send. 29 | /// let msg = err.take().unwrap(); 30 | /// assert_eq!("msg", msg.to_str()?); 31 | /// } 32 | /// // Since `ErrorKind` is non-exhaustive, need an 33 | /// // extra wildcard arm to account for potential future variants. 34 | /// _ => panic!("unhandled error : {}", err), 35 | /// } 36 | /// } 37 | /// # 38 | /// # Ok(()) 39 | /// # } 40 | /// ``` 41 | /// 42 | /// [`ErrorKind`]: enum.ErrorKind.html 43 | pub struct Error { 44 | kind: ErrorKind, 45 | content: Option, 46 | } 47 | 48 | impl Error { 49 | /// Creates a new `Error` from an `ErrorKind`. 50 | /// 51 | /// The `content` field will be `None`. 52 | pub(crate) fn new(kind: ErrorKind) -> Self { 53 | Self { 54 | kind, 55 | content: None, 56 | } 57 | } 58 | 59 | /// Creates a new `Error` from an `ErrorKind` and some content. 60 | pub(crate) fn with_content(kind: ErrorKind, content: T) -> Self { 61 | Self { 62 | kind, 63 | content: Some(content), 64 | } 65 | } 66 | 67 | /// Returns the kind of error. 68 | pub fn kind(&self) -> ErrorKind { 69 | self.kind 70 | } 71 | 72 | #[deprecated(since = "0.2.1", note = "please use `get` instead")] 73 | pub fn content(&self) -> Option<&T> { 74 | self.content.as_ref() 75 | } 76 | 77 | /// Returns a reference to the content held by the error. 78 | pub fn get(&self) -> Option<&T> { 79 | self.content.as_ref() 80 | } 81 | 82 | #[deprecated(since = "0.2.1", note = "please use `take` instead")] 83 | pub fn take_content(&mut self) -> Option { 84 | self.content.take() 85 | } 86 | 87 | /// Takes the content held by the error, if any, replacing with `None`. 88 | pub fn take(&mut self) -> Option { 89 | self.content.take() 90 | } 91 | 92 | /// This allows casting to any `Error` by replacing the content 93 | /// of the error with `None`. 94 | /// 95 | /// This is not implemented as `Into>` to be explicit since 96 | /// information is lost in the conversion. 97 | pub fn cast(self) -> Error { 98 | Error { 99 | kind: self.kind, 100 | content: None, 101 | } 102 | } 103 | } 104 | 105 | impl std::error::Error for Error { 106 | fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { 107 | self.kind.source() 108 | } 109 | } 110 | 111 | impl fmt::Debug for Error { 112 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 113 | f.debug_struct("Error").field("kind", &self.kind).finish() 114 | } 115 | } 116 | 117 | impl fmt::Display for Error { 118 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 119 | fmt::Display::fmt(&self.kind, f) 120 | } 121 | } 122 | 123 | impl From for Error { 124 | fn from(_error: GroupParseError) -> Self { 125 | Error::new(ErrorKind::InvalidInput("unable to parse group")) 126 | } 127 | } 128 | 129 | impl From for Error { 130 | fn from(error: AddrParseError) -> Self { 131 | Error::new(ErrorKind::InvalidInput(error.msg())) 132 | } 133 | } 134 | 135 | impl From for Error { 136 | fn from(_error: Infallible) -> Self { 137 | unreachable!() 138 | } 139 | } 140 | 141 | impl From> for io::Error { 142 | fn from(err: Error) -> io::Error { 143 | use ErrorKind::*; 144 | match err.kind() { 145 | WouldBlock => io::Error::from(io::ErrorKind::WouldBlock), 146 | HostUnreachable => { 147 | io::Error::new(io::ErrorKind::BrokenPipe, "host unreachable") 148 | } 149 | InvalidCtx => { 150 | io::Error::new(io::ErrorKind::Other, "context terminated") 151 | } 152 | Interrupted => io::Error::from(io::ErrorKind::Interrupted), 153 | AddrInUse => io::Error::from(io::ErrorKind::AddrInUse), 154 | AddrNotAvailable => { 155 | io::Error::from(io::ErrorKind::AddrNotAvailable) 156 | } 157 | NotFound(msg) => io::Error::new(io::ErrorKind::NotFound, msg), 158 | SocketLimit => { 159 | io::Error::new(io::ErrorKind::Other, "socket limit reached") 160 | } 161 | InvalidInput(msg) => { 162 | io::Error::new(io::ErrorKind::InvalidInput, msg) 163 | } 164 | } 165 | } 166 | } 167 | 168 | /// Used to give context to an `Error`. 169 | /// 170 | /// [`Error`]: enum.Error.html 171 | #[derive(Debug, Error, Copy, Clone, PartialEq, Eq, Hash)] 172 | #[non_exhaustive] 173 | pub enum ErrorKind { 174 | /// Non-blocking mode was requested and the message cannot be sent 175 | /// without blocking 176 | #[error("operation would block")] 177 | WouldBlock, 178 | /// Occurs when a [`Server`] socket cannot route a message 179 | /// to a host. 180 | /// 181 | /// [`Server`]: socket/struct.Server.html 182 | #[error("host unreachable")] 183 | HostUnreachable, 184 | /// The context used in the operation was invalidated. Either the 185 | /// context is being terminated, or was already terminated. 186 | /// 187 | /// This error only occurs if: 188 | /// * The `Ctx` is being dropped or was previously dropped. 189 | /// * [`shutdown`] was called. 190 | /// 191 | /// [`Ctx`]: ../ctx/struct.Ctx.html 192 | /// [`shutdown`]: ../ctx/struct.Ctx.html#method.terminate 193 | #[error("context invalidated")] 194 | InvalidCtx, 195 | /// The operation was interrupted by a OS signal delivery. 196 | #[error("interrupted by signal")] 197 | Interrupted, 198 | /// The addr cannot be bound because it is already in use. 199 | #[error("addr in use")] 200 | AddrInUse, 201 | /// A nonexistent interface was requested or the requested address was 202 | /// not local. 203 | #[error("addr not available")] 204 | AddrNotAvailable, 205 | /// An entity was not found. 206 | /// 207 | /// Contains information on the specific entity. 208 | #[error("not found: {}", _0)] 209 | NotFound(&'static str), 210 | /// The open socket limit was reached. 211 | #[error("open socket limit was reached")] 212 | SocketLimit, 213 | /// The user did not follow its usage contract and provided invalid inputs. 214 | /// 215 | /// Contains information on the specific contract breach. 216 | #[error("invalid input: {}", _0)] 217 | InvalidInput(&'static str), 218 | } 219 | 220 | pub(crate) fn msg_from_errno(x: i32) -> String { 221 | unsafe { 222 | let s = sys::zmq_strerror(x); 223 | format!( 224 | "unknown error [{}]: {}", 225 | x, 226 | str::from_utf8(ffi::CStr::from_ptr(s).to_bytes()).unwrap() 227 | ) 228 | } 229 | } 230 | -------------------------------------------------------------------------------- /libzmq/src/core/recv.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | core::{raw::GetRawSocket, *}, 3 | error::{msg_from_errno, Error, ErrorKind}, 4 | msg::Msg, 5 | }; 6 | use libzmq_sys as sys; 7 | use sys::errno; 8 | 9 | use std::{ 10 | os::raw::{c_int, c_void}, 11 | time::Duration, 12 | }; 13 | 14 | fn recv( 15 | socket_ptr: *mut c_void, 16 | msg: &mut Msg, 17 | no_block: bool, 18 | ) -> Result<(), Error> { 19 | let rc = unsafe { 20 | sys::zmq_msg_recv(msg.as_mut_ptr(), socket_ptr, no_block as c_int) 21 | }; 22 | 23 | if rc == -1 { 24 | let errno = unsafe { sys::zmq_errno() }; 25 | let err = match errno { 26 | errno::EAGAIN => Error::new(ErrorKind::WouldBlock), 27 | errno::ENOTSUP => panic!("recv not supported by socket type"), 28 | errno::EFSM => { 29 | panic!("operation cannot be completed in current socket state") 30 | } 31 | errno::ETERM => Error::new(ErrorKind::InvalidCtx), 32 | errno::ENOTSOCK => panic!("invalid socket"), 33 | errno::EINTR => Error::new(ErrorKind::Interrupted), 34 | errno::EFAULT => panic!("invalid message"), 35 | _ => panic!(msg_from_errno(errno)), 36 | }; 37 | 38 | Err(err) 39 | } else { 40 | Ok(()) 41 | } 42 | } 43 | 44 | /// Receive atomic messages in an immutable, thread-safe fashion. 45 | /// 46 | /// Does not support multipart messages. 47 | pub trait RecvMsg: GetRawSocket { 48 | /// Retreive a message from the inbound socket queue. 49 | /// 50 | /// This operation might block until the socket receives a message or, 51 | /// if it is set, until `recv_timeout` expires. 52 | /// 53 | /// # Error 54 | /// The `Msg` is returned as the content of the `Error`. 55 | /// 56 | /// ## Possible Error Variants 57 | /// * [`WouldBlock`] (if `recv_timeout` expires) 58 | /// * [`InvalidCtx`] 59 | /// * [`Interrupted`] 60 | /// 61 | /// [`WouldBlock`]: ../enum.ErrorKind.html#variant.WouldBlock 62 | /// [`InvalidCtx`]: ../enum.ErrorKind.html#variant.InvalidCtx 63 | /// [`Interrupted`]: ../enum.ErrorKind.html#variant.Interrupted 64 | fn recv(&self, msg: &mut Msg) -> Result<(), Error> { 65 | recv(self.raw_socket().as_mut_ptr(), msg, false) 66 | } 67 | 68 | /// Try to retrieve a message from the inbound socket queue without blocking. 69 | /// 70 | /// This polls the socket to determine there is at least on inbound message in 71 | /// the socket queue. If there is, it retuns it, otherwise it errors with 72 | /// [`WouldBlock`]. 73 | /// 74 | /// # Error 75 | /// No message from the inbound queue is lost if there is an error. 76 | /// 77 | /// ## Possible Error Variants 78 | /// * [`WouldBlock`] 79 | /// * [`InvalidCtx`] 80 | /// * [`Interrupted`] 81 | /// 82 | /// [`WouldBlock`]: ../enum.ErrorKind.html#variant.WouldBlock 83 | /// [`InvalidCtx`]: ../enum.ErrorKind.html#variant.InvalidCtx 84 | /// [`Interrupted`]: ../enum.ErrorKind.html#variant.Interrupted 85 | fn try_recv(&self, msg: &mut Msg) -> Result<(), Error> { 86 | recv(self.raw_socket().as_mut_ptr(), msg, true) 87 | } 88 | 89 | /// A convenience function that allocates a [`Msg`] with the same properties 90 | /// as [`recv`]. 91 | /// 92 | /// [`recv`]: #method.recv 93 | /// [`Msg`]: ../msg/struct.Msg.html 94 | fn recv_msg(&self) -> Result { 95 | let mut msg = Msg::new(); 96 | self.recv(&mut msg)?; 97 | 98 | Ok(msg) 99 | } 100 | 101 | /// A convenience function that allocates a [`Msg`] with the same properties 102 | /// as [`try_recv`]. 103 | /// 104 | /// [`try_recv`]: #method.recv 105 | /// [`Msg`]: ../msg/struct.Msg.html 106 | fn try_recv_msg(&self) -> Result { 107 | let mut msg = Msg::new(); 108 | self.try_recv(&mut msg)?; 109 | 110 | Ok(msg) 111 | } 112 | 113 | /// The high water mark for incoming messages on the specified socket. 114 | /// 115 | /// The high water mark is a hard limit on the maximum number of 116 | /// incoming messages ØMQ shall queue in memory. 117 | /// 118 | /// If this limit has been reached the socket shall enter the `mute state`. 119 | /// 120 | /// # Default 121 | /// `1000` 122 | /// 123 | /// # Example 124 | /// ``` 125 | /// # fn main() -> Result<(), anyhow::Error> { 126 | /// use libzmq::{prelude::*, *}; 127 | /// 128 | /// let client = ClientBuilder::new().build()?; 129 | /// assert_eq!(client.recv_hwm()?, 1000); 130 | /// 131 | /// # 132 | /// # Ok(()) 133 | /// # } 134 | /// ``` 135 | fn recv_hwm(&self) -> Result { 136 | self.raw_socket().recv_hwm() 137 | } 138 | 139 | /// Set the high water mark for inbound messages on the specified socket. 140 | /// 141 | /// The high water mark is a hard limit on the maximum number of 142 | /// outstanding messages ØMQ shall queue in memory. 143 | /// 144 | /// If this limit has been reached the socket shall enter the `mute state`. 145 | /// 146 | /// # Usage Contract 147 | /// * The high water mark cannot be zero. 148 | /// 149 | /// # Returned Error 150 | /// * [`InvalidInput`] 151 | /// 152 | /// # Default 153 | /// 1000 154 | /// 155 | /// [`InvalidInput`]: ../enum.ErrorKind.html#variant.InvalidInput 156 | fn set_recv_hwm(&self, hwm: i32) -> Result<(), Error> { 157 | self.raw_socket().set_recv_hwm(hwm) 158 | } 159 | 160 | /// The timeout for [`recv`] on the socket. 161 | /// 162 | /// If some timeout is specified, [`recv`] will return 163 | /// [`WouldBlock`] after the duration is elapsed. Otherwise it 164 | /// will until a message is received. 165 | fn recv_timeout(&self) -> Result { 166 | self.raw_socket().recv_timeout() 167 | } 168 | 169 | /// Sets the timeout for [`recv`] on the socket. 170 | /// 171 | /// If some timeout is specified, [`recv`] will return 172 | /// [`WouldBlock`] after the duration is elapsed. Otherwise it 173 | /// will until a message is received. 174 | /// 175 | /// # Default 176 | /// `Infinite` 177 | fn set_recv_timeout

(&self, period: P) -> Result<(), Error> 178 | where 179 | P: Into, 180 | { 181 | self.raw_socket().set_recv_timeout(period.into()) 182 | } 183 | } 184 | 185 | #[derive(Debug, Default, Clone, PartialEq, Eq, Hash)] 186 | #[doc(hidden)] 187 | pub struct RecvConfig { 188 | pub(crate) recv_hwm: HighWaterMark, 189 | pub(crate) recv_timeout: Period, 190 | } 191 | 192 | impl RecvConfig { 193 | pub(crate) fn apply(&self, socket: &S) -> Result<(), Error> { 194 | socket.set_recv_hwm(self.recv_hwm.into())?; 195 | socket.set_recv_timeout(self.recv_timeout)?; 196 | 197 | Ok(()) 198 | } 199 | } 200 | 201 | #[doc(hidden)] 202 | pub trait GetRecvConfig: private::Sealed { 203 | fn recv_config(&self) -> &RecvConfig; 204 | 205 | fn recv_config_mut(&mut self) -> &mut RecvConfig; 206 | } 207 | 208 | /// A set of provided methods for the configuration of a socket that implements `RecvMsg`. 209 | pub trait ConfigureRecv: GetRecvConfig { 210 | fn recv_hwm(&self) -> i32 { 211 | self.recv_config().recv_hwm.into() 212 | } 213 | 214 | fn set_recv_hwm(&mut self, hwm: i32) { 215 | self.recv_config_mut().recv_hwm = HighWaterMark(hwm); 216 | } 217 | 218 | fn recv_timeout(&self) -> Period { 219 | self.recv_config().recv_timeout 220 | } 221 | 222 | fn set_recv_timeout(&mut self, period: Period) { 223 | self.recv_config_mut().recv_timeout = period; 224 | } 225 | } 226 | 227 | /// A set of provided methods for the builder of a socket that implements `RecvMsg`. 228 | pub trait BuildRecv: GetRecvConfig { 229 | fn recv_hwm(&mut self, hwm: i32) -> &mut Self { 230 | self.recv_config_mut().recv_hwm = HighWaterMark(hwm); 231 | self 232 | } 233 | 234 | fn recv_timeout(&mut self, timeout: Duration) -> &mut Self { 235 | self.recv_config_mut().recv_timeout = Finite(timeout); 236 | self 237 | } 238 | } 239 | -------------------------------------------------------------------------------- /libzmq/src/auth/mechanism.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | use crate::{prelude::TryFrom, *}; 3 | 4 | use serde::{Deserialize, Serialize}; 5 | 6 | use std::option; 7 | 8 | /// Credentials for a `PLAIN` client. 9 | /// # Example 10 | /// ``` 11 | /// use libzmq::auth::*; 12 | /// 13 | /// let creds = PlainClientCreds::new("user", "pass"); 14 | /// ``` 15 | #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] 16 | pub struct PlainClientCreds { 17 | pub(crate) username: String, 18 | pub(crate) password: String, 19 | } 20 | 21 | impl PlainClientCreds { 22 | /// Create a new `PlainClientCreds` from a username and password. 23 | pub fn new(username: U, password: P) -> Self 24 | where 25 | U: Into, 26 | P: Into, 27 | { 28 | Self { 29 | username: username.into(), 30 | password: password.into(), 31 | } 32 | } 33 | 34 | /// Returns a reference to the username. 35 | pub fn username(&self) -> &str { 36 | &self.username 37 | } 38 | 39 | /// Returns a reference to the password. 40 | pub fn password(&self) -> &str { 41 | &self.password 42 | } 43 | } 44 | 45 | impl<'a> From<&'a PlainClientCreds> for PlainClientCreds { 46 | fn from(creds: &'a PlainClientCreds) -> Self { 47 | creds.to_owned() 48 | } 49 | } 50 | 51 | impl<'a> From<&'a PlainClientCreds> for Mechanism { 52 | fn from(creds: &'a PlainClientCreds) -> Self { 53 | Self::from(creds.to_owned()) 54 | } 55 | } 56 | 57 | impl From for Mechanism { 58 | fn from(creds: PlainClientCreds) -> Self { 59 | Mechanism::PlainClient(creds) 60 | } 61 | } 62 | 63 | impl IntoIterator for PlainClientCreds { 64 | type Item = Self; 65 | type IntoIter = option::IntoIter; 66 | 67 | fn into_iter(self) -> Self::IntoIter { 68 | Some(self).into_iter() 69 | } 70 | } 71 | 72 | impl<'a> IntoIterator for &'a PlainClientCreds { 73 | type Item = Self; 74 | type IntoIter = option::IntoIter; 75 | 76 | fn into_iter(self) -> Self::IntoIter { 77 | Some(self).into_iter() 78 | } 79 | } 80 | 81 | /// Credentials for a `Curve` client. 82 | /// 83 | /// # Example 84 | /// ``` 85 | /// use libzmq::auth::*; 86 | /// 87 | /// let server_cert = CurveCert::new_unique(); 88 | /// let client_cert = CurveCert::new_unique(); 89 | /// 90 | /// let creds = CurveClientCreds::new(server_cert.public()) 91 | /// .add_cert(client_cert); 92 | /// ``` 93 | #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] 94 | pub struct CurveClientCreds { 95 | pub(crate) client: Option, 96 | pub(crate) server: CurvePublicKey, 97 | } 98 | 99 | impl CurveClientCreds { 100 | /// Create a new `CurveClientCreds` from server's `CurvePublicKey`. 101 | pub fn new(server: S) -> Self 102 | where 103 | S: Into, 104 | { 105 | Self { 106 | client: None, 107 | server: server.into(), 108 | } 109 | } 110 | 111 | /// Associates a client `CurveCert` with the credentials. 112 | pub fn add_cert(mut self, client: C) -> Self 113 | where 114 | C: Into, 115 | { 116 | self.client = Some(client.into()); 117 | self 118 | } 119 | 120 | /// Returns a reference to the client certificate. 121 | pub fn cert(&self) -> Option<&CurveCert> { 122 | self.client.as_ref() 123 | } 124 | 125 | /// Returns a reference to the server public key. 126 | pub fn server(&self) -> &CurvePublicKey { 127 | &self.server 128 | } 129 | } 130 | 131 | impl<'a> From<&'a CurveClientCreds> for CurveClientCreds { 132 | fn from(creds: &'a CurveClientCreds) -> Self { 133 | creds.to_owned() 134 | } 135 | } 136 | 137 | impl<'a> From<&'a CurveClientCreds> for Mechanism { 138 | fn from(creds: &'a CurveClientCreds) -> Self { 139 | Mechanism::CurveClient(creds.to_owned()) 140 | } 141 | } 142 | 143 | impl From for Mechanism { 144 | fn from(creds: CurveClientCreds) -> Self { 145 | Mechanism::CurveClient(creds) 146 | } 147 | } 148 | 149 | /// Credentials for a `Curve` server. 150 | /// # Example 151 | /// ``` 152 | /// use libzmq::auth::*; 153 | /// 154 | /// let server_cert = CurveCert::new_unique(); 155 | /// 156 | /// let creds = CurveServerCreds::new(server_cert.secret()); 157 | /// ``` 158 | #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] 159 | pub struct CurveServerCreds { 160 | /// The server's `CurveSecretKey`. 161 | pub(crate) secret: CurveSecretKey, 162 | } 163 | 164 | impl CurveServerCreds { 165 | /// Create a new `CurveServerCreds` from a server secret `CurveSecretKey`. 166 | pub fn new(secret: S) -> Self 167 | where 168 | S: Into, 169 | { 170 | Self { 171 | secret: secret.into(), 172 | } 173 | } 174 | 175 | /// Returns a reference to the server secret key. 176 | pub fn secret(&self) -> &CurveSecretKey { 177 | &self.secret 178 | } 179 | } 180 | 181 | impl<'a> From<&'a CurveServerCreds> for CurveServerCreds { 182 | fn from(creds: &'a CurveServerCreds) -> Self { 183 | creds.to_owned() 184 | } 185 | } 186 | 187 | impl<'a> From<&'a CurveServerCreds> for Mechanism { 188 | fn from(creds: &'a CurveServerCreds) -> Self { 189 | Mechanism::CurveServer(creds.to_owned()) 190 | } 191 | } 192 | 193 | impl From for Mechanism { 194 | fn from(creds: CurveServerCreds) -> Self { 195 | Mechanism::CurveServer(creds) 196 | } 197 | } 198 | 199 | /// A socket's `Mechanism`. 200 | /// 201 | /// The `Mechanism` is used to configure the authentication and encryption 202 | /// strategy to use between two connected sockets. 203 | /// 204 | /// By default the `Null` 205 | /// mechanism is used, meaning there is no attempt authentication nor encryption. 206 | #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] 207 | #[serde(rename_all = "snake_case")] 208 | pub enum Mechanism { 209 | /// No encryption or authentication. 210 | /// 211 | /// A socket using the `Null` mechanism connect or accept connections from 212 | /// sockets also using the `Null` mechanism. 213 | Null, 214 | /// Plain text authentication with no encryption. 215 | /// 216 | /// A socket using the `PlainClient` mechanism connects to sockets using 217 | /// the `PlainServer` mechanism. 218 | PlainClient(PlainClientCreds), 219 | /// Plain text authentication with no encryption. 220 | /// 221 | /// A socket using the `PlainServer` mechanism accept connections from 222 | /// sockets using the `PlainClient` mechanism. 223 | PlainServer, 224 | /// Secure authentication and encryption using the `Curve` public-key 225 | /// mechanism. 226 | /// 227 | /// By default authentication is done using a whitelist of public keys. 228 | /// However, authentication can be disabled. 229 | /// 230 | /// A socket using the `CurveClient` mechanism connects to socket using the 231 | /// `CurveServer` mechanism. 232 | CurveClient(CurveClientCreds), 233 | /// Secure authentication and encryption using the `Curve` public-key 234 | /// mechanism. 235 | /// 236 | /// A socket using the `CurveServer` mechanism accepts connections from 237 | /// sockets using the `CurveClient` mechanism. 238 | CurveServer(CurveServerCreds), 239 | } 240 | 241 | impl<'a> From<&'a Mechanism> for Mechanism { 242 | fn from(mechanism: &'a Mechanism) -> Self { 243 | mechanism.to_owned() 244 | } 245 | } 246 | 247 | impl Default for Mechanism { 248 | fn default() -> Self { 249 | Mechanism::Null 250 | } 251 | } 252 | 253 | #[derive(Debug, Copy, Clone, PartialEq, Eq)] 254 | pub(crate) enum MechanismName { 255 | Null, 256 | Plain, 257 | Curve, 258 | } 259 | 260 | #[derive(Debug, Error)] 261 | #[error("unsupported mechanism")] 262 | pub(crate) struct InvalidMechanismName; 263 | 264 | impl<'a> TryFrom<&'a str> for MechanismName { 265 | type Error = InvalidMechanismName; 266 | 267 | fn try_from(s: &'a str) -> Result { 268 | match s { 269 | "NULL" => Ok(MechanismName::Null), 270 | "PLAIN" => Ok(MechanismName::Plain), 271 | "CURVE" => Ok(MechanismName::Curve), 272 | _ => Err(InvalidMechanismName), 273 | } 274 | } 275 | } 276 | -------------------------------------------------------------------------------- /libzmq/src/core/send.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | core::*, 3 | error::{msg_from_errno, Error, ErrorKind}, 4 | msg::Msg, 5 | }; 6 | use libzmq_sys as sys; 7 | use sys::errno; 8 | 9 | use std::{ 10 | os::raw::{c_int, c_void}, 11 | time::Duration, 12 | }; 13 | 14 | fn send( 15 | socket_ptr: *mut c_void, 16 | mut msg: Msg, 17 | no_block: bool, 18 | ) -> Result<(), Error> { 19 | let rc = unsafe { 20 | sys::zmq_msg_send(msg.as_mut_ptr(), socket_ptr, no_block as c_int) 21 | }; 22 | 23 | if rc == -1 { 24 | let errno = unsafe { sys::zmq_errno() }; 25 | let err = match errno { 26 | errno::EAGAIN => Error::with_content(ErrorKind::WouldBlock, msg), 27 | errno::ENOTSUP => panic!("send is not supported by socket type"), 28 | errno::EINVAL => { 29 | panic!("multipart messages are not supported by socket type") 30 | } 31 | errno::EFSM => { 32 | panic!("operation cannot be completed in current socket state") 33 | } 34 | errno::ETERM => Error::with_content(ErrorKind::InvalidCtx, msg), 35 | errno::ENOTSOCK => panic!("invalid socket"), 36 | errno::EINTR => Error::with_content(ErrorKind::Interrupted, msg), 37 | errno::EFAULT => panic!("invalid message"), 38 | errno::EHOSTUNREACH => { 39 | Error::with_content(ErrorKind::HostUnreachable, msg) 40 | } 41 | _ => panic!(msg_from_errno(errno)), 42 | }; 43 | 44 | Err(err) 45 | } else { 46 | Ok(()) 47 | } 48 | } 49 | 50 | /// Send messages in a thread-safe fashion. 51 | /// 52 | /// Does not support multipart messages. 53 | pub trait SendMsg: GetRawSocket { 54 | /// Push a message into the outgoing socket queue. 55 | /// 56 | /// This operation might block until the mute state end or, 57 | /// if it set, `send_timeout` expires. 58 | /// 59 | /// If the message is a `Msg`, `Vec`, `[u8]`, or a `String`, it is not copied. 60 | /// 61 | /// # Success 62 | /// The message was queued and now belongs to ØMQ 63 | /// 64 | /// # Error 65 | /// In case of an error, the message is not queued and 66 | /// the ownership is returned. 67 | /// 68 | /// ## Possible Error Variants 69 | /// * [`WouldBlock`] (if `send_timeout` expires) 70 | /// * [`InvalidCtx`] 71 | /// * [`Interrupted`] 72 | /// * [`HostUnreachable`] (only for [`Server`] socket) 73 | /// 74 | /// [`zmq_msg_send`]: http://api.zeromq.org/master:zmq-msg-send 75 | /// [`WouldBlock`]: ../enum.ErrorKind.html#variant.WouldBlock 76 | /// [`InvalidCtx`]: ../enum.ErrorKind.html#variant.InvalidCtx 77 | /// [`Interrupted`]: ../enum.ErrorKind.html#variant.Interrupted 78 | /// [`HostUnreachable`]: ../enum.ErrorKind.html#variant.HostUnreachable 79 | /// [`Server`]: struct.Server.html 80 | fn send(&self, msg: M) -> Result<(), Error> 81 | where 82 | M: Into, 83 | { 84 | send(self.raw_socket().as_mut_ptr(), msg.into(), false) 85 | } 86 | 87 | /// Try to push a message into the outgoing socket queue without blocking. 88 | /// 89 | /// If the action would block, it returns a [`WouldBlock`] error, otherwise 90 | /// the message is pushed into the outgoing queue. 91 | /// 92 | /// If the message is a `Msg`, `Vec`, `[u8]`, or a `String`, it is not copied. 93 | /// 94 | /// # Success 95 | /// The message was queued and now belongs to ØMQ 96 | /// 97 | /// # Error 98 | /// In case of an error, the message is not queued and 99 | /// the ownership is returned. 100 | /// 101 | /// ## Possible Error Variants 102 | /// * [`WouldBlock`] 103 | /// * [`InvalidCtx`] 104 | /// * [`Interrupted`] 105 | /// * [`HostUnreachable`] (only for [`Server`] socket) 106 | /// 107 | /// [`zmq_msg_send`]: http://api.zeromq.org/master:zmq-msg-send 108 | /// [`WouldBlock`]: ../enum.ErrorKind.html#variant.WouldBlock 109 | /// [`InvalidCtx`]: ../enum.ErrorKind.html#variant.InvalidCtx 110 | /// [`Interrupted`]: ../enum.ErrorKind.html#variant.Interrupted 111 | /// [`HostUnreachable`]: ../enum.ErrorKind.html#variant.HostUnreachable 112 | /// [`Server`]: struct.Server.html 113 | fn try_send(&self, msg: M) -> Result<(), Error> 114 | where 115 | M: Into, 116 | { 117 | send(self.raw_socket().as_mut_ptr(), msg.into(), true) 118 | } 119 | 120 | /// The high water mark for outbound messages on the specified socket. 121 | /// 122 | /// The high water mark is a hard limit on the maximum number of 123 | /// outstanding messages ØMQ shall queue in memory. 124 | /// 125 | /// If this limit has been reached the socket shall enter the `mute state`. 126 | /// 127 | /// # Default 128 | /// `1000` 129 | /// 130 | /// # Example 131 | /// ``` 132 | /// # fn main() -> Result<(), anyhow::Error> { 133 | /// use libzmq::{prelude::*, *}; 134 | /// 135 | /// let client = ClientBuilder::new().build()?; 136 | /// assert_eq!(client.send_hwm()?, 1000); 137 | /// 138 | /// # 139 | /// # Ok(()) 140 | /// # } 141 | /// ``` 142 | fn send_hwm(&self) -> Result { 143 | self.raw_socket().send_hwm() 144 | } 145 | 146 | /// Set the high water mark for outbound messages on the specified socket. 147 | /// 148 | /// The high water mark is a hard limit on the maximum number of 149 | /// outstanding messages ØMQ shall queue in memory. 150 | /// 151 | /// If this limit has been reached the socket shall enter the `mute state`. 152 | /// 153 | /// # Usage Contract 154 | /// * The high water mark cannot be zero. 155 | /// 156 | /// # Returned Error 157 | /// * [`InvalidInput`](on contract violation) 158 | /// 159 | /// # Default value 160 | /// 1000 161 | /// 162 | /// [`InvalidInput`]: ../enum.ErrorKind.html#variant.InvalidInput 163 | fn set_send_hwm(&self, hwm: i32) -> Result<(), Error> { 164 | self.raw_socket().set_send_hwm(hwm) 165 | } 166 | 167 | /// Sets the timeout for [`send`] on the socket. 168 | /// 169 | /// If some timeout is specified, the [`send`] will return 170 | /// [`WouldBlock`] after the duration is elapsed. Otherwise, 171 | /// it will block until the message is sent. 172 | fn send_timeout(&self) -> Result { 173 | self.raw_socket().send_timeout() 174 | } 175 | 176 | /// Sets the timeout for [`send`] on the socket. 177 | /// 178 | /// If some timeout is specified, the [`send`] will return 179 | /// [`WouldBlock`] after the duration is elapsed. Otherwise, 180 | /// it will block until the message is sent. 181 | /// 182 | /// # Default Value 183 | /// `Infinite` 184 | /// 185 | /// # Example 186 | /// ``` 187 | /// # fn main() -> Result<(), anyhow::Error> { 188 | /// use libzmq::{prelude::*, Client, ErrorKind}; 189 | /// use std::time::Duration; 190 | /// 191 | /// let client = Client::new()?; 192 | /// client.set_send_timeout(Some(Duration::from_millis(1)))?; 193 | /// 194 | /// // The client is in mute state so the following would block forever 195 | /// // if a timeout wasn't specified. Instead, it will block for 1ms. 196 | /// let err = client.send("msg").unwrap_err(); 197 | /// assert_eq!(ErrorKind::WouldBlock, err.kind()); 198 | /// # 199 | /// # Ok(()) 200 | /// # } 201 | /// ``` 202 | fn set_send_timeout

(&self, period: P) -> Result<(), Error> 203 | where 204 | P: Into, 205 | { 206 | self.raw_socket().set_send_timeout(period.into()) 207 | } 208 | } 209 | 210 | #[derive(Debug, Default, Clone, PartialEq, Eq, Hash)] 211 | #[doc(hidden)] 212 | pub struct SendConfig { 213 | pub(crate) send_hwm: HighWaterMark, 214 | pub(crate) send_timeout: Period, 215 | } 216 | 217 | impl SendConfig { 218 | pub(crate) fn apply(&self, socket: &S) -> Result<(), Error> { 219 | socket.set_send_hwm(self.send_hwm.into())?; 220 | socket.set_send_timeout(self.send_timeout)?; 221 | 222 | Ok(()) 223 | } 224 | } 225 | 226 | #[doc(hidden)] 227 | pub trait GetSendConfig: private::Sealed { 228 | fn send_config(&self) -> &SendConfig; 229 | 230 | fn send_config_mut(&mut self) -> &mut SendConfig; 231 | } 232 | 233 | /// A set of provided methods for the configuration of socket that implements `SendMsg`. 234 | pub trait ConfigureSend: GetSendConfig { 235 | fn send_hwm(&self) -> i32 { 236 | self.send_config().send_hwm.into() 237 | } 238 | 239 | fn set_send_hwm(&mut self, hwm: i32) { 240 | self.send_config_mut().send_hwm = HighWaterMark(hwm); 241 | } 242 | 243 | fn send_timeout(&self) -> Period { 244 | self.send_config().send_timeout 245 | } 246 | 247 | fn set_send_timeout(&mut self, period: Period) { 248 | self.send_config_mut().send_timeout = period; 249 | } 250 | } 251 | 252 | /// A set of provided methods for the builder of a socket that implements `SendMsg`. 253 | pub trait BuildSend: GetSendConfig { 254 | fn send_hwm(&mut self, hwm: i32) -> &mut Self { 255 | self.send_config_mut().send_hwm = HighWaterMark(hwm); 256 | self 257 | } 258 | 259 | fn send_timeout(&mut self, timeout: Duration) -> &mut Self { 260 | self.send_config_mut().send_timeout = Finite(timeout); 261 | self 262 | } 263 | } 264 | -------------------------------------------------------------------------------- /libzmq/src/group.rs: -------------------------------------------------------------------------------- 1 | //! Message groups used by the `Radio` and `Dish` sockets. 2 | 3 | use crate::prelude::TryFrom; 4 | 5 | use serde::{Deserialize, Deserializer, Serialize, Serializer}; 6 | use thiserror::Error; 7 | 8 | use std::{ 9 | borrow::{Borrow, Cow, ToOwned}, 10 | ffi::{CStr, CString}, 11 | fmt, ops, option, str, 12 | }; 13 | 14 | /// The maximum allowed number of characters in a group. 15 | pub const MAX_GROUP_SIZE: usize = 15; 16 | 17 | /// An error returned when trying to parse a `GroupSlice` or `Group`. 18 | /// 19 | /// This error occurs from a string that exceeds [`MAX_GROUP_SIZE`] char. 20 | /// 21 | /// [`MAX_GROUP_SIZE`]: constant.MAX_GROUP_SIZE.html 22 | #[derive(Debug, Error, Copy, Clone, PartialEq, Eq, Hash)] 23 | #[error("unable to parse group: {}", msg)] 24 | pub struct GroupParseError { 25 | msg: &'static str, 26 | } 27 | 28 | impl GroupParseError { 29 | fn new(msg: &'static str) -> Self { 30 | Self { msg } 31 | } 32 | } 33 | 34 | /// A slice to a [`Group`] 35 | /// 36 | /// A `GroupSlice` cannot be constructed directly. It is either obtained from a 37 | /// [`Group`] or from [`Msg::group`]. 38 | /// 39 | /// Namely, the length this group identifier must not exceed [`MAX_GROUP_SIZE`]. 40 | /// 41 | /// [`MAX_GROUP_SIZE`]: constant.MAX_GROUP_SIZE.html 42 | /// [`Group`]: struct.Group.html 43 | /// [`Msg`]: struct.Msg.html#method.group 44 | #[derive(PartialEq, Eq, Hash)] 45 | pub struct GroupSlice { 46 | inner: CStr, 47 | } 48 | 49 | impl GroupSlice { 50 | pub(crate) fn from_c_str_unchecked(c_str: &CStr) -> &GroupSlice { 51 | unsafe { &*(c_str as *const CStr as *const GroupSlice) } 52 | } 53 | 54 | pub fn as_c_str(&self) -> &CStr { 55 | &self.inner 56 | } 57 | 58 | pub fn to_str(&self) -> Result<&str, str::Utf8Error> { 59 | self.inner.to_str() 60 | } 61 | 62 | pub fn to_string_lossy(&self) -> Cow { 63 | self.inner.to_string_lossy() 64 | } 65 | 66 | pub fn to_bytes(&self) -> &[u8] { 67 | self.inner.to_bytes() 68 | } 69 | } 70 | 71 | impl fmt::Debug for GroupSlice { 72 | fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { 73 | fmt::Debug::fmt(&self.inner, formatter) 74 | } 75 | } 76 | 77 | impl<'a> From<&'a Group> for &'a GroupSlice { 78 | fn from(s: &'a Group) -> Self { 79 | s.borrow() 80 | } 81 | } 82 | 83 | impl ToOwned for GroupSlice { 84 | type Owned = Group; 85 | 86 | fn to_owned(&self) -> Self::Owned { 87 | Group { 88 | inner: self.inner.to_owned(), 89 | } 90 | } 91 | } 92 | 93 | impl PartialEq for GroupSlice { 94 | fn eq(&self, other: &str) -> bool { 95 | self.to_bytes() == other.as_bytes() 96 | } 97 | } 98 | 99 | impl PartialEq for str { 100 | fn eq(&self, other: &GroupSlice) -> bool { 101 | self.as_bytes() == other.to_bytes() 102 | } 103 | } 104 | 105 | impl PartialEq for GroupSlice { 106 | fn eq(&self, other: &Group) -> bool { 107 | self.as_c_str() == other.as_c_str() 108 | } 109 | } 110 | 111 | impl PartialEq for &GroupSlice { 112 | fn eq(&self, other: &Group) -> bool { 113 | self.as_c_str() == other.as_c_str() 114 | } 115 | } 116 | 117 | impl AsRef for GroupSlice { 118 | fn as_ref(&self) -> &CStr { 119 | self.borrow() 120 | } 121 | } 122 | 123 | impl ops::Deref for GroupSlice { 124 | type Target = CStr; 125 | 126 | #[inline] 127 | fn deref(&self) -> &CStr { 128 | self.as_c_str() 129 | } 130 | } 131 | 132 | impl<'a> fmt::Display for &'a GroupSlice { 133 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 134 | write!(f, "{}", &self.inner.to_string_lossy()) 135 | } 136 | } 137 | 138 | impl<'a> IntoIterator for &'a GroupSlice { 139 | type Item = &'a GroupSlice; 140 | type IntoIter = option::IntoIter<&'a GroupSlice>; 141 | 142 | fn into_iter(self) -> Self::IntoIter { 143 | Some(self).into_iter() 144 | } 145 | } 146 | 147 | /// An `CString` that is a valid ØMQ group identifier. 148 | /// 149 | /// Namely, the length this group identifier must not exceed [`MAX_GROUP_SIZE`]. 150 | /// 151 | /// # Example 152 | /// ``` 153 | /// # fn main() -> Result<(), anyhow::Error> { 154 | /// use libzmq::{prelude::TryInto, Group}; 155 | /// 156 | /// let string = "abc".to_owned(); 157 | /// 158 | /// let group: Group = string.try_into()?; 159 | /// assert_eq!(group, "abc"); 160 | /// # 161 | /// # Ok(()) 162 | /// # } 163 | /// ``` 164 | /// [`MAX_GROUP_SIZE`]: constant.MAX_GROUP_SIZE.html 165 | #[derive(Debug, Clone, PartialEq, Eq, Hash)] 166 | pub struct Group { 167 | inner: CString, 168 | } 169 | 170 | impl Group { 171 | /// Creates a new `Group` from a container of bytes. 172 | /// 173 | /// A valid `Group` should not exceed [`MAX_GROUP_SIZE`] char and 174 | /// not contain any `nul` bytes. 175 | /// 176 | /// [`MAX_GROUP_SIZE`]: constant.MAX_GROUP_SIZE.html 177 | pub fn new(bytes: B) -> Result 178 | where 179 | B: Into>, 180 | { 181 | let bytes = bytes.into(); 182 | if bytes.len() > MAX_GROUP_SIZE { 183 | Err(GroupParseError::new("cannot exceed MAX_GROUP_SIZE char")) 184 | } else { 185 | let inner = CString::new(bytes) 186 | .map_err(|_| GroupParseError::new("cannot contain nul char"))?; 187 | Ok(Self { inner }) 188 | } 189 | } 190 | 191 | pub fn as_c_str(&self) -> &CStr { 192 | self.inner.as_c_str() 193 | } 194 | 195 | pub fn to_str(&self) -> Result<&str, str::Utf8Error> { 196 | self.inner.to_str() 197 | } 198 | 199 | pub fn to_string_lossy(&self) -> Cow { 200 | self.inner.to_string_lossy() 201 | } 202 | 203 | pub fn as_bytes(&self) -> &[u8] { 204 | self.inner.as_bytes() 205 | } 206 | } 207 | 208 | impl<'a> From<&'a GroupSlice> for Group { 209 | fn from(s: &'a GroupSlice) -> Self { 210 | s.to_owned() 211 | } 212 | } 213 | 214 | impl From for CString { 215 | fn from(g: Group) -> CString { 216 | g.inner 217 | } 218 | } 219 | 220 | impl<'a> From<&'a Group> for Group { 221 | fn from(g: &'a Group) -> Group { 222 | g.to_owned() 223 | } 224 | } 225 | 226 | impl TryFrom for Group { 227 | type Error = GroupParseError; 228 | fn try_from(value: String) -> Result { 229 | Self::new(value) 230 | } 231 | } 232 | 233 | impl<'a> TryFrom<&'a String> for Group { 234 | type Error = GroupParseError; 235 | fn try_from(value: &'a String) -> Result { 236 | Self::new(value.to_owned()) 237 | } 238 | } 239 | 240 | impl<'a> TryFrom<&'a str> for Group { 241 | type Error = GroupParseError; 242 | fn try_from(value: &'a str) -> Result { 243 | Self::new(value.to_owned()) 244 | } 245 | } 246 | 247 | impl str::FromStr for Group { 248 | type Err = GroupParseError; 249 | 250 | fn from_str(s: &str) -> Result { 251 | Self::try_from(s.to_owned()) 252 | } 253 | } 254 | 255 | impl fmt::Display for Group { 256 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 257 | write!(f, "{}", self.inner.to_string_lossy()) 258 | } 259 | } 260 | 261 | impl Borrow for Group { 262 | fn borrow(&self) -> &GroupSlice { 263 | GroupSlice::from_c_str_unchecked(self.inner.as_c_str()) 264 | } 265 | } 266 | 267 | impl AsRef for Group { 268 | fn as_ref(&self) -> &GroupSlice { 269 | self.borrow() 270 | } 271 | } 272 | 273 | impl ops::Deref for Group { 274 | type Target = GroupSlice; 275 | 276 | #[inline] 277 | fn deref(&self) -> &GroupSlice { 278 | self.borrow() 279 | } 280 | } 281 | 282 | impl<'a> PartialEq for Group { 283 | fn eq(&self, other: &GroupSlice) -> bool { 284 | self.as_c_str() == other.as_c_str() 285 | } 286 | } 287 | 288 | impl<'a> PartialEq<&GroupSlice> for Group { 289 | fn eq(&self, other: &&GroupSlice) -> bool { 290 | self.as_c_str() == other.as_c_str() 291 | } 292 | } 293 | 294 | impl PartialEq<[u8]> for Group { 295 | fn eq(&self, other: &[u8]) -> bool { 296 | self.as_bytes() == other 297 | } 298 | } 299 | 300 | impl PartialEq for [u8] { 301 | fn eq(&self, other: &Group) -> bool { 302 | self == other.as_bytes() 303 | } 304 | } 305 | 306 | impl PartialEq for Group { 307 | fn eq(&self, other: &str) -> bool { 308 | self.as_bytes() == other.as_bytes() 309 | } 310 | } 311 | 312 | impl PartialEq<&str> for Group { 313 | fn eq(&self, other: &&str) -> bool { 314 | self.as_bytes() == other.as_bytes() 315 | } 316 | } 317 | 318 | impl PartialEq for str { 319 | fn eq(&self, other: &Group) -> bool { 320 | other.as_bytes() == self.as_bytes() 321 | } 322 | } 323 | 324 | impl IntoIterator for Group { 325 | type Item = Group; 326 | type IntoIter = option::IntoIter; 327 | 328 | fn into_iter(self) -> Self::IntoIter { 329 | Some(self).into_iter() 330 | } 331 | } 332 | 333 | impl<'a> IntoIterator for &'a Group { 334 | type Item = &'a Group; 335 | type IntoIter = option::IntoIter<&'a Group>; 336 | 337 | fn into_iter(self) -> Self::IntoIter { 338 | Some(self).into_iter() 339 | } 340 | } 341 | 342 | impl Serialize for Group { 343 | fn serialize(&self, serializer: S) -> Result 344 | where 345 | S: Serializer, 346 | { 347 | serde_with::rust::display_fromstr::serialize(self, serializer) 348 | } 349 | } 350 | 351 | impl<'de> Deserialize<'de> for Group { 352 | fn deserialize(deserializer: D) -> Result 353 | where 354 | D: Deserializer<'de>, 355 | { 356 | serde_with::rust::display_fromstr::deserialize(deserializer) 357 | } 358 | } 359 | -------------------------------------------------------------------------------- /libzmq/src/socket/scatter.rs: -------------------------------------------------------------------------------- 1 | use crate::{addr::Endpoint, auth::*, core::*, error::*, Ctx, CtxHandle}; 2 | 3 | use serde::{Deserialize, Serialize}; 4 | 5 | use std::{str, sync::Arc}; 6 | 7 | /// A `Scatter` socket is used to pipeline messages to workers. 8 | /// 9 | /// Messages are round-robined to all connected [`Gather`] sockets. 10 | /// 11 | /// # Summary of Characteristics 12 | /// | Characteristic | Value | 13 | /// |:-------------------------:|:----------------------:| 14 | /// | Compatible peer sockets | [`Gather`] | 15 | /// | Direction | Unidirectional | 16 | /// | Send/receive pattern | Send only | 17 | /// | Outgoing routing strategy | Round-robin | 18 | /// | Incoming routing strategy | Fair-queued | 19 | /// | Action in mute state | Block | 20 | /// 21 | /// # Example 22 | /// ``` 23 | /// # fn main() -> Result<(), anyhow::Error> { 24 | /// use libzmq::{prelude::*, *}; 25 | /// use std::time::Duration; 26 | /// 27 | /// let addr = InprocAddr::new_unique(); 28 | /// 29 | /// // Our load balancing producer. 30 | /// let lb = ScatterBuilder::new() 31 | /// .bind(&addr) 32 | /// .build()?; 33 | /// 34 | /// let worker_a = GatherBuilder::new() 35 | /// .connect(&addr) 36 | /// .recv_hwm(1) 37 | /// .recv_timeout(Duration::from_millis(100)) 38 | /// .build()?; 39 | /// 40 | /// let worker_b = GatherBuilder::new() 41 | /// .connect(&addr) 42 | /// .recv_hwm(1) 43 | /// .recv_timeout(Duration::from_millis(100)) 44 | /// .build()?; 45 | /// 46 | /// // Send messages to workers in a round-robin fashion. 47 | /// lb.send("")?; 48 | /// lb.send("")?; 49 | /// 50 | /// assert!(worker_a.recv_msg()?.is_empty()); 51 | /// assert!(worker_b.recv_msg()?.is_empty()); 52 | /// # 53 | /// # Ok(()) 54 | /// # } 55 | /// ``` 56 | /// 57 | /// [`Gather`]: struct.Gather.html 58 | #[derive(Debug, Clone)] 59 | pub struct Scatter { 60 | inner: Arc, 61 | } 62 | 63 | impl Scatter { 64 | /// Create a `Scatter` socket from the [`global context`] 65 | /// 66 | /// # Returned Error Variants 67 | /// * [`InvalidCtx`] 68 | /// * [`SocketLimit`] 69 | /// 70 | /// [`InvalidCtx`]: enum.ErrorKind.html#variant.InvalidCtx 71 | /// [`SocketLimit`]: enum.ErrorKind.html#variant.SocketLimit 72 | /// [`global context`]: struct.Ctx.html#method.global 73 | pub fn new() -> Result { 74 | let inner = Arc::new(RawSocket::new(RawSocketType::Scatter)?); 75 | 76 | Ok(Self { inner }) 77 | } 78 | 79 | /// Create a `Scatter` socket associated with a specific context 80 | /// from a `CtxHandle`. 81 | /// 82 | /// # Returned Error Variants 83 | /// * [`InvalidCtx`] 84 | /// * [`SocketLimit`] 85 | /// 86 | /// [`InvalidCtx`]: enum.ErrorKind.html#variant.InvalidCtx 87 | /// [`SocketLimit`]: enum.ErrorKind.html#variant.SocketLimit 88 | pub fn with_ctx(handle: CtxHandle) -> Result { 89 | let inner = 90 | Arc::new(RawSocket::with_ctx(RawSocketType::Scatter, handle)?); 91 | 92 | Ok(Self { inner }) 93 | } 94 | 95 | /// Returns a handle to the context of the socket. 96 | pub fn ctx(&self) -> CtxHandle { 97 | self.inner.ctx() 98 | } 99 | } 100 | 101 | impl PartialEq for Scatter { 102 | fn eq(&self, other: &Scatter) -> bool { 103 | self.inner == other.inner 104 | } 105 | } 106 | 107 | impl Eq for Scatter {} 108 | 109 | impl GetRawSocket for Scatter { 110 | fn raw_socket(&self) -> &RawSocket { 111 | &self.inner 112 | } 113 | } 114 | 115 | impl Heartbeating for Scatter {} 116 | impl Socket for Scatter {} 117 | impl SendMsg for Scatter {} 118 | 119 | unsafe impl Send for Scatter {} 120 | unsafe impl Sync for Scatter {} 121 | 122 | /// A configuration for a `Scatter`. 123 | /// 124 | /// Especially helpfull in config files. 125 | // We can't derive and use #[serde(flatten)] because of this issue: 126 | // https://github.com/serde-rs/serde/issues/1346 127 | #[derive(Debug, Default, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] 128 | #[serde(from = "FlatScatterConfig")] 129 | #[serde(into = "FlatScatterConfig")] 130 | pub struct ScatterConfig { 131 | socket_config: SocketConfig, 132 | send_config: SendConfig, 133 | heartbeat_config: HeartbeatingConfig, 134 | } 135 | 136 | impl ScatterConfig { 137 | pub fn new() -> Self { 138 | Self::default() 139 | } 140 | 141 | pub fn build(&self) -> Result { 142 | self.with_ctx(Ctx::global()) 143 | } 144 | 145 | pub fn with_ctx(&self, handle: CtxHandle) -> Result { 146 | let scatter = Scatter::with_ctx(handle)?; 147 | self.apply(&scatter)?; 148 | 149 | Ok(scatter) 150 | } 151 | 152 | pub fn apply(&self, scatter: &Scatter) -> Result<(), Error> { 153 | self.send_config.apply(scatter)?; 154 | self.socket_config.apply(scatter)?; 155 | 156 | Ok(()) 157 | } 158 | } 159 | 160 | #[derive(Clone, Serialize, Deserialize)] 161 | struct FlatScatterConfig { 162 | connect: Option>, 163 | bind: Option>, 164 | heartbeat: Option, 165 | send_hwm: HighWaterMark, 166 | send_timeout: Period, 167 | mechanism: Option, 168 | } 169 | 170 | impl From for FlatScatterConfig { 171 | fn from(config: ScatterConfig) -> Self { 172 | let socket_config = config.socket_config; 173 | let send_config = config.send_config; 174 | let heartbeat_config = config.heartbeat_config; 175 | Self { 176 | connect: socket_config.connect, 177 | bind: socket_config.bind, 178 | heartbeat: heartbeat_config.heartbeat, 179 | mechanism: socket_config.mechanism, 180 | send_hwm: send_config.send_hwm, 181 | send_timeout: send_config.send_timeout, 182 | } 183 | } 184 | } 185 | 186 | impl From for ScatterConfig { 187 | fn from(flat: FlatScatterConfig) -> Self { 188 | let socket_config = SocketConfig { 189 | connect: flat.connect, 190 | bind: flat.bind, 191 | mechanism: flat.mechanism, 192 | }; 193 | let send_config = SendConfig { 194 | send_hwm: flat.send_hwm, 195 | send_timeout: flat.send_timeout, 196 | }; 197 | let heartbeat_config = HeartbeatingConfig { 198 | heartbeat: flat.heartbeat, 199 | }; 200 | Self { 201 | socket_config, 202 | send_config, 203 | heartbeat_config, 204 | } 205 | } 206 | } 207 | impl GetSocketConfig for ScatterConfig { 208 | fn socket_config(&self) -> &SocketConfig { 209 | &self.socket_config 210 | } 211 | 212 | fn socket_config_mut(&mut self) -> &mut SocketConfig { 213 | &mut self.socket_config 214 | } 215 | } 216 | 217 | impl ConfigureSocket for ScatterConfig {} 218 | 219 | impl GetSendConfig for ScatterConfig { 220 | fn send_config(&self) -> &SendConfig { 221 | &self.send_config 222 | } 223 | 224 | fn send_config_mut(&mut self) -> &mut SendConfig { 225 | &mut self.send_config 226 | } 227 | } 228 | 229 | impl ConfigureSend for ScatterConfig {} 230 | 231 | impl GetHeartbeatingConfig for ScatterConfig { 232 | fn heartbeat_config(&self) -> &HeartbeatingConfig { 233 | &self.heartbeat_config 234 | } 235 | 236 | fn heartbeat_config_mut(&mut self) -> &mut HeartbeatingConfig { 237 | &mut self.heartbeat_config 238 | } 239 | } 240 | 241 | impl ConfigureHeartbeating for ScatterConfig {} 242 | 243 | /// A builder for a `Scatter`. 244 | /// 245 | /// Allows for ergonomic one line socket configuration. 246 | #[derive(Debug, Default, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] 247 | pub struct ScatterBuilder { 248 | inner: ScatterConfig, 249 | } 250 | 251 | impl ScatterBuilder { 252 | pub fn new() -> Self { 253 | Self::default() 254 | } 255 | 256 | pub fn build(&self) -> Result { 257 | self.inner.build() 258 | } 259 | 260 | pub fn with_ctx(&self, handle: CtxHandle) -> Result { 261 | self.inner.with_ctx(handle) 262 | } 263 | } 264 | 265 | impl GetSocketConfig for ScatterBuilder { 266 | fn socket_config(&self) -> &SocketConfig { 267 | self.inner.socket_config() 268 | } 269 | 270 | fn socket_config_mut(&mut self) -> &mut SocketConfig { 271 | self.inner.socket_config_mut() 272 | } 273 | } 274 | 275 | impl BuildSocket for ScatterBuilder {} 276 | 277 | impl GetSendConfig for ScatterBuilder { 278 | fn send_config(&self) -> &SendConfig { 279 | self.inner.send_config() 280 | } 281 | 282 | fn send_config_mut(&mut self) -> &mut SendConfig { 283 | self.inner.send_config_mut() 284 | } 285 | } 286 | 287 | impl BuildSend for ScatterBuilder {} 288 | 289 | impl GetHeartbeatingConfig for ScatterBuilder { 290 | fn heartbeat_config(&self) -> &HeartbeatingConfig { 291 | self.inner.heartbeat_config() 292 | } 293 | 294 | fn heartbeat_config_mut(&mut self) -> &mut HeartbeatingConfig { 295 | self.inner.heartbeat_config_mut() 296 | } 297 | } 298 | 299 | impl BuildHeartbeating for ScatterBuilder {} 300 | 301 | #[cfg(test)] 302 | mod test { 303 | use super::*; 304 | use crate::*; 305 | use std::time::Duration; 306 | 307 | #[test] 308 | fn test_ser_de() { 309 | let config = ScatterConfig::new(); 310 | 311 | let ron = serde_yaml::to_string(&config).unwrap(); 312 | let de: ScatterConfig = serde_yaml::from_str(&ron).unwrap(); 313 | assert_eq!(config, de); 314 | } 315 | 316 | #[test] 317 | fn test_scatter() { 318 | let addr = InprocAddr::new_unique(); 319 | 320 | // Our load balancing producer. 321 | let lb = ScatterBuilder::new().bind(&addr).build().unwrap(); 322 | 323 | let worker_a = GatherBuilder::new() 324 | .connect(&addr) 325 | .recv_hwm(1) 326 | .recv_timeout(Duration::from_millis(300)) 327 | .build() 328 | .unwrap(); 329 | 330 | let worker_b = GatherBuilder::new() 331 | .connect(&addr) 332 | .recv_hwm(1) 333 | .recv_timeout(Duration::from_millis(300)) 334 | .build() 335 | .unwrap(); 336 | 337 | // Send messages to workers in a round-robin fashion. 338 | lb.send("").unwrap(); 339 | lb.send("").unwrap(); 340 | 341 | assert!(worker_a.recv_msg().unwrap().is_empty()); 342 | assert!(worker_b.recv_msg().unwrap().is_empty()); 343 | } 344 | } 345 | -------------------------------------------------------------------------------- /libzmq/src/socket/gather.rs: -------------------------------------------------------------------------------- 1 | use crate::{addr::Endpoint, auth::*, core::*, error::*, Ctx, CtxHandle}; 2 | 3 | use serde::{Deserialize, Serialize}; 4 | 5 | use std::{str, sync::Arc}; 6 | 7 | /// A `Gather` socket is used to receive pipelined messages. 8 | /// 9 | /// Messages are fair-queued from among all connected [`Scatter`] sockets. 10 | /// 11 | /// # Summary of Characteristics 12 | /// | Characteristic | Value | 13 | /// |:-------------------------:|:----------------------:| 14 | /// | Compatible peer sockets | [`Scatter`] | 15 | /// | Direction | Unidirectional | 16 | /// | Send/receive pattern | Receive only | 17 | /// | Outgoing routing strategy | Round-robin | 18 | /// | Incoming routing strategy | Fair-queued | 19 | /// | Action in mute state | Block | 20 | /// 21 | /// # Example 22 | /// ``` 23 | /// # fn main() -> Result<(), anyhow::Error> { 24 | /// use libzmq::{prelude::*, *}; 25 | /// 26 | /// let addr_a = InprocAddr::new_unique(); 27 | /// let addr_b = InprocAddr::new_unique(); 28 | /// 29 | /// // Create our two load balancers. 30 | /// let lb_a = ScatterBuilder::new() 31 | /// .bind(&addr_a) 32 | /// .build()?; 33 | /// let lb_b = ScatterBuilder::new() 34 | /// .bind(&addr_b) 35 | /// .build()?; 36 | /// 37 | /// // Connect the worker to both load balancers. 38 | /// let worker = GatherBuilder::new() 39 | /// .connect(&[addr_a, addr_b]) 40 | /// .recv_hwm(1) 41 | /// .build()?; 42 | /// 43 | /// for _ in 0..100 { 44 | /// lb_a.try_send("a")?; 45 | /// } 46 | /// for _ in 0..100 { 47 | /// lb_b.try_send("b")?; 48 | /// } 49 | /// 50 | /// // The messages received should be fair-queues from 51 | /// // our two load balancers. 52 | /// let mut msg = Msg::new(); 53 | /// for i in 0..200 { 54 | /// worker.try_recv(&mut msg)?; 55 | /// if i % 2 == 0 { 56 | /// assert_eq!(msg.to_str(), Ok("a")); 57 | /// } else { 58 | /// assert_eq!(msg.to_str(), Ok("b")); 59 | /// } 60 | /// } 61 | /// # 62 | /// # Ok(()) 63 | /// # } 64 | /// ``` 65 | /// 66 | /// [`Scatter`]: struct.Scatter.html 67 | #[derive(Debug, Clone)] 68 | pub struct Gather { 69 | inner: Arc, 70 | } 71 | 72 | impl Gather { 73 | /// Create a `Gather` socket from the [`global context`] 74 | /// 75 | /// # Returned Error Variants 76 | /// * [`InvalidCtx`] 77 | /// * [`SocketLimit`] 78 | /// 79 | /// [`InvalidCtx`]: enum.ErrorKind.html#variant.InvalidCtx 80 | /// [`SocketLimit`]: enum.ErrorKind.html#variant.SocketLimit 81 | /// [`global context`]: struct.Ctx.html#method.global 82 | pub fn new() -> Result { 83 | let inner = Arc::new(RawSocket::new(RawSocketType::Gather)?); 84 | 85 | Ok(Self { inner }) 86 | } 87 | 88 | /// Create a `Gather` socket associated with a specific context 89 | /// from a `CtxHandle`. 90 | /// 91 | /// # Returned Error Variants 92 | /// * [`InvalidCtx`] 93 | /// * [`SocketLimit`] 94 | /// 95 | /// [`InvalidCtx`]: enum.ErrorKind.html#variant.InvalidCtx 96 | /// [`SocketLimit`]: enum.ErrorKind.html#variant.SocketLimit 97 | pub fn with_ctx(handle: CtxHandle) -> Result { 98 | let inner = 99 | Arc::new(RawSocket::with_ctx(RawSocketType::Gather, handle)?); 100 | 101 | Ok(Self { inner }) 102 | } 103 | 104 | /// Returns a handle to the context of the socket. 105 | pub fn ctx(&self) -> CtxHandle { 106 | self.inner.ctx() 107 | } 108 | } 109 | 110 | impl PartialEq for Gather { 111 | fn eq(&self, other: &Gather) -> bool { 112 | self.inner == other.inner 113 | } 114 | } 115 | 116 | impl Eq for Gather {} 117 | 118 | impl GetRawSocket for Gather { 119 | fn raw_socket(&self) -> &RawSocket { 120 | &self.inner 121 | } 122 | } 123 | 124 | impl Heartbeating for Gather {} 125 | impl Socket for Gather {} 126 | impl RecvMsg for Gather {} 127 | 128 | unsafe impl Send for Gather {} 129 | unsafe impl Sync for Gather {} 130 | 131 | /// A configuration for a `Gather`. 132 | /// 133 | /// Especially helpful in config files. 134 | // We can't derive and use #[serde(flatten)] because of this issue: 135 | // https://github.com/serde-rs/serde/issues/1346 136 | #[derive(Debug, Default, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] 137 | #[serde(from = "FlatGatherConfig")] 138 | #[serde(into = "FlatGatherConfig")] 139 | pub struct GatherConfig { 140 | socket_config: SocketConfig, 141 | recv_config: RecvConfig, 142 | heartbeat_config: HeartbeatingConfig, 143 | } 144 | 145 | impl GatherConfig { 146 | pub fn new() -> Self { 147 | Self::default() 148 | } 149 | 150 | pub fn build(&self) -> Result { 151 | self.with_ctx(Ctx::global()) 152 | } 153 | 154 | pub fn with_ctx(&self, handle: CtxHandle) -> Result { 155 | let gather = Gather::with_ctx(handle)?; 156 | self.apply(&gather)?; 157 | 158 | Ok(gather) 159 | } 160 | 161 | pub fn apply(&self, gather: &Gather) -> Result<(), Error> { 162 | self.recv_config.apply(gather)?; 163 | self.socket_config.apply(gather)?; 164 | 165 | Ok(()) 166 | } 167 | } 168 | 169 | #[derive(Clone, Serialize, Deserialize)] 170 | struct FlatGatherConfig { 171 | connect: Option>, 172 | bind: Option>, 173 | heartbeat: Option, 174 | recv_hwm: HighWaterMark, 175 | recv_timeout: Period, 176 | mechanism: Option, 177 | } 178 | 179 | impl From for FlatGatherConfig { 180 | fn from(config: GatherConfig) -> Self { 181 | let socket_config = config.socket_config; 182 | let recv_config = config.recv_config; 183 | let heartbeat_config = config.heartbeat_config; 184 | Self { 185 | connect: socket_config.connect, 186 | bind: socket_config.bind, 187 | heartbeat: heartbeat_config.heartbeat, 188 | mechanism: socket_config.mechanism, 189 | recv_hwm: recv_config.recv_hwm, 190 | recv_timeout: recv_config.recv_timeout, 191 | } 192 | } 193 | } 194 | 195 | impl From for GatherConfig { 196 | fn from(flat: FlatGatherConfig) -> Self { 197 | let socket_config = SocketConfig { 198 | connect: flat.connect, 199 | bind: flat.bind, 200 | mechanism: flat.mechanism, 201 | }; 202 | let recv_config = RecvConfig { 203 | recv_hwm: flat.recv_hwm, 204 | recv_timeout: flat.recv_timeout, 205 | }; 206 | let heartbeat_config = HeartbeatingConfig { 207 | heartbeat: flat.heartbeat, 208 | }; 209 | Self { 210 | socket_config, 211 | recv_config, 212 | heartbeat_config, 213 | } 214 | } 215 | } 216 | impl GetSocketConfig for GatherConfig { 217 | fn socket_config(&self) -> &SocketConfig { 218 | &self.socket_config 219 | } 220 | 221 | fn socket_config_mut(&mut self) -> &mut SocketConfig { 222 | &mut self.socket_config 223 | } 224 | } 225 | 226 | impl ConfigureSocket for GatherConfig {} 227 | 228 | impl GetRecvConfig for GatherConfig { 229 | fn recv_config(&self) -> &RecvConfig { 230 | &self.recv_config 231 | } 232 | 233 | fn recv_config_mut(&mut self) -> &mut RecvConfig { 234 | &mut self.recv_config 235 | } 236 | } 237 | 238 | impl ConfigureRecv for GatherConfig {} 239 | 240 | impl GetHeartbeatingConfig for GatherConfig { 241 | fn heartbeat_config(&self) -> &HeartbeatingConfig { 242 | &self.heartbeat_config 243 | } 244 | 245 | fn heartbeat_config_mut(&mut self) -> &mut HeartbeatingConfig { 246 | &mut self.heartbeat_config 247 | } 248 | } 249 | 250 | impl ConfigureHeartbeating for GatherConfig {} 251 | 252 | /// A builder for a `Gather`. 253 | /// 254 | /// Allows for ergonomic one line socket configuration. 255 | #[derive(Debug, Default, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] 256 | pub struct GatherBuilder { 257 | inner: GatherConfig, 258 | } 259 | 260 | impl GatherBuilder { 261 | pub fn new() -> Self { 262 | Self::default() 263 | } 264 | 265 | pub fn build(&self) -> Result { 266 | self.inner.build() 267 | } 268 | 269 | pub fn with_ctx(&self, handle: CtxHandle) -> Result { 270 | self.inner.with_ctx(handle) 271 | } 272 | } 273 | 274 | impl GetSocketConfig for GatherBuilder { 275 | fn socket_config(&self) -> &SocketConfig { 276 | self.inner.socket_config() 277 | } 278 | 279 | fn socket_config_mut(&mut self) -> &mut SocketConfig { 280 | self.inner.socket_config_mut() 281 | } 282 | } 283 | 284 | impl BuildSocket for GatherBuilder {} 285 | 286 | impl GetRecvConfig for GatherBuilder { 287 | fn recv_config(&self) -> &RecvConfig { 288 | self.inner.recv_config() 289 | } 290 | 291 | fn recv_config_mut(&mut self) -> &mut RecvConfig { 292 | self.inner.recv_config_mut() 293 | } 294 | } 295 | 296 | impl BuildRecv for GatherBuilder {} 297 | 298 | impl GetHeartbeatingConfig for GatherBuilder { 299 | fn heartbeat_config(&self) -> &HeartbeatingConfig { 300 | self.inner.heartbeat_config() 301 | } 302 | 303 | fn heartbeat_config_mut(&mut self) -> &mut HeartbeatingConfig { 304 | self.inner.heartbeat_config_mut() 305 | } 306 | } 307 | 308 | impl BuildHeartbeating for GatherBuilder {} 309 | 310 | #[cfg(test)] 311 | mod test { 312 | use super::*; 313 | use crate::*; 314 | 315 | use std::time::Duration; 316 | 317 | #[test] 318 | fn test_ser_de() { 319 | let config = GatherConfig::new(); 320 | 321 | let ron = serde_yaml::to_string(&config).unwrap(); 322 | let de: GatherConfig = serde_yaml::from_str(&ron).unwrap(); 323 | assert_eq!(config, de); 324 | } 325 | 326 | #[test] 327 | fn test_issue_125() { 328 | let gather = Gather::new().unwrap(); 329 | gather 330 | .set_recv_timeout(Some(Duration::from_secs(3))) 331 | .unwrap(); 332 | } 333 | 334 | #[test] 335 | fn test_gather() { 336 | let addr_a = InprocAddr::new_unique(); 337 | let addr_b = InprocAddr::new_unique(); 338 | 339 | // Create our two load balancers. 340 | let lb_a = ScatterBuilder::new().bind(&addr_a).build().unwrap(); 341 | let lb_b = ScatterBuilder::new().bind(&addr_b).build().unwrap(); 342 | 343 | // Connected the worker to both load balancers. 344 | let worker = GatherBuilder::new() 345 | .connect(&[addr_a, addr_b]) 346 | .recv_hwm(1) 347 | .build() 348 | .unwrap(); 349 | 350 | for _ in 0..100 { 351 | lb_a.try_send("a").unwrap(); 352 | } 353 | for _ in 0..100 { 354 | lb_b.try_send("b").unwrap(); 355 | } 356 | 357 | // The messages received should be fair-queues amongst 358 | // our two load balancers. 359 | let mut msg = Msg::new(); 360 | for i in 0..200 { 361 | worker.try_recv(&mut msg).unwrap(); 362 | if i % 2 == 0 { 363 | assert_eq!(msg.to_str(), Ok("a")); 364 | } else { 365 | assert_eq!(msg.to_str(), Ok("b")); 366 | } 367 | } 368 | } 369 | } 370 | -------------------------------------------------------------------------------- /libzmq/src/socket/client.rs: -------------------------------------------------------------------------------- 1 | use crate::{addr::Endpoint, auth::*, core::*, error::*, Ctx, CtxHandle}; 2 | 3 | use serde::{Deserialize, Serialize}; 4 | 5 | use std::sync::Arc; 6 | 7 | /// A `Client` socket is used for advanced request-reply messaging. 8 | /// 9 | /// `Client` sockets are threadsafe and can be used from multiple threads at the 10 | /// same time. Note that replies from a `Server` socket will go to the first 11 | /// client thread that calls `recv`. If you need to get replies back to the 12 | /// originating thread, use one `Client` socket per thread. 13 | /// 14 | /// When a `Client` socket is connected to multiple sockets, outgoing 15 | /// messages are distributed between connected peers on a round-robin basis. 16 | /// Likewise, the `Client` socket receives messages fairly from each connected peer. 17 | /// 18 | /// # Mute State 19 | /// When `Client` socket enters the mute state due to having reached the high water 20 | /// mark, or if there are no peers at all, then any send operations on the 21 | /// socket shall block until the mute state ends or at least one peer becomes 22 | /// available for sending; messages are not discarded. 23 | /// 24 | /// # Summary of Characteristics 25 | /// | Characteristic | Value | 26 | /// |:-------------------------:|:----------------------:| 27 | /// | Compatible peer sockets | [`Server`] | 28 | /// | Direction | Bidirectional | 29 | /// | Send/receive pattern | Unrestricted | 30 | /// | Outgoing routing strategy | Round-robin | 31 | /// | Incoming routing strategy | Fair-queued | 32 | /// | Action in mute state | Block | 33 | /// 34 | /// # Example 35 | /// ``` 36 | /// # fn main() -> Result<(), anyhow::Error> { 37 | /// use libzmq::{prelude::*, *}; 38 | /// 39 | /// // Use a system assigned port. 40 | /// let addr: TcpAddr = "127.0.0.1:*".try_into()?; 41 | /// 42 | /// let server = ServerBuilder::new() 43 | /// .bind(addr) 44 | /// .build()?; 45 | /// 46 | /// // Retrieve the addr that was assigned. 47 | /// let bound = server.last_endpoint()?; 48 | /// 49 | /// let client = ClientBuilder::new() 50 | /// .connect(bound) 51 | /// .build()?; 52 | /// 53 | /// // Send a string request. 54 | /// client.send("tell me something")?; 55 | /// 56 | /// // Receive the client request. 57 | /// let msg = server.recv_msg()?; 58 | /// let id = msg.routing_id().unwrap(); 59 | /// 60 | /// // Reply to the client. 61 | /// server.route("it takes 224 bits to store a i32 in java", id)?; 62 | /// 63 | /// // We send as much replies as we want. 64 | /// server.route("also don't talk to me", id)?; 65 | /// 66 | /// // Retreive the first reply. 67 | /// let mut msg = client.recv_msg()?; 68 | /// // And the second. 69 | /// client.recv(&mut msg)?; 70 | /// # 71 | /// # Ok(()) 72 | /// # } 73 | /// ``` 74 | /// 75 | /// [`Server`]: struct.Server.html 76 | #[derive(Debug, Clone, PartialEq, Eq)] 77 | pub struct Client { 78 | inner: Arc, 79 | } 80 | 81 | impl Client { 82 | /// Create a `Client` socket from the [`global context`] 83 | /// 84 | /// # Returned Error Variants 85 | /// * [`InvalidCtx`] 86 | /// * [`SocketLimit`] 87 | /// 88 | /// [`InvalidCtx`]: enum.ErrorKind.html#variant.InvalidCtx 89 | /// [`SocketLimit`]: enum.ErrorKind.html#variant.SocketLimit 90 | /// [`global context`]: struct.Ctx.html#method.global 91 | pub fn new() -> Result { 92 | let inner = Arc::new(RawSocket::new(RawSocketType::Client)?); 93 | 94 | Ok(Self { inner }) 95 | } 96 | 97 | /// Create a `Client` socket associated with a specific context 98 | /// from a `CtxHandle`. 99 | /// 100 | /// # Returned Error Variants 101 | /// * [`InvalidCtx`] 102 | /// * [`SocketLimit`] 103 | /// 104 | /// [`InvalidCtx`]: enum.ErrorKind.html#variant.InvalidCtx 105 | /// [`SocketLimit`]: enum.ErrorKind.html#variant.SocketLimit 106 | pub fn with_ctx(handle: CtxHandle) -> Result { 107 | let inner = 108 | Arc::new(RawSocket::with_ctx(RawSocketType::Client, handle)?); 109 | 110 | Ok(Self { inner }) 111 | } 112 | 113 | /// Returns the handle to the `Ctx` of the socket. 114 | pub fn ctx(&self) -> CtxHandle { 115 | self.inner.ctx() 116 | } 117 | } 118 | 119 | impl GetRawSocket for Client { 120 | fn raw_socket(&self) -> &RawSocket { 121 | &self.inner 122 | } 123 | } 124 | 125 | impl Heartbeating for Client {} 126 | impl Socket for Client {} 127 | impl SendMsg for Client {} 128 | impl RecvMsg for Client {} 129 | 130 | unsafe impl Send for Client {} 131 | unsafe impl Sync for Client {} 132 | 133 | /// A configuration for a `Client`. 134 | /// 135 | /// Especially helpfull in config files. 136 | // We can't derive and use #[serde(flatten)] because of this issue: 137 | // https://github.com/serde-rs/serde/issues/1346. 138 | #[derive(Debug, Default, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] 139 | #[serde(into = "FlatClientConfig")] 140 | #[serde(from = "FlatClientConfig")] 141 | pub struct ClientConfig { 142 | socket_config: SocketConfig, 143 | send_config: SendConfig, 144 | recv_config: RecvConfig, 145 | heartbeat_config: HeartbeatingConfig, 146 | } 147 | 148 | impl ClientConfig { 149 | pub fn new() -> Self { 150 | Self::default() 151 | } 152 | 153 | pub fn build(&self) -> Result { 154 | self.with_ctx(Ctx::global()) 155 | } 156 | 157 | pub fn with_ctx(&self, handle: CtxHandle) -> Result { 158 | let client = Client::with_ctx(handle)?; 159 | self.apply(&client)?; 160 | 161 | Ok(client) 162 | } 163 | 164 | pub fn apply(&self, client: &Client) -> Result<(), Error> { 165 | self.send_config.apply(client)?; 166 | self.recv_config.apply(client)?; 167 | self.heartbeat_config.apply(client)?; 168 | self.socket_config.apply(client)?; 169 | 170 | Ok(()) 171 | } 172 | } 173 | 174 | #[derive(Clone, Serialize, Deserialize)] 175 | struct FlatClientConfig { 176 | connect: Option>, 177 | bind: Option>, 178 | heartbeat: Option, 179 | send_hwm: HighWaterMark, 180 | send_timeout: Period, 181 | recv_hwm: HighWaterMark, 182 | recv_timeout: Period, 183 | mechanism: Option, 184 | } 185 | 186 | impl From for FlatClientConfig { 187 | fn from(config: ClientConfig) -> Self { 188 | let socket_config = config.socket_config; 189 | let send_config = config.send_config; 190 | let recv_config = config.recv_config; 191 | let heartbeat_config = config.heartbeat_config; 192 | Self { 193 | connect: socket_config.connect, 194 | bind: socket_config.bind, 195 | heartbeat: heartbeat_config.heartbeat, 196 | mechanism: socket_config.mechanism, 197 | send_hwm: send_config.send_hwm, 198 | send_timeout: send_config.send_timeout, 199 | recv_hwm: recv_config.recv_hwm, 200 | recv_timeout: recv_config.recv_timeout, 201 | } 202 | } 203 | } 204 | 205 | impl From for ClientConfig { 206 | fn from(flat: FlatClientConfig) -> Self { 207 | let socket_config = SocketConfig { 208 | connect: flat.connect, 209 | bind: flat.bind, 210 | mechanism: flat.mechanism, 211 | }; 212 | let send_config = SendConfig { 213 | send_hwm: flat.send_hwm, 214 | send_timeout: flat.send_timeout, 215 | }; 216 | let recv_config = RecvConfig { 217 | recv_hwm: flat.recv_hwm, 218 | recv_timeout: flat.recv_timeout, 219 | }; 220 | let heartbeat_config = HeartbeatingConfig { 221 | heartbeat: flat.heartbeat, 222 | }; 223 | Self { 224 | socket_config, 225 | send_config, 226 | recv_config, 227 | heartbeat_config, 228 | } 229 | } 230 | } 231 | 232 | impl GetSocketConfig for ClientConfig { 233 | fn socket_config(&self) -> &SocketConfig { 234 | &self.socket_config 235 | } 236 | 237 | fn socket_config_mut(&mut self) -> &mut SocketConfig { 238 | &mut self.socket_config 239 | } 240 | } 241 | 242 | impl ConfigureSocket for ClientConfig {} 243 | 244 | impl GetRecvConfig for ClientConfig { 245 | fn recv_config(&self) -> &RecvConfig { 246 | &self.recv_config 247 | } 248 | 249 | fn recv_config_mut(&mut self) -> &mut RecvConfig { 250 | &mut self.recv_config 251 | } 252 | } 253 | 254 | impl ConfigureRecv for ClientConfig {} 255 | 256 | impl GetSendConfig for ClientConfig { 257 | fn send_config(&self) -> &SendConfig { 258 | &self.send_config 259 | } 260 | 261 | fn send_config_mut(&mut self) -> &mut SendConfig { 262 | &mut self.send_config 263 | } 264 | } 265 | 266 | impl ConfigureSend for ClientConfig {} 267 | 268 | impl GetHeartbeatingConfig for ClientConfig { 269 | fn heartbeat_config(&self) -> &HeartbeatingConfig { 270 | &self.heartbeat_config 271 | } 272 | 273 | fn heartbeat_config_mut(&mut self) -> &mut HeartbeatingConfig { 274 | &mut self.heartbeat_config 275 | } 276 | } 277 | 278 | impl ConfigureHeartbeating for ClientConfig {} 279 | 280 | /// A builder for a `Client`. 281 | /// 282 | /// Allows for ergonomic one line socket configuration. 283 | #[derive(Debug, Default, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] 284 | pub struct ClientBuilder { 285 | inner: ClientConfig, 286 | } 287 | 288 | impl ClientBuilder { 289 | pub fn new() -> Self { 290 | Self::default() 291 | } 292 | 293 | pub fn build(&self) -> Result { 294 | self.inner.build() 295 | } 296 | 297 | pub fn with_ctx(&self, handle: CtxHandle) -> Result { 298 | self.inner.with_ctx(handle) 299 | } 300 | } 301 | 302 | impl GetSocketConfig for ClientBuilder { 303 | fn socket_config(&self) -> &SocketConfig { 304 | self.inner.socket_config() 305 | } 306 | 307 | fn socket_config_mut(&mut self) -> &mut SocketConfig { 308 | self.inner.socket_config_mut() 309 | } 310 | } 311 | 312 | impl BuildSocket for ClientBuilder {} 313 | 314 | impl GetSendConfig for ClientBuilder { 315 | fn send_config(&self) -> &SendConfig { 316 | self.inner.send_config() 317 | } 318 | 319 | fn send_config_mut(&mut self) -> &mut SendConfig { 320 | self.inner.send_config_mut() 321 | } 322 | } 323 | 324 | impl BuildSend for ClientBuilder {} 325 | 326 | impl GetRecvConfig for ClientBuilder { 327 | fn recv_config(&self) -> &RecvConfig { 328 | self.inner.recv_config() 329 | } 330 | 331 | fn recv_config_mut(&mut self) -> &mut RecvConfig { 332 | self.inner.recv_config_mut() 333 | } 334 | } 335 | 336 | impl BuildRecv for ClientBuilder {} 337 | 338 | impl GetHeartbeatingConfig for ClientBuilder { 339 | fn heartbeat_config(&self) -> &HeartbeatingConfig { 340 | self.inner.heartbeat_config() 341 | } 342 | 343 | fn heartbeat_config_mut(&mut self) -> &mut HeartbeatingConfig { 344 | self.inner.heartbeat_config_mut() 345 | } 346 | } 347 | 348 | impl BuildHeartbeating for ClientBuilder {} 349 | 350 | #[cfg(test)] 351 | mod test { 352 | use super::*; 353 | use crate::{prelude::TryInto, InprocAddr}; 354 | 355 | #[test] 356 | fn test_ser_de() { 357 | let addr: InprocAddr = "test".try_into().unwrap(); 358 | 359 | let mut config = ClientConfig::new(); 360 | config.set_connect(Some(&addr)); 361 | 362 | let ron = serde_yaml::to_string(&config).unwrap(); 363 | let de: ClientConfig = serde_yaml::from_str(&ron).unwrap(); 364 | assert_eq!(config, de); 365 | } 366 | } 367 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | 203 | -------------------------------------------------------------------------------- /libzmq/LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | 203 | -------------------------------------------------------------------------------- /libzmq-sys/LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | 203 | -------------------------------------------------------------------------------- /libzmq/src/socket/radio.rs: -------------------------------------------------------------------------------- 1 | use crate::{addr::Endpoint, auth::*, core::*, error::*, *}; 2 | 3 | use serde::{Deserialize, Serialize}; 4 | 5 | use std::sync::Arc; 6 | 7 | /// A `Radio` socket is used by a publisher to distribute data to [`Dish`] 8 | /// sockets. 9 | /// 10 | /// Each message sent belong to a group. By default, the group is "". 11 | /// This group can be specified by using [`set_group`] or using the convenience 12 | /// method [`transmit`]. Messages are distributed to all members of a group. 13 | /// 14 | /// # Mute State 15 | /// When a `Radio` socket enters the mute state due to having reached the 16 | /// high water mark for a subscriber, then any messages that would be sent to 17 | /// the subscriber in question shall instead be dropped until the mute state ends. 18 | /// 19 | /// # Summary of Characteristics 20 | /// | Characteristic | Value | 21 | /// |:-------------------------:|:--------------:| 22 | /// | Compatible peer sockets | [`Dish`] | 23 | /// | Direction | Unidirectional | 24 | /// | Send/receive pattern | Send only | 25 | /// | Incoming routing strategy | N/A | 26 | /// | Outgoing routing strategy | Fan out | 27 | /// | Action in mute state | Drop | 28 | /// 29 | /// # Example 30 | /// ``` 31 | /// # fn main() -> Result<(), anyhow::Error> { 32 | /// use libzmq::{prelude::*, *}; 33 | /// use std::{thread, time::Duration}; 34 | /// 35 | /// let addr: TcpAddr = "127.0.0.1:*".try_into()?; 36 | /// 37 | /// let radio = RadioBuilder::new() 38 | /// .bind(addr) 39 | /// .build()?; 40 | /// 41 | /// let bound = radio.last_endpoint()?; 42 | /// let a: Group = "A".try_into()?; 43 | /// let b: Group = "B".try_into()?; 44 | /// 45 | /// let dish_a = DishBuilder::new() 46 | /// .connect(&bound) 47 | /// .join(&a) 48 | /// .build()?; 49 | /// 50 | /// let dish_b = DishBuilder::new() 51 | /// .connect(bound) 52 | /// .join(&b) 53 | /// .build()?; 54 | /// 55 | /// // Start the feed. It has no conceptual start nor end, thus we 56 | /// // don't synchronize with the subscribers. 57 | /// thread::spawn(move || { 58 | /// let a: Group = "A".try_into().unwrap(); 59 | /// let b: Group = "B".try_into().unwrap(); 60 | /// let mut count = 0; 61 | /// loop { 62 | /// let msg = Msg::new(); 63 | /// // Alternate between the two groups. 64 | /// let group = if count % 2 == 0 { 65 | /// &a 66 | /// } else { 67 | /// &b 68 | /// }; 69 | /// 70 | /// radio.transmit(msg, group).unwrap(); 71 | /// 72 | /// thread::sleep(Duration::from_millis(1)); 73 | /// count += 1; 74 | /// } 75 | /// }); 76 | /// 77 | /// // Each dish will only receive the messages from their respective groups. 78 | /// let msg = dish_a.recv_msg()?; 79 | /// assert_eq!(msg.group().unwrap(), &a); 80 | /// 81 | /// let msg = dish_b.recv_msg()?; 82 | /// assert_eq!(msg.group().unwrap(), &b); 83 | /// # 84 | /// # Ok(()) 85 | /// # } 86 | /// ``` 87 | /// 88 | /// [`Dish`]: struct.Dish.html 89 | /// [`set_group`]: struct.Msg.html#method.set_group 90 | /// [`transmit`]: #method.transmit 91 | #[derive(Debug, Clone, PartialEq, Eq)] 92 | pub struct Radio { 93 | inner: Arc, 94 | } 95 | 96 | impl Radio { 97 | /// Create a `Radio` socket from the [`global context`] 98 | /// 99 | /// # Returned Error Variants 100 | /// * [`InvalidCtx`] 101 | /// * [`SocketLimit`] 102 | /// 103 | /// [`InvalidCtx`]: enum.ErrorKind.html#variant.InvalidCtx 104 | /// [`SocketLimit`]: enum.ErrorKind.html#variant.SocketLimit 105 | /// [`global context`]: struct.Ctx.html#method.global 106 | pub fn new() -> Result { 107 | let inner = Arc::new(RawSocket::new(RawSocketType::Radio)?); 108 | 109 | Ok(Self { inner }) 110 | } 111 | 112 | /// Create a `Radio` socket associated with a specific context 113 | /// from a `CtxHandle`. 114 | /// 115 | /// # Returned Error Variants 116 | /// * [`InvalidCtx`] 117 | /// * [`SocketLimit`] 118 | /// 119 | /// [`InvalidCtx`]: enum.ErrorKind.html#variant.InvalidCtx 120 | /// [`SocketLimit`]: enum.ErrorKind.html#variant.SocketLimit 121 | pub fn with_ctx(handle: CtxHandle) -> Result { 122 | let inner = 123 | Arc::new(RawSocket::with_ctx(RawSocketType::Radio, handle)?); 124 | 125 | Ok(Self { inner }) 126 | } 127 | 128 | /// Returns a reference to the context of the socket. 129 | pub fn ctx(&self) -> CtxHandle { 130 | self.inner.ctx() 131 | } 132 | 133 | /// Returns `true` if the `no_drop` option is set. 134 | pub fn no_drop(&self) -> Result { 135 | self.inner.no_drop() 136 | } 137 | 138 | /// Sets the socket's behaviour to block instead of drop messages when 139 | /// in the `mute state`. 140 | /// 141 | /// # Default value 142 | /// `false` 143 | /// 144 | /// [`WouldBlock`]: enum.ErrorKind.html#variant.WouldBlock 145 | /// [`send_hwm`]: #method.send_hwm 146 | pub fn set_no_drop(&self, enabled: bool) -> Result<(), Error> { 147 | self.inner.set_no_drop(enabled) 148 | } 149 | 150 | /// Push a message into the outgoing socket queue with the specified group. 151 | /// 152 | /// This is a convenience function that sets the `Msg`'s group then 153 | /// sends it. 154 | /// 155 | /// See [`send`] for more information. 156 | /// 157 | /// [`send`]: prelude/trait.SendMsg.html#method.send 158 | pub fn transmit(&self, msg: M, group: G) -> Result<(), Error> 159 | where 160 | M: Into, 161 | G: AsRef, 162 | { 163 | let mut msg = msg.into(); 164 | msg.set_group(group); 165 | self.send(msg) 166 | } 167 | 168 | /// Try to push a message into the outgoing socket queue with the specified 169 | /// group. 170 | /// 171 | /// This is a convenience function that sets the `Msg`'s group then 172 | /// tries sends it. 173 | /// 174 | /// See [`try_send`] for more information. 175 | /// 176 | /// [`try_send`]: prelude/trait.SendMsg.html#method.try_send 177 | pub fn try_transmit(&self, msg: M, group: G) -> Result<(), Error> 178 | where 179 | M: Into, 180 | G: AsRef, 181 | { 182 | let mut msg = msg.into(); 183 | msg.set_group(group); 184 | self.try_send(msg) 185 | } 186 | } 187 | 188 | impl GetRawSocket for Radio { 189 | fn raw_socket(&self) -> &RawSocket { 190 | &self.inner 191 | } 192 | } 193 | 194 | impl Socket for Radio {} 195 | impl SendMsg for Radio {} 196 | 197 | unsafe impl Send for Radio {} 198 | unsafe impl Sync for Radio {} 199 | 200 | /// A configuration for a `Radio`. 201 | /// 202 | /// Especially helpfull in config files. 203 | #[derive(Debug, Default, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] 204 | #[serde(from = "FlatRadioConfig")] 205 | #[serde(into = "FlatRadioConfig")] 206 | pub struct RadioConfig { 207 | socket_config: SocketConfig, 208 | send_config: SendConfig, 209 | no_drop: Option, 210 | } 211 | 212 | impl RadioConfig { 213 | pub fn new() -> Self { 214 | Self::default() 215 | } 216 | 217 | pub fn build(&self) -> Result { 218 | self.with_ctx(Ctx::global()) 219 | } 220 | 221 | pub fn with_ctx(&self, handle: CtxHandle) -> Result { 222 | let radio = Radio::with_ctx(handle)?; 223 | self.apply(&radio)?; 224 | 225 | Ok(radio) 226 | } 227 | 228 | /// Returns `true` if the `no_drop` option is set. 229 | pub fn no_drop(&self) -> bool { 230 | self.no_drop.unwrap_or_default() 231 | } 232 | 233 | /// Returns `true` if the `no_drop` option is set. 234 | pub fn set_no_drop(&mut self, cond: bool) { 235 | self.no_drop = Some(cond); 236 | } 237 | 238 | pub fn apply(&self, radio: &Radio) -> Result<(), Error> { 239 | if let Some(enabled) = self.no_drop { 240 | radio.set_no_drop(enabled)?; 241 | } 242 | self.send_config.apply(radio)?; 243 | self.socket_config.apply(radio)?; 244 | 245 | Ok(()) 246 | } 247 | } 248 | 249 | // We can't derive and use #[serde(flatten)] because of this issue: 250 | // https://github.com/serde-rs/serde/issues/1346 251 | // Wish there was a better way. 252 | #[derive(Serialize, Deserialize)] 253 | struct FlatRadioConfig { 254 | connect: Option>, 255 | bind: Option>, 256 | send_hwm: HighWaterMark, 257 | send_timeout: Period, 258 | no_drop: Option, 259 | mechanism: Option, 260 | } 261 | 262 | impl From for FlatRadioConfig { 263 | fn from(config: RadioConfig) -> Self { 264 | let socket_config = config.socket_config; 265 | let send_config = config.send_config; 266 | Self { 267 | connect: socket_config.connect, 268 | bind: socket_config.bind, 269 | send_hwm: send_config.send_hwm, 270 | send_timeout: send_config.send_timeout, 271 | no_drop: config.no_drop, 272 | mechanism: socket_config.mechanism, 273 | } 274 | } 275 | } 276 | 277 | impl From for RadioConfig { 278 | fn from(flat: FlatRadioConfig) -> Self { 279 | let socket_config = SocketConfig { 280 | connect: flat.connect, 281 | bind: flat.bind, 282 | mechanism: flat.mechanism, 283 | }; 284 | let send_config = SendConfig { 285 | send_hwm: flat.send_hwm, 286 | send_timeout: flat.send_timeout, 287 | }; 288 | Self { 289 | socket_config, 290 | send_config, 291 | no_drop: flat.no_drop, 292 | } 293 | } 294 | } 295 | 296 | impl GetSocketConfig for RadioConfig { 297 | fn socket_config(&self) -> &SocketConfig { 298 | &self.socket_config 299 | } 300 | 301 | fn socket_config_mut(&mut self) -> &mut SocketConfig { 302 | &mut self.socket_config 303 | } 304 | } 305 | 306 | impl ConfigureSocket for RadioConfig {} 307 | 308 | impl GetSendConfig for RadioConfig { 309 | fn send_config(&self) -> &SendConfig { 310 | &self.send_config 311 | } 312 | 313 | fn send_config_mut(&mut self) -> &mut SendConfig { 314 | &mut self.send_config 315 | } 316 | } 317 | 318 | impl ConfigureSend for RadioConfig {} 319 | 320 | /// A builder for a `Radio`. 321 | /// 322 | /// Allows for ergonomic one line socket configuration. 323 | #[derive(Debug, Default, Clone, PartialEq, Eq, Hash)] 324 | pub struct RadioBuilder { 325 | inner: RadioConfig, 326 | } 327 | 328 | impl RadioBuilder { 329 | pub fn new() -> Self { 330 | Self::default() 331 | } 332 | 333 | pub fn no_drop(&mut self) -> &mut Self { 334 | self.inner.set_no_drop(true); 335 | self 336 | } 337 | 338 | pub fn build(&self) -> Result { 339 | self.inner.build() 340 | } 341 | 342 | pub fn with_ctx(&self, handle: CtxHandle) -> Result { 343 | self.inner.with_ctx(handle) 344 | } 345 | } 346 | 347 | impl GetSocketConfig for RadioBuilder { 348 | fn socket_config(&self) -> &SocketConfig { 349 | self.inner.socket_config() 350 | } 351 | 352 | fn socket_config_mut(&mut self) -> &mut SocketConfig { 353 | self.inner.socket_config_mut() 354 | } 355 | } 356 | 357 | impl BuildSocket for RadioBuilder {} 358 | 359 | impl GetSendConfig for RadioBuilder { 360 | fn send_config(&self) -> &SendConfig { 361 | self.inner.send_config() 362 | } 363 | 364 | fn send_config_mut(&mut self) -> &mut SendConfig { 365 | self.inner.send_config_mut() 366 | } 367 | } 368 | 369 | impl BuildSend for RadioBuilder {} 370 | 371 | #[cfg(test)] 372 | mod test { 373 | use super::*; 374 | 375 | #[test] 376 | fn test_ser_de() { 377 | let config = RadioConfig::new(); 378 | 379 | let ron = serde_yaml::to_string(&config).unwrap(); 380 | let de: RadioConfig = serde_yaml::from_str(&ron).unwrap(); 381 | assert_eq!(config, de); 382 | } 383 | } 384 | -------------------------------------------------------------------------------- /libzmq/src/core/sockopt.rs: -------------------------------------------------------------------------------- 1 | use crate::error::{msg_from_errno, Error, ErrorKind}; 2 | use libzmq_sys as sys; 3 | use sys::errno; 4 | 5 | use libc::{c_int, size_t}; 6 | 7 | use std::{ 8 | ffi::CString, 9 | os::raw::c_void, 10 | time::Duration, 11 | {mem, ptr, str}, 12 | }; 13 | 14 | // This is the value `czmq` uses. 15 | // https://github.com/zeromq/czmq/blob/master/src/zsock_option.inc#L389 16 | const MAX_OPTION_SIZE: size_t = 255; 17 | 18 | #[derive(Copy, Clone, Debug)] 19 | #[allow(dead_code)] 20 | pub(crate) enum SocketOption { 21 | Backlog = sys::ZMQ_BACKLOG as isize, 22 | ConnectTimeout = sys::ZMQ_CONNECT_TIMEOUT as isize, 23 | FileDescriptor = sys::ZMQ_FD as isize, 24 | HeartbeatInterval = sys::ZMQ_HEARTBEAT_IVL as isize, 25 | HeartbeatTimeout = sys::ZMQ_HEARTBEAT_TIMEOUT as isize, 26 | HeartbeatTtl = sys::ZMQ_HEARTBEAT_TTL as isize, 27 | SendHighWaterMark = sys::ZMQ_SNDHWM as isize, 28 | SendTimeout = sys::ZMQ_SNDTIMEO as isize, 29 | RecvHighWaterMark = sys::ZMQ_RCVHWM as isize, 30 | RecvTimeout = sys::ZMQ_RCVTIMEO as isize, 31 | NoDrop = sys::ZMQ_XPUB_NODROP as isize, 32 | Linger = sys::ZMQ_LINGER as isize, 33 | LastEndpoint = sys::ZMQ_LAST_ENDPOINT as isize, 34 | PlainPassword = sys::ZMQ_PLAIN_PASSWORD as isize, 35 | PlainUsername = sys::ZMQ_PLAIN_USERNAME as isize, 36 | PlainServer = sys::ZMQ_PLAIN_SERVER as isize, 37 | EnforceDomain = sys::ZMQ_ZAP_ENFORCE_DOMAIN as isize, 38 | ZapDomain = sys::ZMQ_ZAP_DOMAIN as isize, 39 | Subscribe = sys::ZMQ_SUBSCRIBE as isize, 40 | Unsubscribe = sys::ZMQ_UNSUBSCRIBE as isize, 41 | CurvePublicKey = sys::ZMQ_CURVE_PUBLICKEY as isize, 42 | CurveSecretKey = sys::ZMQ_CURVE_SECRETKEY as isize, 43 | CurveServer = sys::ZMQ_CURVE_SERVER as isize, 44 | CurveServerKey = sys::ZMQ_CURVE_SERVERKEY as isize, 45 | InBatchSize = sys::ZMQ_IN_BATCH_SIZE as isize, 46 | OutBatchSize = sys::ZMQ_OUT_BATCH_SIZE as isize, 47 | } 48 | 49 | impl From for c_int { 50 | fn from(s: SocketOption) -> c_int { 51 | match s { 52 | SocketOption::Backlog => SocketOption::Backlog as c_int, 53 | SocketOption::ConnectTimeout => { 54 | SocketOption::ConnectTimeout as c_int 55 | } 56 | SocketOption::FileDescriptor => { 57 | SocketOption::FileDescriptor as c_int 58 | } 59 | SocketOption::HeartbeatInterval => { 60 | SocketOption::HeartbeatInterval as c_int 61 | } 62 | SocketOption::HeartbeatTimeout => { 63 | SocketOption::HeartbeatTimeout as c_int 64 | } 65 | SocketOption::HeartbeatTtl => SocketOption::HeartbeatTtl as c_int, 66 | SocketOption::SendHighWaterMark => { 67 | SocketOption::SendHighWaterMark as c_int 68 | } 69 | SocketOption::SendTimeout => SocketOption::SendTimeout as c_int, 70 | SocketOption::RecvHighWaterMark => { 71 | SocketOption::RecvHighWaterMark as c_int 72 | } 73 | SocketOption::RecvTimeout => SocketOption::RecvTimeout as c_int, 74 | SocketOption::NoDrop => SocketOption::NoDrop as c_int, 75 | SocketOption::Linger => SocketOption::Linger as c_int, 76 | SocketOption::LastEndpoint => SocketOption::LastEndpoint as c_int, 77 | SocketOption::PlainPassword => SocketOption::PlainPassword as c_int, 78 | SocketOption::PlainUsername => SocketOption::PlainUsername as c_int, 79 | SocketOption::PlainServer => SocketOption::PlainServer as c_int, 80 | SocketOption::EnforceDomain => SocketOption::EnforceDomain as c_int, 81 | SocketOption::ZapDomain => SocketOption::ZapDomain as c_int, 82 | SocketOption::Subscribe => SocketOption::Subscribe as c_int, 83 | SocketOption::Unsubscribe => SocketOption::Unsubscribe as c_int, 84 | SocketOption::CurvePublicKey => { 85 | SocketOption::CurvePublicKey as c_int 86 | } 87 | SocketOption::CurveSecretKey => { 88 | SocketOption::CurveSecretKey as c_int 89 | } 90 | SocketOption::CurveServer => SocketOption::CurveServer as c_int, 91 | SocketOption::CurveServerKey => { 92 | SocketOption::CurveServerKey as c_int 93 | } 94 | SocketOption::InBatchSize => SocketOption::InBatchSize as c_int, 95 | SocketOption::OutBatchSize => SocketOption::OutBatchSize as c_int, 96 | } 97 | } 98 | } 99 | 100 | fn getsockopt( 101 | mut_sock_ptr: *mut c_void, 102 | option: SocketOption, 103 | mut_value_ptr: *mut c_void, 104 | size: &mut size_t, 105 | ) -> Result<(), Error> { 106 | let rc = unsafe { 107 | sys::zmq_getsockopt(mut_sock_ptr, option.into(), mut_value_ptr, size) 108 | }; 109 | 110 | if rc == -1 { 111 | let errno = unsafe { sys::zmq_errno() }; 112 | let err = match errno { 113 | errno::EINVAL => panic!("invalid option"), 114 | errno::ETERM => Error::new(ErrorKind::InvalidCtx), 115 | errno::ENOTSOCK => panic!("invalid socket"), 116 | errno::EINTR => Error::new(ErrorKind::Interrupted), 117 | _ => panic!(msg_from_errno(errno)), 118 | }; 119 | 120 | Err(err) 121 | } else { 122 | Ok(()) 123 | } 124 | } 125 | 126 | pub(crate) fn getsockopt_bool( 127 | mut_sock_ptr: *mut c_void, 128 | option: SocketOption, 129 | ) -> Result { 130 | let mut value = c_int::default(); 131 | let mut size = mem::size_of::(); 132 | let value_ptr = &mut value as *mut c_int as *mut c_void; 133 | 134 | getsockopt(mut_sock_ptr, option, value_ptr, &mut size)?; 135 | 136 | Ok(value != 0) 137 | } 138 | 139 | pub(crate) fn getsockopt_scalar( 140 | mut_sock_ptr: *mut c_void, 141 | option: SocketOption, 142 | ) -> Result 143 | where 144 | T: Default, 145 | { 146 | let mut value = T::default(); 147 | let mut size = mem::size_of::(); 148 | let value_ptr = &mut value as *mut T as *mut c_void; 149 | 150 | getsockopt(mut_sock_ptr, option, value_ptr, &mut size)?; 151 | 152 | Ok(value) 153 | } 154 | 155 | pub(crate) fn getsockopt_bytes( 156 | mut_sock_ptr: *mut c_void, 157 | option: SocketOption, 158 | ) -> Result>, Error> { 159 | let mut size = MAX_OPTION_SIZE; 160 | let mut value = vec![0u8; size]; 161 | let value_ptr = value.as_mut_ptr() as *mut c_void; 162 | 163 | getsockopt(mut_sock_ptr, option, value_ptr, &mut size)?; 164 | 165 | if size == 0 { 166 | Ok(None) 167 | } else { 168 | value.truncate(size); 169 | Ok(Some(value)) 170 | } 171 | } 172 | 173 | pub(crate) fn getsockopt_string( 174 | mut_sock_ptr: *mut c_void, 175 | option: SocketOption, 176 | ) -> Result, Error> { 177 | match getsockopt_bytes(mut_sock_ptr, option)? { 178 | Some(mut bytes) => { 179 | // Remove null byte. 180 | bytes.pop(); 181 | 182 | if bytes.is_empty() { 183 | Ok(None) 184 | } else { 185 | let c_str = unsafe { CString::from_vec_unchecked(bytes) }; 186 | Ok(Some(c_str.into_string().unwrap())) 187 | } 188 | } 189 | None => Ok(None), 190 | } 191 | } 192 | 193 | pub(crate) fn getsockopt_option_duration( 194 | mut_sock_ptr: *mut c_void, 195 | option: SocketOption, 196 | none_value: i32, 197 | ) -> Result, Error> { 198 | let ms: i32 = getsockopt_scalar(mut_sock_ptr, option)?; 199 | if ms == none_value { 200 | Ok(None) 201 | } else { 202 | Ok(Some(Duration::from_millis(ms as u64))) 203 | } 204 | } 205 | 206 | fn setsockopt( 207 | mut_sock_ptr: *mut c_void, 208 | option: SocketOption, 209 | value_ptr: *const c_void, 210 | size: size_t, 211 | ) -> Result<(), Error> { 212 | let rc = unsafe { 213 | sys::zmq_setsockopt(mut_sock_ptr, option.into(), value_ptr, size) 214 | }; 215 | 216 | if rc == -1 { 217 | let errno = unsafe { sys::zmq_errno() }; 218 | let err = match errno { 219 | errno::EINVAL => panic!("invalid option"), 220 | errno::ETERM => Error::new(ErrorKind::InvalidCtx), 221 | errno::ENOTSOCK => panic!("invalid socket"), 222 | errno::EINTR => Error::new(ErrorKind::Interrupted), 223 | _ => panic!(msg_from_errno(errno)), 224 | }; 225 | 226 | Err(err) 227 | } else { 228 | Ok(()) 229 | } 230 | } 231 | 232 | pub(crate) fn setsockopt_bool( 233 | mut_sock_ptr: *mut c_void, 234 | option: SocketOption, 235 | value: bool, 236 | ) -> Result<(), Error> { 237 | let value = value as c_int; 238 | let size = mem::size_of::() as size_t; 239 | let value_ptr = &value as *const c_int as *const c_void; 240 | 241 | setsockopt(mut_sock_ptr, option, value_ptr, size) 242 | } 243 | 244 | pub(crate) fn setsockopt_scalar( 245 | mut_sock_ptr: *mut c_void, 246 | option: SocketOption, 247 | value: T, 248 | ) -> Result<(), Error> { 249 | let size = mem::size_of::() as size_t; 250 | let value_ptr = &value as *const T as *const c_void; 251 | 252 | setsockopt(mut_sock_ptr, option, value_ptr, size) 253 | } 254 | 255 | pub(crate) fn setsockopt_option_scalar( 256 | mut_sock_ptr: *mut c_void, 257 | option: SocketOption, 258 | maybe: Option, 259 | none_value: T, 260 | ) -> Result<(), Error> 261 | where 262 | T: Eq, 263 | { 264 | let size = mem::size_of::() as size_t; 265 | 266 | match maybe { 267 | Some(value) => { 268 | let value_ptr = &value as *const T as *const c_void; 269 | setsockopt(mut_sock_ptr, option, value_ptr, size) 270 | } 271 | None => { 272 | let value_ptr = &none_value as *const T as *const c_void; 273 | setsockopt(mut_sock_ptr, option, value_ptr, size) 274 | } 275 | } 276 | } 277 | 278 | pub(crate) fn setsockopt_bytes( 279 | mut_sock_ptr: *mut c_void, 280 | option: SocketOption, 281 | maybe: Option<&[u8]>, 282 | ) -> Result<(), Error> { 283 | match maybe { 284 | Some(bytes) => { 285 | let size = bytes.len(); 286 | let value_ptr = bytes.as_ptr() as *const c_void; 287 | setsockopt(mut_sock_ptr, option, value_ptr, size) 288 | } 289 | None => setsockopt_null(mut_sock_ptr, option), 290 | } 291 | } 292 | 293 | pub(crate) fn setsockopt_str( 294 | mut_sock_ptr: *mut c_void, 295 | option: SocketOption, 296 | maybe: Option<&str>, 297 | ) -> Result<(), Error> { 298 | // No need to add a terminating zero byte. 299 | // http://api.zeromq.org/master:zmq-setsockopt 300 | setsockopt_bytes(mut_sock_ptr, option, maybe.map(str::as_bytes)) 301 | } 302 | 303 | pub(crate) fn setsockopt_null( 304 | mut_sock_ptr: *mut c_void, 305 | option: SocketOption, 306 | ) -> Result<(), Error> { 307 | setsockopt(mut_sock_ptr, option, ptr::null(), 0) 308 | } 309 | 310 | pub(crate) fn setsockopt_option_duration( 311 | mut_sock_ptr: *mut c_void, 312 | option: SocketOption, 313 | maybe: Option, 314 | none_value: i32, 315 | ) -> Result<(), Error> { 316 | if let Some(duration) = maybe { 317 | check_duration(duration)?; 318 | } 319 | 320 | setsockopt_option_scalar( 321 | mut_sock_ptr, 322 | option, 323 | maybe.map(|d| d.as_millis() as i32), 324 | none_value, 325 | ) 326 | } 327 | 328 | pub(crate) fn setsockopt_duration( 329 | mut_sock_ptr: *mut c_void, 330 | option: SocketOption, 331 | duration: Duration, 332 | ) -> Result<(), Error> { 333 | check_duration(duration)?; 334 | setsockopt_scalar(mut_sock_ptr, option, duration.as_millis() as i32) 335 | } 336 | 337 | fn check_duration(duration: Duration) -> Result<(), Error> { 338 | if duration.as_millis() > i32::max_value() as u128 { 339 | Err(Error::new(ErrorKind::InvalidInput( 340 | "ms in duration cannot be greater than i32::MAX", 341 | ))) 342 | } else { 343 | Ok(()) 344 | } 345 | } 346 | --------------------------------------------------------------------------------