Repository: bytebeamio/rumqtt (language: rust)

Issue:
parse_url does not handle ssl/tls like document implies
The document says:
> NOTE: A url must be prefixed with one of either tcp://, mqtt://, ssl://,mqtts://, ws:// or wss:// to denote the protocol for establishing a connection with the broker.

https://docs.rs/rumqttc/0.14.0/rumqttc/struct.MqttOptions.html#method.parse_url

While `"mqtts" | "ssl"` is simply not handled:
https://github.com/bytebeamio/rumqtt/blob/2f11546b64d4ab86b2b49642d8b7068f624992a9/rumqttc/src/lib.rs#L611-L620
similar to #434 

At least the document should be updated to prevent confusion.


Discussion / hints:

Fix patch:
diff --git a/rumqttc/Cargo.toml b/rumqttc/Cargo.toml
index 978c75f6e..d895e03ac 100644
--- a/rumqttc/Cargo.toml
+++ b/rumqttc/Cargo.toml
@@ -29,6 +29,7 @@ rustls-pemfile = { version = "0.3", optional = true }
 thiserror = "1"
 tokio = { version = "1", features = ["rt", "macros", "io-util", "net", "time"] }
 tokio-rustls = { version = "=0.23.3", optional = true }
+rustls-native-certs = "0.6"
 url = { version = "2", default-features = false, optional = true }
 ws_stream_tungstenite = { version = "0.7", default-features = false, features = ["tokio_io"], optional = true }
 
