diff --git a/.gitignore b/.gitignore index 090a1f02d..6324c955a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ .idea .DS_Store + +include/aws/crt/Config.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 7e71bd17e..a51520c94 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -16,6 +16,9 @@ if (NOT CMAKE_CXX_STANDARD) set(CMAKE_CXX_STANDARD 11) endif() +set(AWS_CRT_CPP_VERSION "v0.3.0") +configure_file(include/aws/crt/Config.h.in ${CMAKE_CURRENT_LIST_DIR}/include/aws/crt/Config.h @ONLY) + if (BUILD_DEPS) set(AWS_DEPS_BUILD_DIR ${CMAKE_CURRENT_BINARY_DIR}/deps) if (DEFINED CMAKE_INSTALL_PREFIX) @@ -107,6 +110,10 @@ file(GLOB AWS_CRT_IO_HEADERS "include/aws/crt/io/*.h" ) +file(GLOB AWS_CRT_IOT_HEADERS + "include/aws/iot/*.h" +) + file(GLOB AWS_CRT_MQTT_HEADERS "include/aws/crt/mqtt/*.h" ) @@ -123,6 +130,7 @@ file(GLOB AWS_CRT_CPP_HEADERS ${AWS_CRT_HEADERS} ${AWS_CRT_CRYPTO_HEADERS} ${AWS_CRT_IO_HEADERS} + ${AWS_CRT_IOT_HEADERS} ${AWS_CRT_MQTT_HEADERS} ${AWS_CRT_HTTP_HEADERS} ${AWS_CRT_EXTERNAL_HEADERS} @@ -140,6 +148,10 @@ file (GLOB AWS_CRT_IO_SRC "source/io/*.cpp" ) +file (GLOB AWS_CRT_IOT_SRC + "source/iot/*.cpp" +) + file (GLOB AWS_CRT_MQTT_SRC "source/mqtt/*.cpp" ) @@ -156,6 +168,7 @@ file(GLOB AWS_CRT_CPP_SRC ${AWS_CRT_SRC} ${AWS_CRT_CRYPTO_SRC} ${AWS_CRT_IO_SRC} + ${AWS_CRT_IOT_SRC} ${AWS_CRT_MQTT_SRC} ${AWS_CRT_HTTP_SRC} ${AWS_CRT_EXTERNAL_CRC} @@ -166,6 +179,7 @@ if (WIN32) source_group("Header Files\\aws\\crt" FILES ${AWS_CRT_HEADERS}) source_group("Header Files\\aws\\crt\\crypto" FILES ${AWS_CRT_CRYPTO_HEADERS}) source_group("Header Files\\aws\\crt\\io" FILES ${AWS_CRT_IO_HEADERS}) + source_group("Header Files\\aws\\iot" FILES ${AWS_CRT_IOT_HEADERS}) source_group("Header Files\\aws\\crt\\mqtt" FILES ${AWS_CRT_MQTT_HEADERS}) source_group("Header Files\\aws\\crt\\http" FILES ${AWS_CRT_HTTP_HEADERS}) source_group("Header Files\\aws\\crt\\external" FILES ${AWS_CRT_EXTERNAL_HEADERS}) @@ -173,6 +187,7 @@ if (WIN32) source_group("Source Files" FILES ${AWS_CRT_SRC}) source_group("Source Files\\crypto" FILES ${AWS_CRT_CRYPTO_SRC}) source_group("Source Files\\io" FILES ${AWS_CRT_IO_SRC}) + source_group("Source Files\\iot" FILES ${AWS_CRT_IOT_SRC}) source_group("Source Files\\mqtt" FILES ${AWS_CRT_MQTT_SRC}) source_group("Source Files\\http" FILES ${AWS_CRT_HTTP_SRC}) source_group("Source Files\\external" FILES ${AWS_CRT_EXTERNAL_SRC}) @@ -214,6 +229,7 @@ target_link_libraries(${CMAKE_PROJECT_NAME} AWS::aws-c-cal AWS::aws-c-http AWS:: install(FILES ${AWS_CRT_HEADERS} DESTINATION "include/aws/crt" COMPONENT Development) install(FILES ${AWS_CRT_CRYPTO_HEADERS} DESTINATION "include/aws/crt/crypto" COMPONENT Development) install(FILES ${AWS_CRT_IO_HEADERS} DESTINATION "include/aws/crt/io" COMPONENT Development) +install(FILES ${AWS_CRT_IOT_HEADERS} DESTINATION "include/aws/iot" COMPONENT Development) install(FILES ${AWS_CRT_MQTT_HEADERS} DESTINATION "include/aws/crt/mqtt" COMPONENT Development) install(FILES ${AWS_CRT_HTTP_HEADERS} DESTINATION "include/aws/crt/http" COMPONENT Development) install(FILES ${AWS_CRT_EXTERNAL_HEADERS} DESTINATION "include/aws/crt/external" COMPONENT Development) diff --git a/include/aws/crt/Api.h b/include/aws/crt/Api.h index ef9c8e411..f7eee590f 100644 --- a/include/aws/crt/Api.h +++ b/include/aws/crt/Api.h @@ -37,5 +37,6 @@ namespace Aws AWS_CRT_CPP_API void LoadErrorStrings() noexcept; AWS_CRT_CPP_API const char *ErrorDebugString(int error) noexcept; + AWS_CRT_CPP_API int LastError() noexcept; } // namespace Crt } // namespace Aws diff --git a/include/aws/crt/Config.h.in b/include/aws/crt/Config.h.in new file mode 100644 index 000000000..122c36379 --- /dev/null +++ b/include/aws/crt/Config.h.in @@ -0,0 +1,16 @@ +#pragma once +/* + * Copyright 2010-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ +#cmakedefine AWS_CRT_CPP_VERSION "@AWS_CRT_CPP_VERSION@" diff --git a/include/aws/crt/io/TlsOptions.h b/include/aws/crt/io/TlsOptions.h index 06e3b6618..16f47bccb 100644 --- a/include/aws/crt/io/TlsOptions.h +++ b/include/aws/crt/io/TlsOptions.h @@ -37,9 +37,12 @@ namespace Aws friend class TlsContext; public: + TlsContextOptions() noexcept; ~TlsContextOptions(); - TlsContextOptions(const TlsContextOptions &) noexcept = default; - TlsContextOptions &operator=(const TlsContextOptions &) noexcept = default; + TlsContextOptions(const TlsContextOptions &) noexcept = delete; + TlsContextOptions &operator=(const TlsContextOptions &) noexcept = delete; + TlsContextOptions(TlsContextOptions &&) noexcept; + TlsContextOptions &operator=(TlsContextOptions &&) noexcept; /** * Initializes TlsContextOptions with secure by default options, with @@ -48,8 +51,7 @@ namespace Aws static TlsContextOptions InitDefaultClient(Allocator *allocator = DefaultAllocator()) noexcept; /** * Initializes TlsContextOptions with secure by default options, with - * client certificate and private key. These are paths to a file on disk. These - * strings must remain in memory for the lifetime of the returned object. These files + * client certificate and private key. These are paths to a file on disk. These files * must be in the PEM format. */ static TlsContextOptions InitClientWithMtls( @@ -57,6 +59,16 @@ namespace Aws const char *pkey_path, Allocator *allocator = DefaultAllocator()) noexcept; + /** + * Initializes TlsContextOptions with secure by default options, with + * client certificate and private key. These are in memory buffers. These buffers + * must be in the PEM format. + */ + static TlsContextOptions InitClientWithMtls( + const ByteCursor &cert, + const ByteCursor &pkey, + Allocator *allocator = DefaultAllocator()) noexcept; + #ifdef __APPLE__ /** * Initializes TlsContextOptions with secure by default options, with @@ -101,10 +113,11 @@ namespace Aws */ void OverrideDefaultTrustStore(const char *caPath, const char *caFile) noexcept; + void OverrideDefaultTrustStore(const ByteCursor &ca) noexcept; + private: aws_tls_ctx_options m_options; - - TlsContextOptions() noexcept; + bool m_isInit; }; /** @@ -156,6 +169,7 @@ namespace Aws class AWS_CRT_CPP_API TlsContext final { public: + TlsContext() noexcept; TlsContext( TlsContextOptions &options, TlsMode mode, diff --git a/include/aws/crt/mqtt/MqttClient.h b/include/aws/crt/mqtt/MqttClient.h index 5989317cf..bc2c833ec 100644 --- a/include/aws/crt/mqtt/MqttClient.h +++ b/include/aws/crt/mqtt/MqttClient.h @@ -192,7 +192,7 @@ namespace Aws aws_mqtt_client *m_owningClient; aws_mqtt_client_connection *m_underlyingConnection; std::atomic m_connectionState; - ByteBuf m_hostNameBuf; + String m_hostName; uint16_t m_port; Io::TlsConnectionOptions m_tlsOptions; Io::SocketOptions m_socketOptions; diff --git a/include/aws/iot/MqttClient.h b/include/aws/iot/MqttClient.h new file mode 100644 index 000000000..84372457b --- /dev/null +++ b/include/aws/iot/MqttClient.h @@ -0,0 +1,159 @@ +#pragma once +/* + * Copyright 2010-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ +#include +#include + +namespace Aws +{ + namespace Iot + { + class MqttClient; + + /** + * Represents a unique configuration for connecting to a single endpoint. You can use a single instance of this + * class PER endpoint you want to connect to. This object must live through the lifetime of your connection. + */ + class AWS_CRT_CPP_API MqttClientConnectionConfig final + { + public: + MqttClientConnectionConfig() noexcept; + MqttClientConnectionConfig( + const Crt::String &endpoint, + uint16_t port, + const Crt::Io::SocketOptions &socketOptions, + Crt::Io::TlsContext &&tlsContext); + + explicit operator bool() const noexcept { return m_context ? true : false; } + + private: + Crt::String m_endpoint; + uint16_t m_port; + Crt::Io::TlsContext m_context; + Crt::Io::SocketOptions m_socketOptions; + + friend class MqttClient; + }; + + /** + * Represents configuration parameters for building a MqttClientConnectionConfig object. You can use a single + * instance of this class PER MqttClientConnectionConfig you want to generate. If you want to generate a config + * for a different endpoint or port etc... you need a new instance of this class. + */ + class AWS_CRT_CPP_API MqttClientConnectionConfigBuilder final + { + public: + /** + * Sets the builder up for MTLS using certPath and pkeyPath. These are files on disk and must be in the PEM + * format. + */ + MqttClientConnectionConfigBuilder( + const char *certPath, + const char *pkeyPath, + Crt::Allocator *allocator = Crt::DefaultAllocator()) noexcept; + /** + * Sets the builder up for MTLS using cert and pkey. These are in-memory buffers and must be in the PEM + * format. + */ + MqttClientConnectionConfigBuilder( + const Crt::ByteCursor &cert, + const Crt::ByteCursor &pkey, + Crt::Allocator *allocator = Crt::DefaultAllocator()) noexcept; + + /** + * Sets endpoint to connect to. + */ + MqttClientConnectionConfigBuilder &WithEndpoint(const Crt::String &endpoint); + + /** + * Sets endpoint to connect to. + */ + MqttClientConnectionConfigBuilder &WithEndpoint(Crt::String &&endpoint); + + /** + * Overrides the default port. By default, if ALPN is supported, 443 will be used. Otherwise 8883 will be + * used. If you specify 443 and ALPN is not supported, we will still attempt to connect over 443 without + * ALPN. + */ + MqttClientConnectionConfigBuilder &WithPortOverride(uint16_t port) noexcept; + + /** + * Sets the certificate authority for the endpoint you're connecting to. This is a path to a file on disk + * and must be in PEM format. + */ + MqttClientConnectionConfigBuilder &WithCertificateAuthority(const char *caPath) noexcept; + + /** + * Sets the certificate authority for the endpoint you're connecting to. This is an in-memory buffer and + * must be in PEM format. + */ + MqttClientConnectionConfigBuilder &WithCertificateAuthority(const Crt::ByteCursor &cert) noexcept; + + /** TCP option: Enables TCP keep alive. Defaults to off. */ + MqttClientConnectionConfigBuilder &WithTcpKeepAlive() noexcept; + + /** TCP option: Sets the connect timeout. Defaults to 3 seconds. */ + MqttClientConnectionConfigBuilder &WithTcpConnectTimeout(uint32_t connectTimeoutMs) noexcept; + + /** TCP option: Sets time before keep alive probes are sent. Defaults to kernel defaults */ + MqttClientConnectionConfigBuilder &WithTcpKeepAliveTimeout(uint16_t keepAliveTimeoutSecs) noexcept; + + /** + * TCP option: Sets the frequency of sending keep alive probes in seconds once the keep alive timeout + * expires. Defaults to kernel defaults. + */ + MqttClientConnectionConfigBuilder &WithTcpKeepAliveInterval(uint16_t keepAliveIntervalSecs) noexcept; + + /** + * TCP option: Sets the amount of keep alive probes allowed to fail before the connection is terminated. + * Defaults to kernel defaults. + */ + MqttClientConnectionConfigBuilder &WithTcpKeepAliveMaxProbes(uint16_t maxProbes) noexcept; + + /** + * Builds a client configuration object from the set options. + */ + MqttClientConnectionConfig Build() noexcept; + + private: + Crt::Allocator *m_allocator; + Crt::String m_endpoint; + uint16_t m_portOverride; + Crt::Io::SocketOptions m_socketOptions; + Crt::Io::TlsContextOptions m_contextOptions; + }; + + /** + * AWS IOT specific Mqtt Client. Sets defaults for using the AWS IOT service. You'll need an instance of + * MqttClientConnectionConfig to use. Once NewConnection returns, you use it's return value identically + * to how you would use Aws::Crt::Mqtt::MqttConnection + */ + class AWS_CRT_CPP_API MqttClient final + { + public: + MqttClient( + Crt::Io::ClientBootstrap &bootstrap, + Crt::Allocator *allocator = Crt::DefaultAllocator()) noexcept; + + std::shared_ptr NewConnection(const MqttClientConnectionConfig &config) noexcept; + + int LastError() const noexcept { return m_client.LastError(); } + explicit operator bool() const noexcept { return m_client ? true : false; } + + private: + Crt::Mqtt::MqttClient m_client; + }; + } // namespace Iot +} // namespace Aws \ No newline at end of file diff --git a/samples/mqtt_pub_sub/main.cpp b/samples/mqtt_pub_sub/main.cpp index a7a9e1c5c..9a863d797 100644 --- a/samples/mqtt_pub_sub/main.cpp +++ b/samples/mqtt_pub_sub/main.cpp @@ -13,9 +13,11 @@ * permissions and limitations under the License. */ #include +#include + +#include #include -#include #include #include #include @@ -110,54 +112,6 @@ int main(int argc, char *argv[]) stderr, "Event Loop Group Creation failed with error %s\n", ErrorDebugString(eventLoopGroup.LastError())); exit(-1); } - /* - * We're using Mutual TLS for Mqtt, so we need to load our client certificates - */ - Io::TlsContextOptions tlsCtxOptions = - Io::TlsContextOptions::InitClientWithMtls(certificatePath.c_str(), keyPath.c_str()); - /* - * If we have a custom CA, set that up here. - */ - if (!caFile.empty()) - { - tlsCtxOptions.OverrideDefaultTrustStore(nullptr, caFile.c_str()); - } - - uint16_t port = 8883; - if (Io::TlsContextOptions::IsAlpnSupported()) - { - /* - * Use ALPN to negotiate the mqtt protocol on a normal - * TLS port if possible. - */ - tlsCtxOptions.SetAlpnList("x-amzn-mqtt-ca"); - port = 443; - } - - Io::TlsContext tlsCtx(tlsCtxOptions, Io::TlsMode::CLIENT); - - if (!tlsCtx) - { - fprintf(stderr, "Tls Context creation failed with error %s\n", ErrorDebugString(tlsCtx.LastError())); - exit(-1); - } - - /* - * Default Socket options to use. IPV4 will be ignored based on what DNS - * tells us. - */ - Io::SocketOptions socketOptions; - socketOptions.connect_timeout_ms = 3000; - socketOptions.domain = AWS_SOCKET_IPV4; - socketOptions.type = AWS_SOCKET_STREAM; - /* Configuring the socket with low keep-alive values will detect disconnects quickly. - * Not every platform supports configuration of socket keep-alive, - * so if this does not work for you try configuring MQTT's keep-alive values - * in MqttClient.Connect() */ - socketOptions.keep_alive_interval_sec = 1; - socketOptions.keep_alive_timeout_sec = 1; - socketOptions.keep_alive_max_failed_probes = 1; - socketOptions.keepalive = true; Aws::Crt::Io::DefaultHostResolver defaultHostResolver(eventLoopGroup, 1, 5); Io::ClientBootstrap bootstrap(eventLoopGroup, defaultHostResolver); @@ -168,13 +122,18 @@ int main(int argc, char *argv[]) exit(-1); } - /* - * Now Create a client. This can not throw. - * An instance of a client must outlive its connections. - * It is the users responsibility to make sure of this. - */ - Mqtt::MqttClient mqttClient(bootstrap); + auto clientConfig = Aws::Iot::MqttClientConnectionConfigBuilder(certificatePath.c_str(), keyPath.c_str()) + .WithEndpoint(endpoint) + .WithCertificateAuthority(caFile.c_str()) + .Build(); + + if (!clientConfig) + { + fprintf(stderr, "Client Configuration initialization failed with error %s\n", ErrorDebugString(LastError())); + exit(-1); + } + Aws::Iot::MqttClient mqttClient(bootstrap); /* * Since no exceptions are used, always check the bool operator * when an error could have occurred. @@ -185,12 +144,11 @@ int main(int argc, char *argv[]) exit(-1); } - auto connectionOptions = tlsCtx.NewConnectionOptions(); /* * Now create a connection object. Note: This type is move only * and its underlying memory is managed by the client. */ - auto connection = mqttClient.NewConnection(endpoint.c_str(), port, socketOptions, connectionOptions); + auto connection = mqttClient.NewConnection(clientConfig); if (!*connection) { diff --git a/source/Api.cpp b/source/Api.cpp index c7d852312..4083c5461 100644 --- a/source/Api.cpp +++ b/source/Api.cpp @@ -63,5 +63,8 @@ namespace Aws } const char *ErrorDebugString(int error) noexcept { return aws_error_debug_str(error); } + + int LastError() noexcept { return aws_last_error(); } + } // namespace Crt } // namespace Aws diff --git a/source/io/TlsOptions.cpp b/source/io/TlsOptions.cpp index ea33192e4..ade0185d1 100644 --- a/source/io/TlsOptions.cpp +++ b/source/io/TlsOptions.cpp @@ -22,14 +22,47 @@ namespace Aws { namespace Io { - TlsContextOptions::~TlsContextOptions() { aws_tls_ctx_options_clean_up(&m_options); } + TlsContextOptions::~TlsContextOptions() + { + if (m_isInit) + { + aws_tls_ctx_options_clean_up(&m_options); + } + } + + TlsContextOptions::TlsContextOptions() noexcept : m_isInit(false) { AWS_ZERO_STRUCT(m_options); } + + TlsContextOptions::TlsContextOptions(TlsContextOptions &&other) noexcept + { + m_options = other.m_options; + m_isInit = other.m_isInit; + AWS_ZERO_STRUCT(other.m_options); + other.m_isInit = false; + } + + TlsContextOptions &TlsContextOptions::operator=(TlsContextOptions &&other) noexcept + { + if (&other != this) + { + if (m_isInit) + { + aws_tls_ctx_options_clean_up(&m_options); + } - TlsContextOptions::TlsContextOptions() noexcept { AWS_ZERO_STRUCT(m_options); } + m_options = other.m_options; + m_isInit = other.m_isInit; + AWS_ZERO_STRUCT(other.m_options); + other.m_isInit = false; + } + + return *this; + } TlsContextOptions TlsContextOptions::InitDefaultClient(Allocator *allocator) noexcept { TlsContextOptions ctxOptions; aws_tls_ctx_options_init_default_client(&ctxOptions.m_options, allocator); + ctxOptions.m_isInit = true; return ctxOptions; } @@ -39,7 +72,28 @@ namespace Aws Allocator *allocator) noexcept { TlsContextOptions ctxOptions; - aws_tls_ctx_options_init_client_mtls_from_path(&ctxOptions.m_options, allocator, certPath, pKeyPath); + if (!aws_tls_ctx_options_init_client_mtls_from_path( + &ctxOptions.m_options, allocator, certPath, pKeyPath)) + { + ctxOptions.m_isInit = true; + } + return ctxOptions; + } + + TlsContextOptions TlsContextOptions::InitClientWithMtls( + const ByteCursor &cert, + const ByteCursor &pkey, + Allocator *allocator) noexcept + { + TlsContextOptions ctxOptions; + if (!aws_tls_ctx_options_init_client_mtls( + &ctxOptions.m_options, + allocator, + const_cast(&cert), + const_cast(&pkey))) + { + ctxOptions.m_isInit = true; + } return ctxOptions; } #ifdef __APPLE__ @@ -50,8 +104,11 @@ namespace Aws { TlsContextOptions ctxOptions; struct aws_byte_cursor password = aws_byte_cursor_from_c_str(pkcs12Pwd); - aws_tls_ctx_options_init_client_mtls_pkcs12_from_path( - &ctxOptions.m_options, allocator, pkcs12Path, &password); + if (!aws_tls_ctx_options_init_client_mtls_pkcs12_from_path( + &ctxOptions.m_options, allocator, pkcs12Path, &password)) + { + ctxOptions.m_isInit = true; + } return ctxOptions; } #endif @@ -60,17 +117,34 @@ namespace Aws void TlsContextOptions::SetAlpnList(const char *alpn_list) noexcept { - aws_tls_ctx_options_set_alpn_list(&m_options, alpn_list); + if (m_isInit) + { + aws_tls_ctx_options_set_alpn_list(&m_options, alpn_list); + } } void TlsContextOptions::SetVerifyPeer(bool verify_peer) noexcept { - aws_tls_ctx_options_set_verify_peer(&m_options, verify_peer); + if (m_isInit) + { + aws_tls_ctx_options_set_verify_peer(&m_options, verify_peer); + } } void TlsContextOptions::OverrideDefaultTrustStore(const char *caPath, const char *caFile) noexcept { - aws_tls_ctx_options_override_default_trust_store_from_path(&m_options, caPath, caFile); + if (m_isInit) + { + aws_tls_ctx_options_override_default_trust_store_from_path(&m_options, caPath, caFile); + } + } + + void TlsContextOptions::OverrideDefaultTrustStore(const ByteCursor &ca) noexcept + { + if (m_isInit) + { + aws_tls_ctx_options_override_default_trust_store(&m_options, const_cast(&ca)); + } } void InitTlsStaticState(Aws::Crt::Allocator *alloc) noexcept { aws_tls_init_static_state(alloc); } @@ -192,8 +266,10 @@ namespace Aws return true; } + TlsContext::TlsContext() noexcept : m_ctx(nullptr), m_lastError(AWS_ERROR_SUCCESS) {} + TlsContext::TlsContext(TlsContextOptions &options, TlsMode mode, Allocator *allocator) noexcept - : m_ctx(nullptr), m_lastError(AWS_OP_SUCCESS) + : m_ctx(nullptr), m_lastError(AWS_ERROR_SUCCESS) { if (mode == TlsMode::CLIENT) { diff --git a/source/iot/MqttClient.cpp b/source/iot/MqttClient.cpp new file mode 100644 index 000000000..fcfad6401 --- /dev/null +++ b/source/iot/MqttClient.cpp @@ -0,0 +1,172 @@ +/* + * Copyright 2010-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ +#include + +#include + +namespace Aws +{ + namespace Iot + { + MqttClientConnectionConfig::MqttClientConnectionConfig() noexcept : m_port(0) + { + AWS_ZERO_STRUCT(m_socketOptions); + } + + MqttClientConnectionConfig::MqttClientConnectionConfig( + const Crt::String &endpoint, + uint16_t port, + const Crt::Io::SocketOptions &socketOptions, + Crt::Io::TlsContext &&tlsContext) + : m_endpoint(endpoint), m_port(port), m_context(std::move(tlsContext)), m_socketOptions(socketOptions) + { + } + + MqttClientConnectionConfigBuilder::MqttClientConnectionConfigBuilder( + const char *certPath, + const char *pkeyPath, + Crt::Allocator *allocator) noexcept + : m_allocator(allocator), m_portOverride(0) + { + m_contextOptions = Crt::Io::TlsContextOptions::InitClientWithMtls(certPath, pkeyPath, allocator); + AWS_ZERO_STRUCT(m_socketOptions); + m_socketOptions.connect_timeout_ms = 3000; + } + + MqttClientConnectionConfigBuilder::MqttClientConnectionConfigBuilder( + const Crt::ByteCursor &cert, + const Crt::ByteCursor &pkey, + Crt::Allocator *allocator) noexcept + : m_allocator(allocator), m_portOverride(0) + { + m_contextOptions = Crt::Io::TlsContextOptions::InitClientWithMtls(cert, pkey, allocator); + AWS_ZERO_STRUCT(m_socketOptions); + m_socketOptions.connect_timeout_ms = 3000; + } + + MqttClientConnectionConfigBuilder &MqttClientConnectionConfigBuilder::WithEndpoint(const Crt::String &endpoint) + { + m_endpoint = endpoint; + return *this; + } + + MqttClientConnectionConfigBuilder &MqttClientConnectionConfigBuilder::WithEndpoint(Crt::String &&endpoint) + { + m_endpoint = std::move(endpoint); + return *this; + } + + MqttClientConnectionConfigBuilder &MqttClientConnectionConfigBuilder::WithPortOverride(uint16_t port) noexcept + { + m_portOverride = port; + return *this; + } + + MqttClientConnectionConfigBuilder &MqttClientConnectionConfigBuilder::WithCertificateAuthority( + const char *caPath) noexcept + { + m_contextOptions.OverrideDefaultTrustStore(nullptr, caPath); + return *this; + } + + MqttClientConnectionConfigBuilder &MqttClientConnectionConfigBuilder::WithCertificateAuthority( + const Crt::ByteCursor &cert) noexcept + { + m_contextOptions.OverrideDefaultTrustStore(cert); + return *this; + } + + MqttClientConnectionConfigBuilder &MqttClientConnectionConfigBuilder::WithTcpKeepAlive() noexcept + { + m_socketOptions.keepalive = true; + return *this; + } + MqttClientConnectionConfigBuilder &MqttClientConnectionConfigBuilder::WithTcpConnectTimeout( + uint32_t connectTimeoutMs) noexcept + { + m_socketOptions.connect_timeout_ms = connectTimeoutMs; + return *this; + } + + MqttClientConnectionConfigBuilder &MqttClientConnectionConfigBuilder::WithTcpKeepAliveTimeout( + uint16_t keepAliveTimeoutSecs) noexcept + { + m_socketOptions.keep_alive_timeout_sec = keepAliveTimeoutSecs; + return *this; + } + + MqttClientConnectionConfigBuilder &MqttClientConnectionConfigBuilder::WithTcpKeepAliveInterval( + uint16_t keepAliveIntervalSecs) noexcept + { + m_socketOptions.keep_alive_interval_sec = keepAliveIntervalSecs; + return *this; + } + + MqttClientConnectionConfigBuilder &MqttClientConnectionConfigBuilder::WithTcpKeepAliveMaxProbes( + uint16_t maxProbes) noexcept + { + m_socketOptions.keep_alive_max_failed_probes = maxProbes; + return *this; + } + + MqttClientConnectionConfig MqttClientConnectionConfigBuilder::Build() noexcept + { + uint16_t port = m_portOverride; + + if (!m_portOverride) + { + port = 8883; + + if (Crt::Io::TlsContextOptions::IsAlpnSupported()) + { + port = 443; + } + } + + if (port == 443 && Crt::Io::TlsContextOptions::IsAlpnSupported()) + { + m_contextOptions.SetAlpnList("x-amzn-mqtt-ca"); + } + + return MqttClientConnectionConfig( + m_endpoint, + port, + m_socketOptions, + Crt::Io::TlsContext(m_contextOptions, Crt::Io::TlsMode::CLIENT, m_allocator)); + } + + MqttClient::MqttClient(Crt::Io::ClientBootstrap &bootstrap, Crt::Allocator *allocator) noexcept + : m_client(bootstrap, allocator) + { + } + + std::shared_ptr MqttClient::NewConnection( + const MqttClientConnectionConfig &config) noexcept + { + auto newConnection = m_client.NewConnection( + config.m_endpoint.c_str(), + config.m_port, + config.m_socketOptions, + config.m_context.NewConnectionOptions()); + + if (newConnection && newConnection->SetLogin("?SDK=CPPv2&Version=" AWS_CRT_CPP_VERSION, nullptr)) + { + return newConnection; + } + + return nullptr; + } + } // namespace Iot +} // namespace Aws \ No newline at end of file diff --git a/source/mqtt/MqttClient.cpp b/source/mqtt/MqttClient.cpp index 8d291b479..154cb2112 100644 --- a/source/mqtt/MqttClient.cpp +++ b/source/mqtt/MqttClient.cpp @@ -230,7 +230,7 @@ namespace Aws const Io::TlsConnectionOptions *tlsConnOptions) { - self->m_hostNameBuf = aws_byte_buf_from_c_str(hostName); + self->m_hostName = String(hostName); self->m_port = port; if (tlsConnOptions) @@ -281,10 +281,7 @@ namespace Aws if (*this) { aws_mqtt_client_connection_destroy(m_underlyingConnection); - aws_byte_buf_clean_up(&m_hostNameBuf); } - - AWS_ZERO_STRUCT(*this); } MqttConnection::operator bool() const noexcept { return m_underlyingConnection != nullptr; } @@ -305,9 +302,16 @@ namespace Aws { ByteBuf userNameBuf = aws_byte_buf_from_c_str(userName); ByteCursor userNameCur = aws_byte_cursor_from_buf(&userNameBuf); - ByteBuf pwdBuf = aws_byte_buf_from_c_str(password); - ByteCursor pwdCur = aws_byte_cursor_from_buf(&pwdBuf); - return aws_mqtt_client_connection_set_login(m_underlyingConnection, &userNameCur, &pwdCur) == 0; + + ByteCursor *pwdCurPtr = nullptr; + ByteCursor pwdCur; + + if (password) + { + pwdCur = ByteCursorFromCString(password); + pwdCurPtr = &pwdCur; + } + return aws_mqtt_client_connection_set_login(m_underlyingConnection, &userNameCur, pwdCurPtr) == 0; } bool MqttConnection::Connect( @@ -319,7 +323,8 @@ namespace Aws aws_mqtt_connection_options options; AWS_ZERO_STRUCT(options); options.client_id = aws_byte_cursor_from_c_str(clientId); - options.host_name = aws_byte_cursor_from_buf(&m_hostNameBuf); + options.host_name = aws_byte_cursor_from_array( + reinterpret_cast(m_hostName.data()), m_hostName.length()); options.tls_options = m_useTls ? const_cast(m_tlsOptions.GetUnderlyingHandle()) : nullptr; options.port = m_port;