From 615549bab4e2cf2d2fd60e7350b843707d954de3 Mon Sep 17 00:00:00 2001 From: olivakar Date: Wed, 21 Feb 2024 23:04:55 +0000 Subject: [PATCH 1/4] first sample --- .devcontainer/devcontainer.json | 4 +- .devcontainer/postCreateCommand.sh | 6 +- mqttclients/rust/conn/Cargo.toml | 9 + mqttclients/rust/conn/src/lib.rs | 97 +++++++++++ scenarios/getting_started/README.md | 21 ++- .../rust/getting_started/Cargo.toml | 15 ++ .../rust/getting_started/src/main.rs | 161 ++++++++++++++++++ 7 files changed, 310 insertions(+), 3 deletions(-) create mode 100644 mqttclients/rust/conn/Cargo.toml create mode 100644 mqttclients/rust/conn/src/lib.rs create mode 100644 scenarios/getting_started/rust/getting_started/Cargo.toml create mode 100644 scenarios/getting_started/rust/getting_started/src/main.rs diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 3889564..c6cbf12 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -1,7 +1,9 @@ { "image": "mcr.microsoft.com/devcontainers/universal:2", "features": { - "ghcr.io/devcontainers/features/azure-cli:1": {} + "ghcr.io/devcontainers/features/azure-cli:1": {}, + "ghcr.io/devcontainers/features/rust:1": {}, + "ghcr.io/rio/features/k9s:1": {} }, "postCreateCommand": "bash ./.devcontainer/postCreateCommand.sh", "customizations": { diff --git a/.devcontainer/postCreateCommand.sh b/.devcontainer/postCreateCommand.sh index 4a474e1..568e8b1 100644 --- a/.devcontainer/postCreateCommand.sh +++ b/.devcontainer/postCreateCommand.sh @@ -13,4 +13,8 @@ sudo apt-get install cmake -y #Install step cli wget https://github.com/smallstep/cli/releases/download/v0.24.4/step-cli_0.24.4_amd64.deb sudo dpkg -i step-cli_0.24.4_amd64.deb -rm step-cli_0.24.4_amd64.deb \ No newline at end of file +rm step-cli_0.24.4_amd64.deb + +#Install rust +# curl --proto '=https' --tlsv1.2 https://sh.rustup.rs -sSf | sh +sudo apt install libssl-dev build-essential cmake \ No newline at end of file diff --git a/mqttclients/rust/conn/Cargo.toml b/mqttclients/rust/conn/Cargo.toml new file mode 100644 index 0000000..8847c7f --- /dev/null +++ b/mqttclients/rust/conn/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "conn" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +dotenv = "0.15.0" \ No newline at end of file diff --git a/mqttclients/rust/conn/src/lib.rs b/mqttclients/rust/conn/src/lib.rs new file mode 100644 index 0000000..3090d3b --- /dev/null +++ b/mqttclients/rust/conn/src/lib.rs @@ -0,0 +1,97 @@ +// Define the connection_settings module +pub mod connection_settings { + // Import necessary modules + use std::collections::HashMap; + use std::env; + use std::error::Error; + + // Define a struct to represent the connection settings + #[derive(Debug)] + pub struct ConnectionSettings { + pub mqtt_host_name: String, + pub mqtt_tcp_port: u16, + pub mqtt_use_tls: bool, + pub mqtt_clean_session: bool, + pub mqtt_keep_alive_in_seconds: u16, + pub mqtt_client_id: String, + pub mqtt_username: Option, + pub mqtt_password: Option, + pub mqtt_ca_file: Option, + pub mqtt_cert_file: Option, + pub mqtt_key_file: Option, + pub mqtt_key_file_password: Option, + } + + // Implement a method to convert strings to integers + fn convert_to_int(value: &str, name: &str) -> Result> { + value.parse::().map_err(|_| format!("{} must be an integer", name).into()) + } + + // Implement a method to convert strings to booleans + fn convert_to_bool(value: &str, name: &str) -> Result> { + match value.to_lowercase().as_str() { + "true" => Ok(true), + "false" => Ok(false), + _ => Err(format!("{} must be true or false", name).into()), + } + } + + // Define other helper functions or associated items here + pub fn get_connection_settings(env_filename: Option<&str>) -> Result> { + // Load environment variables from .env file if provided + if let Some(filename) = env_filename { + dotenv::from_path(filename)?; + } + + // Read environment variables into a HashMap + let mut env_values: HashMap = env::vars().collect(); + + // Define default values + let default_values: HashMap<&str, &str> = [ + ("MQTT_TCP_PORT", "8883"), + ("MQTT_USE_TLS", "true"), + ("MQTT_CLEAN_SESSION", "true"), + ("MQTT_KEEP_ALIVE_IN_SECONDS", "30"), + ] + .iter() + .cloned() + .collect(); + + // Merge default values with environment variables + for (key, value) in default_values.iter() { + env_values.entry(key.to_string()).or_insert(value.to_string()); + } + + // Extract connection settings from environment variables + let mqtt_host_name = env_values.remove("MQTT_HOST_NAME").ok_or("MQTT_HOST_NAME must be set")?; + let mqtt_tcp_port = convert_to_int(env_values.remove("MQTT_TCP_PORT").unwrap().as_str(), "MQTT_TCP_PORT")?; + let mqtt_use_tls = convert_to_bool(env_values.remove("MQTT_USE_TLS").unwrap().as_str(), "MQTT_USE_TLS")?; + let mqtt_clean_session = convert_to_bool(env_values.remove("MQTT_CLEAN_SESSION").unwrap().as_str(), "MQTT_CLEAN_SESSION")?; + let mqtt_keep_alive_in_seconds = convert_to_int(env_values.remove("MQTT_KEEP_ALIVE_IN_SECONDS").unwrap().as_str(), "MQTT_KEEP_ALIVE_IN_SECONDS")?; + let mqtt_client_id = env_values.remove("MQTT_CLIENT_ID").unwrap_or_else(|| "".to_string()); + let mqtt_username = env_values.remove("MQTT_USERNAME"); + let mqtt_password = env_values.remove("MQTT_PASSWORD"); + let mqtt_ca_file = env_values.remove("MQTT_CA_FILE"); + let mqtt_cert_file = env_values.remove("MQTT_CERT_FILE"); + let mqtt_key_file = env_values.remove("MQTT_KEY_FILE"); + let mqtt_key_file_password = env_values.remove("MQTT_KEY_FILE_PASSWORD"); + + // Construct ConnectionSettings struct + let settings = ConnectionSettings { + mqtt_host_name, + mqtt_tcp_port, + mqtt_use_tls, + mqtt_clean_session, + mqtt_keep_alive_in_seconds, + mqtt_client_id, + mqtt_username, + mqtt_password, + mqtt_ca_file, + mqtt_cert_file, + mqtt_key_file, + mqtt_key_file_password, + }; + + Ok(settings) + } +} diff --git a/scenarios/getting_started/README.md b/scenarios/getting_started/README.md index 2f550a5..33934a0 100644 --- a/scenarios/getting_started/README.md +++ b/scenarios/getting_started/README.md @@ -224,4 +224,23 @@ Run the sample using settings from an envfile ```bash # from folder scenarios/getting_started go/bin/getting_started .env -``` \ No newline at end of file +``` + +### Rust +*The commands below assume you are in the MqttApplicationSamples/scenarios/getting_started directory.*s + +To compile the sample run: +```bash +cargo build --manifest-path rust/getting_started/Cargo.toml +``` +TO run the sample do: +```bash +cargo run --manifest-path rust/getting_started/Cargo.toml +``` + +To set debug level on mqtt client do +```bash +RUST_LOG=debug cargo run --manifest-path rust/getting_started/Cargo.toml +``` + + diff --git a/scenarios/getting_started/rust/getting_started/Cargo.toml b/scenarios/getting_started/rust/getting_started/Cargo.toml new file mode 100644 index 0000000..57d9456 --- /dev/null +++ b/scenarios/getting_started/rust/getting_started/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "getting_started" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +futures = "0.3" +# openssl = "0.10.36" +paho-mqtt = { version = "0.12.3", features = ["bundled"] } +dotenv = "0.15.0" +env_logger = "0.11.2" +ctrlc = "3.3.0" +conn = { path = "../../../../mqttclients/rust/conn" } diff --git a/scenarios/getting_started/rust/getting_started/src/main.rs b/scenarios/getting_started/rust/getting_started/src/main.rs new file mode 100644 index 0000000..5ddcc01 --- /dev/null +++ b/scenarios/getting_started/rust/getting_started/src/main.rs @@ -0,0 +1,161 @@ +use conn::connection_settings; +use dotenv::dotenv; +use paho_mqtt as mqtt; +use paho_mqtt::SslOptionsBuilder; +use paho_mqtt::SslVersion; +use paho_mqtt::MQTT_VERSION_5; +use std::time::Duration; + +fn main() { + // Initialize the logger from the environment + env_logger::init(); + + dotenv().ok(); + let conn_settings = connection_settings::get_connection_settings(Some(".env")).unwrap(); + let mqtt_clean_session = conn_settings.mqtt_clean_session; + if !mqtt_clean_session { + println!("This sample does not support connecting with existing sessions"); + std::process::exit(1); + } + let port = conn_settings.mqtt_tcp_port; + let hostname = conn_settings.mqtt_host_name; + let keepalive = conn_settings.mqtt_keep_alive_in_seconds; + let client_id = conn_settings.mqtt_client_id; + let mut protocol = "tcp"; + + // Set up connection builder + let mut conn_opts_builder = mqtt::ConnectOptionsBuilder::with_mqtt_version(MQTT_VERSION_5); + conn_opts_builder + .keep_alive_interval(Duration::from_secs(keepalive.into())) + .clean_start(mqtt_clean_session); + + if let Some(username) = &conn_settings.mqtt_username { + conn_opts_builder.user_name(username); + } + + if let Some(password) = &conn_settings.mqtt_password { + conn_opts_builder.password(password); + } + + if conn_settings.mqtt_use_tls { + protocol = "mqtts"; + + // Create an SSL options builder + let mut ssl_opts_builder = SslOptionsBuilder::new(); + ssl_opts_builder.ssl_version(SslVersion::Tls_1_2); + + // Trust store (CA file) + if let Some(ca_file_path) = conn_settings.mqtt_ca_file { + // Handle the Result returned by trust_store + match ssl_opts_builder.trust_store(ca_file_path) { + Ok(_) => { + // Trust store loaded successfully, continue + } + Err(err) => { + eprintln!("Failed to load trust store: {:?}", err); + return; + } + } + } + + // Certificate file + if let Some(cert_file_path) = conn_settings.mqtt_cert_file { + // ssl_opts_builder = ssl_opts_builder.key_store(cert_file_path); + match ssl_opts_builder.key_store(cert_file_path) { + Ok(_) => { + // Key store loaded successfully, continue + } + Err(err) => { + eprintln!("Failed to load key store: {:?}", err); + return; + } + } + } + + if let Some(key_file_path) = conn_settings.mqtt_key_file { + match ssl_opts_builder.private_key(key_file_path) { + Ok(_) => { + // Key store loaded successfully, continue + } + Err(err) => { + eprintln!("Failed to load private key: {:?}", err); + return; + } + } + } + + if let Some(key_password) = conn_settings.mqtt_key_file_password { + ssl_opts_builder.private_key_password(key_password); + } else { + // Handle the case where key_password is None, if needed. + } + let ssl_opts = ssl_opts_builder.finalize(); + conn_opts_builder.ssl_options(ssl_opts); + } + + let host = format!("{}://{}:{}", protocol, hostname, port); + println!("Host is {}", host); + + let create_options = mqtt::CreateOptionsBuilder::new() + .server_uri(host) + .client_id(client_id) + .finalize(); + + // Set up MQTT client + let client = mqtt::Client::new(create_options).unwrap_or_else(|err| { + eprintln!("Error creating the client: {}", err); + std::process::exit(1); + }); + + // Initialize the consumer before connecting + let rx = client.start_consuming(); + + let conn_opts = conn_opts_builder.finalize(); + + // Connect to the broker + if let Err(err) = client.connect(conn_opts) { + eprintln!("Failed to connect to the broker: {}", err); + std::process::exit(1); + } + println!("Connected to the broker"); + + // Subscribe to a topic + // sample/# + if let Err(err) = client.subscribe("sample/#", mqtt::QOS_0) { + eprintln!("Failed to subscribe to topics: {}", err); + std::process::exit(1); + } + println!("Subscribed to topics"); + + // Publish a message + let msg = mqtt::Message::new("sample/topic1", "hello world!", mqtt::QOS_0); + if let Err(err) = client.publish(msg) { + eprintln!("Failed to publish message: {}", err); + std::process::exit(1); + } + println!("Published message"); + + // ^C handler will stop the consumer, breaking us out of the loop, below + let ctrlc_cli = client.clone(); + ctrlc::set_handler(move || { + ctrlc_cli.stop_consuming(); + }) + .expect("Error setting Ctrl-C handler"); + + // Just loop on incoming messages. + println!("\nWaiting for messages.."); + for msg in rx.iter() { + if let Some(msg) = msg { + println!("Message received is {}", msg); + } else if client.is_connected() { + break; + } + } + + // Disconnect from the broker + if let Err(err) = client.disconnect(None) { + eprintln!("Failed to disconnect from the broker: {}", err); + std::process::exit(1); + } + println!("Disconnected from the broker"); +} From de673a5cb29563c223aaadb0671a2db091f85f64 Mon Sep 17 00:00:00 2001 From: olivakar Date: Thu, 22 Feb 2024 23:15:11 +0000 Subject: [PATCH 2/4] wip --- Setup.md | 9 +++++++++ scenarios/getting_started/README.md | 3 ++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/Setup.md b/Setup.md index 98baa8a..37632df 100644 --- a/Setup.md +++ b/Setup.md @@ -181,3 +181,12 @@ See [c extensions](./mqttclients/c/README.md) for more details. ### Python Python samples have been tested with python 3.10.4, to install follow the instructions from https://www.python.org/downloads/ + +### RUST + +Rust samples have been tested `rustc 1.76.0` and `cargo 1.76.0`. This codespace would be preconfigured with rust. +To install rust and cargo otherwise follow instructions from https://doc.rust-lang.org/book/ch01-01-installation.html + +Please additionally do `sudo apt install libssl-dev build-essential cmake `to use Paho MQtt Rust libary on linux. +If there are any problems related to Paho Mqtt Library for rust please follow this page https://github.com/eclipse/paho.mqtt.rust as well. + diff --git a/scenarios/getting_started/README.md b/scenarios/getting_started/README.md index 33934a0..896861f 100644 --- a/scenarios/getting_started/README.md +++ b/scenarios/getting_started/README.md @@ -227,7 +227,8 @@ Run the sample using settings from an envfile ``` ### Rust -*The commands below assume you are in the MqttApplicationSamples/scenarios/getting_started directory.*s +*The commands below assume you are in the MqttApplicationSamples/scenarios/getting_started directory.* +*Assumes that we have cargo and rust installed as well.* To compile the sample run: ```bash From 92f870ff73ae17c4218ae255af5c8094dd12c21f Mon Sep 17 00:00:00 2001 From: olivakar Date: Fri, 23 Feb 2024 01:07:51 +0000 Subject: [PATCH 3/4] not k9s --- .devcontainer/devcontainer.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index c6cbf12..6d46667 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -2,8 +2,7 @@ "image": "mcr.microsoft.com/devcontainers/universal:2", "features": { "ghcr.io/devcontainers/features/azure-cli:1": {}, - "ghcr.io/devcontainers/features/rust:1": {}, - "ghcr.io/rio/features/k9s:1": {} + "ghcr.io/devcontainers/features/rust:1": {} }, "postCreateCommand": "bash ./.devcontainer/postCreateCommand.sh", "customizations": { From fb3185925fd18ec9b0d9a23b20c58ba23ec57cc2 Mon Sep 17 00:00:00 2001 From: olivakar Date: Fri, 23 Feb 2024 22:13:38 +0000 Subject: [PATCH 4/4] add ws cargo --- Cargo.toml | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 Cargo.toml diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..4165a09 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,6 @@ +[workspace] + +members = [ + "scenarios/getting_started/rust/getting_started", + "mqttclients/rust/conn", +]