diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..dfcb4a1 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,4 @@ +[submodule "source/dependency/coreJSON"] + path = source/dependency/coreJSON + url = https://github.com/FreeRTOS/coreJSON + update = none diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..4951583 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,32 @@ +cmake_minimum_required(VERSION 3.6.3) +project(kvssignaling C) + +find_library(COREJSON_LIBRARY NAMES corejson ${coreJSON} REQUIRED) + +include(signalingFilePaths.cmake) + +if(BUILD_SHARED_LIBS) + add_library(kvssignaling SHARED ${SIGNALING_SOURCES}) +else() + add_library(kvssignaling STATIC ${SIGNALING_SOURCES}) +endif() + +target_include_directories(kvssignaling PUBLIC + ${SIGNALING_INCLUDE_PUBLIC_DIRS} + ${COREJSON_INCLUDE_DIRS}) + +set_target_properties(kvssignaling PROPERTIES + INSTALL_RPATH "${CMAKE_INSTALL_LIBDIR}" + INSTALL_RPATH_USE_LINK_PATH TRUE) +target_link_libraries(kvssignaling PRIVATE ${COREJSON_LIBRARY}) + +# install header files +install( + FILES ${SIGNALING_INCLUDE_PUBLIC_FILES} + DESTINATION include/kvssignaling) + +install( + TARGETS kvssignaling + ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}" + LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}" + RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}") diff --git a/README.md b/README.md index 847260c..d1ba990 100644 --- a/README.md +++ b/README.md @@ -1,17 +1,36 @@ -## My Project - -TODO: Fill this README out! - -Be sure to: - -* Change the title in this README -* Edit your repository description on GitHub - -## Security - -See [CONTRIBUTING](CONTRIBUTING.md#security-issue-notifications) for more information. +## amazon-kinesis-video-streams-signaling + +The goal of the Signaling library is to enable communication with the Amazon +Kinesis Video Signaling Service. Refer to +[AWS KVS API Reference](https://docs.aws.amazon.com/kinesisvideostreams/latest/dg/API_Reference.html) +and [WebRTC Websocket APIs](https://docs.aws.amazon.com/kinesisvideostreams-webrtc-dg/latest/devguide/kvswebrtc-websocket-apis.html) +for more details. + +## What is Signaling? + +The Amazon Kinesis Video Signaling Service facilitates peer-to-peer +communication by providing signaling channels that enable applications to +discover, establish, control, and terminate connections through the exchange of +signaling messages. This library provides APIs to construct requests to send to +the Signaling Service and to parse responses from the Signaling Service. + +- Note that this library has a dependency on [coreJSON](https://github.com/FreeRTOS/coreJSON). + +## Using the library + +1. Use the `Signaling_Construct*` APIs to construct requests to send to the + Signaling Service. + - Ensure to authenticate and sign the constructed requests using the Signature + Version 4 (SigV4) [authentication flow](https://docs.aws.amazon.com/AmazonS3/latest/API/sig-v4-authenticating-requests.html) + before sending them. +2. Use the `Signaling_Parse*` APIs to parse the responses from the Signaling + Service. +3. Use `Signaling_ConstructWssMessage` and `Signaling_ParseWssRecvMessage` APIs + to communicate with the WSS endpoint. + - Ensure to authenticate and sign the constructed messages using the + Signature Version 4 (SigV4) [authentication flow](https://docs.aws.amazon.com/AmazonS3/latest/API/sig-v4-authenticating-requests.html) + before sending them. ## License This project is licensed under the Apache-2.0 License. - diff --git a/signalingFilePaths.cmake b/signalingFilePaths.cmake new file mode 100644 index 0000000..7b5225e --- /dev/null +++ b/signalingFilePaths.cmake @@ -0,0 +1,19 @@ +# This file is to add source files and include directories +# into variables so that it can be reused from different repositories +# in their Cmake based build system by including this file. +# +# Files specific to the repository such as test runner, platform tests +# are not added to the variables. + +# Signaling library source files. +set( SIGNALING_SOURCES + "${CMAKE_CURRENT_LIST_DIR}/source/signaling_api.c" ) + +# Signaling library Public Include directories. +set( SIGNALING_INCLUDE_PUBLIC_DIRS + "${CMAKE_CURRENT_LIST_DIR}/source/include" ) + +# Signaling library public include header files. +set( SIGNALING_INCLUDE_PUBLIC_FILES + "${CMAKE_CURRENT_LIST_DIR}/source/include/signaling_api.h" + "${CMAKE_CURRENT_LIST_DIR}/source/include/signaling_data_types.h" ) diff --git a/source/dependency/coreJSON b/source/dependency/coreJSON new file mode 160000 index 0000000..dc1ab91 --- /dev/null +++ b/source/dependency/coreJSON @@ -0,0 +1 @@ +Subproject commit dc1ab9130a1fb99b801a2a1fa8e9f42239f752be diff --git a/source/include/signaling_api.h b/source/include/signaling_api.h new file mode 100644 index 0000000..a8437a9 --- /dev/null +++ b/source/include/signaling_api.h @@ -0,0 +1,322 @@ +/** + * @file signaling_api.h + * @brief API list for Signaling component. + */ +#ifndef SIGNALING_API_H +#define SIGNALING_API_H + +/* *INDENT-OFF* */ +#ifdef __cplusplus +extern "C" { +#endif +/* *INDENT-ON* */ + +#include "signaling_data_types.h" + +/*-----------------------------------------------------------*/ + +/** + * @brief This function is used to construct request to query signaling channel information. + * + * @param[in] pAwsRegion The AWS region. + * @param[in] pChannelName The channel name set in AWS account. + * @param[out] pRequestBuffer The output structure includes URI, body buffers, and their sizes. + * + * @return Returns one of the following: + * - #SIGNALING_RESULT_OK, if initialization was performed without error. + * - #SIGNALING_RESULT_BAD_PARAM, if any mandatory parameters is NULL. + * - #SIGNALING_RESULT_SNPRINTF_ERROR, if snprintf returns negative value. + * - #SIGNALING_RESULT_OUT_OF_MEMORY, if buffer is not enough to store constructed message. + * + * @note Refer to https://docs.aws.amazon.com/kinesisvideostreams/latest/dg/API_DescribeSignalingChannel.html for details. + */ +SignalingResult_t Signaling_ConstructDescribeSignalingChannelRequest( SignalingAwsRegion_t * pAwsRegion, + SignalingChannelName_t * pChannelName, + SignalingRequest_t * pRequestBuffer ); + +/** + * @brief This function is used to parse response of describe signaling channel. + * + * @param[in] pMessage Raw response from the URL of getting signaling channel endpoints. + * @param[in] messageLength Length of raw message. + * @param[out] pChannelInfo The output structure includes the signaling channel information. + * + * @return Returns one of the following: + * - #SIGNALING_RESULT_OK, if initialization was performed without error. + * - #SIGNALING_RESULT_BAD_PARAM, if any mandatory parameters is NULL. + * - #SIGNALING_RESULT_INVALID_JSON, if raw message is not a valid JSON message. + * - #SIGNALING_RESULT_UNEXPECTED_RESPONSE, if the message isn't the expected one. + * - #SIGNALING_RESULT_INVALID_CHANNEL_NAME, if the channel name is longer than array size. + * - #SIGNALING_RESULT_INVALID_CHANNEL_TYPE, if the channel type is longer than array size. + * - #SIGNALING_RESULT_INVALID_TTL, if the TTL is longer than array size. + * + * @note Refer to https://docs.aws.amazon.com/kinesisvideostreams/latest/dg/API_DescribeSignalingChannel.html for details. + */ +SignalingResult_t Signaling_ParseDescribeSignalingChannelResponse( const char * pMessage, + size_t messageLength, + SignalingChannelInfo_t * pChannelInfo ); + +/** + * @brief This function is used to construct request to query media storage configuration. + * + * @param[in] pAwsRegion The AWS region. + * @param[in] pChannelArn The channel ARN which gets from Signaling_ConstructDescribeSignalingChannelRequest. + * @param[out] pRequestBuffer The output structure includes URI, body buffers, and their sizes. + * + * @return Returns one of the following: + * - #SIGNALING_RESULT_OK, if initialization was performed without error. + * - #SIGNALING_RESULT_BAD_PARAM, if any mandatory parameters is NULL. + * - #SIGNALING_RESULT_SNPRINTF_ERROR, if snprintf returns negative value. + * - #SIGNALING_RESULT_OUT_OF_MEMORY, if buffer is not enough to store constructed message. + * + * @note Refer to https://docs.aws.amazon.com/kinesisvideostreams/latest/dg/API_DescribeMediaStorageConfiguration.html for details. + */ +SignalingResult_t Signaling_ConstructDescribeMediaStorageConfigRequest( SignalingAwsRegion_t * pAwsRegion, + SignalingChannelArn_t * pChannelArn, + SignalingRequest_t * pRequestBuffer ); + +/** + * @brief This function is used to parse response of describe media storage configurations. + * + * @param[in] pMessage Raw response from the URL of getting signaling channel endpoints. + * @param[in] messageLength Length of raw message. + * @param[out] pMediaStorageConfig The output structure includes the media storage configuration properties. + * + * @return Returns one of the following: + * - #SIGNALING_RESULT_OK, if initialization was performed without error. + * - #SIGNALING_RESULT_BAD_PARAM, if any mandatory parameters is NULL. + * - #SIGNALING_RESULT_INVALID_JSON, if raw message is not a valid JSON message. + * - #SIGNALING_RESULT_UNEXPECTED_RESPONSE, if the message isn't the expected one. + * + * @note Refer to https://docs.aws.amazon.com/kinesisvideostreams/latest/dg/API_DescribeMediaStorageConfiguration.html for details. + */ +SignalingResult_t Signaling_ParseDescribeMediaStorageConfigResponse( const char * pMessage, + size_t messageLength, + SignalingMediaStorageConfig_t * pMediaStorageConfig ); + +/** + * @brief This function is used to construct request to create signaling channel. + * + * @param[in] pAwsRegion The AWS region. + * @param[in] pCreateSignalingChannelRequestInfo The parameters that needed to construct request to create signaling channel. + * @param[out] pRequestBuffer The output structure includes URI, body buffers, and their sizes. + * + * @return Returns one of the following: + * - #SIGNALING_RESULT_OK, if initialization was performed without error. + * - #SIGNALING_RESULT_BAD_PARAM, if any mandatory parameters is NULL. + * - #SIGNALING_RESULT_SNPRINTF_ERROR, if snprintf returns negative value. + * - #SIGNALING_RESULT_OUT_OF_MEMORY, if buffer is not enough to store constructed message. + * + * @note Refer to https://docs.aws.amazon.com/kinesisvideostreams/latest/dg/API_CreateSignalingChannel.html for details. + */ +SignalingResult_t Signaling_ConstructCreateSignalingChannelRequest( SignalingAwsRegion_t * pAwsRegion, + CreateSignalingChannelRequestInfo_t * pCreateSignalingChannelRequestInfo, + SignalingRequest_t * pRequestBuffer ); + +/** + * @brief This function is used to parse response of creating signaling channel. + * + * @param[in] pMessage Raw response from the URL of getting signaling channel endpoints. + * @param[in] messageLength Length of raw message. + * @param[out] pChannelArn The output structure includes the ARN of created channel. + * + * @return Returns one of the following: + * - #SIGNALING_RESULT_OK, if initialization was performed without error. + * - #SIGNALING_RESULT_BAD_PARAM, if any mandatory parameters is NULL. + * - #SIGNALING_RESULT_INVALID_JSON, if raw message is not a valid JSON message. + * - #SIGNALING_RESULT_UNEXPECTED_RESPONSE, if the message isn't the expected one. + * - #SIGNALING_RESULT_INVALID_TTL, if the TTL of ICE server config is invalid. + * + * @note Refer to https://docs.aws.amazon.com/kinesisvideostreams/latest/dg/API_CreateSignalingChannel.html for details. + */ +SignalingResult_t Signaling_ParseCreateSignalingChannelResponse( const char * pMessage, + size_t messageLength, + SignalingChannelArn_t * pChannelArn ); + +/** + * @brief This function is used to construct request to query signaling channel endpoints. + * + * @param[in] pAwsRegion The AWS region. + * @param[in] pGetSignalingChannelEndpointRequestInfo The parameters that needed to construct request to query signaling channel endpoints. + * @param[out] pRequestBuffer The output structure includes URI, body buffers, and their sizes. + * + * @return Returns one of the following: + * - #SIGNALING_RESULT_OK, if initialization was performed without error. + * - #SIGNALING_RESULT_BAD_PARAM, if any mandatory parameters is NULL. + * - #SIGNALING_RESULT_SNPRINTF_ERROR, if snprintf returns negative value. + * - #SIGNALING_RESULT_OUT_OF_MEMORY, if buffer is not enough to store constructed message. + * + * @note Refer to https://docs.aws.amazon.com/kinesisvideostreams/latest/dg/API_GetSignalingChannelEndpoint.html for details. + */ +SignalingResult_t Signaling_ConstructGetSignalingChannelEndpointRequest( SignalingAwsRegion_t * pAwsRegion, + GetSignalingChannelEndpointRequestInfo_t * pGetSignalingChannelEndpointRequestInfo, + SignalingRequest_t * pRequestBuffer ); + +/** + * @brief This function is used to parse response of get signaling channel endpoints. + * + * @param[in] pMessage Raw response from the URL of getting signaling channel endpoints. + * @param[in] messageLength Length of raw message. + * @param[out] pSignalingChannelEndpoints The output structure includes endpoints of the signaling channel for websocket secure, HTTPS and WebRTC. + * + * @return Returns one of the following: + * - #SIGNALING_RESULT_OK, if initialization was performed without error. + * - #SIGNALING_RESULT_BAD_PARAM, if any mandatory parameters is NULL. + * - #SIGNALING_RESULT_INVALID_JSON, if raw message is not a valid JSON message. + * - #SIGNALING_RESULT_UNEXPECTED_RESPONSE, if the message isn't the expected one. + * - #SIGNALING_RESULT_INVALID_PROTOCOL, if protocol type in endpoint is invalid. + * + * @note Refer to https://docs.aws.amazon.com/kinesisvideostreams/latest/dg/API_GetSignalingChannelEndpoint.html for details. + */ +SignalingResult_t Signaling_ParseGetSignalingChannelEndpointResponse( const char * pMessage, + size_t messageLength, + SignalingChannelEndpoints_t * pSignalingChannelEndpoints ); + +/** + * @brief This function is used to construct request to get ICE server configs. + * + * @param[in] pHttpsEndpoint The HTTPS endpoint get from Signaling_ConstructGetSignalingChannelEndpointRequest. + * @param[in] pGetIceServerConfigRequestInfo The parameters that needed to construct request to get ICE server configs. + * @param[out] pRequestBuffer The output structure includes URI, body buffers, and their sizes. + * + * @return Returns one of the following: + * - #SIGNALING_RESULT_OK, if initialization was performed without error. + * - #SIGNALING_RESULT_BAD_PARAM, if any mandatory parameters is NULL. + * - #SIGNALING_RESULT_SNPRINTF_ERROR, if snprintf returns negative value. + * - #SIGNALING_RESULT_OUT_OF_MEMORY, if buffer is not enough to store constructed message. + * + * @note Refer to https://docs.aws.amazon.com/kinesisvideostreams/latest/dg/API_signaling_GetIceServerConfig.html for details. + */ +SignalingResult_t Signaling_ConstructGetIceServerConfigRequest( SignalingChannelEndpoint_t * pHttpsEndpoint, + GetIceServerConfigRequestInfo_t * pGetIceServerConfigRequestInfo, + SignalingRequest_t * pRequestBuffer ); + +/** + * @brief This function is used to parse response of get signaling channel endpoints. + * + * @param[in] pMessage Raw response from the URL of getting signaling channel endpoints. + * @param[in] messageLength Length of raw message. + * @param[out] pIceServers The output structures include a list of ICE servers' information, user must provide memory to store this information. + * @param[in, out] pIceServers The maximum number of ICE servers the memory can store, provide the exact ICE server number while return. + * + * @return Returns one of the following: + * - #SIGNALING_RESULT_OK, if initialization was performed without error. + * - #SIGNALING_RESULT_BAD_PARAM, if any mandatory parameters is NULL. + * - #SIGNALING_RESULT_INVALID_JSON, if raw message is not a valid JSON message. + * - #SIGNALING_RESULT_UNEXPECTED_RESPONSE, if the message isn't the expected one. + * - #SIGNALING_RESULT_INVALID_TTL, if the TTL of ICE server config is invalid. + * + * @note Refer to https://docs.aws.amazon.com/kinesisvideostreams/latest/dg/API_signaling_GetIceServerConfig.html for details. + */ +SignalingResult_t Signaling_ParseGetIceServerConfigResponse( const char * pMessage, + size_t messageLength, + SignalingIceServer_t * pIceServers, + size_t * pNumIceServers ); + +/** + * @brief This function is used to construct request to join storage session. + * + * @param[in] pWebrtcEndpoint The webrtc endpoint get from Signaling_ConstructGetSignalingChannelEndpointRequest. + * @param[in] pJoinStorageSessionRequestInfo The parameters that needed to construct request to join storage session. + * @param[out] pRequestBuffer The output structure includes URI, body buffers, and their sizes. + * + * @return Returns one of the following: + * - #SIGNALING_RESULT_OK, if initialization was performed without error. + * - #SIGNALING_RESULT_BAD_PARAM, if any mandatory parameters is NULL. + * - #SIGNALING_RESULT_SNPRINTF_ERROR, if snprintf returns negative value. + * - #SIGNALING_RESULT_OUT_OF_MEMORY, if buffer is not enough to store constructed message. + * + * @note Refer to https://docs.aws.amazon.com/kinesisvideostreams/latest/dg/API_webrtc_JoinStorageSession.html for details. + */ +SignalingResult_t Signaling_ConstructJoinStorageSessionRequest( SignalingChannelEndpoint_t * pWebrtcEndpoint, + JoinStorageSessionRequestInfo_t * pJoinStorageSessionRequestInfo, + SignalingRequest_t * pRequestBuffer ); + +/** + * @brief This function is used to construct request to delete signaling channel. + * + * @param[in] pAwsRegion The AWS region. + * @param[in] pDeleteSignalingChannelRequestInfo The parameters that needed to construct request to delete signaling channel. + * @param[out] pRequestBuffer The output structure includes URI, body buffers, and their sizes. + * + * @return Returns one of the following: + * - #SIGNALING_RESULT_OK, if initialization was performed without error. + * - #SIGNALING_RESULT_BAD_PARAM, if any mandatory parameters is NULL. + * - #SIGNALING_RESULT_SNPRINTF_ERROR, if snprintf returns negative value. + * - #SIGNALING_RESULT_OUT_OF_MEMORY, if buffer is not enough to store constructed message. + * + * @note Refer to https://docs.aws.amazon.com/kinesisvideostreams-webrtc-dg/latest/devguide/kvswebrtc-websocket-apis-7.html for details. + */ +SignalingResult_t Signaling_ConstructDeleteSignalingChannelRequest( SignalingAwsRegion_t * pAwsRegion, + DeleteSignalingChannelRequestInfo_t * pDeleteSignalingChannelRequestInfo, + SignalingRequest_t * pRequestBuffer ); + +/** + * @brief This function is used to construct request to connect with websocket secure endpoint. + * + * @param[in] pWssEndpoint The Websocket endpoint get from Signaling_ConstructGetSignalingChannelEndpointRequest. + * @param[in] pConnectWssEndpointRequestInfo The parameters that needed to construct request. + * @param[out] pRequestBuffer The output structure includes URI, body buffers, and their sizes. + * + * @return Returns one of the following: + * - #SIGNALING_RESULT_OK, if initialization was performed without error. + * - #SIGNALING_RESULT_BAD_PARAM, if any mandatory parameters is NULL. + * - #SIGNALING_RESULT_SNPRINTF_ERROR, if snprintf returns negative value. + * - #SIGNALING_RESULT_OUT_OF_MEMORY, if buffer is not enough to store constructed message. + * + * @note Refer to https://docs.aws.amazon.com/kinesisvideostreams-webrtc-dg/latest/devguide/kvswebrtc-websocket-apis.html for details. + */ +SignalingResult_t Signaling_ConstructConnectWssEndpointRequest( SignalingChannelEndpoint_t * pWssEndpoint, + ConnectWssEndpointRequestInfo_t * pConnectWssEndpointRequestInfo, + SignalingRequest_t * pRequestBuffer ); + +/** + * @brief This function is used to construct event message to websocket secure endpoint. + * + * @param[in] pWssSendMessage The event structure to construct message. + * @param[out] pBuffer The buffer to store constructed message. + * @param[out] pBufferLength The length of the buffer that stores the constructed message. + * + * @return Returns one of the following: + * - #SIGNALING_RESULT_OK, if initialization was performed without error. + * - #SIGNALING_RESULT_BAD_PARAM, if any mandatory parameters is NULL. + * - #SIGNALING_RESULT_SNPRINTF_ERROR, if snprintf returns negative value. + * - #SIGNALING_RESULT_OUT_OF_MEMORY, if buffer is not enough to store constructed message. + * + * @note Refer to https://docs.aws.amazon.com/kinesisvideostreams-webrtc-dg/latest/devguide/kvswebrtc-websocket-apis.html for details. + */ +SignalingResult_t Signaling_ConstructWssMessage( WssSendMessage_t * pWssSendMessage, + char * pBuffer, + size_t * pBufferLength ); + +/** + * @brief This function is used to parse event message from websocket secure endpoint. + * + * @param[in] pMessage Raw event message from websocket secure endpoint. + * @param[in] messageLength Length of raw event message. + * @param[in, out] pWssRecvMessage The parsed message is encapsulated within a structure that + * employs pointers and size fields to represent the data. + * + * @return Returns one of the following: + * - #SIGNALING_RESULT_OK, if initialization was performed without error. + * - #SIGNALING_RESULT_BAD_PARAM, if any mandatory parameters is NULL. + * - #SIGNALING_RESULT_INVALID_JSON, if raw message is not a valid JSON message. + * - #SIGNALING_RESULT_UNEXPECTED_RESPONSE, if the message isn't the expected one. + * - #SIGNALING_RESULT_INVALID_STATUS_RESPONSE, if statusResponse doesn't contain correct formatted message. + * - #SIGNALING_RESULT_INVALID_TTL, if the TTL of ICE server config is invalid. + * + * @note Refer to https://docs.aws.amazon.com/kinesisvideostreams-webrtc-dg/latest/devguide/kvswebrtc-websocket-apis-7.html for details. + */ +SignalingResult_t Signaling_ParseWssRecvMessage( const char * pMessage, + size_t messageLength, + WssRecvMessage_t * pWssRecvMessage ); + +/*-----------------------------------------------------------*/ + +/* *INDENT-OFF* */ +#ifdef __cplusplus +} +#endif +/* *INDENT-ON* */ + +#endif /* SIGNALING_API_H */ diff --git a/source/include/signaling_data_types.h b/source/include/signaling_data_types.h new file mode 100644 index 0000000..3663834 --- /dev/null +++ b/source/include/signaling_data_types.h @@ -0,0 +1,378 @@ +#ifndef SIGNALING_DATA_TYPES_H +#define SIGNALING_DATA_TYPES_H + +/* Standard includes. */ +#include +#include + +/*-----------------------------------------------------------*/ + +/** + * Maximum number of URIs for an ICE server. + */ +#define SIGNALING_ICE_SERVER_MAX_URIS ( 4 ) + +/** + * Maximum length of a channel name. + */ +#define SIGNALING_CHANNEL_NAME_MAX_LEN ( 256 ) + +/*-----------------------------------------------------------*/ + +/** + * @brief Constants for signaling channel TTL. + * Refer https://docs.aws.amazon.com/kinesisvideostreams/latest/dg/API_SingleMasterConfiguration.html#KinesisVideo-Type-SingleMasterConfiguration-MessageTtlSeconds for details. + */ +#define SIGNALING_CHANNEL_TTL_SECONDS_BUFFER_MAX ( 5 ) /* Maximum length of buffer needed to store TTL. */ +#define SIGNALING_CHANNEL_TTL_SECONDS_MIN ( 5 ) +#define SIGNALING_CHANNEL_TTL_SECONDS_MAX ( 120 ) + +/** + * @brief Constants for ICE server TTL. + * Refer https://docs.aws.amazon.com/kinesisvideostreams/latest/dg/API_signaling_IceServer.html for details. + */ +#define SIGNALING_ICE_SERVER_TTL_SECONDS_BUFFER_MAX ( 7 ) /* Maximum length of buffer needed to store TTL. */ +#define SIGNALING_ICE_SERVER_TTL_SECONDS_MIN ( 30 ) +#define SIGNALING_ICE_SERVER_TTL_SECONDS_MAX ( 86400 ) + +/*-----------------------------------------------------------*/ + +/** + * @ingroup signaling_enum_types + * @brief Return code of Signaling component. + */ +typedef enum SignalingResult +{ + SIGNALING_RESULT_OK, + SIGNALING_RESULT_BAD_PARAM, + SIGNALING_RESULT_SNPRINTF_ERROR, + SIGNALING_RESULT_OUT_OF_MEMORY, + SIGNALING_RESULT_INVALID_JSON, + SIGNALING_RESULT_UNEXPECTED_RESPONSE, + SIGNALING_RESULT_INVALID_TTL, + SIGNALING_RESULT_INVALID_ENDPOINT, + SIGNALING_RESULT_INVALID_PROTOCOL, + SIGNALING_RESULT_INVALID_CHANNEL_NAME, + SIGNALING_RESULT_INVALID_CHANNEL_TYPE, + SIGNALING_RESULT_INVALID_ICE_SERVER_COUNT, + SIGNALING_RESULT_INVALID_ICE_SERVER_URIS_COUNT, + SIGNALING_RESULT_INVALID_STATUS_RESPONSE, + SIGNALING_RESULT_REGION_LENGTH_TOO_LARGE, +} SignalingResult_t; + +/** + * @ingroup signaling_enum_types + * @brief Protocol type of endpoints while parsing get signaling channel endpoint response. + * Refer to https://docs.aws.amazon.com/kinesisvideostreams/latest/dg/API_GetSignalingChannelEndpoint.html for more detail. + */ +typedef enum SignalingProtocol +{ + SIGNALING_PROTOCOL_NONE = 0, + SIGNALING_PROTOCOL_WEBSOCKET_SECURE = 1, + SIGNALING_PROTOCOL_HTTPS = 2, + SIGNALING_PROTOCOL_WEBRTC = 4, + SIGNALING_PROTOCOL_MAX = 0xFF, +} SignalingProtocol_t; + +/** + * @ingroup signaling_enum_types + * @brief Type of signaling channel. + * Refer to https://docs.aws.amazon.com/kinesisvideostreams/latest/dg/API_ChannelInfo.html#KinesisVideo-Type-ChannelInfo-ChannelType for more detail. + */ +/* We cannot name it SignalingChannelType_t in order to avoid naming conflict + * with a data type in the existing KVS SDK. */ +typedef enum SignalingTypeChannel +{ + SIGNALING_TYPE_CHANNEL_UNKNOWN, + SIGNALING_TYPE_CHANNEL_SINGLE_MASTER, +} SignalingTypeChannel_t; + +/** + * @ingroup signaling_enum_types + * @brief Role of current signaling request. + * Refer to https://docs.aws.amazon.com/kinesisvideostreams-webrtc-dg/latest/devguide/kvswebrtc-how-it-works.html for more detail. + */ +typedef enum SignalingRole +{ + SIGNALING_ROLE_NONE = 0, + SIGNALING_ROLE_MASTER, + SIGNALING_ROLE_VIEWER, +} SignalingRole_t; + +/** + * @ingroup signaling_enum_types + * @brief Type of signaling message. + * Refer to https://docs.aws.amazon.com/kinesisvideostreams-webrtc-dg/latest/devguide/kvswebrtc-websocket-apis-7.html for more detail. + */ +/* We cannot name it SignalingMessageType_t in order to avoid naming conflict + * with a data type in the existing KVS SDK. */ +typedef enum SignalingTypeMessage +{ + SIGNALING_TYPE_MESSAGE_UNKNOWN = 0, + SIGNALING_TYPE_MESSAGE_SDP_OFFER, + SIGNALING_TYPE_MESSAGE_SDP_ANSWER, + SIGNALING_TYPE_MESSAGE_ICE_CANDIDATE, + SIGNALING_TYPE_MESSAGE_GO_AWAY, + SIGNALING_TYPE_MESSAGE_RECONNECT_ICE_SERVER, + SIGNALING_TYPE_MESSAGE_STATUS_RESPONSE, +} SignalingTypeMessage_t; + +/*-----------------------------------------------------------*/ + +/** + * @ingroup signaling_enum_types + * @brief Basic format of the signaling request. + */ +typedef struct SignalingRequest +{ + char * pUrl; + size_t urlLength; + char * pBody; + size_t bodyLength; +} SignalingRequest_t; + +/** + * @ingroup signaling_enum_types + * @brief Basic format of the AWS region name. + */ +typedef struct SignalingAwsRegion +{ + const char * pAwsRegion; + size_t awsRegionLength; +} SignalingAwsRegion_t; + +/** + * @ingroup signaling_enum_types + * @brief Basic format of the channel name. + */ +typedef struct SignalingChannelName +{ + const char * pChannelName; + size_t channelNameLength; +} SignalingChannelName_t; + +/** + * @ingroup signaling_enum_types + * @brief Basic format of the signaling channel ARN. + */ +typedef struct SignalingChannelArn +{ + const char * pChannelArn; + size_t channelArnLength; +} SignalingChannelArn_t; + +/** + * @ingroup signaling_enum_types + * @brief A structure that encapsulates a signaling channel's metadata and properties. + * Refer to https://docs.aws.amazon.com/kinesisvideostreams/latest/dg/API_ChannelInfo.html for more detail. + */ +typedef struct SignalingChannelInfo +{ + SignalingChannelArn_t channelArn; + SignalingChannelName_t channelName; + const char * pChannelStatus; + size_t channelStatusLength; + SignalingTypeChannel_t channelType; + const char * pVersion; + size_t versionLength; + const char * pCreationTime; + size_t creationTimeLength; + uint32_t messageTtlSeconds; +} SignalingChannelInfo_t; + +/** + * @ingroup signaling_enum_types + * @brief The structure of DescribeMediaStorageConfiguration response. + * Refer to https://docs.aws.amazon.com/kinesisvideostreams/latest/dg/API_DescribeMediaStorageConfiguration.html for more detail. + */ +typedef struct SignalingMediaStorageConfig +{ + const char * pStatus; + size_t statusLength; + const char * pStreamArn; + size_t streamArnLength; +} SignalingMediaStorageConfig_t; + +/** + * @ingroup signaling_enum_types + * @brief Tags to create signaling channel. + * Refer to https://docs.aws.amazon.com/kinesisvideostreams/latest/dg/API_Tag.html for more detail. + */ +typedef struct SignalingTag +{ + char * pName; + size_t nameLength; + char * pValue; + size_t valueLength; +} SignalingTag_t; + +/** + * @ingroup signaling_enum_types + * @brief Basic endpoint structure. + */ +typedef struct SignalingChannelEndpoint +{ + const char * pEndpoint; + size_t endpointLength; +} SignalingChannelEndpoint_t; + +/** + * @ingroup signaling_enum_types + * @brief The structure of GetSignalingChannelEndpoint response. + * Refer to https://docs.aws.amazon.com/kinesisvideostreams/latest/dg/API_GetSignalingChannelEndpoint.html for more detail. + */ +typedef struct SignalingChannelEndpoints +{ + SignalingChannelEndpoint_t wssEndpoint; + SignalingChannelEndpoint_t httpsEndpoint; + SignalingChannelEndpoint_t webrtcEndpoint; +} SignalingChannelEndpoints_t; + +/** + * @ingroup signaling_enum_types + * @brief A structure for the ICE server connection data. + * Refer to https://docs.aws.amazon.com/kinesisvideostreams/latest/dg/API_signaling_IceServer.html for more detail. + */ +typedef struct SignalingIceServer +{ + const char * pPassword; + size_t passwordLength; + uint32_t messageTtlSeconds; + const char * pUris[ SIGNALING_ICE_SERVER_MAX_URIS ]; + size_t urisLength[ SIGNALING_ICE_SERVER_MAX_URIS ]; + uint32_t urisNum; + const char * pUserName; + size_t userNameLength; +} SignalingIceServer_t; + +/** + * @ingroup signaling_enum_types + * @brief The structure of CreateSignalingChannel request. + * Refer to https://docs.aws.amazon.com/kinesisvideostreams/latest/dg/API_CreateSignalingChannel.html for more detail. + */ +typedef struct CreateSignalingChannelRequestInfo +{ + SignalingChannelName_t channelName; + SignalingTypeChannel_t channelType; + uint32_t messageTtlSeconds; + SignalingTag_t * pTags; + size_t numTags; +} CreateSignalingChannelRequestInfo_t; + +/** + * @ingroup signaling_enum_types + * @brief The structure of GetSignalingChannelEndpoint request. + * Refer to https://docs.aws.amazon.com/kinesisvideostreams/latest/dg/API_GetSignalingChannelEndpoint.html for more detail. + */ +typedef struct GetSignalingChannelEndpointRequestInfo +{ + SignalingChannelArn_t channelArn; + uint8_t protocols; /* Bitwise OR of SignalingProtocol_t values. */ + SignalingRole_t role; +} GetSignalingChannelEndpointRequestInfo_t; + +/** + * @ingroup signaling_enum_types + * @brief The structure of GetIceServerConfig request. + * Refer to https://docs.aws.amazon.com/kinesisvideostreams/latest/dg/API_signaling_GetIceServerConfig.html for more detail. + */ +typedef struct GetIceServerConfigRequestInfo +{ + SignalingChannelArn_t channelArn; + char * pClientId; + size_t clientIdLength; +} GetIceServerConfigRequestInfo_t; + +/** + * @ingroup signaling_enum_types + * @brief The structure of JoinStorageSession request. + * Refer to https://docs.aws.amazon.com/kinesisvideostreams/latest/dg/API_webrtc_JoinStorageSession.html for more detail. + */ +typedef struct JoinStorageSessionRequestInfo +{ + SignalingChannelArn_t channelArn; + SignalingRole_t role; + char * pClientId; + size_t clientIdLength; +} JoinStorageSessionRequestInfo_t; + +/** + * @ingroup signaling_enum_types + * @brief The structure of DeleteSignalingChannel request. + * Refer to https://docs.aws.amazon.com/kinesisvideostreams/latest/dg/API_DeleteSignalingChannel.html for more detail. + */ +typedef struct DeleteSignalingChannelRequestInfo +{ + SignalingChannelArn_t channelArn; + char * pVersion; + size_t versionLength; +} DeleteSignalingChannelRequestInfo_t; + +/** + * @ingroup signaling_enum_types + * @brief The structure of GetSignalingChannelEndpoint request. + * Refer to https://docs.aws.amazon.com/kinesisvideostreams/latest/dg/API_GetSignalingChannelEndpoint.html for more detail. + */ +typedef struct ConnectWssEndpointRequestInfo +{ + SignalingChannelArn_t channelArn; + SignalingRole_t role; + const char * pClientId; + size_t clientIdLength; +} ConnectWssEndpointRequestInfo_t; + +/** + * @ingroup signaling_enum_types + * @brief The structure to send message to websocket secure endpoint. + * Refer to https://docs.aws.amazon.com/kinesisvideostreams-webrtc-dg/latest/devguide/kvswebrtc-websocket-apis3.html, + * https://docs.aws.amazon.com/kinesisvideostreams-webrtc-dg/latest/devguide/kvswebrtc-websocket-apis4.html, + * https://docs.aws.amazon.com/kinesisvideostreams-webrtc-dg/latest/devguide/kvswebrtc-websocket-apis5.html and + * https://docs.aws.amazon.com/kinesisvideostreams-webrtc-dg/latest/devguide/kvswebrtc-websocket-apis6.html for more detail. + */ +typedef struct WssSendMessage +{ + SignalingTypeMessage_t messageType; + const char * pRecipientClientId; + size_t recipientClientIdLength; + const char * pBase64EncodedMessage; + size_t base64EncodedMessageLength; + const char * pCorrelationId; + size_t correlationIdLength; +} WssSendMessage_t; + +/** + * @ingroup signaling_enum_types + * @brief The status response structure in receive event message from websocket secure endpoint. + * Refer to https://docs.aws.amazon.com/kinesisvideostreams-webrtc-dg/latest/devguide/kvswebrtc-websocket-apis-7.html for more detail. + */ +typedef struct WssStatusResponse +{ + const char * pCorrelationId; + size_t correlationIdLength; + const char * pErrorType; + size_t errorTypeLength; + const char * pStatusCode; + size_t statusCodeLength; + const char * pDescription; + size_t descriptionLength; +} WssStatusResponse_t; + +/** + * @ingroup signaling_enum_types + * @brief The event message structure from websocket secure endpoint. Note that when received message is a SDP offer, it might append ICE servers + * configurations in the message. So user can update the server configuration by this information. + * Refer to https://docs.aws.amazon.com/kinesisvideostreams-webrtc-dg/latest/devguide/kvswebrtc-websocket-apis-7.html for more detail. + */ +typedef struct WssRecvMessage +{ + const char * pSenderClientId; + size_t senderClientIdLength; + SignalingTypeMessage_t messageType; + const char * pBase64EncodedPayload; + size_t base64EncodedPayloadLength; + WssStatusResponse_t statusResponse; +} WssRecvMessage_t; + +/*-----------------------------------------------------------*/ + +#endif /* SIGNALING_DATA_TYPES_H */ diff --git a/source/signaling_api.c b/source/signaling_api.c new file mode 100644 index 0000000..4cffb03 --- /dev/null +++ b/source/signaling_api.c @@ -0,0 +1,1628 @@ +/* Standard includes. */ +#include +#include +#include + +/* API includes. */ +#include "signaling_api.h" + +/* CoreJSON includes. */ +#include "core_json.h" + +/** + * Helper macro to check if the AWS region is China region. + */ +#define SIGNALING_IS_CHINA_REGION( pAwsRegion ) \ + ( ( ( pAwsRegion )->awsRegionLength >= 3 ) && \ + ( strncmp( "cn-", ( pAwsRegion )->pAwsRegion, 3 ) == 0 ) ) + +/* Longest protocol string is is "WSS","HTTPS","WEBRTC". */ +#define SIGNALING_GET_ENDPOINT_PROTOCOL_MAX_STRING_LENGTH ( 23 ) /* Includes NULL terminator. */ + +/*-----------------------------------------------------------*/ + +static SignalingResult_t InterpretSnprintfReturnValue( int snprintfRetVal, + size_t bufferLength ); + +static char * GetStringFromMessageType( SignalingTypeMessage_t messageType ); + +static void ParseUris( const char * pUris, + size_t urisLength, + SignalingIceServer_t * pIceServer ); + +static SignalingResult_t ParseIceServerList( const char * pIceServerListBuffer, + size_t iceServerListBufferLength, + SignalingIceServer_t * pIceServers, + size_t * pNumIceServers ); + +/*-----------------------------------------------------------*/ + +static SignalingResult_t InterpretSnprintfReturnValue( int snprintfRetVal, + size_t bufferLength ) +{ + SignalingResult_t result; + + if( snprintfRetVal < 0 ) + { + result = SIGNALING_RESULT_SNPRINTF_ERROR; + } + else if( snprintfRetVal >= bufferLength ) + { + result = SIGNALING_RESULT_OUT_OF_MEMORY; + } + else + { + result = SIGNALING_RESULT_OK; + } + + return result; +} + +/*-----------------------------------------------------------*/ + +static char * GetStringFromMessageType( SignalingTypeMessage_t messageType ) +{ + char * ret = NULL; + + switch( messageType ) + { + case SIGNALING_TYPE_MESSAGE_SDP_OFFER: + ret = "SDP_OFFER"; + break; + + case SIGNALING_TYPE_MESSAGE_SDP_ANSWER: + ret = "SDP_ANSWER"; + break; + + case SIGNALING_TYPE_MESSAGE_ICE_CANDIDATE: + ret = "ICE_CANDIDATE"; + break; + + default: + ret = "UNKNOWN"; + break; + } + + return ret; +} + +/*-----------------------------------------------------------*/ + +static void ParseUris( const char * pUris, + size_t urisLength, + SignalingIceServer_t * pIceServer ) +{ + JSONStatus_t jsonResult = JSONSuccess; + size_t start = 0, next = 0; + JSONPair_t pair = { 0 }; + + jsonResult = JSON_Iterate( pUris, urisLength, &( start ), &( next ), &( pair ) ); + + while( jsonResult == JSONSuccess ) + { + pIceServer->pUris[ pIceServer->urisNum ] = pair.value; + pIceServer->urisLength[ pIceServer->urisNum ] = pair.valueLength; + pIceServer->urisNum++; + + jsonResult = JSON_Iterate( pUris, urisLength, &( start ), &( next ), &( pair ) ); + } +} + +/*-----------------------------------------------------------*/ + +static SignalingResult_t ParseIceServerList( const char * pIceServerListBuffer, + size_t iceServerListBufferLength, + SignalingIceServer_t * pIceServers, + size_t * pNumIceServers ) +{ + SignalingResult_t result = SIGNALING_RESULT_OK; + JSONStatus_t jsonResult = JSONSuccess; + size_t start = 0, next = 0; + JSONPair_t pair = { 0 }; + const char * pIceServerBuffer; + size_t iceServerBufferLength; + size_t iceServerStart = 0, iceServerNext = 0; + char ttlSecondsBuffer[ SIGNALING_ICE_SERVER_TTL_SECONDS_BUFFER_MAX ] = { 0 }; + size_t iceServerCount = 0; + + if( ( pIceServerListBuffer == NULL ) || + ( pIceServers == NULL ) ) + { + result = SIGNALING_RESULT_BAD_PARAM; + } + + while( ( result == SIGNALING_RESULT_OK ) && + ( iceServerCount < *pNumIceServers ) ) + { + jsonResult = JSON_Iterate( pIceServerListBuffer, iceServerListBufferLength, &( start ), &( next ), &( pair ) ); + + if( jsonResult == JSONSuccess ) + { + pIceServerBuffer = pair.value; + iceServerBufferLength = pair.valueLength; + iceServerStart = 0; + iceServerNext = 0; + + jsonResult = JSON_Iterate( pIceServerBuffer, iceServerBufferLength, &( iceServerStart ), &( iceServerNext ), &( pair ) ); + + while( jsonResult == JSONSuccess ) + { + if( strncmp( pair.key, "Password", pair.keyLength ) == 0 ) + { + pIceServers[ iceServerCount ].pPassword = pair.value; + pIceServers[ iceServerCount ].passwordLength = pair.valueLength; + } + else if( strncmp( pair.key, "Ttl", pair.keyLength ) == 0 ) + { + if( pair.valueLength >= SIGNALING_ICE_SERVER_TTL_SECONDS_BUFFER_MAX ) + { + /* Unexpected TTL value from cloud. */ + result = SIGNALING_RESULT_INVALID_TTL; + break; + } + + strncpy( ttlSecondsBuffer, pair.value, pair.valueLength ); + pIceServers[ iceServerCount ].messageTtlSeconds = ( uint32_t ) strtoul( ttlSecondsBuffer, NULL, 10 ); + + if( ( pIceServers[ iceServerCount ].messageTtlSeconds < SIGNALING_ICE_SERVER_TTL_SECONDS_MIN ) || + ( pIceServers[ iceServerCount ].messageTtlSeconds > SIGNALING_ICE_SERVER_TTL_SECONDS_MAX ) ) + { + /* Unexpected TTL value from cloud. */ + result = SIGNALING_RESULT_INVALID_TTL; + break; + } + } + else if( strncmp( pair.key, "Uris", pair.keyLength ) == 0 ) + { + ParseUris( pair.value, pair.valueLength, &( pIceServers[ iceServerCount ] ) ); + } + else if( strncmp( pair.key, "Username", pair.keyLength ) == 0 ) + { + pIceServers[ iceServerCount ].pUserName = pair.value; + pIceServers[ iceServerCount ].userNameLength = pair.valueLength; + } + else + { + /* Skip unknown messages. */ + } + + jsonResult = JSON_Iterate( pIceServerBuffer, iceServerBufferLength, &( iceServerStart ), &( iceServerNext ), &( pair ) ); + } + + iceServerCount++; + } + else + { + /* All parsed. */ + break; + } + } + + if( result == SIGNALING_RESULT_OK ) + { + *pNumIceServers = iceServerCount; + } + + return result; +} + +/*-----------------------------------------------------------*/ + +SignalingResult_t Signaling_ConstructDescribeSignalingChannelRequest( SignalingAwsRegion_t * pAwsRegion, + SignalingChannelName_t * pChannelName, + SignalingRequest_t * pRequestBuffer ) +{ + SignalingResult_t result = SIGNALING_RESULT_OK; + int snprintfRetVal = 0; + + if( ( pAwsRegion == NULL ) || + ( pAwsRegion->pAwsRegion == NULL ) || + ( pChannelName == NULL ) || + ( pRequestBuffer == NULL ) || + ( pRequestBuffer->pUrl == NULL ) || + ( pRequestBuffer->pBody == NULL ) ) + { + result = SIGNALING_RESULT_BAD_PARAM; + } + + if( result == SIGNALING_RESULT_OK ) + { + if( SIGNALING_IS_CHINA_REGION( pAwsRegion ) ) + { + snprintfRetVal = snprintf( pRequestBuffer->pUrl, + pRequestBuffer->urlLength, + "https://kinesisvideo.%.*s.amazonaws.com.cn/describeSignalingChannel", + ( int ) pAwsRegion->awsRegionLength, + pAwsRegion->pAwsRegion ); + } + else + { + snprintfRetVal = snprintf( pRequestBuffer->pUrl, + pRequestBuffer->urlLength, + "https://kinesisvideo.%.*s.amazonaws.com/describeSignalingChannel", + ( int ) pAwsRegion->awsRegionLength, + pAwsRegion->pAwsRegion ); + } + + result = InterpretSnprintfReturnValue( snprintfRetVal, pRequestBuffer->urlLength ); + + if( result == SIGNALING_RESULT_OK ) + { + pRequestBuffer->urlLength = snprintfRetVal; + } + } + + if( result == SIGNALING_RESULT_OK ) + { + snprintfRetVal = snprintf( pRequestBuffer->pBody, + pRequestBuffer->bodyLength, + "{" + "\"ChannelName\":\"%.*s\"" + "}", + ( int ) pChannelName->channelNameLength, + pChannelName->pChannelName ); + + result = InterpretSnprintfReturnValue( snprintfRetVal, pRequestBuffer->bodyLength ); + + if( result == SIGNALING_RESULT_OK ) + { + pRequestBuffer->bodyLength = snprintfRetVal; + } + } + + return result; +} + +/*-----------------------------------------------------------*/ + +SignalingResult_t Signaling_ParseDescribeSignalingChannelResponse( const char * pMessage, + size_t messageLength, + SignalingChannelInfo_t * pChannelInfo ) +{ + SignalingResult_t result = SIGNALING_RESULT_OK; + JSONStatus_t jsonResult; + size_t start = 0, next = 0; + JSONPair_t pair = { 0 }; + const char * pChannelInfoBuffer = NULL; + size_t channelInfoBufferLength; + size_t channelInfoStart = 0, channelInfoNext = 0; + char ttlSecondsBuffer[ SIGNALING_CHANNEL_TTL_SECONDS_BUFFER_MAX ] = { 0 }; + + if( ( pMessage == NULL ) || + ( pChannelInfo == NULL ) ) + { + result = SIGNALING_RESULT_BAD_PARAM; + } + + if( result == SIGNALING_RESULT_OK ) + { + jsonResult = JSON_Validate( pMessage, messageLength ); + + if( jsonResult != JSONSuccess ) + { + result = SIGNALING_RESULT_INVALID_JSON; + } + } + + if( result == SIGNALING_RESULT_OK ) + { + memset( pChannelInfo, 0, sizeof( SignalingChannelInfo_t ) ); + + jsonResult = JSON_Iterate( pMessage, messageLength, &( start ), &( next ), &( pair ) ); + + if( jsonResult == JSONSuccess ) + { + if( ( pair.jsonType != JSONObject ) || + ( pair.keyLength != strlen( "ChannelInfo" ) ) || + ( strncmp( pair.key, "ChannelInfo", pair.keyLength ) != 0 ) ) + { + result = SIGNALING_RESULT_UNEXPECTED_RESPONSE; + } + else + { + pChannelInfoBuffer = pair.value; + channelInfoBufferLength = pair.valueLength; + } + } + else + { + result = SIGNALING_RESULT_INVALID_JSON; + } + } + + if( result == SIGNALING_RESULT_OK ) + { + jsonResult = JSON_Iterate( pChannelInfoBuffer, channelInfoBufferLength, &( channelInfoStart ), &( channelInfoNext ), &( pair ) ); + + while( jsonResult == JSONSuccess ) + { + if( strncmp( pair.key, "ChannelARN", pair.keyLength ) == 0 ) + { + pChannelInfo->channelArn.pChannelArn = pair.value; + pChannelInfo->channelArn.channelArnLength = pair.valueLength; + } + else if( strncmp( pair.key, "ChannelName", pair.keyLength ) == 0 ) + { + if( pair.valueLength < SIGNALING_CHANNEL_NAME_MAX_LEN ) + { + pChannelInfo->channelName.pChannelName = pair.value; + pChannelInfo->channelName.channelNameLength = pair.valueLength; + } + else + { + result = SIGNALING_RESULT_INVALID_CHANNEL_NAME; + } + } + else if( strncmp( pair.key, "ChannelStatus", pair.keyLength ) == 0 ) + { + pChannelInfo->pChannelStatus = pair.value; + pChannelInfo->channelStatusLength = pair.valueLength; + } + else if( strncmp( pair.key, "ChannelType", pair.keyLength ) == 0 ) + { + if( strncmp( pair.value, "SINGLE_MASTER", pair.valueLength ) == 0 ) + { + pChannelInfo->channelType = SIGNALING_TYPE_CHANNEL_SINGLE_MASTER; + } + else + { + result = SIGNALING_RESULT_INVALID_CHANNEL_TYPE; + } + } + else if( strncmp( pair.key, "CreationTime", pair.keyLength ) == 0 ) + { + /* We do not need CreationTime as of now. */ + } + else if( strncmp( pair.key, "SingleMasterConfiguration", pair.keyLength ) == 0 ) + { + const char * pConfigBuffer = pair.value; + size_t configBufferLength = pair.valueLength; + size_t configStart = 0, configNext = 0; + + jsonResult = JSON_Iterate( pConfigBuffer, configBufferLength, &( configStart ), &( configNext ), &( pair ) ); + + if( jsonResult == JSONSuccess ) + { + if( strncmp( pair.key, "MessageTtlSeconds", pair.keyLength ) == 0 ) + { + if( pair.valueLength >= SIGNALING_CHANNEL_TTL_SECONDS_BUFFER_MAX ) + { + /* Unexpected TTL value from cloud. */ + result = SIGNALING_RESULT_INVALID_TTL; + break; + } + + strncpy( ttlSecondsBuffer, pair.value, pair.valueLength ); + pChannelInfo->messageTtlSeconds = ( uint32_t ) strtoul( ttlSecondsBuffer, NULL, 10 ); + + if( ( pChannelInfo->messageTtlSeconds < SIGNALING_CHANNEL_TTL_SECONDS_MIN ) || + ( pChannelInfo->messageTtlSeconds > SIGNALING_CHANNEL_TTL_SECONDS_MAX ) ) + { + /* Unexpected TTL value from cloud. */ + result = SIGNALING_RESULT_INVALID_TTL; + } + } + else + { + /* Unknown attribute. */ + result = SIGNALING_RESULT_INVALID_JSON; + } + } + else + { + /* Invalid single master configuration. */ + result = SIGNALING_RESULT_INVALID_JSON; + } + } + else if( strncmp( pair.key, "Version", pair.keyLength ) == 0 ) + { + pChannelInfo->pVersion = pair.value; + pChannelInfo->versionLength = pair.valueLength; + } + else { + /* Skip unknown attributes. */ + } + + if( result != SIGNALING_RESULT_OK ) + { + break; + } + + jsonResult = JSON_Iterate( pChannelInfoBuffer, channelInfoBufferLength, &( channelInfoStart ), &( channelInfoNext ), &( pair ) ); + } + } + + return result; +} + +/*-----------------------------------------------------------*/ + +SignalingResult_t Signaling_ConstructDescribeMediaStorageConfigRequest( SignalingAwsRegion_t * pAwsRegion, + SignalingChannelArn_t * pChannelArn, + SignalingRequest_t * pRequestBuffer ) +{ + SignalingResult_t result = SIGNALING_RESULT_OK; + int snprintfRetVal = 0; + + if( ( pAwsRegion == NULL ) || + ( pAwsRegion->pAwsRegion == NULL ) || + ( pChannelArn == NULL ) || + ( pRequestBuffer == NULL ) || + ( pRequestBuffer->pUrl == NULL ) || + ( pRequestBuffer->pBody == NULL ) || + ( pChannelArn->pChannelArn == NULL ) ) + { + result = SIGNALING_RESULT_BAD_PARAM; + } + + if( result == SIGNALING_RESULT_OK ) + { + if( SIGNALING_IS_CHINA_REGION( pAwsRegion ) ) + { + snprintfRetVal = snprintf( pRequestBuffer->pUrl, + pRequestBuffer->urlLength, + "https://kinesisvideo.%.*s.amazonaws.com.cn/describeMediaStorageConfiguration", + ( int ) pAwsRegion->awsRegionLength, + pAwsRegion->pAwsRegion ); + } + else + { + snprintfRetVal = snprintf( pRequestBuffer->pUrl, + pRequestBuffer->urlLength, + "https://kinesisvideo.%.*s.amazonaws.com/describeMediaStorageConfiguration", + ( int ) pAwsRegion->awsRegionLength, + pAwsRegion->pAwsRegion ); + } + + result = InterpretSnprintfReturnValue( snprintfRetVal, pRequestBuffer->urlLength ); + + if( result == SIGNALING_RESULT_OK ) + { + pRequestBuffer->urlLength = snprintfRetVal; + } + } + + if( result == SIGNALING_RESULT_OK ) + { + snprintfRetVal = snprintf( pRequestBuffer->pBody, + pRequestBuffer->bodyLength, + "{" + "\"ChannelARN\":\"%.*s\"" + "}", + ( int ) pChannelArn->channelArnLength, + pChannelArn->pChannelArn ); + + result = InterpretSnprintfReturnValue( snprintfRetVal, pRequestBuffer->bodyLength ); + + if( result == SIGNALING_RESULT_OK ) + { + pRequestBuffer->bodyLength = snprintfRetVal; + } + } + + return result; +} + +/*-----------------------------------------------------------*/ + +SignalingResult_t Signaling_ParseDescribeMediaStorageConfigResponse( const char * pMessage, + size_t messageLength, + SignalingMediaStorageConfig_t * pMediaStorageConfig ) +{ + SignalingResult_t result = SIGNALING_RESULT_OK; + JSONStatus_t jsonResult; + size_t start = 0, next = 0; + JSONPair_t pair = { 0 }; + const char * pMediaStorageConfigBuffer = NULL; + size_t mediaStorageConfigBufferLength; + size_t mediaStorageConfigStart = 0, mediaStorageConfigNext = 0; + + if( ( pMessage == NULL ) || + ( pMediaStorageConfig == NULL ) ) + { + result = SIGNALING_RESULT_BAD_PARAM; + } + + if( result == SIGNALING_RESULT_OK ) + { + jsonResult = JSON_Validate( pMessage, messageLength ); + + if( jsonResult != JSONSuccess ) + { + result = SIGNALING_RESULT_INVALID_JSON; + } + } + + if( result == SIGNALING_RESULT_OK ) + { + memset( pMediaStorageConfig, 0, sizeof( SignalingMediaStorageConfig_t ) ); + + jsonResult = JSON_Iterate( pMessage, messageLength, &( start ), &( next ), &( pair ) ); + + if( jsonResult == JSONSuccess ) + { + if( ( pair.jsonType != JSONObject ) || + ( pair.keyLength != strlen( "MediaStorageConfiguration" ) ) || + ( strncmp( pair.key, "MediaStorageConfiguration", pair.keyLength ) != 0 ) ) + { + result = SIGNALING_RESULT_UNEXPECTED_RESPONSE; + } + else + { + pMediaStorageConfigBuffer = pair.value; + mediaStorageConfigBufferLength = pair.valueLength; + } + } + else { + result = SIGNALING_RESULT_INVALID_JSON; + } + } + + if( result == SIGNALING_RESULT_OK ) + { + jsonResult = JSON_Iterate( pMediaStorageConfigBuffer, mediaStorageConfigBufferLength, &( mediaStorageConfigStart ), &( mediaStorageConfigNext ), &( pair ) ); + + while( jsonResult == JSONSuccess ) + { + if( strncmp( pair.key, "Status", pair.keyLength ) == 0 ) + { + pMediaStorageConfig->pStatus = pair.value; + pMediaStorageConfig->statusLength = pair.valueLength; + } + else if( strncmp( pair.key, "StreamARN", pair.keyLength ) == 0 ) + { + pMediaStorageConfig->pStreamArn = pair.value; + pMediaStorageConfig->streamArnLength = pair.valueLength; + } + else + { + /* Skip unknown attributes. */ + } + + jsonResult = JSON_Iterate( pMediaStorageConfigBuffer, mediaStorageConfigBufferLength, &( mediaStorageConfigStart ), &( mediaStorageConfigNext ), &( pair ) ); + } + } + + return result; +} + +/*-----------------------------------------------------------*/ + +SignalingResult_t Signaling_ConstructCreateSignalingChannelRequest( SignalingAwsRegion_t * pAwsRegion, + CreateSignalingChannelRequestInfo_t * pCreateSignalingChannelRequestInfo, + SignalingRequest_t * pRequestBuffer ) +{ + SignalingResult_t result = SIGNALING_RESULT_OK; + int snprintfRetVal = 0; + size_t remainingLength = 0, currentIndex = 0, i; + + if( ( pAwsRegion == NULL ) || + ( pAwsRegion->pAwsRegion == NULL ) || + ( pRequestBuffer == NULL ) || + ( pCreateSignalingChannelRequestInfo == NULL ) || + ( pRequestBuffer->pUrl == NULL ) || + ( pRequestBuffer->pBody == NULL ) || + ( ( pCreateSignalingChannelRequestInfo->numTags > 0 ) && ( pCreateSignalingChannelRequestInfo->pTags == NULL ) ) || + ( pCreateSignalingChannelRequestInfo->channelName.channelNameLength >= SIGNALING_CHANNEL_NAME_MAX_LEN ) ) + { + result = SIGNALING_RESULT_BAD_PARAM; + } + + if( result == SIGNALING_RESULT_OK ) + { + if( SIGNALING_IS_CHINA_REGION( pAwsRegion ) ) + { + snprintfRetVal = snprintf( pRequestBuffer->pUrl, + pRequestBuffer->urlLength, + "https://kinesisvideo.%.*s.amazonaws.com.cn/createSignalingChannel", + ( int ) pAwsRegion->awsRegionLength, + pAwsRegion->pAwsRegion ); + } + else + { + snprintfRetVal = snprintf( pRequestBuffer->pUrl, + pRequestBuffer->urlLength, + "https://kinesisvideo.%.*s.amazonaws.com/createSignalingChannel", + ( int ) pAwsRegion->awsRegionLength, + pAwsRegion->pAwsRegion ); + } + + result = InterpretSnprintfReturnValue( snprintfRetVal, pRequestBuffer->urlLength ); + + if( result == SIGNALING_RESULT_OK ) + { + pRequestBuffer->urlLength = snprintfRetVal; + } + } + + if( result == SIGNALING_RESULT_OK ) + { + remainingLength = pRequestBuffer->bodyLength; + + snprintfRetVal = snprintf( &( pRequestBuffer->pBody[ currentIndex ] ), + remainingLength, + "{" + "\"ChannelName\":\"%.*s\"," + "\"ChannelType\":\"%s\"," + "\"SingleMasterConfiguration\":" + "{" + "\"MessageTtlSeconds\":%u" + "}", + ( int ) pCreateSignalingChannelRequestInfo->channelName.channelNameLength, + pCreateSignalingChannelRequestInfo->channelName.pChannelName, + ( pCreateSignalingChannelRequestInfo->channelType == SIGNALING_TYPE_CHANNEL_SINGLE_MASTER ) ? "SINGLE_MASTER" : "UNKOWN", + pCreateSignalingChannelRequestInfo->messageTtlSeconds ); + + result = InterpretSnprintfReturnValue( snprintfRetVal, remainingLength ); + + if( result == SIGNALING_RESULT_OK ) + { + pRequestBuffer->bodyLength = snprintfRetVal; + remainingLength -= snprintfRetVal; + currentIndex += snprintfRetVal; + } + } + + if( ( result == SIGNALING_RESULT_OK ) && ( pCreateSignalingChannelRequestInfo->numTags > 0 ) ) + { + snprintfRetVal = snprintf( &( pRequestBuffer->pBody[ currentIndex ] ), + remainingLength, + "," + "\"Tags\":" + "[" ); + + result = InterpretSnprintfReturnValue( snprintfRetVal, remainingLength ); + + if( result == SIGNALING_RESULT_OK ) + { + pRequestBuffer->bodyLength += snprintfRetVal; + remainingLength -= snprintfRetVal; + currentIndex += snprintfRetVal; + } + + for( i = 0; ( i < pCreateSignalingChannelRequestInfo->numTags ) && ( result == SIGNALING_RESULT_OK ); i++ ) + { + if( i == 0 ) + { + snprintfRetVal = snprintf( &( pRequestBuffer->pBody[ currentIndex ] ), + remainingLength, + "{" + "\"Key\":\"%.*s\"," + "\"Value\":\"%.*s\"" + "}", + ( int ) pCreateSignalingChannelRequestInfo->pTags[ i ].nameLength, + pCreateSignalingChannelRequestInfo->pTags[ i ].pName, + ( int ) pCreateSignalingChannelRequestInfo->pTags[ i ].valueLength, + pCreateSignalingChannelRequestInfo->pTags[ i ].pValue ); + } + else + { + snprintfRetVal = snprintf( &( pRequestBuffer->pBody[ currentIndex ] ), + remainingLength, + ",{" + "\"Key\":\"%.*s\"," + "\"Value\":\"%.*s\"" + "}", + ( int ) pCreateSignalingChannelRequestInfo->pTags[ i ].nameLength, + pCreateSignalingChannelRequestInfo->pTags[ i ].pName, + ( int ) pCreateSignalingChannelRequestInfo->pTags[ i ].valueLength, + pCreateSignalingChannelRequestInfo->pTags[ i ].pValue ); + } + + result = InterpretSnprintfReturnValue( snprintfRetVal, remainingLength ); + + if( result == SIGNALING_RESULT_OK ) + { + pRequestBuffer->bodyLength += snprintfRetVal; + remainingLength -= snprintfRetVal; + currentIndex += snprintfRetVal; + } + } + + if( result == SIGNALING_RESULT_OK ) + { + snprintfRetVal = snprintf( &( pRequestBuffer->pBody[ currentIndex ] ), + remainingLength, + "]" ); + + result = InterpretSnprintfReturnValue( snprintfRetVal, remainingLength ); + + if( result == SIGNALING_RESULT_OK ) + { + pRequestBuffer->bodyLength += snprintfRetVal; + remainingLength -= snprintfRetVal; + currentIndex += snprintfRetVal; + } + } + } + + + if( result == SIGNALING_RESULT_OK ) + { + snprintfRetVal = snprintf( &( pRequestBuffer->pBody[ currentIndex ] ), + remainingLength, + "}" ); + + result = InterpretSnprintfReturnValue( snprintfRetVal, remainingLength ); + + if( result == SIGNALING_RESULT_OK ) + { + pRequestBuffer->bodyLength += snprintfRetVal; + remainingLength -= snprintfRetVal; + currentIndex += snprintfRetVal; + } + } + + return result; +} + +/*-----------------------------------------------------------*/ + +SignalingResult_t Signaling_ParseCreateSignalingChannelResponse( const char * pMessage, + size_t messageLength, + SignalingChannelArn_t * pChannelArn ) +{ + SignalingResult_t result = SIGNALING_RESULT_OK; + JSONStatus_t jsonResult; + size_t start = 0, next = 0; + JSONPair_t pair = { 0 }; + + if( ( pMessage == NULL ) || + ( pChannelArn == NULL ) ) + { + result = SIGNALING_RESULT_BAD_PARAM; + } + + if( result == SIGNALING_RESULT_OK ) + { + jsonResult = JSON_Validate( pMessage, messageLength ); + + if( jsonResult != JSONSuccess ) + { + result = SIGNALING_RESULT_INVALID_JSON; + } + } + + if( result == SIGNALING_RESULT_OK ) + { + memset( pChannelArn, 0, sizeof( SignalingChannelArn_t ) ); + + jsonResult = JSON_Iterate( pMessage, messageLength, &( start ), &( next ), &( pair ) ); + + if( jsonResult == JSONSuccess ) + { + if( strncmp( pair.key, "ChannelARN", pair.keyLength ) == 0 ) + { + pChannelArn->pChannelArn = pair.value; + pChannelArn->channelArnLength = pair.valueLength; + } + } + else + { + result = SIGNALING_RESULT_UNEXPECTED_RESPONSE; + } + } + + return result; +} + +/*-----------------------------------------------------------*/ + +SignalingResult_t Signaling_ConstructGetSignalingChannelEndpointRequest( SignalingAwsRegion_t * pAwsRegion, + GetSignalingChannelEndpointRequestInfo_t * pGetSignalingChannelEndpointRequestInfo, + SignalingRequest_t * pRequestBuffer ) +{ + SignalingResult_t result = SIGNALING_RESULT_OK; + int snprintfRetVal = 0; + char protocolsString[ SIGNALING_GET_ENDPOINT_PROTOCOL_MAX_STRING_LENGTH ] = { 0 }; + size_t protocolIndex = 0; + uint8_t isFirstProtocol = 1; + + if( ( pAwsRegion == NULL ) || + ( pAwsRegion->pAwsRegion == NULL ) || + ( pRequestBuffer == NULL ) || + ( pGetSignalingChannelEndpointRequestInfo == NULL ) || + ( pRequestBuffer->pUrl == NULL ) || + ( pRequestBuffer->pBody == NULL ) || + ( pGetSignalingChannelEndpointRequestInfo->channelArn.pChannelArn == NULL ) || + ( ( pGetSignalingChannelEndpointRequestInfo->role != SIGNALING_ROLE_MASTER ) && ( pGetSignalingChannelEndpointRequestInfo->role != SIGNALING_ROLE_VIEWER ) ) ) + { + result = SIGNALING_RESULT_BAD_PARAM; + } + + if( result == SIGNALING_RESULT_OK ) + { + if( SIGNALING_IS_CHINA_REGION( pAwsRegion ) ) + { + snprintfRetVal = snprintf( pRequestBuffer->pUrl, + pRequestBuffer->urlLength, + "https://kinesisvideo.%.*s.amazonaws.com.cn/getSignalingChannelEndpoint", + ( int ) pAwsRegion->awsRegionLength, + pAwsRegion->pAwsRegion ); + } + else + { + snprintfRetVal = snprintf( pRequestBuffer->pUrl, + pRequestBuffer->urlLength, + "https://kinesisvideo.%.*s.amazonaws.com/getSignalingChannelEndpoint", + ( int ) pAwsRegion->awsRegionLength, + pAwsRegion->pAwsRegion ); + } + + result = InterpretSnprintfReturnValue( snprintfRetVal, pRequestBuffer->urlLength ); + + if( result == SIGNALING_RESULT_OK ) + { + pRequestBuffer->urlLength = snprintfRetVal; + } + } + + if( result == SIGNALING_RESULT_OK ) + { + if( ( pGetSignalingChannelEndpointRequestInfo->protocols & SIGNALING_PROTOCOL_WEBSOCKET_SECURE ) != 0 ) + { + strncpy( &( protocolsString[ protocolIndex ] ), "\"WSS\"", 5 ); + protocolIndex += 5; + + isFirstProtocol = 0; + } + + if( ( pGetSignalingChannelEndpointRequestInfo->protocols & SIGNALING_PROTOCOL_HTTPS ) != 0 ) + { + if( isFirstProtocol == 0 ) + { + strncpy( &( protocolsString[ protocolIndex ] ), ",", 1 ); + protocolIndex += 1; + } + strncpy( &( protocolsString[ protocolIndex ] ), "\"HTTPS\"", 7 ); + protocolIndex += 7; + + isFirstProtocol = 0; + } + + if( ( pGetSignalingChannelEndpointRequestInfo->protocols & SIGNALING_PROTOCOL_WEBRTC ) != 0 ) + { + if( isFirstProtocol == 0 ) + { + strncpy( &( protocolsString[ protocolIndex ] ), ",", 1 ); + protocolIndex += 1; + } + strncpy( &( protocolsString[ protocolIndex ] ), "\"WEBRTC\"", 8 ); + protocolIndex += 8; + + isFirstProtocol = 0; + } + } + + if( result == SIGNALING_RESULT_OK ) + { + snprintfRetVal = snprintf( pRequestBuffer->pBody, + pRequestBuffer->bodyLength, + "{" + "\"ChannelARN\":\"%.*s\"," + "\"SingleMasterChannelEndpointConfiguration\":" + "{" + "\"Protocols\":[%.*s]," + "\"Role\":\"%s\"" + "}" + "}", + ( int ) pGetSignalingChannelEndpointRequestInfo->channelArn.channelArnLength, + pGetSignalingChannelEndpointRequestInfo->channelArn.pChannelArn, + ( int ) protocolIndex, + protocolsString, + ( pGetSignalingChannelEndpointRequestInfo->role == SIGNALING_ROLE_MASTER ) ? "MASTER" : "VIEWER" ); + + result = InterpretSnprintfReturnValue( snprintfRetVal, pRequestBuffer->bodyLength ); + + if( result == SIGNALING_RESULT_OK ) + { + pRequestBuffer->bodyLength = snprintfRetVal; + } + } + + return result; +} + +/*-----------------------------------------------------------*/ + +SignalingResult_t Signaling_ParseGetSignalingChannelEndpointResponse( const char * pMessage, + size_t messageLength, + SignalingChannelEndpoints_t * pSignalingChannelEndpoints ) +{ + SignalingResult_t result = SIGNALING_RESULT_OK; + JSONStatus_t jsonResult; + size_t start = 0, next = 0; + JSONPair_t pair = { 0 }; + const char * pResourceEndpointListBuffer = NULL; + size_t resourceEndpointListBufferLength; + size_t resourceEndpointStart = 0, resourceEndpointNext = 0; + const char * pEndpointListBuffer = NULL; + size_t endpointListBufferLength; + size_t endpointListStart = 0, endpointListNext = 0; + const char * pEndpoint = NULL; + size_t endpointLength; + const char * pProtocol = NULL; + size_t protocolLength; + + if( ( pMessage == NULL ) || + ( pSignalingChannelEndpoints == NULL ) ) + { + result = SIGNALING_RESULT_BAD_PARAM; + } + + if( result == SIGNALING_RESULT_OK ) + { + jsonResult = JSON_Validate( pMessage, messageLength ); + + if( jsonResult != JSONSuccess ) + { + result = SIGNALING_RESULT_INVALID_JSON; + } + } + + if( result == SIGNALING_RESULT_OK ) + { + memset( pSignalingChannelEndpoints, 0, sizeof( SignalingChannelEndpoints_t ) ); + + jsonResult = JSON_Iterate( pMessage, messageLength, &( start ), &( next ), &( pair ) ); + + if( jsonResult == JSONSuccess ) + { + if( ( pair.jsonType != JSONArray ) || + ( pair.keyLength != strlen( "ResourceEndpointList" ) ) || + ( strncmp( pair.key, "ResourceEndpointList", pair.keyLength ) != 0 ) ) + { + result = SIGNALING_RESULT_UNEXPECTED_RESPONSE; + } + else + { + pResourceEndpointListBuffer = pair.value; + resourceEndpointListBufferLength = pair.valueLength; + } + } + else + { + result = SIGNALING_RESULT_INVALID_JSON; + } + } + + while( result == SIGNALING_RESULT_OK ) + { + jsonResult = JSON_Iterate( pResourceEndpointListBuffer, resourceEndpointListBufferLength, &( resourceEndpointStart ), &( resourceEndpointNext ), &( pair ) ); + + if( jsonResult == JSONSuccess ) + { + pEndpointListBuffer = pair.value; + endpointListBufferLength = pair.valueLength; + endpointListStart = 0; + endpointListNext = 0; + pEndpoint = NULL; + endpointLength = 0; + pProtocol = NULL; + protocolLength = 0; + + jsonResult = JSON_Iterate( pEndpointListBuffer, endpointListBufferLength, &( endpointListStart ), &( endpointListNext ), &( pair ) ); + while( jsonResult == JSONSuccess ) + { + if( strncmp( pair.key, "Protocol", pair.keyLength ) == 0 ) + { + pProtocol = pair.value; + protocolLength = pair.valueLength; + } + else if( strncmp( pair.key, "ResourceEndpoint", pair.keyLength ) == 0 ) + { + pEndpoint = pair.value; + endpointLength = pair.valueLength; + } + + jsonResult = JSON_Iterate( pEndpointListBuffer, endpointListBufferLength, &( endpointListStart ), &( endpointListNext ), &( pair ) ); + } + + if( ( pEndpoint != NULL ) && ( pProtocol != NULL ) ) + { + if( ( strncmp( pProtocol, "WSS", 3 ) == 0 ) || + ( strncmp( pProtocol, "wss", 3 ) == 0 ) ) + { + pSignalingChannelEndpoints->wssEndpoint.pEndpoint = pEndpoint; + pSignalingChannelEndpoints->wssEndpoint.endpointLength = endpointLength; + } + else if( ( strncmp( pProtocol, "HTTPS", 5 ) == 0 ) || + ( strncmp( pProtocol, "https", 5 ) == 0 ) ) + { + pSignalingChannelEndpoints->httpsEndpoint.pEndpoint = pEndpoint; + pSignalingChannelEndpoints->httpsEndpoint.endpointLength = endpointLength; + } + else if( ( strncmp( pProtocol, "WEBRTC", 6 ) == 0 ) || + ( strncmp( pProtocol, "webrtc", 6 ) == 0 ) ) + { + pSignalingChannelEndpoints->webrtcEndpoint.pEndpoint = pEndpoint; + pSignalingChannelEndpoints->webrtcEndpoint.endpointLength = endpointLength; + } + else + { + result = SIGNALING_RESULT_INVALID_PROTOCOL; + } + } + } + else + { + /* All parsed. */ + break; + } + } + + return result; +} + +/*-----------------------------------------------------------*/ + +SignalingResult_t Signaling_ConstructGetIceServerConfigRequest( SignalingChannelEndpoint_t * pHttpsEndpoint, + GetIceServerConfigRequestInfo_t * pGetIceServerConfigRequestInfo, + SignalingRequest_t * pRequestBuffer ) +{ + SignalingResult_t result = SIGNALING_RESULT_OK; + int snprintfRetVal = 0; + + if( ( pHttpsEndpoint == NULL ) || + ( pHttpsEndpoint->pEndpoint == NULL ) || + ( pRequestBuffer == NULL ) || + ( pGetIceServerConfigRequestInfo == NULL ) || + ( pRequestBuffer->pUrl == NULL ) || + ( pRequestBuffer->pBody == NULL ) || + ( pGetIceServerConfigRequestInfo->pClientId == NULL ) ) + { + result = SIGNALING_RESULT_BAD_PARAM; + } + + if( result == SIGNALING_RESULT_OK ) + { + snprintfRetVal = snprintf( pRequestBuffer->pUrl, + pRequestBuffer->urlLength, + "%.*s/v1/get-ice-server-config", + ( int ) pHttpsEndpoint->endpointLength, + pHttpsEndpoint->pEndpoint ); + + result = InterpretSnprintfReturnValue( snprintfRetVal, pRequestBuffer->urlLength ); + + if( result == SIGNALING_RESULT_OK ) + { + pRequestBuffer->urlLength = snprintfRetVal; + } + } + + if( result == SIGNALING_RESULT_OK ) + { + snprintfRetVal = snprintf( pRequestBuffer->pBody, + pRequestBuffer->bodyLength, + "{" + "\"ChannelARN\":\"%.*s\"," + "\"ClientId\":\"%.*s\"," + "\"Service\":\"TURN\"" + "}", + ( int ) pGetIceServerConfigRequestInfo->channelArn.channelArnLength, + pGetIceServerConfigRequestInfo->channelArn.pChannelArn, + ( int ) pGetIceServerConfigRequestInfo->clientIdLength, + pGetIceServerConfigRequestInfo->pClientId ); + + result = InterpretSnprintfReturnValue( snprintfRetVal, pRequestBuffer->bodyLength ); + + if( result == SIGNALING_RESULT_OK ) + { + pRequestBuffer->bodyLength = snprintfRetVal; + } + } + + return result; +} + +/*-----------------------------------------------------------*/ + +SignalingResult_t Signaling_ParseGetIceServerConfigResponse( const char * pMessage, + size_t messageLength, + SignalingIceServer_t * pIceServers, + size_t * pNumIceServers ) +{ + SignalingResult_t result = SIGNALING_RESULT_OK; + JSONStatus_t jsonResult; + size_t start = 0, next = 0; + JSONPair_t pair = { 0 }; + const char * pIceServerListBuffer = NULL; + size_t iceServerListBufferLength; + + if( ( pMessage == NULL ) || + ( pIceServers == NULL ) || + ( pNumIceServers == NULL ) ) + { + result = SIGNALING_RESULT_BAD_PARAM; + } + + if( result == SIGNALING_RESULT_OK ) + { + jsonResult = JSON_Validate( pMessage, messageLength ); + + if( jsonResult != JSONSuccess ) + { + result = SIGNALING_RESULT_INVALID_JSON; + } + } + + if( result == SIGNALING_RESULT_OK ) + { + memset( pIceServers, 0, sizeof( SignalingIceServer_t ) * ( *pNumIceServers ) ); + + jsonResult = JSON_Iterate( pMessage, messageLength, &( start ), &( next ), &( pair ) ); + + if( jsonResult == JSONSuccess ) + { + if( ( pair.jsonType != JSONArray ) || + ( pair.keyLength != strlen( "IceServerList" ) ) || + ( strncmp( pair.key, "IceServerList", pair.keyLength ) != 0 ) ) + { + result = SIGNALING_RESULT_UNEXPECTED_RESPONSE; + } + else + { + pIceServerListBuffer = pair.value; + iceServerListBufferLength = pair.valueLength; + } + } + else + { + result = SIGNALING_RESULT_INVALID_JSON; + } + } + + if( result == SIGNALING_RESULT_OK ) + { + result = ParseIceServerList( pIceServerListBuffer, iceServerListBufferLength, pIceServers, pNumIceServers ); + } + + return result; +} + +/*-----------------------------------------------------------*/ + +SignalingResult_t Signaling_ConstructJoinStorageSessionRequest( SignalingChannelEndpoint_t * pWebrtcEndpoint, + JoinStorageSessionRequestInfo_t * pJoinStorageSessionRequestInfo, + SignalingRequest_t * pRequestBuffer ) +{ + SignalingResult_t result = SIGNALING_RESULT_OK; + int snprintfRetVal = 0; + + if( ( pWebrtcEndpoint == NULL ) || + ( pWebrtcEndpoint->pEndpoint == NULL ) || + ( pRequestBuffer == NULL ) || + ( pJoinStorageSessionRequestInfo == NULL ) || + ( pRequestBuffer->pUrl == NULL ) || + ( pRequestBuffer->pBody == NULL ) || + ( pJoinStorageSessionRequestInfo->channelArn.pChannelArn == NULL ) ) + { + result = SIGNALING_RESULT_BAD_PARAM; + } + + if( result == SIGNALING_RESULT_OK ) + { + snprintfRetVal = snprintf( pRequestBuffer->pUrl, + pRequestBuffer->urlLength, + "%.*s/joinStorageSession", + ( int ) pWebrtcEndpoint->endpointLength, + pWebrtcEndpoint->pEndpoint ); + + result = InterpretSnprintfReturnValue( snprintfRetVal, pRequestBuffer->urlLength ); + + if( result == SIGNALING_RESULT_OK ) + { + pRequestBuffer->urlLength = snprintfRetVal; + } + } + + if( result == SIGNALING_RESULT_OK ) + { + + if( pJoinStorageSessionRequestInfo->role == SIGNALING_ROLE_MASTER ) + { + snprintfRetVal = snprintf( pRequestBuffer->pBody, + pRequestBuffer->bodyLength, + "{" + "\"channelArn\":\"%.*s\"" + "}", + ( int ) pJoinStorageSessionRequestInfo->channelArn.channelArnLength, + pJoinStorageSessionRequestInfo->channelArn.pChannelArn ); + } + else + { + snprintfRetVal = snprintf( pRequestBuffer->pBody, + pRequestBuffer->bodyLength, + "{" + "\"channelArn\":\"%.*s\"," + "\"clientId\":\"%.*s\"" + "}", + ( int ) pJoinStorageSessionRequestInfo->channelArn.channelArnLength, + pJoinStorageSessionRequestInfo->channelArn.pChannelArn, + ( int ) pJoinStorageSessionRequestInfo->clientIdLength, + pJoinStorageSessionRequestInfo->pClientId ); + } + + result = InterpretSnprintfReturnValue( snprintfRetVal, pRequestBuffer->bodyLength ); + + if( result == SIGNALING_RESULT_OK ) + { + pRequestBuffer->bodyLength = snprintfRetVal; + } + } + + return result; +} + +/*-----------------------------------------------------------*/ + +SignalingResult_t Signaling_ConstructDeleteSignalingChannelRequest( SignalingAwsRegion_t * pAwsRegion, + DeleteSignalingChannelRequestInfo_t * pDeleteSignalingChannelRequestInfo, + SignalingRequest_t * pRequestBuffer ) +{ + SignalingResult_t result = SIGNALING_RESULT_OK; + int snprintfRetVal = 0; + + if( ( pAwsRegion == NULL ) || + ( pAwsRegion->pAwsRegion == NULL ) || + ( pRequestBuffer == NULL ) || + ( pDeleteSignalingChannelRequestInfo == NULL ) || + ( pRequestBuffer->pUrl == NULL ) || + ( pRequestBuffer->pBody == NULL ) || + ( pDeleteSignalingChannelRequestInfo->channelArn.pChannelArn == NULL ) || + ( pDeleteSignalingChannelRequestInfo->pVersion == NULL ) ) + { + result = SIGNALING_RESULT_BAD_PARAM; + } + + if( result == SIGNALING_RESULT_OK ) + { + if( SIGNALING_IS_CHINA_REGION( pAwsRegion ) ) + { + snprintfRetVal = snprintf( pRequestBuffer->pUrl, + pRequestBuffer->urlLength, + "https://kinesisvideo.%.*s.amazonaws.com.cn/deleteSignalingChannel", + ( int ) pAwsRegion->awsRegionLength, + pAwsRegion->pAwsRegion ); + } + else + { + snprintfRetVal = snprintf( pRequestBuffer->pUrl, + pRequestBuffer->urlLength, + "https://kinesisvideo.%.*s.amazonaws.com/deleteSignalingChannel", + ( int ) pAwsRegion->awsRegionLength, + pAwsRegion->pAwsRegion ); + } + + result = InterpretSnprintfReturnValue( snprintfRetVal, pRequestBuffer->urlLength ); + + if( result == SIGNALING_RESULT_OK ) + { + pRequestBuffer->urlLength = snprintfRetVal; + } + } + + if( result == SIGNALING_RESULT_OK ) + { + snprintfRetVal = snprintf( pRequestBuffer->pBody, + pRequestBuffer->bodyLength, + "{" + "\"ChannelARN\":\"%.*s\"," + "\"CurrentVersion\":\"%.*s\"" + "}", + ( int ) pDeleteSignalingChannelRequestInfo->channelArn.channelArnLength, + pDeleteSignalingChannelRequestInfo->channelArn.pChannelArn, + ( int ) pDeleteSignalingChannelRequestInfo->versionLength, + pDeleteSignalingChannelRequestInfo->pVersion ); + + result = InterpretSnprintfReturnValue( snprintfRetVal, pRequestBuffer->bodyLength ); + + if( result == SIGNALING_RESULT_OK ) + { + pRequestBuffer->bodyLength = snprintfRetVal; + } + } + + return result; +} + +/*-----------------------------------------------------------*/ + +SignalingResult_t Signaling_ConstructConnectWssEndpointRequest( SignalingChannelEndpoint_t * pWssEndpoint, + ConnectWssEndpointRequestInfo_t * pConnectWssEndpointRequestInfo, + SignalingRequest_t * pRequestBuffer ) +{ + SignalingResult_t result = SIGNALING_RESULT_OK; + int snprintfRetVal = 0; + + if( ( pWssEndpoint == NULL ) || + ( pWssEndpoint->pEndpoint == NULL ) || + ( pRequestBuffer == NULL ) || + ( pConnectWssEndpointRequestInfo == NULL ) || + ( pRequestBuffer->pUrl == NULL ) || + ( pConnectWssEndpointRequestInfo->channelArn.pChannelArn == NULL ) || + ( ( pConnectWssEndpointRequestInfo->role != SIGNALING_ROLE_MASTER ) && + ( pConnectWssEndpointRequestInfo->role != SIGNALING_ROLE_VIEWER ) ) || + ( ( pConnectWssEndpointRequestInfo->role == SIGNALING_ROLE_VIEWER ) && + ( pConnectWssEndpointRequestInfo->pClientId == NULL ) ) ) + { + result = SIGNALING_RESULT_BAD_PARAM; + } + + if( result == SIGNALING_RESULT_OK ) + { + if( pConnectWssEndpointRequestInfo->role == SIGNALING_ROLE_MASTER ) + { + snprintfRetVal = snprintf( pRequestBuffer->pUrl, + pRequestBuffer->urlLength, + "%.*s?X-Amz-ChannelARN=%.*s", + ( int ) pWssEndpoint->endpointLength, + pWssEndpoint->pEndpoint, + ( int ) pConnectWssEndpointRequestInfo->channelArn.channelArnLength, + pConnectWssEndpointRequestInfo->channelArn.pChannelArn ); + } + else + { + snprintfRetVal = snprintf( pRequestBuffer->pUrl, + pRequestBuffer->urlLength, + "%.*s?X-Amz-ChannelARN=%.*s&X-Amz-ClientId=%.*s", + ( int ) pWssEndpoint->endpointLength, + pWssEndpoint->pEndpoint, + ( int ) pConnectWssEndpointRequestInfo->channelArn.channelArnLength, + pConnectWssEndpointRequestInfo->channelArn.pChannelArn, + ( int ) pConnectWssEndpointRequestInfo->clientIdLength, + pConnectWssEndpointRequestInfo->pClientId ); + } + + result = InterpretSnprintfReturnValue( snprintfRetVal, pRequestBuffer->urlLength ); + + if( result == SIGNALING_RESULT_OK ) + { + pRequestBuffer->urlLength = snprintfRetVal; + } + } + + return result; +} + +/*-----------------------------------------------------------*/ + +SignalingResult_t Signaling_ConstructWssMessage( WssSendMessage_t * pWssSendMessage, + char * pBuffer, + size_t * pBufferLength ) +{ + SignalingResult_t result = SIGNALING_RESULT_OK; + int snprintfRetVal = 0; + size_t remainingLength = *pBufferLength; + size_t currentIndex = 0; + + if( ( pWssSendMessage == NULL ) || + ( pBuffer == NULL ) || + ( pWssSendMessage->pBase64EncodedMessage == NULL ) || + ( pWssSendMessage->pRecipientClientId == NULL ) ) + { + result = SIGNALING_RESULT_BAD_PARAM; + } + + if( result == SIGNALING_RESULT_OK ) + { + snprintfRetVal = snprintf( &( pBuffer[ currentIndex ] ), + remainingLength, + "{" + "\"action\":\"%s\"," + "\"RecipientClientId\":\"%.*s\"," + "\"MessagePayload\": \"%.*s\"", + GetStringFromMessageType( pWssSendMessage->messageType ), + ( int ) pWssSendMessage->recipientClientIdLength, + pWssSendMessage->pRecipientClientId, + ( int ) pWssSendMessage->base64EncodedMessageLength, + pWssSendMessage->pBase64EncodedMessage ); + + result = InterpretSnprintfReturnValue( snprintfRetVal, remainingLength ); + + if( result == SIGNALING_RESULT_OK ) + { + remainingLength -= snprintfRetVal; + currentIndex += snprintfRetVal; + } + } + + /* Append correlation ID. */ + if( ( result == SIGNALING_RESULT_OK ) && + ( pWssSendMessage->correlationIdLength > 0 ) ) + { + snprintfRetVal = snprintf( &( pBuffer[ currentIndex ] ), + remainingLength, + "," + "\"CorrelationId\":\"%.*s\"", + ( int ) pWssSendMessage->correlationIdLength, + pWssSendMessage->pCorrelationId ); + + result = InterpretSnprintfReturnValue( snprintfRetVal, remainingLength ); + + if( result == SIGNALING_RESULT_OK ) + { + remainingLength -= snprintfRetVal; + currentIndex += snprintfRetVal; + } + } + + if( result == SIGNALING_RESULT_OK ) + { + snprintfRetVal = snprintf( &( pBuffer[ currentIndex ] ), + remainingLength, + "}" ); + + result = InterpretSnprintfReturnValue( snprintfRetVal, remainingLength ); + + if( result == SIGNALING_RESULT_OK ) + { + remainingLength -= snprintfRetVal; + currentIndex += snprintfRetVal; + } + } + + if( result == SIGNALING_RESULT_OK ) + { + *pBufferLength = currentIndex; + } + + return result; +} + +/*-----------------------------------------------------------*/ + +SignalingResult_t Signaling_ParseWssRecvMessage( const char * pMessage, + size_t messageLength, + WssRecvMessage_t * pWssRecvMessage ) +{ + SignalingResult_t result = SIGNALING_RESULT_OK; + JSONStatus_t jsonResult; + size_t start = 0, next = 0; + JSONPair_t pair = { 0 }; + const char * pStatusResponseBuffer = NULL; + size_t statusResponseBufferLength = 0; + size_t statusResponseStart = 0, statusResponseNext = 0; + + if( ( pMessage == NULL ) || + ( pWssRecvMessage == NULL ) ) + { + result = SIGNALING_RESULT_BAD_PARAM; + } + + /* Exclude null terminator in messageLength. */ + if( pMessage[ messageLength - 1 ] == '\0' ) + { + messageLength--; + } + + if( result == SIGNALING_RESULT_OK ) + { + jsonResult = JSON_Validate( pMessage, messageLength ); + + if( jsonResult != JSONSuccess ) + { + result = SIGNALING_RESULT_INVALID_JSON; + } + } + + if( result == SIGNALING_RESULT_OK ) + { + pWssRecvMessage->pSenderClientId = NULL; + pWssRecvMessage->senderClientIdLength = 0; + pWssRecvMessage->messageType = SIGNALING_TYPE_MESSAGE_UNKNOWN; + pWssRecvMessage->pBase64EncodedPayload = NULL; + pWssRecvMessage->base64EncodedPayloadLength = 0; + memset( &( pWssRecvMessage->statusResponse ), 0, sizeof( WssStatusResponse_t ) ); + + jsonResult = JSON_Iterate( pMessage, messageLength, &( start ), &( next ), &( pair ) ); + + while( jsonResult == JSONSuccess ) + { + if( strncmp( pair.key, "senderClientId", pair.keyLength ) == 0 ) + { + pWssRecvMessage->pSenderClientId = pair.value; + pWssRecvMessage->senderClientIdLength = pair.valueLength; + } + else if( strncmp( pair.key, "messageType", pair.keyLength ) == 0 ) + { + if( strncmp( pair.value, "SDP_OFFER", pair.valueLength ) == 0 ) + { + pWssRecvMessage->messageType = SIGNALING_TYPE_MESSAGE_SDP_OFFER; + } + else if( strncmp( pair.value, "SDP_ANSWER", pair.valueLength ) == 0 ) + { + pWssRecvMessage->messageType = SIGNALING_TYPE_MESSAGE_SDP_ANSWER; + } + else if( strncmp( pair.value, "ICE_CANDIDATE", pair.valueLength ) == 0 ) + { + pWssRecvMessage->messageType = SIGNALING_TYPE_MESSAGE_ICE_CANDIDATE; + } + else if( strncmp( pair.value, "GO_AWAY", pair.valueLength ) == 0 ) + { + pWssRecvMessage->messageType = SIGNALING_TYPE_MESSAGE_GO_AWAY; + } + else if( strncmp( pair.value, "RECONNECT_ICE_SERVER", pair.valueLength ) == 0 ) + { + pWssRecvMessage->messageType = SIGNALING_TYPE_MESSAGE_RECONNECT_ICE_SERVER; + } + else if( strncmp( pair.value, "STATUS_RESPONSE", pair.valueLength ) == 0 ) + { + pWssRecvMessage->messageType = SIGNALING_TYPE_MESSAGE_STATUS_RESPONSE; + } + else + { + pWssRecvMessage->messageType = SIGNALING_TYPE_MESSAGE_UNKNOWN; + } + } + else if( strncmp( pair.key, "messagePayload", pair.keyLength ) == 0 ) + { + pWssRecvMessage->pBase64EncodedPayload = pair.value; + pWssRecvMessage->base64EncodedPayloadLength = pair.valueLength; + } + else if( strncmp( pair.key, "statusResponse", pair.keyLength ) == 0 ) + { + if( pair.jsonType == JSONObject ) + { + pStatusResponseBuffer = pair.value; + statusResponseBufferLength = pair.valueLength; + } + else + { + result = SIGNALING_RESULT_UNEXPECTED_RESPONSE; + break; + } + } + else + { + /* Do nothing, ignore unknown tags. */ + } + + jsonResult = JSON_Iterate( pMessage, messageLength, &( start ), &( next ), &( pair ) ); + } + } + + if( ( result == SIGNALING_RESULT_OK ) && ( pStatusResponseBuffer != NULL ) ) + { + jsonResult = JSON_Iterate( pStatusResponseBuffer, statusResponseBufferLength, &( statusResponseStart ), &( statusResponseNext ), &( pair ) ); + + if( jsonResult != JSONSuccess ) + { + result = SIGNALING_RESULT_INVALID_STATUS_RESPONSE; + } + + while( jsonResult == JSONSuccess ) + { + if( strncmp( pair.key, "correlationId", pair.keyLength ) == 0 ) + { + pWssRecvMessage->statusResponse.pCorrelationId = pair.value; + pWssRecvMessage->statusResponse.correlationIdLength = pair.valueLength; + } + else if( strncmp( pair.key, "errorType", pair.keyLength ) == 0 ) + { + pWssRecvMessage->statusResponse.pErrorType = pair.value; + pWssRecvMessage->statusResponse.errorTypeLength = pair.valueLength; + } + else if( strncmp( pair.key, "statusCode", pair.keyLength ) == 0 ) + { + pWssRecvMessage->statusResponse.pStatusCode = pair.value; + pWssRecvMessage->statusResponse.statusCodeLength = pair.valueLength; + } + else if( strncmp( pair.key, "description", pair.keyLength ) == 0 ) + { + pWssRecvMessage->statusResponse.pDescription = pair.value; + pWssRecvMessage->statusResponse.descriptionLength = pair.valueLength; + } + else + { + /* Do nothing, ignore unknown tags. */ + } + + jsonResult = JSON_Iterate( pStatusResponseBuffer, statusResponseBufferLength, &( statusResponseStart ), &( statusResponseNext ), &( pair ) ); + } + } + + return result; +} + +/*-----------------------------------------------------------*/