diff --git a/codebuild/integration-tests.sh b/codebuild/integration-tests.sh new file mode 100755 index 000000000..d0852c7cc --- /dev/null +++ b/codebuild/integration-tests.sh @@ -0,0 +1,19 @@ +set -e + +env + +pushd $CODEBUILD_SRC_DIR/secure_tunneling/tests + +mkdir _build +cd _build +cmake -DCMAKE_PREFIX_PATH=/tmp/install .. +make -j + +tunnel_info=$(aws iotsecuretunneling open-tunnel --destination-config services=ssh,ssh2,ssh3 --timeout-config maxLifetimeTimeoutMinutes=10) && echo -e "$tunnel_info" > /tmp/tunnel_info.pem + +export SECTUN_SOURCE_TOKEN=$(sed '4!d' /tmp/tunnel_info.pem | cut -d'"' -f4) +export SECTUN_DESTINATION_TOKEN=$(sed '5!d' /tmp/tunnel_info.pem | cut -d'"' -f4) +export SECTUN_ENDPOINT="data.tunneling.iot.us-east-1.amazonaws.com" + +echo "Running Secure Tunnel Test" +./secure_tunnel_test diff --git a/codebuild/linux-integration-tests.yml b/codebuild/linux-integration-tests.yml new file mode 100644 index 000000000..1ae3cbe22 --- /dev/null +++ b/codebuild/linux-integration-tests.yml @@ -0,0 +1,25 @@ +version: 0.2 +env: + shell: bash +#this buildspec assumes the ubuntu 14 image +phases: + install: + commands: + - wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | apt-key add - + - add-apt-repository ppa:ubuntu-toolchain-r/test + - apt-add-repository "deb http://apt.llvm.org/bionic/ llvm-toolchain-bionic-8 main" + - apt-get update -y + - apt-get install cmake -y -f + + build: + commands: + - echo Build started on `date` + # Building of dependencies happens in setup-linux + - $CODEBUILD_SRC_DIR/codebuild/samples/setup-linux.sh + + # Run the integration tests + - $CODEBUILD_SRC_DIR/codebuild/integration-tests.sh + post_build: + commands: + - echo Build completed on `date` + diff --git a/crt/aws-c-iot b/crt/aws-c-iot index 09ded2b5e..0f98a14e8 160000 --- a/crt/aws-c-iot +++ b/crt/aws-c-iot @@ -1 +1 @@ -Subproject commit 09ded2b5e5bd34bbcf0fd71b5482381cf7f08627 +Subproject commit 0f98a14e87744fbacc50e854459a482d3664bcf3 diff --git a/crt/aws-crt-cpp b/crt/aws-crt-cpp index 917548f5d..f3634df6e 160000 --- a/crt/aws-crt-cpp +++ b/crt/aws-crt-cpp @@ -1 +1 @@ -Subproject commit 917548f5dcc2dfd6dbc08abfaf0134033b466d1d +Subproject commit f3634df6e3907555e38c272260518fe7a9405d36 diff --git a/documents/Secure_Tunnel_Userguide.md b/documents/Secure_Tunnel_Userguide.md index 86402fb05..84ffcfd6c 100644 --- a/documents/Secure_Tunnel_Userguide.md +++ b/documents/Secure_Tunnel_Userguide.md @@ -43,7 +43,7 @@ When a WebSocket upgrade request fails to connect, this callback will return an ### OnConnectionShutdown When the WebSocket connection shuts down, this callback will be invoked. -### OnSendDataComplete +### OnSendMessageComplete When a message has been completely written to the socket, this callback will be invoked. ### OnMessageReceived @@ -55,6 +55,12 @@ When a stream is started by a Source connected to the Destination, the Destinati ### OnStreamStopped When an open stream is closed, this callback will be invoked and return the stopped stream's information. +### OnConnectionStarted +When a connection start message is received and a new active connection is established, the Destination will invoke this callback and return the connection information. + +### OnConnectionReset +When a connection has ended either in error or closed intentionally by the secure tunnel peer, the client will invoke this callback and return the connection information. + ### OnSessionReset When the Secure Tunnel service requests the Secure Tunnel client fully reset, this callback is invoked. @@ -135,21 +141,24 @@ if (!secureTunnel->Start()) return -1; } ``` +### Reconnecting +A Secure Tunnel Client that has been started will attempt to reconnect upon a failed connection attempt or disconnection until `Stop()` is called. If the secure tunnel closes or the access tokens change, the Secure Tunnel Client will become disconnected and continue to attempt a reconnection until `Stop()` is called. The Secure Tunnel Client implements a Full Jitter Backoff Algorithm along with an exponential back off timer. More information on both can be found here: https://aws.amazon.com/blogs/architecture/exponential-backoff-and-jitter/ ## Stop -Invoking `Stop()` on the Secure Tunnel Client breaks the current connection (if any) and moves the client into an idle state. +Invoking `Stop()` on the Secure Tunnel Client breaks the current connection (if any) and moves the client into an idle state. A Secure Tunnel Client that has been stopped will no longer attempt to reconnect and is ready to be cleaned up. ```cpp if(!secureTunnel->Stop()){ fprintf(stdout, "Failed to stop the Secure Tunnel connection session. Exiting..\n"); } ``` -# Multiplexing -You can use multiple data streams per Secure Tunnel by using the [Multiplexing](https://docs.aws.amazon.com/iot/latest/developerguide/multiplexing.html) feature. +# Multiplexing and Simultaneous TCP Connections +You can use multiple data streams per Secure Tunnel by using the [Multiplexing and Simultaneous TCP Connections](https://docs.aws.amazon.com/iot/latest/developerguide/multiplexing.html) features. + ## Opening a Secure Tunnel with Multiplexing To use Multiplexing, a Secure Tunnel must be created with one to three "services". A Secure Tunnel can be opened through the AWS IoT console [Secure Tunnel Hub](https://console.aws.amazon.com/iot/home#/tunnelhub) or by using the [OpenTunnel API](https://docs.aws.amazon.com/iot/latest/apireference/API_Operations_AWS_IoT_Secure_Tunneling.html). Both of these methods allow you to add services with whichever names suit your needs. ## Services Within the Secure Tunnel Client -On a successfull connection to a Secure Tunnel, the Secure Tunnel Client will invoke the `OnConnectionSuccess` callback. This callback will return `ConnectionSuccessEventData` that will contain any available Service Ids that can be used for multiplexing. Below is an example of how to set the callback using the Secure Tunnel Builder and check whether a Service Id is available. +On a successfull connection to a Secure Tunnel, the Secure Tunnel Client will invoke the `OnConnectionSuccess` callback. This callback will return `ConnectionSuccessEventData` which contains any available Service Ids that can be used for multiplexing. Below is an example of how to set the callback using the Secure Tunnel Builder and check whether a Service Id is available. ```cpp // Create Secure Tunnel Builder SecureTunnelBuilder builder = SecureTunnelBuilder(...); @@ -191,6 +200,8 @@ builder.WithOnConnectionSuccess([&](SecureTunnel *secureTunnel, const Connection ``` ## Using Service Ids Service Ids can be added to outbound Messages as shown below in the Send Message example. If the Service Id is both available on the current Secure Tunnel and there is an open stream with a Source device on that Service Id, the message will be sent. If the Service Id does not exist on the current Secure Tunnel or there is no currently active stream available on that Service Id, the Message will not be sent and a Warning will be logged. The `OnStreamStarted` callback is invoked when a stream is started and it returns a `StreamStartedEventData` which can be parsed to determine if a stream was started using a Service Id for Multiplexing. Incoming messages can also be parsed to determine if a Service Id has been set as shown above in the [Setting Secure Tunnel Callbacks](#setting-secure-tunnel-callbacks) code example. +## Using Connection Ids +Connection Ids can be added to outbound Messages as shown below in the Send Message example. If there is an active stream currently open using the combination of the Service Id and Connection Id, the message will be sent. If a Connection Id is not set on an outbound message, a Connecion Id of 1 is assumed and applied to the Message. When additional streams are activated, the `OnConnectionStarted` callback is invoked and returns a `ConnectionStartedEventData` which can be parsed to determine the Connection Id of the newly activated stream. A Connection Id will also be present in the `StreamStartedEventData` that is returned when the `OnStreamStarted` callback is invoked. # Secure Tunnel Operations ## Send Message @@ -201,11 +212,16 @@ Crt::String serviceId_string = "ssh"; Crt::String message_string = "any payload"; ByteCursor serviceId = ByteCursorFromString(serviceId_string); +uint32_t connectionId = 1 ByteCursor payload = ByteCursorFromString(message_string); // Create Message std::shared_ptr message = std::make_shared(); +// Add a Service Id message->withServiceId(serviceId); +// Add a Connection Id +message->withConnectionId(connectionId); +// Add a payload message->withPayload(payload); // Send Message diff --git a/samples/secure_tunneling/secure_tunnel/README.md b/samples/secure_tunneling/secure_tunnel/README.md index 46b895baf..37cc66751 100644 --- a/samples/secure_tunneling/secure_tunnel/README.md +++ b/samples/secure_tunneling/secure_tunnel/README.md @@ -1,72 +1,33 @@ -## Secure Tunnel +# Secure Tunnel [**Return to main sample list**](../../README.md) -This sample uses AWS IoT [Secure Tunneling](https://docs.aws.amazon.com/iot/latest/developerguide/secure-tunneling.html) Service to connect a destination and a source with each other through the AWS Secure Tunnel endpoint using access tokens using the [V2WebSocketProtocol](https://github.com/aws-samples/aws-iot-securetunneling-localproxy/blob/main/V2WebSocketProtocolGuide.md). For more information, see the [Secure Tunnel Userguide](../../../documents/Secure_Tunnel_Userguide.md) - -Your IoT Core Thing's [Policy](https://docs.aws.amazon.com/iot/latest/developerguide/iot-policies.html) must provide privileges for this sample to connect, subscribe, publish, and receive. Below is a sample policy that can be used on your IoT Core Thing that will allow this sample to run as intended. - -
-(see sample policy) -
-{
-  "Version": "2012-10-17",
-  "Statement": [
-    {
-      "Effect": "Allow",
-      "Action": [
-        "iot:Publish",
-        "iot:Receive"
-      ],
-      "Resource": [
-        "arn:aws:iot:region:account:topic/$aws/things//tunnels/notify"
-      ]
-    },
-    {
-      "Effect": "Allow",
-      "Action": [
-        "iot:Subscribe"
-      ],
-      "Resource": [
-        "arn:aws:iot:region:account:topicfilter/$aws/things//tunnels/notify"
-      ]
-    },
-    {
-      "Effect": "Allow",
-      "Action": [
-        "iot:Connect"
-      ],
-      "Resource": [
-        "arn:aws:iot:region:account:client/test-*"
-      ]
-    }
-  ]
-}
-
- -Replace with the following with the data from your AWS account: -* ``: The AWS IoT Core region where you created your AWS IoT Core thing you wish to use with this sample. For example `us-east-1`. -* ``: Your AWS IoT Core account ID. This is the set of numbers in the top right next to your AWS account name when using the AWS IoT Core website. -* ``: The name of your AWS IoT Core thing you want the device connection to be associated with - -Note that in a real application, you may want to avoid the use of wildcards in your ClientID or use them selectively. Please follow best practices when working with AWS on production applications using the SDK. Also, for the purposes of this sample, please make sure your policy allows a client ID of `test-*` to connect or use `--client_id ` to send the client ID your policy supports. +This sample uses AWS IoT [Secure Tunneling](https://docs.aws.amazon.com/iot/latest/developerguide/secure-tunneling.html) Service to connect a destination or a source Secure Tunnel Client to an AWS Secure Tunnel endpoint using access tokens using the [V3WebSocketProtocol](https://github.com/aws-samples/aws-iot-securetunneling-localproxy/blob/main/V3WebSocketProtocolGuide.md). For more information, see the [Secure Tunnel Userguide](../../../documents/Secure_Tunnel_Userguide.md) ## How to run Create a new secure tunnel in the AWS IoT console (https://console.aws.amazon.com/iot/) (AWS IoT/Manage/Tunnels/Create tunnel) and retrieve the destination and source access tokens. (https://docs.aws.amazon.com/iot/latest/developerguide/secure-tunneling-tutorial-open-tunnel.html). Once you have these tokens, you are ready to open a secure tunnel. +### Destination Mode + To run the sample with a destination access token in destination mode (default), you can use the following command: ``` sh -./secure_tunnel --region --access_token_file +./secure_tunnel --signing_region --access_token_file ``` -However, for this sample to work, it will also need another instance of the sample running in source mode. You can run another instance of the sample in source mode using the same command, but adding the `--localProxyModeSource` flag: +The sample will create a Secure Tunnel connection and remain connected in `DESTINATION MODE` and will echo any messages it receives through the Secure Tunnel back to the Source Device. + +### Source Mode + +While the focus of the Secure Tunnel Client for the IoT Device SDK is to connect with Secure Tunnels in `DESTINATION MODE` we also support connecting in `SOURCE MODE`. The token file should be the Source Token in this instance and you must add the `--localProxyModeSource` flag: ``` sh -./secure_tunnel --region --access_token_file --localProxyModeSource +./secure_tunnel --signing_region --access_token_file --localProxyModeSource ``` Then two samples will then connect to each other through the AWS Secure Tunnel endpoint and establish a stream through which data can be transmitted in either direction. +The sample will create a Secure Tunnel connection in `SOURCE MODE` and will open a stream using an available `Service Id`. It will then send n messages on the opened stream. It will then create a new simultaneous TCP connection on the stream and send an additional n messages on the new TCP connection. It will then exit. +### Proxy Note that a proxy server may be used via the `--proxy_host` and `--proxy_port` argument. If the proxy server requires a user name and password to connect, you can use `--proxy_user_name` and `--proxy_password` to in the sample to pass the required data to the sample. diff --git a/samples/secure_tunneling/secure_tunnel/main.cpp b/samples/secure_tunneling/secure_tunnel/main.cpp index 5597f90ed..c5beadaf4 100644 --- a/samples/secure_tunneling/secure_tunnel/main.cpp +++ b/samples/secure_tunneling/secure_tunnel/main.cpp @@ -5,7 +5,6 @@ #include #include #include -#include #include #include @@ -21,158 +20,105 @@ using namespace std::chrono_literals; void logMessage(std::shared_ptr message) { + + fprintf(stdout, "Message received"); if (message->getServiceId().has_value()) { - if (message->getPayload().has_value()) - { - fprintf( - stdout, - "Message received on service id:'" PRInSTR "' with payload:'" PRInSTR "'\n", - AWS_BYTE_CURSOR_PRI(message->getServiceId().value()), - AWS_BYTE_CURSOR_PRI(message->getPayload().value())); - } - else - { - fprintf( - stdout, - "Message with service id:'" PRInSTR "' with no payload.\n", - AWS_BYTE_CURSOR_PRI(message->getServiceId().value())); - } - return; + fprintf(stdout, " on service id:'" PRInSTR "'", AWS_BYTE_CURSOR_PRI(message->getServiceId().value())); } + + if (message->getConnectionId() > 0) + { + fprintf(stdout, " with connection id: (%d)", message->getConnectionId()); + } + if (message->getPayload().has_value()) { - fprintf( - stdout, - "Message received with payload:'" PRInSTR "'\n", - AWS_BYTE_CURSOR_PRI(message->getPayload().value())); + fprintf(stdout, " with payload: '" PRInSTR "'", AWS_BYTE_CURSOR_PRI(message->getPayload().value())); } -} -void setupCommandLineUtils(Utils::CommandLineUtils *cmdUtils, int argc, char *argv[]) -{ - cmdUtils->AddCommonProxyCommands(); - cmdUtils->RegisterProgramName("secure_tunnel"); - cmdUtils->RegisterCommand("region", "", "The region of your secure tunnel"); - cmdUtils->RegisterCommand( - "ca_file", "", "Path to AmazonRootCA1.pem (optional, system trust store used by default)."); - cmdUtils->RegisterCommand( - "access_token_file", "", "Path to the tunneling access token file (optional if --access_token used)."); - cmdUtils->RegisterCommand( - "access_token", "", "Tunneling access token (optional if --access_token_file used)."); - cmdUtils->RegisterCommand( - "local_proxy_mode_source", "", "Use to set local proxy mode to source (optional, default='destination')."); - cmdUtils->RegisterCommand( - "client_token", "", "Tunneling access token (optional if --client_token_file used)."); - cmdUtils->RegisterCommand("message", "", "Message to send (optional, default='Hello World!')."); - cmdUtils->RegisterCommand( - "proxy_user_name", "", "User name passed if proxy server requires a user name (optional)"); - cmdUtils->RegisterCommand( - "proxy_password", "", "Password passed if proxy server requires a password (optional)"); - cmdUtils->RegisterCommand("count", "", "Number of messages to send before completing (optional, default='5')"); - cmdUtils->AddLoggingCommands(); - const char **const_argv = (const char **)argv; - cmdUtils->SendArguments(const_argv, const_argv + argc); + fprintf(stdout, "\n"); } -void setupCommandLineValues( - Utils::cmdData *cmdData, - String *endpoint, - String *accessToken, - String *clientToken, - String *caFile, - String *proxyHost, - String *proxyUserName, - String *proxyPassword, - uint16_t &proxyPort, - uint16_t &messageCount, - aws_secure_tunneling_local_proxy_mode &localProxyMode, - String *payloadMessage) +void logConnectionData(const ConnectionSuccessEventData &eventData) { - // Generate secure tunneling endpoint using region - String region = cmdData->input_signingRegion; - endpoint->assign("data.tunneling.iot." + region + ".amazonaws.com"); - - String tempAccessToken; - // An access token is required to connect to the secure tunnel service - if (cmdData->input_accessToken != "") + if (eventData.connectionData->getServiceId1().has_value()) { - tempAccessToken = cmdData->input_accessToken; - } - else if (cmdData->input_accessTokenFile != "") - { - tempAccessToken = cmdData->input_accessTokenFile; - std::ifstream accessTokenFile(tempAccessToken.c_str()); - if (accessTokenFile.is_open()) - { - getline(accessTokenFile, tempAccessToken); - accessTokenFile.close(); - } - else + fprintf( + stdout, + "Secure Tunnel connected with Service IDs '" PRInSTR "'", + AWS_BYTE_CURSOR_PRI(eventData.connectionData->getServiceId1().value())); + + if (eventData.connectionData->getServiceId2().has_value()) { - fprintf(stderr, "Failed to open access token file"); - exit(-1); + fprintf(stdout, ", '" PRInSTR "'", AWS_BYTE_CURSOR_PRI(eventData.connectionData->getServiceId2().value())); + if (eventData.connectionData->getServiceId3().has_value()) + { + fprintf( + stdout, ", '" PRInSTR "'", AWS_BYTE_CURSOR_PRI(eventData.connectionData->getServiceId3().value())); + } } + fprintf(stdout, "\n"); } else { - fprintf(stderr, "--access_token_file or --access_token must be set to connect through a secure tunnel"); - exit(-1); + fprintf(stdout, "Secure Tunnel connected with no Service IDs available\n"); } - accessToken->assign(tempAccessToken); +} - String tempClientToken; - /** - * A client token is optional as one will be automatically generated if it is absent but it is recommended the - * customer provides their own so they can reuse it with other secure tunnel clients after the secure tunnel client - * is terminated. - */ - if (cmdData->input_clientToken != "") - { - tempClientToken = cmdData->input_clientToken; - } - else if (cmdData->input_clientTokenFile != "") +void logStreamStartData(const StreamStartedEventData &eventData) +{ + fprintf(stdout, "Stream started"); + + if (eventData.streamStartedData->getServiceId().has_value()) { - tempClientToken = cmdData->input_clientTokenFile; - std::ifstream clientTokenFile(tempClientToken.c_str()); - if (clientTokenFile.is_open()) - { - getline(clientTokenFile, tempClientToken); - clientTokenFile.close(); - } - else - { - fprintf(stderr, "Failed to open client token file\n"); - exit(-1); - } + fprintf( + stdout, + " on service id: '" PRInSTR, + AWS_BYTE_CURSOR_PRI(eventData.streamStartedData->getServiceId().value())); } - clientToken->assign(tempClientToken); - if (cmdData->input_ca != "") + if (eventData.streamStartedData->getConnectionId() > 0) { - caFile->assign(cmdData->input_ca); + fprintf(stdout, " with Connection Id: (%d)", eventData.streamStartedData->getConnectionId()); } - if (cmdData->input_proxyHost != "" || cmdData->input_proxyPort != 0) + fprintf(stdout, "\n"); +} + +void logStreamStoppedData(const StreamStoppedEventData &eventData) +{ + fprintf(stdout, "Stream"); + + if (eventData.streamStoppedData->getServiceId().has_value()) { - proxyHost->assign(cmdData->input_proxyHost); - proxyPort = static_cast(cmdData->input_proxyPort); - proxyUserName->assign(cmdData->input_proxy_user_name); - proxyPassword->assign(cmdData->input_proxy_password); + fprintf( + stdout, + " on service id: '" PRInSTR, + AWS_BYTE_CURSOR_PRI(eventData.streamStoppedData->getServiceId().value())); } - // localProxyMode is set to destination by default unless flag is set to source - if (cmdData->input_localProxyModeSource != "destination") + fprintf(stdout, " stopped\n"); +} + +void logConnectionStartedData(const ConnectionStartedEventData &eventData) +{ + fprintf(stdout, "Connection started"); + + if (eventData.connectionStartedData->getServiceId().has_value()) { - localProxyMode = AWS_SECURE_TUNNELING_SOURCE_MODE; + fprintf( + stdout, + " on service id: '" PRInSTR, + AWS_BYTE_CURSOR_PRI(eventData.connectionStartedData->getServiceId().value())); } - else + + if (eventData.connectionStartedData->getConnectionId() > 0) { - localProxyMode = AWS_SECURE_TUNNELING_DESTINATION_MODE; + fprintf(stdout, " with Connection Id: (%d)", eventData.connectionStartedData->getConnectionId()); } - payloadMessage->assign(cmdData->input_message); - messageCount = static_cast(cmdData->input_count); + fprintf(stdout, "\n"); } int main(int argc, char *argv[]) @@ -188,8 +134,6 @@ int main(int argc, char *argv[]) * In a real world application you probably don't want to enforce synchronous behavior * but this is a sample console application, so we'll just do that with a condition variable. */ - std::promise connectionCompletedPromise; - std::promise connectionClosedPromise; std::promise clientStoppedPromise; // service id storage for use in sample @@ -197,33 +141,24 @@ int main(int argc, char *argv[]) AWS_ZERO_STRUCT(m_serviceIdStorage); Aws::Crt::Optional m_serviceId; - String endpoint; - String accessToken; - String clientToken; - String caFile; - String proxyHost; - uint16_t proxyPort(8080); - String proxyUserName; - String proxyPassword; aws_secure_tunneling_local_proxy_mode localProxyMode; - String payloadMessage; - uint16_t messageCount(5); + + /* Connection Id is used for Simultaneous HTTP Connections (Protocl V3) */ + uint32_t connectionId = 1; + bool keepRunning = true; + uint16_t messagesSent = 0; /*********************** Parse Arguments ***************************/ Utils::cmdData cmdData = Utils::parseSampleInputSecureTunnel(argc, argv, &apiHandle); - setupCommandLineValues( - &cmdData, - &endpoint, - &accessToken, - &clientToken, - &caFile, - &proxyHost, - &proxyUserName, - &proxyPassword, - proxyPort, - messageCount, - localProxyMode, - &payloadMessage); + // localProxyMode is set to destination by default unless flag is set to source + if (cmdData.input_localProxyModeSource != "destination") + { + localProxyMode = AWS_SECURE_TUNNELING_SOURCE_MODE; + } + else + { + localProxyMode = AWS_SECURE_TUNNELING_DESTINATION_MODE; + } if (apiHandle.GetOrCreateStaticDefaultClientBootstrap()->LastError() != AWS_ERROR_SUCCESS) { @@ -235,107 +170,121 @@ int main(int argc, char *argv[]) } // Use a SecureTunnelBuilder to set up and build the secure tunnel client - SecureTunnelBuilder builder = SecureTunnelBuilder(allocator, accessToken.c_str(), localProxyMode, endpoint.c_str()); + SecureTunnelBuilder builder = SecureTunnelBuilder( + allocator, cmdData.input_accessToken.c_str(), localProxyMode, cmdData.input_endpoint.c_str()); - if (caFile.length() > 0) + if (cmdData.input_ca != "") { - builder.WithRootCa(caFile.c_str()); + builder.WithRootCa(cmdData.input_ca.c_str()); } - builder.WithClientToken(clientToken.c_str()); - - // Add callbacks using the builder + /* Proxy Options */ + if (cmdData.input_proxyHost != "") + { + auto proxyOptions = Aws::Crt::Http::HttpClientConnectionProxyOptions(); + proxyOptions.HostName = cmdData.input_proxyHost != ""; + proxyOptions.Port = static_cast(cmdData.input_proxyPort); - builder.WithOnConnectionSuccess([&](SecureTunnel *secureTunnel, const ConnectionSuccessEventData &eventData) { - if (eventData.connectionData->getServiceId1().has_value()) + // Set up Proxy Strategy if a user name and password is provided + if (cmdData.input_proxy_user_name != "" || cmdData.input_proxy_password != "") { - // If secure tunnel is using service ids, store one for future use - aws_byte_buf_clean_up(&m_serviceIdStorage); - AWS_ZERO_STRUCT(m_serviceIdStorage); - aws_byte_buf_init_copy_from_cursor( - &m_serviceIdStorage, allocator, eventData.connectionData->getServiceId1().value()); - m_serviceId = aws_byte_cursor_from_buf(&m_serviceIdStorage); - - fprintf( - stdout, - "Secure Tunnel connected with Service IDs '" PRInSTR "'", - AWS_BYTE_CURSOR_PRI(eventData.connectionData->getServiceId1().value())); - if (eventData.connectionData->getServiceId2().has_value()) - { - fprintf( - stdout, ", '" PRInSTR "'", AWS_BYTE_CURSOR_PRI(eventData.connectionData->getServiceId2().value())); - if (eventData.connectionData->getServiceId3().has_value()) - { - fprintf( - stdout, - ", '" PRInSTR "'", - AWS_BYTE_CURSOR_PRI(eventData.connectionData->getServiceId3().value())); - } - } - fprintf(stdout, "\n"); - - // Stream Start can only be called from Source Mode - if (localProxyMode == AWS_SECURE_TUNNELING_SOURCE_MODE) - { - fprintf( - stdout, - "Sending Stream Start request with service id:'" PRInSTR "'\n", - AWS_BYTE_CURSOR_PRI(eventData.connectionData->getServiceId1().value())); - secureTunnel->SendStreamStart(eventData.connectionData->getServiceId1().value()); - } + fprintf(stdout, "Creating proxy strategy\n"); + Aws::Crt::Http::HttpProxyStrategyBasicAuthConfig basicAuthConfig; + basicAuthConfig.ConnectionType = Aws::Crt::Http::AwsHttpProxyConnectionType::Tunneling; + basicAuthConfig.Username = cmdData.input_proxy_user_name.c_str(); + basicAuthConfig.Password = cmdData.input_proxy_password.c_str(); + proxyOptions.ProxyStrategy = + Aws::Crt::Http::HttpProxyStrategy::CreateBasicHttpProxyStrategy(basicAuthConfig, allocator); + proxyOptions.AuthType = Aws::Crt::Http::AwsHttpProxyAuthenticationType::Basic; } else { - fprintf(stdout, "Secure Tunnel is not using Service Ids.\n"); - - // Stream Start can only be called from Source Mode - if (localProxyMode == AWS_SECURE_TUNNELING_SOURCE_MODE) - { - fprintf(stdout, "Sending Stream Start request\n"); - secureTunnel->SendStreamStart(); - } + proxyOptions.AuthType = Aws::Crt::Http::AwsHttpProxyAuthenticationType::None; } - connectionCompletedPromise.set_value(true); - }); - - builder.WithOnConnectionFailure([&](SecureTunnel *secureTunnel, int errorCode) { - (void)secureTunnel; - fprintf(stdout, "Connection attempt failed with error code %d(%s)\n", errorCode, ErrorDebugString(errorCode)); - }); + // Add proxy options to the builder + builder.WithHttpClientConnectionProxyOptions(proxyOptions); + } - builder.WithOnConnectionShutdown([&]() { - fprintf(stdout, "Connection Shutdown\n"); - connectionClosedPromise.set_value(true); - }); + builder.WithClientToken(cmdData.input_clientToken.c_str()); + /* Add callbacks using the builder */ builder.WithOnMessageReceived([&](SecureTunnel *secureTunnel, const MessageReceivedEventData &eventData) { { std::shared_ptr message = eventData.message; logMessage(message); - std::shared_ptr echoMessage; - switch (localProxyMode) + /* Send an echo message back to the Source Device */ + if (localProxyMode == AWS_SECURE_TUNNELING_DESTINATION_MODE) { - case AWS_SECURE_TUNNELING_DESTINATION_MODE: + std::shared_ptr echoMessage = std::make_shared(message->getPayload().value()); + + /* Echo message on same service id received message arrived on */ + if (message->getServiceId().has_value()) + { + echoMessage->WithServiceId(message->getServiceId().value()); + } + + /* Echo message on the same connection id received message arrived on */ + if (message->getConnectionId() > 0) + { + echoMessage->WithConnectionId(message->getConnectionId()); + } + + secureTunnel->SendMessage(echoMessage); + + fprintf(stdout, "Sending Echo Message\n"); + } + } + }); - echoMessage = std::make_shared(message->getPayload().value()); + builder.WithOnSendMessageComplete( + [&](SecureTunnel *secureTunnel, int errorCode, const SendMessageCompleteEventData &eventData) { + (void)secureTunnel; + (void)eventData; - // Echo message on same service id received message came on - if (message->getServiceId().has_value()) - { - echoMessage->withServiceId(message->getServiceId().value()); - } + if (!errorCode) + { + fprintf( + stdout, + "Message of type '" PRInSTR "' sent successfully\n", + AWS_BYTE_CURSOR_PRI(eventData.sendMessageCompleteData->getMessageType())); + } + else + { + fprintf(stdout, "Send Message failed with error code %d(%s)\n", errorCode, ErrorDebugString(errorCode)); + } + }); - secureTunnel->SendMessage(echoMessage); + builder.WithOnConnectionSuccess([&](SecureTunnel *secureTunnel, const ConnectionSuccessEventData &eventData) { + logConnectionData(eventData); - fprintf(stdout, "Sending Echo Message\n"); + /* Stream Start can only be called from Source Mode */ + if (localProxyMode == AWS_SECURE_TUNNELING_SOURCE_MODE) + { + /* Use a Multiplexing (Service Id) if available on this Secure Tunnel */ + if (eventData.connectionData->getServiceId1().has_value()) + { + /* Store the service id for future use */ + aws_byte_buf_clean_up(&m_serviceIdStorage); + AWS_ZERO_STRUCT(m_serviceIdStorage); + aws_byte_buf_init_copy_from_cursor( + &m_serviceIdStorage, allocator, eventData.connectionData->getServiceId1().value()); + m_serviceId = aws_byte_cursor_from_buf(&m_serviceIdStorage); - break; - case AWS_SECURE_TUNNELING_SOURCE_MODE: + fprintf( + stdout, + "Sending Stream Start request on Service Id:'" PRInSTR "' with Connection Id: (%d)\n", + AWS_BYTE_CURSOR_PRI(eventData.connectionData->getServiceId1().value()), + connectionId); - break; + secureTunnel->SendStreamStart(eventData.connectionData->getServiceId1().value(), connectionId); + } + else + { + fprintf(stdout, "Sending Stream Start request\n"); + secureTunnel->SendStreamStart(); } } }); @@ -345,76 +294,43 @@ int main(int argc, char *argv[]) (void)secureTunnel; if (!errorCode) { - std::shared_ptr streamStartedData = eventData.streamStartedData; - - if (streamStartedData->getServiceId().has_value()) - { - fprintf( - stdout, - "Stream started on service id: '" PRInSTR "'\n", - AWS_BYTE_CURSOR_PRI(streamStartedData->getServiceId().value())); - } - else - { - fprintf(stdout, "Stream started using V1 Protocol"); - } + logStreamStartData(eventData); + } + else + { + fprintf(stdout, "Stream Start failed with error code %d(%s)\n", errorCode, ErrorDebugString(errorCode)); } }); - builder.WithOnStreamStopped([&](SecureTunnel *secureTunnel, const StreamStoppedEventData &eventData) { + builder.WithOnConnectionStarted([&](SecureTunnel *secureTunnel, + int errorCode, + const ConnectionStartedEventData &eventData) { (void)secureTunnel; - std::shared_ptr streamStoppedData = eventData.streamStoppedData; - - if (streamStoppedData->getServiceId().has_value()) + if (!errorCode) { - fprintf( - stdout, - "Stream stopped on service id: '" PRInSTR "'\n", - AWS_BYTE_CURSOR_PRI(streamStoppedData->getServiceId().value())); + logConnectionStartedData(eventData); } else { - fprintf(stdout, "Stream stopped using V1 Protocol"); + fprintf(stdout, "Connection Start failed with error code %d(%s)\n", errorCode, ErrorDebugString(errorCode)); } }); + builder.WithOnStreamStopped([&](SecureTunnel *secureTunnel, const StreamStoppedEventData &eventData) { + (void)secureTunnel; + + logStreamStoppedData(eventData); + }); + + builder.WithOnConnectionShutdown([&]() { fprintf(stdout, "Connection Shutdown\n"); }); + builder.WithOnStopped([&](SecureTunnel *secureTunnel) { (void)secureTunnel; fprintf(stdout, "Secure Tunnel has entered Stopped State\n"); clientStoppedPromise.set_value(true); }); - // *********************************************************************************************************************** - // Proxy Options - // *********************************************************************************************************************** - if (proxyHost.length() > 0) - { - auto proxyOptions = Aws::Crt::Http::HttpClientConnectionProxyOptions(); - proxyOptions.HostName = proxyHost.c_str(); - proxyOptions.Port = proxyPort; - - // Set up Proxy Strategy if a user name and password is provided - if (proxyUserName.length() > 0 || proxyPassword.length() > 0) - { - fprintf(stdout, "Creating proxy strategy\n"); - Aws::Crt::Http::HttpProxyStrategyBasicAuthConfig basicAuthConfig; - basicAuthConfig.ConnectionType = Aws::Crt::Http::AwsHttpProxyConnectionType::Tunneling; - basicAuthConfig.Username = proxyUserName.c_str(); - basicAuthConfig.Password = proxyPassword.c_str(); - proxyOptions.ProxyStrategy = - Aws::Crt::Http::HttpProxyStrategy::CreateBasicHttpProxyStrategy(basicAuthConfig, allocator); - proxyOptions.AuthType = Aws::Crt::Http::AwsHttpProxyAuthenticationType::Basic; - } - else - { - proxyOptions.AuthType = Aws::Crt::Http::AwsHttpProxyAuthenticationType::None; - } - - // Add proxy options to the builder - builder.WithHttpClientConnectionProxyOptions(proxyOptions); - } - - // Create Secure Tunnel using the options set with the builder + /* Create Secure Tunnel using the options set with the builder */ std::shared_ptr secureTunnel = builder.Build(); if (!secureTunnel) @@ -430,37 +346,48 @@ int main(int argc, char *argv[]) exit(-1); } - bool keepRunning = true; - uint16_t messagesSent = 0; - - if (connectionCompletedPromise.get_future().get()) + /* + * In Destination mode the Secure Tunnel Client will remain open and echo messages that come in. + * In Source mode the Secure Tunnel Client will send 4 messages and then disconnect and terminate. + */ + while (keepRunning) { - std::this_thread::sleep_for(1000ms); - - /** - * In Destination mode the Secure Tunnel Client will remain open and echo messages that come in. - * In Source mode the Secure Tunnel Client will send 4 messages and then disconnect and terminate. - */ - while (keepRunning) + std::this_thread::sleep_for(2000ms); + if (localProxyMode == AWS_SECURE_TUNNELING_SOURCE_MODE) { - if (localProxyMode == AWS_SECURE_TUNNELING_SOURCE_MODE) + uint16_t messageCount = static_cast(cmdData.input_count); + messagesSent++; + // String toSend = (std::to_string(messagesSent) + ": " + payloadMessage.c_str()).c_str(); + String toSend = (std::to_string(messagesSent) + ": " + cmdData.input_message.c_str()).c_str(); + + if (messagesSent <= messageCount) { - messagesSent++; - String toSend = (std::to_string(messagesSent) + ": " + payloadMessage.c_str()).c_str(); - if (messagesSent <= messageCount) + std::shared_ptr message = std::make_shared(ByteCursorFromCString(toSend.c_str())); + + /* If the secure tunnel has service ids, we will use one for our messages. */ + if (m_serviceId.has_value()) { - std::shared_ptr message = std::make_shared(ByteCursorFromCString(toSend.c_str())); + message->WithServiceId(m_serviceId.value()); + } - // If the secure tunnel has service ids, we will use one for our messages. - if (m_serviceId.has_value()) - { - message->withServiceId(m_serviceId.value()); - } + message->WithConnectionId(connectionId); - secureTunnel->SendMessage(message); + secureTunnel->SendMessage(message); - fprintf(stdout, "Sending Message:\"%s\"\n", toSend.c_str()); + fprintf(stdout, "Sending Message:\"%s\"\n", toSend.c_str()); + } + else + { + /* + * Start a new Simultaneous HTTP Connection using Connection Id 2 and send/receive messages on new + * established stream on the same service id. + */ + if (connectionId == 1 && m_serviceId.has_value()) + { + messagesSent = 0; + connectionId = 2; + secureTunnel->SendConnectionStart(m_serviceId.value(), connectionId); std::this_thread::sleep_for(2000ms); } @@ -478,16 +405,11 @@ int main(int argc, char *argv[]) // Set the Secure Tunnel Client to desire a stopped state if (secureTunnel->Stop() == AWS_OP_ERR) { - fprintf(stderr, "Secure Tunnel Close call failed: %s\n", ErrorDebugString(LastError())); + fprintf(stderr, "Secure Tunnel Stop call failed: %s\n", ErrorDebugString(LastError())); exit(-1); } - if (connectionClosedPromise.get_future().get()) - { - fprintf(stdout, "Secure Tunnel Connection Closed\n"); - } - - // The Secure Tunnel Client at this point will report they are stopped and can be safely removed. + /* The Secure Tunnel Client at this point will report they are stopped and can be safely removed. */ if (clientStoppedPromise.get_future().get()) { secureTunnel = nullptr; diff --git a/samples/secure_tunneling/tunnel_notification/README.md b/samples/secure_tunneling/tunnel_notification/README.md index 94cbf7772..f978ff57f 100644 --- a/samples/secure_tunneling/tunnel_notification/README.md +++ b/samples/secure_tunneling/tunnel_notification/README.md @@ -2,11 +2,13 @@ [**Return to main sample list**](../../README.md) -This sample uses the AWS IoT [Secure Tunneling](https://docs.aws.amazon.com/iot/latest/developerguide/secure-tunneling.html) Service to receive a tunnel notification. +This sample uses an MQTT Client and the AWS IoT [Secure Tunneling](https://docs.aws.amazon.com/iot/latest/developerguide/secure-tunneling.html) Service to receive a tunnel notification and then connects to the Secure Tunnel using a Secure Tunnel Client. -This sample requires you to create a tunnel for your thing. See [instructions here](https://docs.aws.amazon.com/iot/latest/developerguide/secure-tunneling-tutorial.html) on how to create a tunnel. You can also read more about secure tunneling in the [Secure Tunnel Userguide](../../../documents/Secure_Tunnel_Userguide.md). +This sample requires you to create a Secure Tunnel for your thing. See [instructions here](https://docs.aws.amazon.com/iot/latest/developerguide/secure-tunneling-tutorial.html) on how to create a tunnel. You can also read more about secure tunneling in the [Secure Tunnel Userguide](../../../documents/Secure_Tunnel_Userguide.md). -On startup, the sample will wait until it receives, and then displays the tunnel notification. +This sample requires a certificate and key file using Mutual TLS (mTLS). On startup, the device connects to the server using the certificate and key files and subscribes to a specific topic generated along with the Thing Name to listen for any Secure Tunnels that are opened for that specific Thing. When a Secure Tunnel is created for the Thing, it will receive a publish with the Secure Tunnel information which it can then use to create a Secure Tunnel Client and connect to the Secure Tunnel Service in `DESTINATION MODE`. AWS IoT will publish the notification upon creation of a new tunnel and will retransmit a new notification if instructed to "Generate new access tokens". + +Your IoT Core Thing's [Policy](https://docs.aws.amazon.com/iot/latest/developerguide/iot-policies.html) must provide privileges for this sample to connect, subscribe, and receive. Below is a sample policy that can be used on your IoT Core Thing that will allow this sample to run as intended.
(see sample policy) @@ -21,7 +23,7 @@ On startup, the sample will wait until it receives, and then displays the tunnel "iot:Receive" ], "Resource": [ - "arn:aws:iot:region:account:topic/$aws/things//tunnels/notify" + "arn:aws:iot:region:account:topic/$aws/things/thing_name/tunnels/notify" ] }, { @@ -30,7 +32,7 @@ On startup, the sample will wait until it receives, and then displays the tunnel "iot:Subscribe" ], "Resource": [ - "arn:aws:iot:region:account:topicfilter/$aws/things//tunnels/notify" + "arn:aws:iot:region:account:topic/$aws/things/thing_name/tunnels/notify" ] }, { @@ -53,10 +55,11 @@ Replace with the following with the data from your AWS account: Note that in a real application, you may want to avoid the use of wildcards in your ClientID or use them selectively. Please follow best practices when working with AWS on production applications using the SDK. Also, for the purposes of this sample, please make sure your policy allows a client ID of `test-*` to connect or use `--client_id ` to send the client ID your policy supports. -## How to run - -Create a new secure tunnel in the AWS IoT console (https://console.aws.amazon.com/iot/) (AWS IoT/Manage/Tunnels/Create tunnel). Once you have the tunnel open, you can run the sample with the following command: +
+## How to run +To Run this sample, use the following command: ``` sh ./tunnel-notification --endpoint --cert --key --thing_name ``` +Once the MQTT Client is connected, create a new secure tunnel in the AWS IoT console (https://console.aws.amazon.com/iot/) (AWS IoT/Manage/Tunnels/Create tunnel) for the Thing. diff --git a/samples/secure_tunneling/tunnel_notification/main.cpp b/samples/secure_tunneling/tunnel_notification/main.cpp index 8cb88d12b..66aa02453 100644 --- a/samples/secure_tunneling/tunnel_notification/main.cpp +++ b/samples/secure_tunneling/tunnel_notification/main.cpp @@ -3,28 +3,18 @@ * SPDX-License-Identifier: Apache-2.0. */ -#include -#include -#include - #include - #include +#include #include #include -#include -#include #include -#include -#include -#include #include #include "../../utils/CommandLineUtils.h" using namespace std; -using namespace Aws::Iot; using namespace Aws::Crt; using namespace Aws::Crt::Mqtt; using namespace Aws::Iotsecuretunneling; @@ -33,8 +23,11 @@ int main(int argc, char *argv[]) { /************************ Setup ****************************/ + struct aws_allocator *allocator = aws_default_allocator(); // Do the global initialization for the API. ApiHandle apiHandle; + aws_iotdevice_library_init(allocator); + std::shared_ptr secureTunnel; /** * cmdData is the arguments/input from the command line placed into a single struct for @@ -121,32 +114,50 @@ int main(int argc, char *argv[]) { fprintf(stdout, "Received MQTT Tunnel Notification\n"); - std::string clientAccessToken = response->ClientAccessToken->c_str(); - std::string clientMode = response->ClientMode->c_str(); - std::string region = response->Region->c_str(); - fprintf( stdout, - "Recv: Token:%s, Mode:%s, Region:%s\n", - clientAccessToken.c_str(), - clientMode.c_str(), - region.c_str()); + "Recv:\n\tToken:%s\n\tMode:%s\n\tRegion:%s\n", + response->ClientAccessToken->c_str(), + response->ClientMode->c_str(), + response->Region->c_str()); + + Aws::Crt::String region = response->Region->c_str(); + Aws::Crt::String endpoint = "data.tunneling.iot." + region + ".amazonaws.com"; size_t nServices = response->Services->size(); if (nServices <= 0) { - fprintf(stdout, "No service requested\n"); + fprintf(stdout, "\tNo Service Ids requested\n"); } else { - std::string service = response->Services->at(0).c_str(); - fprintf(stdout, "Requested service=%s\n", service.c_str()); - - if (nServices > 1) + for (size_t i = 0; i < nServices; ++i) { - fprintf(stdout, "Multiple services not supported yet\n"); + std::string service = response->Services->at(i).c_str(); + fprintf(stdout, "\tService Id %zu=%s\n", (i + 1), service.c_str()); } } + + SecureTunnelBuilder builder = SecureTunnelBuilder( + allocator, + response->ClientAccessToken->c_str(), + AWS_SECURE_TUNNELING_DESTINATION_MODE, + endpoint.c_str()); + + builder.WithOnConnectionSuccess( + [&](SecureTunnel *secureTunnel, const ConnectionSuccessEventData &eventData) { + (void)secureTunnel; + (void)eventData; + fprintf(stdout, "Secure Tunnel connected to Secure Tunnel Service\n"); + }); + secureTunnel = builder.Build(); + + if (!secureTunnel) + { + fprintf(stderr, "Secure Tunnel Creation failed: %s\n", ErrorDebugString(LastError())); + exit(-1); + } + secureTunnel->Start(); } else { @@ -170,6 +181,7 @@ int main(int argc, char *argv[]) secureClient.SubscribeToTunnelsNotify( request, AWS_MQTT_QOS_AT_LEAST_ONCE, onSubscribeToTunnelsNotifyResponse, OnSubscribeComplete); } + while (1) { std::this_thread::sleep_for(500ms); diff --git a/samples/utils/CommandLineUtils.cpp b/samples/utils/CommandLineUtils.cpp index 653d9053e..7408f3eb1 100644 --- a/samples/utils/CommandLineUtils.cpp +++ b/samples/utils/CommandLineUtils.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include namespace Utils @@ -847,13 +848,48 @@ namespace Utils s_addLoggingSendArgumentsStartLogging(argc, argv, api_handle, &cmdUtils); cmdData returnData = cmdData(); - s_parseCommonMQTTCommands(&cmdUtils, &returnData); returnData.input_signingRegion = cmdUtils.GetCommandRequired(m_cmd_signing_region); + returnData.input_endpoint = "data.tunneling.iot." + returnData.input_signingRegion + ".amazonaws.com"; returnData.input_accessTokenFile = cmdUtils.GetCommandOrDefault(m_cmd_access_token_file, ""); returnData.input_accessToken = cmdUtils.GetCommandOrDefault(m_cmd_access_token, ""); - returnData.input_localProxyModeSource = cmdUtils.GetCommandOrDefault(m_cmd_access_token, "destination"); + if (returnData.input_accessToken == "" && returnData.input_accessTokenFile != "") + { + Aws::Crt::String tempAccessToken; + + std::ifstream accessTokenFile(returnData.input_accessTokenFile.c_str()); + if (accessTokenFile.is_open()) + { + getline(accessTokenFile, returnData.input_accessToken); + accessTokenFile.close(); + } + else + { + fprintf(stderr, "Failed to open access token file"); + exit(-1); + } + } + returnData.input_clientTokenFile = cmdUtils.GetCommandOrDefault(m_cmd_client_token_file, ""); returnData.input_clientToken = cmdUtils.GetCommandOrDefault(m_cmd_client_token, ""); + if (returnData.input_clientToken == "" && returnData.input_clientTokenFile != "") + { + Aws::Crt::String tempAccessToken; + + std::ifstream clientTokenFile(returnData.input_clientTokenFile.c_str()); + if (clientTokenFile.is_open()) + { + getline(clientTokenFile, returnData.input_clientToken); + clientTokenFile.close(); + } + else + { + fprintf(stderr, "Failed to open client token file"); + exit(-1); + } + } + + returnData.input_localProxyModeSource = + cmdUtils.GetCommandOrDefault(m_cmd_local_proxy_mode_source, "destination"); returnData.input_message = cmdUtils.GetCommandOrDefault(m_cmd_message, "Hello World!"); if (cmdUtils.HasCommand(m_cmd_proxy_host)) { diff --git a/secure_tunneling/include/aws/iotsecuretunneling/SecureTunnel.h b/secure_tunneling/include/aws/iotsecuretunneling/SecureTunnel.h index 873805d93..eeb2cd85e 100644 --- a/secure_tunneling/include/aws/iotsecuretunneling/SecureTunnel.h +++ b/secure_tunneling/include/aws/iotsecuretunneling/SecureTunnel.h @@ -27,10 +27,19 @@ namespace Aws Crt::Allocator *allocator = Crt::ApiAllocator()) noexcept; Message(Crt::Allocator *allocator = Crt::ApiAllocator()) noexcept; Message(Crt::ByteCursor payload, Crt::Allocator *allocator = Crt::ApiAllocator()) noexcept; + Message( + Crt::ByteCursor payload, + uint32_t connectionId, + Crt::Allocator *allocator = Crt::ApiAllocator()) noexcept; Message( Crt::ByteCursor serviceId, Crt::ByteCursor payload, Crt::Allocator *allocator = Crt::ApiAllocator()) noexcept; + Message( + Crt::ByteCursor serviceId, + uint32_t connectionId, + Crt::ByteCursor payload, + Crt::Allocator *allocator = Crt::ApiAllocator()) noexcept; /** * Sets the service id for the secure tunnel message. @@ -38,7 +47,15 @@ namespace Aws * @param serviceId The service id for the secure tunnel message. * @return The Message Object after setting the payload. */ - Message &withServiceId(Crt::ByteCursor serviceId) noexcept; + Message &WithServiceId(Crt::ByteCursor serviceId) noexcept; + + /** + * Sets the connection id for the secure tunnel message. + * + * @param connectionId The connection id for the secure tunnel message. + * @return The Message Object after setting the payload. + */ + Message &WithConnectionId(uint32_t connectionId) noexcept; /** * Sets the payload for the secure tunnel message. @@ -46,7 +63,7 @@ namespace Aws * @param payload The payload for the secure tunnel message. * @return The Message Object after setting the payload. */ - Message &withPayload(Crt::ByteCursor payload) noexcept; + Message &WithPayload(Crt::ByteCursor payload) noexcept; bool initializeRawOptions(aws_secure_tunnel_message_view &raw_options) noexcept; @@ -57,6 +74,13 @@ namespace Aws */ const Crt::Optional &getServiceId() const noexcept; + /** + * The connection id of the secure tunnel message. + * + * @return The connection id of the secure tunnel message. + */ + const uint32_t &getConnectionId() const noexcept; + /** * The payload of the secure tunnel message. * @@ -81,6 +105,13 @@ namespace Aws */ Crt::Optional m_serviceId; + /** + * The connection id used for simultaneous TCP connections. + * + * If left empty, a V1 or V2 protocol message is assumed. + */ + uint32_t m_connectionId; + /** * The payload of the secure tunnel message. */ @@ -102,6 +133,54 @@ namespace Aws std::shared_ptr message; }; + /** + * Data model for messages sent out to the WebSocket + */ + class AWS_IOTSECURETUNNELING_API SendMessageCompleteData + { + public: + SendMessageCompleteData( + enum aws_secure_tunnel_message_type type, + Crt::Allocator *allocator = Crt::ApiAllocator()) noexcept; + + /** + * Message Type of sent message. + * + * @return Message Type of sent message. + */ + const Crt::ByteCursor &getMessageType() const noexcept; + + virtual ~SendMessageCompleteData(); + /* Do not allow direct copy or move */ + SendMessageCompleteData(const SendMessageCompleteData &) = delete; + SendMessageCompleteData(SendMessageCompleteData &&) noexcept = delete; + SendMessageCompleteData &operator=(const SendMessageCompleteData &) = delete; + SendMessageCompleteData &operator=(SendMessageCompleteData &&) noexcept = delete; + + private: + Crt::Allocator *m_allocator; + + /** + * Message Type of sent message. + * + */ + Crt::ByteCursor m_messageType; + + /////////////////////////////////////////////////////////////////////////// + // Underlying data storage for internal use + /////////////////////////////////////////////////////////////////////////// + Crt::ByteBuf m_messageTypeStorage; + }; + + /** + * The data returned when a message is sent on the secure tunnel. + */ + struct AWS_IOTSECURETUNNELING_API SendMessageCompleteEventData + { + SendMessageCompleteEventData() : sendMessageCompleteData(nullptr) {} + std::shared_ptr sendMessageCompleteData; + }; + /** * Data model for Secure Tunnel connection view. */ @@ -194,6 +273,13 @@ namespace Aws */ const Crt::Optional &getServiceId() const noexcept; + /** + * The connection id of the secure tunnel message. + * + * @return The connection id of the secure tunnel message. + */ + const uint32_t &getConnectionId() const noexcept; + virtual ~StreamStartedData(); /* Do not allow direct copy or move */ StreamStartedData(const StreamStartedData &) = delete; @@ -211,6 +297,13 @@ namespace Aws */ Crt::Optional m_serviceId; + /** + * The connection id used for simultaneous TCP connections. + * + * If left empty, a V1 or V2 protocol message is assumed. + */ + uint32_t m_connectionId; + /////////////////////////////////////////////////////////////////////////// // Underlying data storage for internal use /////////////////////////////////////////////////////////////////////////// @@ -227,7 +320,7 @@ namespace Aws }; /** - * Data model for started Secure Tunnel streams. + * Data model for stopped Secure Tunnel streams. */ class AWS_IOTSECURETUNNELING_API StreamStoppedData { @@ -243,10 +336,6 @@ namespace Aws */ const Crt::Optional &getServiceId() const noexcept; - /** - * Stream id of the stopped stream. - */ - virtual ~StreamStoppedData(); /* Do not allow direct copy or move */ StreamStoppedData(const StreamStoppedData &) = delete; @@ -258,9 +347,9 @@ namespace Aws Crt::Allocator *m_allocator; /** - * Service id of started stream. + * Service id of stopped stream. * - * If left empty, a V1 protocolstream is assumed. + * If left empty, a V1 protocol stream is assumed. */ Crt::Optional m_serviceId; @@ -279,6 +368,130 @@ namespace Aws std::shared_ptr streamStoppedData; }; + /** + * Data model for opened Secure Tunnel connection. + */ + class AWS_IOTSECURETUNNELING_API ConnectionStartedData + { + public: + ConnectionStartedData( + const aws_secure_tunnel_message_view &raw_options, + Crt::Allocator *allocator = Crt::ApiAllocator()) noexcept; + + /** + * Service id the connection is using. + * + * @return Service id the connection is using. + */ + const Crt::Optional &getServiceId() const noexcept; + + /** + * The connection id of the opened connection. + * + * @return The connection id of the opened connection. + */ + const uint32_t &getConnectionId() const noexcept; + + virtual ~ConnectionStartedData(); + /* Do not allow direct copy or move */ + ConnectionStartedData(const ConnectionStartedData &) = delete; + ConnectionStartedData(ConnectionStartedData &&) noexcept = delete; + ConnectionStartedData &operator=(const ConnectionStartedData &) = delete; + ConnectionStartedData &operator=(ConnectionStartedData &&) noexcept = delete; + + private: + Crt::Allocator *m_allocator; + + /** + * Service id the connection is using. + * + * If left empty, a V1 protocolstream is assumed. + */ + Crt::Optional m_serviceId; + + /** + * The connection id of the opened connection. + * + */ + uint32_t m_connectionId; + + /////////////////////////////////////////////////////////////////////////// + // Underlying data storage for internal use + /////////////////////////////////////////////////////////////////////////// + Crt::ByteBuf m_serviceIdStorage; + }; + + /** + * The data returned when a connection is started on the Secure Tunnel. + */ + struct AWS_IOTSECURETUNNELING_API ConnectionStartedEventData + { + ConnectionStartedEventData() : connectionStartedData(nullptr) {} + std::shared_ptr connectionStartedData; + }; + + /** + * Data model for reset Secure Tunnel connection. + */ + class AWS_IOTSECURETUNNELING_API ConnectionResetData + { + public: + ConnectionResetData( + const aws_secure_tunnel_message_view &raw_options, + Crt::Allocator *allocator = Crt::ApiAllocator()) noexcept; + + /** + * Service id used by reset connection. + * + * @return Service id used by reset connection. + */ + const Crt::Optional &getServiceId() const noexcept; + + /** + * Connection id of the reset connection. + * + * @return Connection id of the reset connection. + */ + const uint32_t &getConnectionId() const noexcept; + + virtual ~ConnectionResetData(); + /* Do not allow direct copy or move */ + ConnectionResetData(const ConnectionResetData &) = delete; + ConnectionResetData(ConnectionResetData &&) noexcept = delete; + ConnectionResetData &operator=(const ConnectionResetData &) = delete; + ConnectionResetData &operator=(ConnectionResetData &&) noexcept = delete; + + private: + Crt::Allocator *m_allocator; + + /** + * Service id used by the reset connection. + * + * If left empty, a V1 protocol stream is assumed. + */ + Crt::Optional m_serviceId; + + /** + * The connection id of the reset connection. + * + */ + uint32_t m_connectionId; + + /////////////////////////////////////////////////////////////////////////// + // Underlying data storage for internal use + /////////////////////////////////////////////////////////////////////////// + Crt::ByteBuf m_serviceIdStorage; + }; + + /** + * The data returned when a stream is reset on the Secure Tunnel. + */ + struct AWS_IOTSECURETUNNELING_API ConnectionResetEventData + { + ConnectionResetEventData() : connectionResetData(nullptr) {} + std::shared_ptr connectionResetData; + }; + class SecureTunnel; // Client callback type definitions @@ -301,9 +514,10 @@ namespace Aws using OnConnectionShutdown = std::function; /** - * Type signature of the callback invoked when data has been sent through the secure tunnel connection. + * Type signature of the callback invoked when message has been sent through the secure tunnel connection. */ - using OnSendDataComplete = std::function; + using OnSendMessageComplete = + std::function; /** * Type signature of the callback invoked when a message is received through the secure tunnel connection. @@ -320,9 +534,21 @@ namespace Aws /** * Type signature of the callback invoked when a stream has been closed */ - using OnStreamStopped = std::function; + /** + * Type signature of the callback invoked when a connection has been started with a source through the secure + * tunnel connection. + */ + using OnConnectionStarted = + std::function; + + /** + * Type signature of the callback invoked when a connection has been reset + */ + using OnConnectionReset = + std::function; + /** * Type signature of the callback invoked when the secure tunnel receives a Session Reset. */ @@ -345,11 +571,14 @@ namespace Aws * Deprecated - Use OnStreamStarted */ using OnStreamStart = std::function; - /** * Deprecated - Use OnStreamStopped */ using OnStreamReset = std::function; + /** + * Deprecated - Use OnSendMessageComplete + */ + using OnSendDataComplete = std::function; /** * Represents a unique configuration for a secure tunnel @@ -388,6 +617,17 @@ namespace Aws const std::string &endpointHost); // Make a copy and save in this object /* Optional members */ + + /** + * Sets TLS options to be used by secure tunnel connection. + * + * @param tslOptions TLS options to use for secure tunnel connection. If provided, the rootCA settings in + * these options will override any rootCA provided to the builder. + * + * @return this builder object + */ + SecureTunnelBuilder &WithTlsConnectionOptions(const Crt::Io::TlsConnectionOptions &tslOptions); + /** * Sets rootCA to be used for this secure tunnel connection overriding the default trust store. * @@ -445,13 +685,14 @@ namespace Aws SecureTunnelBuilder &WithOnConnectionShutdown(OnConnectionShutdown onConnectionShutdown); /** - * Setup callback handler trigged when an Secure Tunnel completes sending data to the secure tunnel service. + * Setup callback handler trigged when an Secure Tunnel completes sending a message to the secure tunnel + * service. * - * @param onSendDataComplete + * @param onSendMessageComplete * * @return this builder object */ - SecureTunnelBuilder &WithOnSendDataComplete(OnSendDataComplete onSendDataComplete); + SecureTunnelBuilder &WithOnSendMessageComplete(OnSendMessageComplete onSendMessageComplete); /** * Setup callback handler trigged when an Secure Tunnel receives a Message through the secure tunnel @@ -482,6 +723,25 @@ namespace Aws */ SecureTunnelBuilder &WithOnStreamStopped(OnStreamStopped onStreamStopped); + /** + * Setup callback handler trigged when an Secure Tunnel starts a connection with a source through the secure + * tunnel service. + * + * @param onConnectionStarted + * + * @return this builder object + */ + SecureTunnelBuilder &WithOnConnectionStarted(OnConnectionStarted onConnectionStarted); + + /** + * Setup callback handler trigged when an Secure Tunnel resets a connection. + * + * @param onConnectionReset + * + * @return this builder object + */ + SecureTunnelBuilder &WithOnConnectionReset(OnConnectionReset onConnectionReset); + /** * Setup callback handler trigged when an Secure Tunnel receives a stream reset. * @@ -522,6 +782,10 @@ namespace Aws * Deprecated - Use WithOnStreamStarted */ SecureTunnelBuilder &WithOnStreamStart(OnStreamStart onStreamStart); + /** + * Deprecated - Use WithOnSendMessageComplete + */ + SecureTunnelBuilder &WithOnSendDataComplete(OnSendDataComplete onSendDataComplete); /** * Will return a shared pointer to a new SecureTunnel that countains a @@ -572,6 +836,12 @@ namespace Aws */ std::string m_clientToken; + /** + * If set, TLS context for secure socket connections. + * If undefined, then default options will be used. + */ + Crt::Optional m_tlsConnectionOptions; + /** * If set, this will be used to override the default trust store. */ @@ -602,9 +872,9 @@ namespace Aws OnConnectionShutdown m_OnConnectionShutdown; /** - * Callback handler trigged when secure tunnel completes sending data to the secure tunnel service. + * Callback handler trigged when secure tunnel completes sending message to the secure tunnel service. */ - OnSendDataComplete m_OnSendDataComplete; + OnSendMessageComplete m_OnSendMessageComplete; /** * Callback handler trigged when secure tunnel receives a message from the secure tunnel service. @@ -627,10 +897,27 @@ namespace Aws * Callback handler trigged when secure tunnel receives a stream reset. * * @param SecureTunnel: The shared secure tunnel - * @param StreamStoppedEventData: Stream Started data + * @param StreamStoppedEventData: Stream stopped data */ OnStreamStopped m_OnStreamStopped; + /** + * Callback handler trigged when secure tunnel receives a connection start from a source device. + * + * @param SecureTunnel: The shared secure tunnel + * @param int: error code + * @param ConnectionStartedEventData: Connection Started data + */ + OnConnectionStarted m_OnConnectionStarted; + + /** + * Callback handler trigged when secure tunnel receives a connection reset. + * + * @param SecureTunnel: The shared secure tunnel + * @param ConnectionResetEventData: Connection reset data + */ + OnConnectionReset m_OnConnectionReset; + /** * Callback handler trigged when secure tunnel receives a session reset from the secure tunnel service. */ @@ -657,6 +944,10 @@ namespace Aws * Deprecated - Use m_OnStreamStopped */ OnStreamReset m_OnStreamReset; + /** + * Deprecated - Use m_OnSendMessageComplete + */ + OnSendDataComplete m_OnSendDataComplete; friend class SecureTunnel; }; @@ -762,6 +1053,61 @@ namespace Aws */ int SendStreamStart(Crt::ByteCursor serviceId); + /** + * Notifies the secure tunnel that you want to start a stream with the Destination device on a specific + * service id. This will result in a V2 stream. + * + * @param serviceId: The Service Id to start a stream on. + * + * @param connectionId: The Connection Id to start the stream on. + * + * @return success/failure in the synchronous logic that kicks off the Stream Start operation + */ + int SendStreamStart(std::string serviceId, uint32_t connectionId); + + /** + * Notifies the secure tunnel that you want to start a stream with the Destination device on a specific + * service id. This will result in a V2 stream. + * + * @param serviceId: The Service Id to start a stream on. + * + * @param connectionId: The Connection Id to start the stream on. + * + * @return success/failure in the synchronous logic that kicks off the Stream Start operation + */ + int SendStreamStart(Crt::ByteCursor serviceId, uint32_t connectionId); + + /** + * Notifies the secure tunnel that you want to start a connection with the Destination device. + * + * @param connectionId: The connection id to start the connection on. + * + * @return success/failure in the synchronous logic that kicks off the Stream Start operation + */ + int SendConnectionStart(uint32_t connectionId); + + /** + * Notifies the secure tunnel that you want to start a connection with the Destination device. + * + * @param serviceId: The Service Id to start the connection on. + * + * @param connectionId: The connection id to start the connection on. + * + * @return success/failure in the synchronous logic that kicks off the Stream Start operation + */ + int SendConnectionStart(std::string serviceId, uint32_t connectionId); + + /** + * Notifies the secure tunnel that you want to start a connection with the Destination device. + * + * @param serviceId: The Service Id to start the connection on. + * + * @param connectionId: The connection id to start the connection on. + * + * @return success/failure in the synchronous logic that kicks off the Stream Start operation + */ + int SendConnectionStart(Crt::ByteCursor serviceId, uint32_t connectionId); + aws_secure_tunnel *GetUnderlyingHandle(); /** @@ -801,20 +1147,24 @@ namespace Aws aws_secure_tunneling_local_proxy_mode localProxyMode, const std::string &endpointHost, + Crt::Io::TlsConnectionOptions *tslOptions, const std::string &rootCa, Crt::Http::HttpClientConnectionProxyOptions *httpClientConnectionProxyOptions, OnConnectionSuccess onConnectionSuccess, OnConnectionFailure onConnectionFailure, - OnConnectionComplete onConnectionComplete, + OnConnectionComplete onConnectionComplete, /* Deprecated */ OnConnectionShutdown onConnectionShutdown, - OnSendDataComplete onSendDataComplete, + OnSendMessageComplete onSendMessageComplete, + OnSendDataComplete onSendDataComplete, /* Deprecated */ OnMessageReceived onMessageReceived, - OnDataReceive onDataReceive, + OnDataReceive onDataReceive, /* Deprecated */ OnStreamStarted onStreamStarted, - OnStreamStart onStreamStart, + OnStreamStart onStreamStart, /* Deprecated */ OnStreamStopped onStreamStopped, - OnStreamReset onStreamReset, + OnStreamReset onStreamReset, /* Deprecated */ + OnConnectionStarted onConnectionStarted, + OnConnectionReset onConnectionReset, OnSessionReset onSessionReset, OnStopped onStopped); @@ -827,6 +1177,10 @@ namespace Aws static void s_OnConnectionFailure(int error_code, void *user_data); static void s_OnConnectionShutdown(int error_code, void *user_data); static void s_OnSendDataComplete(int error_code, void *user_data); + static void s_OnSendMessageComplete( + enum aws_secure_tunnel_message_type type, + int error_code, + void *user_data); static void s_OnStreamStopped( const struct aws_secure_tunnel_message_view *message, int error_code, @@ -838,6 +1192,14 @@ namespace Aws const struct aws_secure_tunnel_message_view *message, int error_code, void *user_data); + static void s_OnConnectionStarted( + const struct aws_secure_tunnel_message_view *message, + int error_code, + void *user_data); + static void s_OnConnectionReset( + const struct aws_secure_tunnel_message_view *message, + int error_code, + void *user_data); void OnTerminationComplete(); @@ -863,9 +1225,9 @@ namespace Aws OnConnectionShutdown m_OnConnectionShutdown; /** - * Callback handler trigged when secure tunnel completes sending data to the secure tunnel service. + * Callback handler trigged when secure tunnel completes sending message to the secure tunnel service. */ - OnSendDataComplete m_OnSendDataComplete; + OnSendMessageComplete m_OnSendMessageComplete; /** * Callback handler trigged when secure tunnel starts a stream with a source device through the secure @@ -878,6 +1240,17 @@ namespace Aws */ OnStreamStopped m_OnStreamStopped; + /** + * Callback handler trigged when secure tunnel starts a connection with a source device through the secure + * tunnel service. + */ + OnConnectionStarted m_OnConnectionStarted; + + /** + * Callback handler trigged when secure tunnel resets a connection + */ + OnConnectionReset m_OnConnectionReset; + /** * Callback handler trigged when secure tunnel receives a session reset from the secure tunnel service. */ @@ -892,7 +1265,7 @@ namespace Aws Crt::Allocator *m_allocator; /** - * Deprecated - m_OnMessageReceived + * Deprecated - Use m_OnMessageReceived */ OnDataReceive m_OnDataReceive; /** @@ -907,6 +1280,10 @@ namespace Aws * Deprecated - Use m_OnStreamStopped */ OnStreamReset m_OnStreamReset; + /** + * Deprecated - Use m_OnMessageSendComplete + */ + OnSendDataComplete m_OnSendDataComplete; std::shared_ptr m_selfRef; diff --git a/secure_tunneling/source/SecureTunnel.cpp b/secure_tunneling/source/SecureTunnel.cpp index b76d182e9..01b3680f2 100644 --- a/secure_tunneling/source/SecureTunnel.cpp +++ b/secure_tunneling/source/SecureTunnel.cpp @@ -53,6 +53,7 @@ namespace Aws { AWS_ZERO_STRUCT(m_payloadStorage); AWS_ZERO_STRUCT(m_serviceIdStorage); + m_connectionId = message.connection_id; setPacketByteBufOptional(m_payload, m_payloadStorage, m_allocator, message.payload); setPacketByteBufOptional(m_serviceId, m_serviceIdStorage, m_allocator, message.service_id); @@ -63,12 +64,26 @@ namespace Aws { AWS_ZERO_STRUCT(m_payloadStorage); AWS_ZERO_STRUCT(m_serviceIdStorage); + m_connectionId = 0; } Message::Message(Crt::ByteCursor payload, Crt::Allocator *allocator) noexcept : m_allocator(allocator) { AWS_ZERO_STRUCT(m_payloadStorage); AWS_ZERO_STRUCT(m_serviceIdStorage); + m_connectionId = 0; + + aws_byte_buf_clean_up(&m_payloadStorage); + aws_byte_buf_init_copy_from_cursor(&m_payloadStorage, m_allocator, payload); + m_payload = aws_byte_cursor_from_buf(&m_payloadStorage); + } + + Message::Message(Crt::ByteCursor payload, uint32_t connectionId, Crt::Allocator *allocator) noexcept + : m_allocator(allocator) + { + AWS_ZERO_STRUCT(m_payloadStorage); + AWS_ZERO_STRUCT(m_serviceIdStorage); + m_connectionId = connectionId; aws_byte_buf_clean_up(&m_payloadStorage); aws_byte_buf_init_copy_from_cursor(&m_payloadStorage, m_allocator, payload); @@ -80,6 +95,7 @@ namespace Aws { AWS_ZERO_STRUCT(m_payloadStorage); AWS_ZERO_STRUCT(m_serviceIdStorage); + m_connectionId = 0; aws_byte_buf_clean_up(&m_payloadStorage); aws_byte_buf_init_copy_from_cursor(&m_payloadStorage, m_allocator, payload); @@ -90,7 +106,27 @@ namespace Aws m_serviceId = aws_byte_cursor_from_buf(&m_serviceIdStorage); } - Message &Message::withPayload(Crt::ByteCursor payload) noexcept + Message::Message( + Crt::ByteCursor serviceId, + uint32_t connectionId, + Crt::ByteCursor payload, + Crt::Allocator *allocator) noexcept + : m_allocator(allocator) + { + AWS_ZERO_STRUCT(m_payloadStorage); + AWS_ZERO_STRUCT(m_serviceIdStorage); + m_connectionId = connectionId; + + aws_byte_buf_clean_up(&m_payloadStorage); + aws_byte_buf_init_copy_from_cursor(&m_payloadStorage, m_allocator, payload); + m_payload = aws_byte_cursor_from_buf(&m_payloadStorage); + + aws_byte_buf_clean_up(&m_serviceIdStorage); + aws_byte_buf_init_copy_from_cursor(&m_serviceIdStorage, m_allocator, serviceId); + m_serviceId = aws_byte_cursor_from_buf(&m_serviceIdStorage); + } + + Message &Message::WithPayload(Crt::ByteCursor payload) noexcept { aws_byte_buf_clean_up(&m_payloadStorage); aws_byte_buf_init_copy_from_cursor(&m_payloadStorage, m_allocator, payload); @@ -98,7 +134,7 @@ namespace Aws return *this; } - Message &Message::withServiceId(Crt::ByteCursor serviceId) noexcept + Message &Message::WithServiceId(Crt::ByteCursor serviceId) noexcept { aws_byte_buf_clean_up(&m_serviceIdStorage); aws_byte_buf_init_copy_from_cursor(&m_serviceIdStorage, m_allocator, serviceId); @@ -106,6 +142,12 @@ namespace Aws return *this; } + Message &Message::WithConnectionId(uint32_t connectionId) noexcept + { + m_connectionId = connectionId; + return *this; + } + bool Message::initializeRawOptions(aws_secure_tunnel_message_view &raw_options) noexcept { AWS_ZERO_STRUCT(raw_options); @@ -117,6 +159,7 @@ namespace Aws { raw_options.service_id = &m_serviceId.value(); } + raw_options.connection_id = m_connectionId; return true; } @@ -125,12 +168,35 @@ namespace Aws const Crt::Optional &Message::getServiceId() const noexcept { return m_serviceId; } + const uint32_t &Message::getConnectionId() const noexcept { return m_connectionId; } + Message::~Message() { aws_byte_buf_clean_up(&m_payloadStorage); aws_byte_buf_clean_up(&m_serviceIdStorage); } + //*********************************************************************************************************************** + /* SendMessageCompleteData */ + //*********************************************************************************************************************** + + SendMessageCompleteData::SendMessageCompleteData( + enum aws_secure_tunnel_message_type type, + Crt::Allocator *allocator) noexcept + : m_allocator(allocator) + { + AWS_ZERO_STRUCT(m_messageTypeStorage); + struct aws_byte_buf typeBuf = aws_byte_buf_from_c_str(aws_secure_tunnel_message_type_to_c_string(type)); + + aws_byte_buf_init_copy(&m_messageTypeStorage, m_allocator, &typeBuf); + aws_byte_buf_clean_up(&typeBuf); + m_messageType = aws_byte_cursor_from_buf(&m_messageTypeStorage); + } + + const Crt::ByteCursor &SendMessageCompleteData::getMessageType() const noexcept { return m_messageType; } + + SendMessageCompleteData::~SendMessageCompleteData() { aws_byte_buf_clean_up(&m_messageTypeStorage); } + //*********************************************************************************************************************** /* ConnectionData */ //*********************************************************************************************************************** @@ -172,10 +238,13 @@ namespace Aws AWS_ZERO_STRUCT(m_serviceIdStorage); setPacketByteBufOptional(m_serviceId, m_serviceIdStorage, m_allocator, message.service_id); + m_connectionId = message.connection_id; } const Crt::Optional &StreamStartedData::getServiceId() const noexcept { return m_serviceId; } + const uint32_t &StreamStartedData::getConnectionId() const noexcept { return m_connectionId; } + StreamStartedData::~StreamStartedData() { aws_byte_buf_clean_up(&m_serviceIdStorage); } //*********************************************************************************************************************** @@ -196,6 +265,51 @@ namespace Aws StreamStoppedData::~StreamStoppedData() { aws_byte_buf_clean_up(&m_serviceIdStorage); } + //*********************************************************************************************************************** + /* ConnectionStartedData */ + //*********************************************************************************************************************** + + ConnectionStartedData::ConnectionStartedData( + const aws_secure_tunnel_message_view &message, + Crt::Allocator *allocator) noexcept + : m_allocator(allocator) + { + AWS_ZERO_STRUCT(m_serviceIdStorage); + + setPacketByteBufOptional(m_serviceId, m_serviceIdStorage, m_allocator, message.service_id); + m_connectionId = message.connection_id; + } + + const Crt::Optional &ConnectionStartedData::getServiceId() const noexcept + { + return m_serviceId; + } + + const uint32_t &ConnectionStartedData::getConnectionId() const noexcept { return m_connectionId; } + + ConnectionStartedData::~ConnectionStartedData() { aws_byte_buf_clean_up(&m_serviceIdStorage); } + + //*********************************************************************************************************************** + /* ConnectionResetData */ + //*********************************************************************************************************************** + + ConnectionResetData::ConnectionResetData( + const aws_secure_tunnel_message_view &message, + Crt::Allocator *allocator) noexcept + : m_allocator(allocator) + { + AWS_ZERO_STRUCT(m_serviceIdStorage); + + setPacketByteBufOptional(m_serviceId, m_serviceIdStorage, m_allocator, message.service_id); + m_connectionId = message.connection_id; + } + + const Crt::Optional &ConnectionResetData::getServiceId() const noexcept { return m_serviceId; } + + const uint32_t &ConnectionResetData::getConnectionId() const noexcept { return m_connectionId; } + + ConnectionResetData::~ConnectionResetData() { aws_byte_buf_clean_up(&m_serviceIdStorage); } + //*********************************************************************************************************************** /* SecureTunnelBuilder */ //*********************************************************************************************************************** @@ -207,9 +321,7 @@ namespace Aws aws_secure_tunneling_local_proxy_mode localProxyMode, const std::string &endpointHost) // Make a copy and save in this object : m_allocator(allocator), m_clientBootstrap(&clientBootstrap), m_socketOptions(socketOptions), - m_accessToken(accessToken), m_localProxyMode(localProxyMode), m_endpointHost(endpointHost), m_rootCa(""), - m_httpClientConnectionProxyOptions(), m_OnConnectionShutdown(), m_OnSendDataComplete(), - m_OnSessionReset(), m_OnConnectionComplete(), m_OnDataReceive(), m_OnStreamStart(), m_OnStreamReset() + m_accessToken(accessToken), m_localProxyMode(localProxyMode), m_endpointHost(endpointHost), m_rootCa("") { } @@ -221,9 +333,7 @@ namespace Aws const std::string &endpointHost) // Make a copy and save in this object : m_allocator(allocator), m_clientBootstrap(Crt::ApiHandle::GetOrCreateStaticDefaultClientBootstrap()), m_socketOptions(socketOptions), m_accessToken(accessToken), m_localProxyMode(localProxyMode), - m_endpointHost(endpointHost), m_rootCa(""), m_httpClientConnectionProxyOptions(), - m_OnConnectionShutdown(), m_OnSendDataComplete(), m_OnSessionReset(), m_OnConnectionComplete(), - m_OnDataReceive(), m_OnStreamStart(), m_OnStreamReset() + m_endpointHost(endpointHost), m_rootCa("") { } @@ -234,10 +344,15 @@ namespace Aws const std::string &endpointHost) // Make a copy and save in this object : m_allocator(allocator), m_clientBootstrap(Crt::ApiHandle::GetOrCreateStaticDefaultClientBootstrap()), m_socketOptions(Crt::Io::SocketOptions()), m_accessToken(accessToken), m_localProxyMode(localProxyMode), - m_endpointHost(endpointHost), m_rootCa(""), m_httpClientConnectionProxyOptions(), - m_OnConnectionShutdown(), m_OnSendDataComplete(), m_OnSessionReset(), m_OnConnectionComplete(), - m_OnDataReceive(), m_OnStreamStart(), m_OnStreamReset() + m_endpointHost(endpointHost), m_rootCa("") + { + } + + SecureTunnelBuilder &SecureTunnelBuilder::WithTlsConnectionOptions( + const Crt::Io::TlsConnectionOptions &tslOptions) { + m_tlsConnectionOptions = tslOptions; + return *this; } SecureTunnelBuilder &SecureTunnelBuilder::WithRootCa(const std::string &rootCa) @@ -271,9 +386,9 @@ namespace Aws return *this; } - SecureTunnelBuilder &SecureTunnelBuilder::WithOnSendDataComplete(OnSendDataComplete onSendDataComplete) + SecureTunnelBuilder &SecureTunnelBuilder::WithOnSendMessageComplete(OnSendMessageComplete onSendMessageComplete) { - m_OnSendDataComplete = std::move(onSendDataComplete); + m_OnSendMessageComplete = std::move(onSendMessageComplete); return *this; } @@ -295,9 +410,15 @@ namespace Aws return *this; } - SecureTunnelBuilder &SecureTunnelBuilder::WithOnStreamReset(OnStreamReset onStreamReset) + SecureTunnelBuilder &SecureTunnelBuilder::WithOnConnectionStarted(OnConnectionStarted onConnectionStarted) { - m_OnStreamReset = std::move(onStreamReset); + m_OnConnectionStarted = std::move(onConnectionStarted); + return *this; + } + + SecureTunnelBuilder &SecureTunnelBuilder::WithOnConnectionReset(OnConnectionReset onConnectionReset) + { + m_OnConnectionReset = std::move(onConnectionReset); return *this; } @@ -331,12 +452,24 @@ namespace Aws m_OnStreamStart = std::move(onStreamStart); return *this; } + /* Deprecated - Use WithOnStreamStopped */ + SecureTunnelBuilder &SecureTunnelBuilder::WithOnStreamReset(OnStreamReset onStreamReset) + { + m_OnStreamReset = std::move(onStreamReset); + return *this; + } /* Deprecated - Use WithOnMessageReceived */ SecureTunnelBuilder &SecureTunnelBuilder::WithOnDataReceive(OnDataReceive onDataReceive) { m_OnDataReceive = std::move(onDataReceive); return *this; } + /* Deprecated - Use WithOnSendMessageComplete */ + SecureTunnelBuilder &SecureTunnelBuilder::WithOnSendDataComplete(OnSendDataComplete onSendDataComplete) + { + m_OnSendDataComplete = std::move(onSendDataComplete); + return *this; + } std::shared_ptr SecureTunnelBuilder::Build() noexcept { @@ -348,12 +481,14 @@ namespace Aws m_clientToken, m_localProxyMode, m_endpointHost, + m_tlsConnectionOptions.has_value() ? &m_tlsConnectionOptions.value() : nullptr, m_rootCa, m_httpClientConnectionProxyOptions.has_value() ? &m_httpClientConnectionProxyOptions.value() : nullptr, m_OnConnectionSuccess, m_OnConnectionFailure, m_OnConnectionComplete, m_OnConnectionShutdown, + m_OnSendMessageComplete, m_OnSendDataComplete, m_OnMessageReceived, m_OnDataReceive, @@ -361,6 +496,8 @@ namespace Aws m_OnStreamStart, m_OnStreamStopped, m_OnStreamReset, + m_OnConnectionStarted, + m_OnConnectionReset, m_OnSessionReset, m_OnStopped)); @@ -389,6 +526,7 @@ namespace Aws aws_secure_tunneling_local_proxy_mode localProxyMode, const std::string &endpointHost, + Crt::Io::TlsConnectionOptions *tslOptions, const std::string &rootCa, Aws::Crt::Http::HttpClientConnectionProxyOptions *httpClientConnectionProxyOptions, @@ -396,6 +534,7 @@ namespace Aws OnConnectionFailure onConnectionFailure, OnConnectionComplete onConnectionComplete, OnConnectionShutdown onConnectionShutdown, + OnSendMessageComplete onSendMessageComplete, OnSendDataComplete onSendDataComplete, OnMessageReceived onMessageReceived, OnDataReceive onDataReceive, @@ -403,6 +542,8 @@ namespace Aws OnStreamStart onStreamStart, OnStreamStopped onStreamStopped, OnStreamReset onStreamReset, + OnConnectionStarted onConnectionStarted, + OnConnectionReset onConnectionReset, OnSessionReset onSessionReset, OnStopped onStopped) { @@ -411,12 +552,15 @@ namespace Aws m_OnConnectionFailure = std::move(onConnectionFailure); m_OnConnectionComplete = std::move(onConnectionComplete); m_OnConnectionShutdown = std::move(onConnectionShutdown); + m_OnSendMessageComplete = std::move(onSendMessageComplete); m_OnSendDataComplete = std::move(onSendDataComplete); m_OnMessageReceived = std::move(onMessageReceived); m_OnDataReceive = std::move(onDataReceive); m_OnStreamStarted = std::move(onStreamStarted); m_OnStreamStart = std::move(onStreamStart); m_OnStreamReset = std::move(onStreamReset); + m_OnConnectionStarted = std::move(onConnectionStarted); + m_OnConnectionReset = std::move(onConnectionReset); m_OnSessionReset = std::move(onSessionReset); m_OnStopped = std::move(onStopped); @@ -431,6 +575,8 @@ namespace Aws config.local_proxy_mode = localProxyMode; config.endpoint_host = aws_byte_cursor_from_c_str(endpointHost.c_str()); + config.tls_options = tslOptions ? tslOptions->GetUnderlyingHandle() : nullptr; + if (rootCa.length() > 0) { config.root_ca = rootCa.c_str(); @@ -445,9 +591,11 @@ namespace Aws config.on_message_received = s_OnMessageReceived; config.on_connection_complete = s_OnConnectionComplete; config.on_connection_shutdown = s_OnConnectionShutdown; - config.on_send_data_complete = s_OnSendDataComplete; + config.on_send_message_complete = s_OnSendMessageComplete; config.on_stream_start = s_OnStreamStarted; config.on_stream_reset = s_OnStreamStopped; + config.on_connection_start = s_OnConnectionStarted; + config.on_connection_reset = s_OnConnectionReset; config.on_session_reset = s_OnSessionReset; config.on_stopped = s_OnStopped; @@ -494,12 +642,14 @@ namespace Aws nullptr, localProxyMode, endpointHost, + nullptr, rootCa, nullptr, nullptr, nullptr, onConnectionComplete, onConnectionShutdown, + nullptr, onSendDataComplete, nullptr, onDataReceive, @@ -507,6 +657,8 @@ namespace Aws onStreamStart, nullptr, onStreamReset, + nullptr, + nullptr, onSessionReset, nullptr) { @@ -539,12 +691,14 @@ namespace Aws nullptr, localProxyMode, endpointHost, + nullptr, rootCa, nullptr, nullptr, nullptr, onConnectionComplete, onConnectionShutdown, + nullptr, onSendDataComplete, nullptr, onDataReceive, @@ -552,6 +706,8 @@ namespace Aws onStreamStart, nullptr, onStreamReset, + nullptr, + nullptr, onSessionReset, nullptr) { @@ -571,10 +727,12 @@ namespace Aws m_OnConnectionSuccess = std::move(other.m_OnConnectionSuccess); m_OnConnectionFailure = std::move(other.m_OnConnectionFailure); m_OnConnectionShutdown = std::move(other.m_OnConnectionShutdown); - m_OnSendDataComplete = std::move(other.m_OnSendDataComplete); + m_OnSendMessageComplete = std::move(other.m_OnSendMessageComplete); m_OnMessageReceived = std::move(other.m_OnMessageReceived); m_OnStreamStarted = std::move(other.m_OnStreamStarted); m_OnStreamReset = std::move(other.m_OnStreamReset); + m_OnConnectionStarted = std::move(other.m_OnConnectionStarted); + m_OnConnectionReset = std::move(other.m_OnConnectionReset); m_OnSessionReset = std::move(other.m_OnSessionReset); m_OnStopped = std::move(other.m_OnStopped); @@ -584,6 +742,8 @@ namespace Aws m_OnDataReceive = std::move(other.m_OnDataReceive); /* Deprecated - Use m_OnStreamStarted */ m_OnStreamStart = std::move(other.m_OnStreamStart); + /* Deprecated - Use m_OnSendMessageComplete */ + m_OnSendDataComplete = std::move(other.m_OnSendDataComplete); m_secure_tunnel = other.m_secure_tunnel; @@ -599,10 +759,12 @@ namespace Aws m_OnConnectionSuccess = std::move(other.m_OnConnectionSuccess); m_OnConnectionFailure = std::move(other.m_OnConnectionFailure); m_OnConnectionShutdown = std::move(other.m_OnConnectionShutdown); - m_OnSendDataComplete = std::move(other.m_OnSendDataComplete); + m_OnSendMessageComplete = std::move(other.m_OnSendMessageComplete); m_OnMessageReceived = std::move(other.m_OnMessageReceived); m_OnStreamStarted = std::move(other.m_OnStreamStarted); m_OnStreamReset = std::move(other.m_OnStreamReset); + m_OnConnectionStarted = std::move(other.m_OnConnectionStarted); + m_OnConnectionReset = std::move(other.m_OnConnectionReset); m_OnSessionReset = std::move(other.m_OnSessionReset); m_OnStopped = std::move(other.m_OnStopped); @@ -612,6 +774,8 @@ namespace Aws m_OnDataReceive = std::move(other.m_OnDataReceive); /* Deprecated - Use m_OnStreamStarted */ m_OnStreamStart = std::move(other.m_OnStreamStart); + /* Deprecated - Use m_OnSendMessageComplete */ + m_OnSendDataComplete = std::move(other.m_OnSendDataComplete); m_secure_tunnel = other.m_secure_tunnel; @@ -661,7 +825,9 @@ namespace Aws } int SecureTunnel::SendStreamStart() { return SendStreamStart(""); } - int SecureTunnel::SendStreamStart(std::string serviceId) + int SecureTunnel::SendStreamStart(std::string serviceId) { return SendStreamStart(serviceId, 0); } + int SecureTunnel::SendStreamStart(Crt::ByteCursor serviceId) { return SendStreamStart(serviceId, 0); } + int SecureTunnel::SendStreamStart(std::string serviceId, uint32_t connectionId) { struct aws_byte_cursor service_id_cur; AWS_ZERO_STRUCT(service_id_cur); @@ -669,16 +835,43 @@ namespace Aws { service_id_cur = aws_byte_cursor_from_c_str(serviceId.c_str()); } - return SendStreamStart(service_id_cur); + return SendStreamStart(service_id_cur, connectionId); } - int SecureTunnel::SendStreamStart(Crt::ByteCursor serviceId) + + int SecureTunnel::SendStreamStart(Crt::ByteCursor serviceId, uint32_t connectionId) { struct aws_secure_tunnel_message_view messageView; AWS_ZERO_STRUCT(messageView); - messageView.service_id = &serviceId; + if (serviceId.len > 0) + { + messageView.service_id = &serviceId; + } + messageView.connection_id = connectionId; return aws_secure_tunnel_stream_start(m_secure_tunnel, &messageView); } + int SecureTunnel::SendConnectionStart(uint32_t connectionId) { return SendConnectionStart("", connectionId); } + + int SecureTunnel::SendConnectionStart(std::string serviceId, uint32_t connectionId) + { + struct aws_byte_cursor service_id_cur; + AWS_ZERO_STRUCT(service_id_cur); + if (serviceId.length() > 0) + { + service_id_cur = aws_byte_cursor_from_c_str(serviceId.c_str()); + } + return SendConnectionStart(service_id_cur, connectionId); + } + + int SecureTunnel::SendConnectionStart(Crt::ByteCursor serviceId, uint32_t connectionId) + { + struct aws_secure_tunnel_message_view messageView; + AWS_ZERO_STRUCT(messageView); + messageView.service_id = &serviceId; + messageView.connection_id = connectionId; + return aws_secure_tunnel_connection_start(m_secure_tunnel, &messageView); + } + int SecureTunnel::SendStreamReset() { return aws_secure_tunnel_stream_reset(m_secure_tunnel, NULL); } aws_secure_tunnel *SecureTunnel::GetUnderlyingHandle() { return m_secure_tunnel; } @@ -728,9 +921,24 @@ namespace Aws } } - void SecureTunnel::s_OnSendDataComplete(int error_code, void *user_data) + void SecureTunnel::s_OnSendMessageComplete( + enum aws_secure_tunnel_message_type type, + int error_code, + void *user_data) { - SecureTunnel *secureTunnel = static_cast(user_data); + auto *secureTunnel = static_cast(user_data); + + if (secureTunnel->m_OnSendMessageComplete) + { + std::shared_ptr packet = + std::make_shared(type, secureTunnel->m_allocator); + SendMessageCompleteEventData eventData; + eventData.sendMessageCompleteData = packet; + secureTunnel->m_OnSendMessageComplete(secureTunnel, error_code, eventData); + return; + } + + /* Fall back on deprecated complete callback */ if (secureTunnel->m_OnSendDataComplete) { secureTunnel->m_OnSendDataComplete(error_code); @@ -831,6 +1039,44 @@ namespace Aws } } + void SecureTunnel::s_OnConnectionStarted( + const struct aws_secure_tunnel_message_view *message, + int error_code, + void *user_data) + { + SecureTunnel *secureTunnel = static_cast(user_data); + if (!error_code) + { + if (secureTunnel->m_OnConnectionStarted) + { + std::shared_ptr packet = + std::make_shared(*message, secureTunnel->m_allocator); + ConnectionStartedEventData eventData; + eventData.connectionStartedData = packet; + secureTunnel->m_OnConnectionStarted(secureTunnel, error_code, eventData); + return; + } + } + } + + void SecureTunnel::s_OnConnectionReset( + const struct aws_secure_tunnel_message_view *message, + int error_code, + void *user_data) + { + SecureTunnel *secureTunnel = static_cast(user_data); + + if (secureTunnel->m_OnConnectionReset) + { + std::shared_ptr packet = + std::make_shared(*message, secureTunnel->m_allocator); + ConnectionResetEventData eventData; + eventData.connectionResetData = packet; + secureTunnel->m_OnConnectionReset(secureTunnel, error_code, eventData); + return; + } + } + void SecureTunnel::s_OnSessionReset(void *user_data) { SecureTunnel *secureTunnel = static_cast(user_data); diff --git a/secure_tunneling/tests/CMakeLists.txt b/secure_tunneling/tests/CMakeLists.txt new file mode 100644 index 000000000..c4ddfecc6 --- /dev/null +++ b/secure_tunneling/tests/CMakeLists.txt @@ -0,0 +1,29 @@ +cmake_minimum_required(VERSION 3.1) +# note: cxx-17 requires cmake 3.8, cxx-20 requires cmake 3.12 +project(secure_tunnel_test CXX) + +file(GLOB SRC_FILES + "*.cpp" + # "../../utils/CommandLineUtils.cpp" + # "../../utils/CommandLineUtils.h" +) + +add_executable(${PROJECT_NAME} ${SRC_FILES}) + +set_target_properties(${PROJECT_NAME} PROPERTIES + CXX_STANDARD 14) + +#set warnings +if (MSVC) + target_compile_options(${PROJECT_NAME} PRIVATE /W4 /WX) +else () + target_compile_options(${PROJECT_NAME} PRIVATE -Wall -Wno-long-long -pedantic -Werror) +endif () + +find_package(aws-crt-cpp REQUIRED) +find_package(IotDeviceCommon-cpp REQUIRED) +find_package(IotSecureTunneling-cpp REQUIRED) + +install(TARGETS ${PROJECT_NAME} DESTINATION bin) + +target_link_libraries(${PROJECT_NAME} AWS::aws-crt-cpp AWS::IotDeviceCommon-cpp AWS::IotSecureTunneling-cpp) diff --git a/secure_tunneling/tests/main.cpp b/secure_tunneling/tests/main.cpp new file mode 100644 index 000000000..bd6606e19 --- /dev/null +++ b/secure_tunneling/tests/main.cpp @@ -0,0 +1,332 @@ +/** + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0. + */ + +#include +#include +#include +#include +#include +#include +#include + +using namespace Aws::Crt; +using namespace Aws::Iotsecuretunneling; +using namespace Aws::Crt::Io; +using namespace std::chrono_literals; + +AWS_STATIC_STRING_FROM_LITERAL(SECTUN_ENDPOINT, "SECTUN_ENDPOINT"); +AWS_STATIC_STRING_FROM_LITERAL(SECTUN_SOURCE_TOKEN, "SECTUN_SOURCE_TOKEN"); +AWS_STATIC_STRING_FROM_LITERAL(SECTUN_DESTINATION_TOKEN, "SECTUN_DESTINATION_TOKEN"); +AWS_STATIC_STRING_FROM_LITERAL(SECTUN_SOURCE_CLIENT_TOKEN, "SECTUN_SOURCE_CLIENT_TOKEN"); +AWS_STATIC_STRING_FROM_LITERAL(SECTUN_DESTINATION_CLIENT_TOKEN, "SECTUN_DESTINATION_CLIENT_TOKEN"); +AWS_STATIC_STRING_FROM_LITERAL(SECTUN_PAYLOAD_MESSAGE, "Payload Message"); + +void setEnvVariable(struct aws_allocator *allocator, const struct aws_string *variable_name, String &stringToSet) +{ + aws_string *awsStringToSet = NULL; + aws_get_environment_value(allocator, variable_name, &awsStringToSet); + stringToSet = awsStringToSet == nullptr ? "" : aws_string_c_str(awsStringToSet); + aws_string_destroy(awsStringToSet); +} + +int main(int argc, char *argv[]) +{ + fprintf(stdout, "Secure Tunnel Test Starting\n"); + struct aws_allocator *allocator = aws_default_allocator(); + ApiHandle apiHandle; + + /* Uncomment to produce logs in codebuild */ + // apiHandle.InitializeLogging(Aws::Crt::LogLevel::Trace, stderr); + + aws_iotdevice_library_init(allocator); + + /* service id storage for use in test */ + Aws::Crt::ByteBuf m_serviceIdStorage; + AWS_ZERO_STRUCT(m_serviceIdStorage); + Aws::Crt::Optional m_serviceId; + + std::promise promiseDestinationConnected; + std::promise promiseSourceConnected; + + std::promise promiseDestinationStreamStarted; + std::promise promiseDestinationConnectionStarted; + + std::promise promiseDestinationReceivedMessage; + std::promise promiseSourceReceivedMessage; + + std::promise promiseDestinationStopped; + std::promise promiseSourceStopped; + + String endpoint; + String destinationToken; + String sourceToken; + String destinationClientToken; + String sourceClientToken; + /* Connection Id is used for Simultaneous HTTP Connections (Protocl V3) */ + uint32_t connectionId = 1; + uint32_t connectionId2 = 2; + + setEnvVariable(allocator, SECTUN_DESTINATION_TOKEN, destinationToken); + setEnvVariable(allocator, SECTUN_SOURCE_TOKEN, sourceToken); + setEnvVariable(allocator, SECTUN_DESTINATION_CLIENT_TOKEN, destinationClientToken); + setEnvVariable(allocator, SECTUN_SOURCE_CLIENT_TOKEN, sourceClientToken); + setEnvVariable(allocator, SECTUN_ENDPOINT, endpoint); + + if (apiHandle.GetOrCreateStaticDefaultClientBootstrap()->LastError() != AWS_ERROR_SUCCESS) + { + fprintf( + stderr, + "ClientBootstrap failed with error %s\n", + ErrorDebugString(apiHandle.GetOrCreateStaticDefaultClientBootstrap()->LastError())); + exit(-1); + } + + /* Use a SecureTunnelBuilder to set up and build the secure tunnel clients */ + SecureTunnelBuilder builderDestination = SecureTunnelBuilder( + allocator, destinationToken.c_str(), AWS_SECURE_TUNNELING_DESTINATION_MODE, endpoint.c_str()); + SecureTunnelBuilder builderSource = + SecureTunnelBuilder(allocator, sourceToken.c_str(), AWS_SECURE_TUNNELING_SOURCE_MODE, endpoint.c_str()); + + if (destinationClientToken.length() > 0) + { + builderDestination.WithClientToken(destinationClientToken.c_str()); + } + if (sourceClientToken.length() > 0) + { + builderSource.WithClientToken(sourceClientToken.c_str()); + } + + builderDestination.WithOnMessageReceived( + [&](SecureTunnel *secureTunnel, const MessageReceivedEventData &eventData) { + { + (void)secureTunnel; + (void)eventData; + fprintf(stdout, "Destination Client Received Message\n"); + promiseDestinationReceivedMessage.set_value(); + } + }); + + builderSource.WithOnMessageReceived([&](SecureTunnel *secureTunnel, const MessageReceivedEventData &eventData) { + { + (void)secureTunnel; + (void)eventData; + fprintf(stdout, "Source Client Received Message\n"); + promiseSourceReceivedMessage.set_value(); + } + }); + + builderDestination.WithOnSendMessageComplete( + [&](SecureTunnel *secureTunnel, int errorCode, const SendMessageCompleteEventData &eventData) { + (void)secureTunnel; + (void)eventData; + + if (!errorCode) + { + fprintf( + stdout, + "Message of type '" PRInSTR "' sent successfully\n", + AWS_BYTE_CURSOR_PRI(eventData.sendMessageCompleteData->getMessageType())); + } + else + { + fprintf(stdout, "Send Message failed with error code %d(%s)\n", errorCode, ErrorDebugString(errorCode)); + aws_byte_buf_clean_up(&m_serviceIdStorage); + exit(-1); + } + }); + + builderSource.WithOnSendMessageComplete( + [&](SecureTunnel *secureTunnel, int errorCode, const SendMessageCompleteEventData &eventData) { + (void)secureTunnel; + (void)eventData; + + if (!errorCode) + { + fprintf( + stdout, + "Message of type '" PRInSTR "' sent successfully\n", + AWS_BYTE_CURSOR_PRI(eventData.sendMessageCompleteData->getMessageType())); + } + else + { + fprintf(stdout, "Send Message failed with error code %d(%s)\n", errorCode, ErrorDebugString(errorCode)); + aws_byte_buf_clean_up(&m_serviceIdStorage); + exit(-1); + } + }); + + builderDestination.WithOnConnectionSuccess( + [&](SecureTunnel *secureTunnel, const ConnectionSuccessEventData &eventData) { + (void)secureTunnel; + (void)eventData; + fprintf(stdout, "Destination Client Connection Success\n"); + promiseDestinationConnected.set_value(); + }); + + builderSource.WithOnConnectionSuccess([&](SecureTunnel *secureTunnel, const ConnectionSuccessEventData &eventData) { + (void)secureTunnel; + (void)eventData; + + fprintf(stdout, "Source Client Connection Success\n"); + + /* Use a Multiplexing (Service Id) if available on this Secure Tunnel */ + if (eventData.connectionData->getServiceId1().has_value()) + { + /* Store the service id for future use */ + aws_byte_buf_clean_up(&m_serviceIdStorage); + AWS_ZERO_STRUCT(m_serviceIdStorage); + aws_byte_buf_init_copy_from_cursor( + &m_serviceIdStorage, allocator, eventData.connectionData->getServiceId1().value()); + m_serviceId = aws_byte_cursor_from_buf(&m_serviceIdStorage); + secureTunnel->SendStreamStart(m_serviceId.value(), connectionId); + fprintf(stdout, "Stream Start sent from Source Client.\n"); + } + else + { + fprintf(stdout, "Secure Tunnel should have service ids set for proper testing\n"); + exit(-1); + } + + promiseSourceConnected.set_value(); + }); + + builderDestination.WithOnStreamStarted( + [&](SecureTunnel *secureTunnel, int errorCode, const StreamStartedEventData &eventData) { + (void)secureTunnel; + (void)eventData; + if (!errorCode) + { + fprintf(stdout, "Destination Client Stream Started with Source Client\n"); + promiseDestinationStreamStarted.set_value(); + } + else + { + fprintf(stdout, "Stream Start failed with error code %d(%s)\n", errorCode, ErrorDebugString(errorCode)); + aws_byte_buf_clean_up(&m_serviceIdStorage); + exit(-1); + } + }); + + builderDestination.WithOnConnectionStarted([&](SecureTunnel *secureTunnel, + int errorCode, + const ConnectionStartedEventData &eventData) { + (void)secureTunnel; + (void)eventData; + if (!errorCode) + { + fprintf(stdout, "Connection Started on Destination Client.\n"); + promiseDestinationConnectionStarted.set_value(); + } + else + { + fprintf(stdout, "Connection Start failed with error code %d(%s)\n", errorCode, ErrorDebugString(errorCode)); + aws_byte_buf_clean_up(&m_serviceIdStorage); + exit(-1); + } + }); + + builderDestination.WithOnConnectionShutdown([&]() { fprintf(stdout, "Destination Connection Shutdown\n"); }); + + builderSource.WithOnConnectionShutdown([&]() { fprintf(stdout, "Source Connection Shutdown\n"); }); + + builderDestination.WithOnStopped([&](SecureTunnel *secureTunnel) { + (void)secureTunnel; + fprintf(stdout, "Destination entered Stopped State\n"); + promiseDestinationStopped.set_value(); + }); + + builderSource.WithOnStopped([&](SecureTunnel *secureTunnel) { + (void)secureTunnel; + fprintf(stdout, "Source has entered Stopped State\n"); + promiseSourceStopped.set_value(); + }); + + /* Create Secure Tunnel using the options set with the builder */ + std::shared_ptr secureTunnelDestination = builderDestination.Build(); + + if (!secureTunnelDestination) + { + fprintf(stderr, "Secure Tunnel Destination Creation failed: %s\n", ErrorDebugString(LastError())); + exit(-1); + } + + std::shared_ptr secureTunnelSource = builderSource.Build(); + if (!secureTunnelSource) + { + fprintf(stderr, "Secure Tunnel Source Creation failed: %s\n", ErrorDebugString(LastError())); + exit(-1); + } + + fprintf(stdout, "Secure Tunnels Created\n"); + + /* Set the Secure Tunnel Client to desire a connected state */ + + if (secureTunnelDestination->Start()) + { + fprintf(stderr, "Secure Tunnel Destination Connect call failed: %s\n", ErrorDebugString(LastError())); + exit(-1); + } + + promiseDestinationConnected.get_future().wait(); + + if (secureTunnelSource->Start()) + { + fprintf(stderr, "Secure Tunnel Source Connect call failed: %s\n", ErrorDebugString(LastError())); + exit(-1); + } + + promiseSourceConnected.get_future().wait(); + + promiseDestinationStreamStarted.get_future().wait(); + + secureTunnelSource->SendConnectionStart(m_serviceId.value(), connectionId2); + + promiseDestinationConnectionStarted.get_future().wait(); + + std::shared_ptr message1 = + std::make_shared(ByteCursorFromCString(aws_string_c_str(SECTUN_PAYLOAD_MESSAGE))); + message1->WithServiceId(m_serviceId.value()); + message1->WithConnectionId(connectionId2); + secureTunnelSource->SendMessage(message1); + fprintf(stdout, "Source Client Sent Message\n"); + + promiseDestinationReceivedMessage.get_future().wait(); + + std::shared_ptr message2 = + std::make_shared(ByteCursorFromCString(aws_string_c_str(SECTUN_PAYLOAD_MESSAGE))); + message2->WithServiceId(m_serviceId.value()); + message2->WithConnectionId(connectionId); + secureTunnelDestination->SendMessage(message2); + fprintf(stdout, "Destination Client Sent Message\n"); + + promiseSourceReceivedMessage.get_future().wait(); + + if (secureTunnelDestination->Stop() == AWS_OP_ERR) + { + fprintf(stderr, "Secure Tunnel Destination Stop call failed: %s\n", ErrorDebugString(LastError())); + aws_byte_buf_clean_up(&m_serviceIdStorage); + exit(-1); + } + + promiseDestinationStopped.get_future().wait(); + secureTunnelDestination = nullptr; + + if (secureTunnelSource->Stop() == AWS_OP_ERR) + { + fprintf(stderr, "Secure Tunnel Source Stop call failed: %s\n", ErrorDebugString(LastError())); + aws_byte_buf_clean_up(&m_serviceIdStorage); + exit(-1); + } + + promiseSourceStopped.get_future().wait(); + secureTunnelSource = nullptr; + + /* Clean Up */ + aws_byte_buf_clean_up(&m_serviceIdStorage); + + fprintf(stdout, "Secure Tunnel Test Completed\n"); + + return 0; +}