diff --git a/rumqttc/src/client.rs b/rumqttc/src/client.rs
index 84436d7de..a8bad452c 100644
--- a/rumqttc/src/client.rs
+++ b/rumqttc/src/client.rs
@@ -33,7 +33,7 @@ impl From<TrySendError<Request>> for ClientError {
 /// An asynchronous client, communicates with MQTT `EventLoop`.
 /// 
 /// This is cloneable and can be used to asynchronously [`publish`](`AsyncClient::publish`),
-/// [`subscribe`](`AsynClient::subscribe`) through the `EventLoop`, which is to be polled parallelly.
+/// [`subscribe`](`AsyncClient::subscribe`) through the `EventLoop`, which is to be polled parallelly.
 ///
 /// **NOTE**: The `EventLoop` must be regularly polled in order to send, receive and process packets 
 /// from the broker, i.e. move ahead.
@@ -217,7 +217,7 @@ fn get_ack_req(publish: &Publish) -> Option<Request> {
 /// A synchronous client, communicates with MQTT `EventLoop`.
 ///
 /// This is cloneable and can be used to synchronously [`publish`](`AsyncClient::publish`),
-/// [`subscribe`](`AsynClient::subscribe`) through the `EventLoop`/`Connection`, which is to be polled in parallel
+/// [`subscribe`](`AsyncClient::subscribe`) through the `EventLoop`/`Connection`, which is to be polled in parallel
 /// by iterating over the object returned by [`Connection.iter()`](Connection::iter) in a separate thread.
 ///
 /// **NOTE**: The `EventLoop`/`Connection` must be regularly polled(`.next()` in case of `Connection`) in order 
diff --git a/rumqttc/src/lib.rs b/rumqttc/src/lib.rs
index 6e65c4b37..539a4bb6a 100644
--- a/rumqttc/src/lib.rs
+++ b/rumqttc/src/lib.rs
@@ -117,6 +117,8 @@ pub use eventloop::{ConnectionError, Event, EventLoop};
 pub use flume::{SendError, Sender, TrySendError};
 pub use mqttbytes::v4::*;
 pub use mqttbytes::*;
+#[cfg(feature = "use-rustls")]
+pub use rustls_native_certs::load_native_certs;
 pub use state::{MqttState, StateError};
 #[cfg(feature = "use-rustls")]
 pub use tls::Error as TlsError;
@@ -124,7 +126,7 @@ pub use tls::Error as TlsError;
 #[cfg(feature = "use-rustls")]
 pub use tokio_rustls;
 #[cfg(feature = "use-rustls")]
-use tokio_rustls::rustls::ClientConfig;
+use tokio_rustls::rustls::{Certificate, ClientConfig, RootCertStore};
 
 pub type Incoming = Packet;
 
@@ -226,6 +228,11 @@ impl Transport {
         Self::Tcp
     }
 
+    #[cfg(feature = "use-rustls")]
+    pub fn tls_with_default_config() -> Self {
+        Self::tls_with_config(Default::default())
+    }
+
     /// Use secure tcp with tls as transport
     #[cfg(feature = "use-rustls")]
     pub fn tls(
@@ -281,6 +288,12 @@ impl Transport {
     pub fn wss_with_config(tls_config: TlsConfiguration) -> Self {
         Self::Wss(tls_config)
     }
+
+    #[cfg(all(feature = "use-rustls", feature = "websocket"))]
+    #[cfg_attr(docsrs, doc(cfg(all(feature = "use-rustls", feature = "websocket"))))]
+    pub fn wss_with_default_config() -> Self {
+        Self::Wss(Default::default())
+    }
 }
 
 /// TLS configuration method
@@ -299,6 +312,22 @@ pub enum TlsConfiguration {
     Rustls(Arc<ClientConfig>),
 }
 
+#[cfg(feature = "use-rustls")]
+impl Default for TlsConfiguration {
+    fn default() -> Self {
+        let mut root_cert_store = RootCertStore::empty();
+        for cert in load_native_certs().expect("could not load platform certs") {
+            root_cert_store.add(&Certificate(cert.0)).unwrap();
+        }
+        let tls_config = ClientConfig::builder()
+            .with_safe_defaults()
+            .with_root_certificates(root_cert_store)
+            .with_no_client_auth();
+
+        Self::Rustls(Arc::new(tls_config))
+    }
+}
+
 #[cfg(feature = "use-rustls")]
 impl From<ClientConfig> for TlsConfiguration {
     fn from(config: ClientConfig) -> Self {
@@ -402,8 +431,24 @@ impl MqttOptions {
     /// let options = MqttOptions::parse_url("mqtt://example.com:1883?client_id=123").unwrap();
     /// ```
     ///
-    /// NOTE: A url must be prefixed with one of either `tcp://`, `mqtt://`, `ssl://`,`mqtts://`,
+    /// **NOTE:** A url must be prefixed with one of either `tcp://`, `mqtt://`, `ssl://`,`mqtts://`,
     /// `ws://` or `wss://` to denote the protocol for establishing a connection with the broker.
+    ///
+    /// **NOTE:** Encrypted connections(i.e. `mqtts://`, `ssl://`, `wss://`) by default use the
+    /// system's root certificates. To configure with custom certificates, one may use the
+    /// [`set_transport`](MqttOptions::set_transport) method.
+    ///
+    /// ```
+    /// # use rumqttc::{MqttOptions, Transport};
+    /// # use tokio_rustls::rustls::ClientConfig;
+    /// # let root_cert_store = rustls::RootCertStore::empty();
+    /// # let client_config = ClientConfig::builder()
+    /// #    .with_safe_defaults()
+    /// #    .with_root_certificates(root_cert_store)
+    /// #    .with_no_client_auth();
+    /// let mut options = MqttOptions::parse_url("mqtts://example.com?client_id=123").unwrap();
+    /// options.set_transport(Transport::tls_with_config(client_config.into()));
+    /// ```
     pub fn parse_url<S: Into<String>>(url: S) -> Result<MqttOptions, OptionError> {
         use std::convert::TryFrom;
 
@@ -612,10 +657,12 @@ impl std::convert::TryFrom<url::Url> for MqttOptions {
             // Encrypted connections are supported, but require explicit TLS configuration. We fall
             // back to the unencrypted transport layer, so that `set_transport` can be used to
             // configure the encrypted transport layer with the provided TLS configuration.
-            "mqtts" | "ssl" => (Transport::Tcp, 8883),
+            "mqtts" | "ssl" => (Transport::tls_with_default_config(), 8883),
             "mqtt" | "tcp" => (Transport::Tcp, 1883),
             #[cfg(feature = "websocket")]
-            "ws" | "wss" => (Transport::Ws, 8000),
+            "ws" => (Transport::Ws, 8000),
+            #[cfg(feature = "websocket")]
+            "wss" => (Transport::wss_with_default_config(), 8000),
             _ => return Err(OptionError::Scheme),
         };
 


Test patch: