Skip to content

Commit

Permalink
Merge pull request #93 from Azure-Samples/ok/rust-getting-started
Browse files Browse the repository at this point in the history
rust getting started
  • Loading branch information
olivakar authored Feb 29, 2024
2 parents 42fadb8 + f6fdbce commit b7f68c9
Show file tree
Hide file tree
Showing 9 changed files with 323 additions and 2 deletions.
3 changes: 2 additions & 1 deletion .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
{
"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": {}
},
"postCreateCommand": "bash ./.devcontainer/postCreateCommand.sh",
"customizations": {
Expand Down
6 changes: 5 additions & 1 deletion .devcontainer/postCreateCommand.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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
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
6 changes: 6 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[workspace]

members = [
"scenarios/getting_started/rust/getting_started",
"mqttclients/rust/conn",
]
9 changes: 9 additions & 0 deletions Setup.md
Original file line number Diff line number Diff line change
Expand Up @@ -195,3 +195,12 @@ npm run build
```

Each of the samples can be run and debugged either from [Visual Studio Code](https://code.visualstudio.com/), or from the command line. See the README file in each scenario for specific instructions.

### 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.

9 changes: 9 additions & 0 deletions mqttclients/rust/conn/Cargo.toml
Original file line number Diff line number Diff line change
@@ -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"
97 changes: 97 additions & 0 deletions mqttclients/rust/conn/src/lib.rs
Original file line number Diff line number Diff line change
@@ -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<String>,
pub mqtt_password: Option<String>,
pub mqtt_ca_file: Option<String>,
pub mqtt_cert_file: Option<String>,
pub mqtt_key_file: Option<String>,
pub mqtt_key_file_password: Option<String>,
}

// Implement a method to convert strings to integers
fn convert_to_int(value: &str, name: &str) -> Result<u16, Box<dyn Error>> {
value.parse::<u16>().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<bool, Box<dyn Error>> {
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<ConnectionSettings, Box<dyn Error>> {
// 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<String, String> = 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)
}
}
19 changes: 19 additions & 0 deletions scenarios/getting_started/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,7 @@ Run the sample using settings from an envfile
# from folder scenarios/getting_started
go/bin/getting_started .env
```

### TypeScript
To build the TypeScript sample run:
>Note: The scenario should already be built from the initial `npm i` command at the root.
Expand Down Expand Up @@ -255,3 +256,21 @@ Using the command line:
```bash
export DEBUG=mqttjs* && node ./ts/gettingStarted/dist/index.js --env-file .env
```

### Rust
*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
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
```
15 changes: 15 additions & 0 deletions scenarios/getting_started/rust/getting_started/Cargo.toml
Original file line number Diff line number Diff line change
@@ -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" }
161 changes: 161 additions & 0 deletions scenarios/getting_started/rust/getting_started/src/main.rs
Original file line number Diff line number Diff line change
@@ -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");
}

0 comments on commit b7f68c9

Please sign in to comment.