diff --git a/.clang-format b/.clang-format index 9f28fe72fe..7e873e3e57 100644 --- a/.clang-format +++ b/.clang-format @@ -47,7 +47,9 @@ IncludeCategories: Priority: 90 - Regex: '' Priority: 50 - - Regex: '^$' + - Regex: '^$' + Priority: 59 + - Regex: '^$' Priority: 60 - Regex: '' Priority: 70 diff --git a/.vscode/cspell.json b/.vscode/cspell.json index 8a29ee28f0..cb732e1ad8 100644 --- a/.vscode/cspell.json +++ b/.vscode/cspell.json @@ -255,9 +255,16 @@ "filename": "CMakePresets.json", "words": [ "ASAN", + "asan", "fsanitize" ] }, + { + "filename": ".clang-format", + "words": [ + "Dont" + ] + }, { "filename": "**/eng/pipelines/templates/**/*.yml", "words": [ diff --git a/CMakePresets.json b/CMakePresets.json index fdca2fd926..9e66e76b8b 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -344,6 +344,16 @@ "RUN_LONG_UNIT_TESTS": true } }, + { + "name": "x86-static-release", + "displayName": "x86 Release, static", + "description": "Windows x86 Release build", + "inherits": [ + "x86-static", + "release-build" + ] + + }, { "name": "x64-debug-tests", "displayName": "x64 Debug With Tests", diff --git a/sdk/core/azure-core-amqp/CHANGELOG.md b/sdk/core/azure-core-amqp/CHANGELOG.md index 1a624d3372..f105958518 100644 --- a/sdk/core/azure-core-amqp/CHANGELOG.md +++ b/sdk/core/azure-core-amqp/CHANGELOG.md @@ -12,6 +12,9 @@ The `Close` method on AMQP Message Sender and Message Receiver now blocks until ### Bugs Fixed +- Fixed uAMQP connection channel so that a channel is released when an END performative is received from the remote node instead of when the END performative is sent to the remote node. +- Enabled more than one uAMQP session to be created on a single connection. + ### Other Changes ## 1.0.0-beta.6 (2024-01-11) diff --git a/sdk/core/azure-core-amqp/CMakeLists.txt b/sdk/core/azure-core-amqp/CMakeLists.txt index 634bdf6a54..96c4ccce62 100644 --- a/sdk/core/azure-core-amqp/CMakeLists.txt +++ b/sdk/core/azure-core-amqp/CMakeLists.txt @@ -61,6 +61,7 @@ find_package(azure_c_shared_utility CONFIG REQUIRED) set (AZURE_CORE_AMQP_HEADER inc/azure/core/amqp.hpp inc/azure/core/amqp/dll_import_export.hpp + inc/azure/core/amqp/internal/amqp_settle_mode.hpp inc/azure/core/amqp/internal/cancellable.hpp inc/azure/core/amqp/internal/claims_based_security.hpp inc/azure/core/amqp/internal/common/async_operation_queue.hpp @@ -79,6 +80,8 @@ set (AZURE_CORE_AMQP_HEADER inc/azure/core/amqp/internal/models/message_source.hpp inc/azure/core/amqp/internal/models/message_target.hpp inc/azure/core/amqp/internal/models/messaging_values.hpp + inc/azure/core/amqp/internal/models/performatives/amqp_detach.hpp + inc/azure/core/amqp/internal/models/performatives/amqp_transfer.hpp inc/azure/core/amqp/internal/network/amqp_header_detect_transport.hpp inc/azure/core/amqp/internal/network/sasl_transport.hpp inc/azure/core/amqp/internal/network/socket_listener.hpp @@ -112,10 +115,12 @@ set(AZURE_CORE_AMQP_SOURCE src/amqp/private/unique_handle.hpp src/amqp/session.cpp src/common/global_state.cpp + src/models/amqp_detach.cpp src/models/amqp_error.cpp src/models/amqp_header.cpp src/models/amqp_message.cpp src/models/amqp_properties.cpp + src/models/amqp_transfer.cpp src/models/amqp_value.cpp src/models/message_source.cpp src/models/message_target.cpp @@ -123,12 +128,15 @@ set(AZURE_CORE_AMQP_SOURCE src/models/private/error_impl.hpp src/models/private/header_impl.hpp src/models/private/message_impl.hpp + src/models/private/performatives/detach_impl.hpp + src/models/private/performatives/transfer_impl.hpp src/models/private/properties_impl.hpp src/models/private/source_impl.hpp src/models/private/target_impl.hpp src/models/private/value_impl.hpp src/network/amqp_header_transport.cpp src/network/private/transport_impl.hpp + src/network/private/transport_impl.hpp src/network/sasl_transport.cpp src/network/socket_listener.cpp src/network/socket_transport.cpp @@ -137,6 +145,7 @@ set(AZURE_CORE_AMQP_SOURCE src/private/package_version.hpp ) + add_library(azure-core-amqp ${AZURE_CORE_AMQP_SOURCE} ${AZURE_CORE_AMQP_HEADER} $) if (VENDOR_UAMQP) diff --git a/sdk/core/azure-core-amqp/cspell.json b/sdk/core/azure-core-amqp/cspell.json index 0fe8512d3d..cd80031e41 100644 --- a/sdk/core/azure-core-amqp/cspell.json +++ b/sdk/core/azure-core-amqp/cspell.json @@ -12,6 +12,7 @@ "words": [ "blang", "dowork", + "performatives", "SASLCLIENTIO", "socketio", "stringized", diff --git a/sdk/core/azure-core-amqp/inc/azure/core/amqp/internal/amqp_settle_mode.hpp b/sdk/core/azure-core-amqp/inc/azure/core/amqp/internal/amqp_settle_mode.hpp new file mode 100644 index 0000000000..ef56b9067e --- /dev/null +++ b/sdk/core/azure-core-amqp/inc/azure/core/amqp/internal/amqp_settle_mode.hpp @@ -0,0 +1,26 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#pragma once + +#include + +namespace Azure { namespace Core { namespace Amqp { namespace _internal { + + enum class SenderSettleMode + { + Unsettled, + Settled, + Mixed, + }; + + std::ostream& operator<<(std::ostream& os, SenderSettleMode const& mode); + + enum class ReceiverSettleMode + { + First, + Second, + }; + std::ostream& operator<<(std::ostream& os, ReceiverSettleMode const& mode); + +}}}} // namespace Azure::Core::Amqp::_internal diff --git a/sdk/core/azure-core-amqp/inc/azure/core/amqp/internal/connection.hpp b/sdk/core/azure-core-amqp/inc/azure/core/amqp/internal/connection.hpp index eadeda04e1..1e3cf99f7e 100644 --- a/sdk/core/azure-core-amqp/inc/azure/core/amqp/internal/connection.hpp +++ b/sdk/core/azure-core-amqp/inc/azure/core/amqp/internal/connection.hpp @@ -35,6 +35,7 @@ namespace Azure { namespace Core { namespace Amqp { namespace Tests { class TestSocketListenerEvents; class LinkSocketListenerEvents; class TestLinks_LinkAttachDetach_Test; + class TestSessions_MultipleSessionBeginEnd_Test; class TestMessages_SenderOpenClose_Test; class TestMessages_TestLocalhostVsTls_Test; class TestMessages_SenderSendAsync_Test; @@ -166,7 +167,7 @@ namespace Azure { namespace Core { namespace Amqp { namespace _internal { */ class ConnectionEvents { protected: - ~ConnectionEvents(){}; + virtual ~ConnectionEvents() = default; public: /** @brief Called when the connection state changes. @@ -181,27 +182,28 @@ namespace Azure { namespace Core { namespace Amqp { namespace _internal { ConnectionState oldState) = 0; - /** @brief Called when a new endpoint connects to the connection. + /** @brief called when an I/O error has occurred on the connection. * * @param connection The connection object. - * @param endpoint The endpoint that connected. - * @return true if the endpoint was accepted, false otherwise. - * - * @remarks Note that this function should only be overriden if the application is listening - * on the connection. */ - virtual bool OnNewEndpoint(Connection const& connection, Endpoint& endpoint) - { - (void)connection; - (void)endpoint; - return false; - } + virtual void OnIOError(Connection const& connection) = 0; + }; - /** @brief called when an I/O error has occurred on the connection. + class ConnectionEndpointEvents { + protected: + virtual ~ConnectionEndpointEvents() = default; + + public: + /** @brief Called when a new endpoint connects to the connection. * * @param connection The connection object. + * @param endpoint The endpoint that connected. + * @return true if the endpoint was accepted, false otherwise. + * + * @remarks Note that this function should only be overriden if + * the application is listening on the connection. */ - virtual void OnIOError(Connection const& connection) = 0; + virtual bool OnNewEndpoint(Connection const& connection, Endpoint& endpoint) = 0; }; /** @brief Options used to create a connection. */ @@ -299,7 +301,8 @@ namespace Azure { namespace Core { namespace Amqp { namespace _internal { Connection( Network::_internal::Transport const& transport, ConnectionOptions const& options, - ConnectionEvents* eventHandler = nullptr); + ConnectionEvents* eventHandler, + ConnectionEndpointEvents* endpointEvents); /** @brief Destroy an AMQP connection */ ~Connection(); @@ -457,6 +460,7 @@ namespace Azure { namespace Core { namespace Amqp { namespace _internal { friend class Azure::Core::Amqp::Tests::TestConnections_ConnectionAttributes_Test; friend class Azure::Core::Amqp::Tests::TestConnections_ConnectionOpenClose_Test; friend class Azure::Core::Amqp::Tests::TestConnections_ConnectionListenClose_Test; + friend class Azure::Core::Amqp::Tests::TestSessions_MultipleSessionBeginEnd_Test; friend class Azure::Core::Amqp::Tests::TestLinks_LinkAttachDetach_Test; friend class Azure::Core::Amqp::Tests::TestMessages_SenderOpenClose_Test; friend class Azure::Core::Amqp::Tests::TestMessages_TestLocalhostVsTls_Test; diff --git a/sdk/core/azure-core-amqp/inc/azure/core/amqp/internal/endpoint.hpp b/sdk/core/azure-core-amqp/inc/azure/core/amqp/internal/endpoint.hpp index 9af14df1f8..3916db090c 100644 --- a/sdk/core/azure-core-amqp/inc/azure/core/amqp/internal/endpoint.hpp +++ b/sdk/core/azure-core-amqp/inc/azure/core/amqp/internal/endpoint.hpp @@ -22,6 +22,7 @@ namespace Azure { namespace Core { namespace Amqp { namespace _detail { class LinkEndpointFactory; }}}} // namespace Azure::Core::Amqp::_detail namespace Azure { namespace Core { namespace Amqp { namespace _internal { + class ConnectionEvents; // An "Endpoint" is an intermediate type used to create sessions in an OnNewSession callback. class Endpoint final { @@ -43,6 +44,7 @@ namespace Azure { namespace Core { namespace Amqp { namespace _internal { return rv; } friend class _detail::EndpointFactory; + friend class _internal::ConnectionEvents; }; // A "Link Endpoint" is an intermediate type used to create new Links in an OnLinkAttached @@ -58,6 +60,9 @@ namespace Azure { namespace Core { namespace Amqp { namespace _internal { } ~LinkEndpoint(){}; + LINK_ENDPOINT_INSTANCE_TAG* Get() const { return m_endpoint; } + std::uint32_t GetHandle() const; + private: LINK_ENDPOINT_INSTANCE_TAG* m_endpoint; diff --git a/sdk/core/azure-core-amqp/inc/azure/core/amqp/internal/link.hpp b/sdk/core/azure-core-amqp/inc/azure/core/amqp/internal/link.hpp index 37ce16d3e4..7f4141e8d7 100644 --- a/sdk/core/azure-core-amqp/inc/azure/core/amqp/internal/link.hpp +++ b/sdk/core/azure-core-amqp/inc/azure/core/amqp/internal/link.hpp @@ -5,7 +5,9 @@ #include "azure/core/amqp/internal/models/message_source.hpp" #include "azure/core/amqp/internal/models/message_target.hpp" +#include "azure/core/amqp/internal/models/performatives/amqp_transfer.hpp" #include "azure/core/amqp/models/amqp_value.hpp" +#include "azure/core/context.hpp" #include #include @@ -40,11 +42,13 @@ namespace Azure { namespace Core { namespace Amqp { namespace _detail { Invalid, Detached, HalfAttachedAttachSent, - HalfAttachAttachReceived, + HalfAttachedAttachReceived, Attached, Error, }; + std::ostream& operator<<(std::ostream& stream, LinkState const& linkState); + enum class LinkTransferResult { Error, @@ -58,9 +62,45 @@ namespace Azure { namespace Core { namespace Amqp { namespace _detail { NotDelivered, Timeout, Cancelled, + Invalid + }; + + class Link; + + class LinkEvents { + public: + virtual Models::AmqpValue OnTransferReceived( +#if defined(TESTING_BUILD) + Link const& link, +#else + std::shared_ptr link, +#endif + Models::_internal::Performatives::AmqpTransfer transfer, + uint32_t payloadSize, + const unsigned char* payloadBytes) + = 0; + virtual void OnLinkStateChanged( +#if defined(TESTING_BUILD) + Link const& link, +#else + std::shared_ptr link, +#endif + LinkState newLinkState, + LinkState previousLinkState) + = 0; + virtual void OnLinkFlowOn( +#if defined(TESTING_BUILD) + Link const& link +#else + std::shared_ptr link +#endif + ) + = 0; + virtual ~LinkEvents() = default; }; #if defined(TESTING_BUILD) + class Link final { public: Link( @@ -68,14 +108,16 @@ namespace Azure { namespace Core { namespace Amqp { namespace _detail { std::string const& name, _internal::SessionRole role, Models::_internal::MessageSource const& source, - Models::_internal::MessageTarget const& target); + Models::_internal::MessageTarget const& target, + LinkEvents* events = nullptr); Link( _internal::Session const& session, _internal::LinkEndpoint& linkEndpoint, std::string const& name, _internal::SessionRole role, Models::_internal::MessageSource const& source, - Models::_internal::MessageTarget const& target); + Models::_internal::MessageTarget const& target, + LinkEvents* events = nullptr); ~Link() noexcept; Link(Link const&) = default; @@ -97,9 +139,14 @@ namespace Azure { namespace Core { namespace Amqp { namespace _detail { uint64_t GetPeerMaxMessageSize() const; - void SetAttachProperties(Models::AmqpValue attachProperties); + void SetAttachProperties(Models::AmqpValue const& attachProperties); void SetMaxLinkCredit(uint32_t maxLinkCredit); + void SetDesiredCapabilities(Models::AmqpValue const& desiredCapabilities); + Models::AmqpValue GetDesiredCapabilities() const; + + void ResetLinkCredit(std::uint32_t linkCredit, bool drain); + std::string GetName() const; Models::_internal::MessageTarget const& GetTarget() const; @@ -109,13 +156,18 @@ namespace Azure { namespace Core { namespace Amqp { namespace _detail { void Attach(); + std::tuple Transfer( + std::vector const& payload, + Azure::Core::Context const& context); + void Detach( bool close, std::string const& errorCondition, std::string const& errorDescription, - Models::AmqpValue& info); + const Models::AmqpValue& info); private: + friend class LinkImpl; Link(std::shared_ptr impl) : m_impl{impl} {} std::shared_ptr m_impl; diff --git a/sdk/core/azure-core-amqp/inc/azure/core/amqp/internal/message_receiver.hpp b/sdk/core/azure-core-amqp/inc/azure/core/amqp/internal/message_receiver.hpp index 88fbf370f1..f47c2b3e91 100644 --- a/sdk/core/azure-core-amqp/inc/azure/core/amqp/internal/message_receiver.hpp +++ b/sdk/core/azure-core-amqp/inc/azure/core/amqp/internal/message_receiver.hpp @@ -3,6 +3,7 @@ #pragma once +#include "azure/core/amqp/internal/amqp_settle_mode.hpp" #include "azure/core/amqp/internal/models/amqp_error.hpp" #include "azure/core/amqp/models/amqp_message.hpp" #include "azure/core/amqp/models/amqp_value.hpp" @@ -34,12 +35,6 @@ namespace Azure { namespace Core { namespace Amqp { namespace _internal { }; std::ostream& operator<<(std::ostream& stream, _internal::MessageReceiverState const& state); - enum class ReceiverSettleMode - { - First, - Second, - }; - class MessageReceiver; struct MessageReceiverOptions final @@ -104,7 +99,10 @@ namespace Azure { namespace Core { namespace Amqp { namespace _internal { MessageReceiver const& receiver, std::shared_ptr const& message) = 0; - virtual void OnMessageReceiverDisconnected(Models::_internal::AmqpError const& error) = 0; + virtual void OnMessageReceiverDisconnected( + MessageReceiver const& receiver, + Models::_internal::AmqpError const& error) + = 0; }; /** @brief MessageReceiver diff --git a/sdk/core/azure-core-amqp/inc/azure/core/amqp/internal/message_sender.hpp b/sdk/core/azure-core-amqp/inc/azure/core/amqp/internal/message_sender.hpp index 704d5df1ce..82989c89a2 100644 --- a/sdk/core/azure-core-amqp/inc/azure/core/amqp/internal/message_sender.hpp +++ b/sdk/core/azure-core-amqp/inc/azure/core/amqp/internal/message_sender.hpp @@ -3,6 +3,7 @@ #pragma once +#include "azure/core/amqp/internal/amqp_settle_mode.hpp" #include "azure/core/amqp/models/amqp_message.hpp" #include "azure/core/amqp/models/amqp_value.hpp" #include "cancellable.hpp" @@ -41,13 +42,6 @@ namespace Azure { namespace Core { namespace Amqp { namespace _internal { }; std::ostream& operator<<(std::ostream& stream, MessageSenderState const& state); - enum class SenderSettleMode - { - Unsettled, - Settled, - Mixed, - }; - class MessageSender; class MessageSenderEvents { protected: @@ -59,7 +53,10 @@ namespace Azure { namespace Core { namespace Amqp { namespace _internal { MessageSenderState newState, MessageSenderState oldState) = 0; - virtual void OnMessageSenderDisconnected(Models::_internal::AmqpError const& error) = 0; + virtual void OnMessageSenderDisconnected( + MessageSender const& sender, + Models::_internal::AmqpError const& error) + = 0; }; struct MessageSenderOptions final @@ -142,6 +139,12 @@ namespace Azure { namespace Core { namespace Amqp { namespace _internal { */ void Close(Context const& context = {}); + /** @brief Gets the name of the underlying link. + * + * @return The name of the underlying link object. + */ + std::string GetLinkName() const; + /** @brief Returns the link negotiated maximum message size * * @return The negotiated maximum message size. diff --git a/sdk/core/azure-core-amqp/inc/azure/core/amqp/internal/models/performatives/amqp_detach.hpp b/sdk/core/azure-core-amqp/inc/azure/core/amqp/internal/models/performatives/amqp_detach.hpp new file mode 100644 index 0000000000..d71f727aaf --- /dev/null +++ b/sdk/core/azure-core-amqp/inc/azure/core/amqp/internal/models/performatives/amqp_detach.hpp @@ -0,0 +1,33 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +// +// This file contains protocol level definitions for the AMQP protocol. + +#pragma once + +#include "azure/core/amqp/internal/models/amqp_error.hpp" + +#include +#include + +namespace Azure { namespace Core { namespace Amqp { namespace Models { namespace _internal { + namespace Performatives { + + /** Detach Performative (0x00000000:0x00000016) + * + * See + * https://docs.oasis-open.org/amqp/core/v1.0/os/amqp-core-transport-v1.0-os.html#type-detach + * for more information. + */ + class AmqpDetach { + public: + AmqpDetach() = default; + + uint32_t Handle{}; + bool Closed{}; + AmqpError Error; + }; + std::ostream& operator<<(std::ostream&, AmqpDetach const&); + +}}}}}} // namespace Azure::Core::Amqp::Models::_internal::Performatives diff --git a/sdk/core/azure-core-amqp/inc/azure/core/amqp/internal/models/performatives/amqp_transfer.hpp b/sdk/core/azure-core-amqp/inc/azure/core/amqp/internal/models/performatives/amqp_transfer.hpp new file mode 100644 index 0000000000..365f22a632 --- /dev/null +++ b/sdk/core/azure-core-amqp/inc/azure/core/amqp/internal/models/performatives/amqp_transfer.hpp @@ -0,0 +1,120 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#pragma once + +#include "azure/core/amqp/dll_import_export.hpp" +#include "azure/core/amqp/internal/amqp_settle_mode.hpp" +#include "azure/core/amqp/models/amqp_message.hpp" +#include "azure/core/amqp/models/amqp_value.hpp" +#include "azure/core/nullable.hpp" + +#include + +namespace Azure { namespace Core { namespace Amqp { namespace Models { namespace _internal { + namespace Performatives { + + struct AmqpTransfer final + { + /** @brief Construct an AmqpError. */ + AmqpTransfer() = default; + + /** @brief Destroy an AmqpError. */ + ~AmqpTransfer() = default; + + /** @brief Copy Constructor */ + AmqpTransfer(AmqpTransfer const&) = default; + + /** @brief Assignment operator */ + AmqpTransfer& operator=(AmqpTransfer const&) = default; + + /** @brief Move Constructor */ + AmqpTransfer(AmqpTransfer&&) = default; + + /** @brief Move assignment operator */ + AmqpTransfer& operator=(AmqpTransfer&&) = default; + + /** @brief The link channel on which the message is transferred. + * + * @remarks For more information, see [AMQP + * Section 2.7.5](https://docs.oasis-open.org/amqp/core/v1.0/os/amqp-core-transport-v1.0-os.html#type-transfer). + */ + std::uint32_t Handle{}; + + /** @brief The Delivery ID for the message. + * + * @remarks For more information, see [AMQP + * Section 2.7.5](https://docs.oasis-open.org/amqp/core/v1.0/os/amqp-core-transport-v1.0-os.html#type-transfer). + */ + Azure::Nullable DeliveryId; + + /** @brief The delivery tag. Uniquely identifies a delivery attempt for a given message on + * this link. + * + * @remarks For more information, see [AMQP + * Section 2.7.5](https://docs.oasis-open.org/amqp/core/v1.0/os/amqp-core-transport-v1.0-os.html#type-transfer). + */ + Azure::Nullable DeliveryTag{}; + + /** @brief The message format code. + * + * @remarks For more information, see [AMQP + * Section 2.7.5](https://docs.oasis-open.org/amqp/core/v1.0/os/amqp-core-transport-v1.0-os.html#type-transfer). + */ + std::uint32_t MessageFormat{Models::AmqpDefaultMessageFormatValue}; + + /** @brief The settled state on the message. + * + * @remarks For more information, see [AMQP + * Section 2.7.5](https://docs.oasis-open.org/amqp/core/v1.0/os/amqp-core-transport-v1.0-os.html#type-transfer). + */ + Azure::Nullable Settled{}; + + /** @brief Indicates that the message has more content. + * + * @remarks For more information, see [AMQP + * Section 2.7.5](https://docs.oasis-open.org/amqp/core/v1.0/os/amqp-core-transport-v1.0-os.html#type-transfer). + */ + bool More{false}; + + /** @brief Indicates the settle mode for the message. + * + * @remarks For more information, see [AMQP + * Section 2.7.5](https://docs.oasis-open.org/amqp/core/v1.0/os/amqp-core-transport-v1.0-os.html#type-transfer). + */ + Azure::Nullable SettleMode{}; + + /** @brief The state of the delivery at the sender. + * + * @remarks For more information, see [AMQP + * Section 2.7.5](https://docs.oasis-open.org/amqp/core/v1.0/os/amqp-core-transport-v1.0-os.html#type-transfer). + */ + AmqpValue State; + + /** @brief Indicates a resumed delivery + * + * @remarks For more information, see [AMQP + * Section 2.7.5](https://docs.oasis-open.org/amqp/core/v1.0/os/amqp-core-transport-v1.0-os.html#type-transfer). + * + */ + bool Resume{false}; + + /** @brief Indicates that the message is aborted. + * + * @remarks For more information, see [AMQP + * Section 2.7.5](https://docs.oasis-open.org/amqp/core/v1.0/os/amqp-core-transport-v1.0-os.html#type-transfer). + * + */ + bool Aborted{false}; + + /** @brief Batchable hint.. + * + * @remarks For more information, see [AMQP + * Section 2.7.5](https://docs.oasis-open.org/amqp/core/v1.0/os/amqp-core-transport-v1.0-os.html#type-transfer). + * + */ + bool Batchable{false}; + }; + std::ostream& operator<<(std::ostream&, AmqpTransfer const&); + +}}}}}} // namespace Azure::Core::Amqp::Models::_internal::Performatives diff --git a/sdk/core/azure-core-amqp/inc/azure/core/amqp/internal/session.hpp b/sdk/core/azure-core-amqp/inc/azure/core/amqp/internal/session.hpp index b08a8b53a1..572bfeabf7 100644 --- a/sdk/core/azure-core-amqp/inc/azure/core/amqp/internal/session.hpp +++ b/sdk/core/azure-core-amqp/inc/azure/core/amqp/internal/session.hpp @@ -3,6 +3,7 @@ #pragma once +#include "azure/core/amqp/internal/models/amqp_error.hpp" #include "azure/core/amqp/models/amqp_value.hpp" #include "common/async_operation_queue.hpp" #include "connection_string_credential.hpp" @@ -27,7 +28,8 @@ namespace Azure { namespace Core { namespace Amqp { namespace Tests { class TestSessions_SimpleSession_Test; class TestSessions_SessionProperties_Test; class TestSessions_SessionBeginEnd_Test; - + class TestSessions_MultipleSessionBeginEnd_Test; + class TestLinks_LinkAttachDetach_Test; class TestSocketListenerEvents; class LinkSocketListenerEvents; class TestMessages_SenderSendAsync_Test; @@ -192,6 +194,21 @@ namespace Azure { namespace Core { namespace Amqp { namespace _internal { */ void End(std::string const& condition_value = {}, std::string const& description = {}); + /** @brief Sends a detach message on the specified link endpoint. + * + * @param linkEndpoint - Link endpoint to detach. + * @param closeLink - Whether to close the link after sending the detach. + * @param error - Error description to send with the detach. + * + * @remarks Note that this function is not intended for use by AMQP clients, it is intended for + * use by AMQP listeners. + * + */ + void SendDetach( + LinkEndpoint const& linkEndpoint, + bool closeLink, + Models::_internal::AmqpError const& error) const; + /** @brief Creates a MessageSender for use in a message listener. * * @param endpoint - Endpoint associated with this message sender. @@ -237,6 +254,9 @@ namespace Azure { namespace Core { namespace Amqp { namespace _internal { friend class Azure::Core::Amqp::Tests::TestSessions_SimpleSession_Test; friend class Azure::Core::Amqp::Tests::TestSessions_SessionProperties_Test; friend class Azure::Core::Amqp::Tests::TestSessions_SessionBeginEnd_Test; + friend class Azure::Core::Amqp::Tests::TestSessions_MultipleSessionBeginEnd_Test; + friend class Azure::Core::Amqp::Tests::TestLinks_LinkAttachDetach_Test; + friend class Azure::Core::Amqp::Tests::TestMessages_SenderSendAsync_Test; #endif // TESTING_BUILD #if SAMPLES_BUILD diff --git a/sdk/core/azure-core-amqp/samples/internal/local_server_sample/local_server_sample.cpp b/sdk/core/azure-core-amqp/samples/internal/local_server_sample/local_server_sample.cpp index c93e2aa976..0a89c5bbd2 100644 --- a/sdk/core/azure-core-amqp/samples/internal/local_server_sample/local_server_sample.cpp +++ b/sdk/core/azure-core-amqp/samples/internal/local_server_sample/local_server_sample.cpp @@ -19,6 +19,7 @@ using namespace Azure::Core::Amqp; namespace LocalServerSample { class SampleEvents : public ConnectionEvents, + public ConnectionEndpointEvents, public SessionEvents, public MessageReceiverEvents, public Network::_detail::SocketListenerEvents { @@ -75,7 +76,7 @@ class SampleEvents : public ConnectionEvents, ConnectionOptions options; options.ContainerId = "some"; options.EnableTrace = true; - auto newConnection{std::make_unique(amqpTransport, options, this)}; + auto newConnection{std::make_unique(amqpTransport, options, this, this)}; m_connection = newConnection.get(); m_connectionQueue.CompleteOperation(std::move(newConnection)); } @@ -149,7 +150,9 @@ class SampleEvents : public ConnectionEvents, m_messageQueue.CompleteOperation(message); return Azure::Core::Amqp::Models::_internal::Messaging::DeliveryAccepted(); } - virtual void OnMessageReceiverDisconnected(Models::_internal::AmqpError const& error) override + virtual void OnMessageReceiverDisconnected( + MessageReceiver const&, + Models::_internal::AmqpError const& error) override { std::cerr << "Message receiver error: " << error << std::endl; } diff --git a/sdk/core/azure-core-amqp/src/amqp/connection.cpp b/sdk/core/azure-core-amqp/src/amqp/connection.cpp index 0bbe487211..b7d8ecd730 100644 --- a/sdk/core/azure-core-amqp/src/amqp/connection.cpp +++ b/sdk/core/azure-core-amqp/src/amqp/connection.cpp @@ -39,9 +39,13 @@ namespace Azure { namespace Core { namespace Amqp { namespace _internal { Connection::Connection( Network::_internal::Transport const& transport, ConnectionOptions const& options, - ConnectionEvents* eventHandler) - : m_impl{ - std::make_shared<_detail::ConnectionImpl>(transport.GetImpl(), options, eventHandler)} + ConnectionEvents* eventHandler, + ConnectionEndpointEvents* endpointEventHandler) + : m_impl{std::make_shared<_detail::ConnectionImpl>( + transport.GetImpl(), + options, + eventHandler, + endpointEventHandler)} { m_impl->FinishConstruction(); } @@ -171,8 +175,10 @@ namespace Azure { namespace Core { namespace Amqp { namespace _detail { ConnectionImpl::ConnectionImpl( std::shared_ptr transport, _internal::ConnectionOptions const& options, - _internal::ConnectionEvents* eventHandler) - : m_hostName{"localhost"}, m_options{options}, m_eventHandler{eventHandler} + _internal::ConnectionEvents* eventHandler, + _internal::ConnectionEndpointEvents* endpointEvents) + : m_hostName{"localhost"}, m_options{options}, m_eventHandler{eventHandler}, + m_endpointEvents{endpointEvents} { EnsureGlobalStateInitialized(); m_transport = transport; @@ -247,7 +253,7 @@ namespace Azure { namespace Core { namespace Amqp { namespace _detail { *m_transport, m_hostName.c_str(), containerId.c_str(), - OnNewEndpointFn, + (m_endpointEvents ? OnNewEndpointFn : nullptr), this, OnConnectionStateChangedFn, this, @@ -387,8 +393,11 @@ namespace Azure { namespace Core { namespace Amqp { namespace _detail { if (newState == CONNECTION_STATE_ERROR || newState == CONNECTION_STATE_END) { // When the connection transitions into the error or end state, it is no longer pollable. - Log::Stream(Logger::Level::Verbose) - << "Connection " << connection->m_containerId << " state changed to " << newState; + if (connection->m_options.EnableTrace) + { + Log::Stream(Logger::Level::Verbose) + << "Connection " << connection->m_containerId << " state changed to " << newState; + } } connection->SetState(ConnectionStateFromCONNECTION_STATE(newState)); } @@ -397,9 +406,9 @@ namespace Azure { namespace Core { namespace Amqp { namespace _detail { { ConnectionImpl* cn = static_cast(context); _internal::Endpoint endpoint(EndpointFactory::CreateEndpoint(newEndpoint)); - if (cn->m_eventHandler) + if (cn->m_endpointEvents) { - return cn->m_eventHandler->OnNewEndpoint( + return cn->m_endpointEvents->OnNewEndpoint( ConnectionFactory::CreateFromInternal(cn->shared_from_this()), endpoint); } return false; diff --git a/sdk/core/azure-core-amqp/src/amqp/connection_string_credential.cpp b/sdk/core/azure-core-amqp/src/amqp/connection_string_credential.cpp index c86bfe5872..461a9245df 100644 --- a/sdk/core/azure-core-amqp/src/amqp/connection_string_credential.cpp +++ b/sdk/core/azure-core-amqp/src/amqp/connection_string_credential.cpp @@ -176,6 +176,10 @@ namespace Azure { namespace Core { namespace Amqp { namespace _internal { sasKeyName, std::chrono::duration_cast(expirationTime.time_since_epoch()) .count()); + if (sasToken == nullptr) + { + throw std::runtime_error("Could not create SAS token."); + } std::string rv(STRING_c_str(sasToken)); STRING_delete(sasToken); STRING_delete(sasKeyValue); diff --git a/sdk/core/azure-core-amqp/src/amqp/link.cpp b/sdk/core/azure-core-amqp/src/amqp/link.cpp index 3369df76cd..ebe4cb51d1 100644 --- a/sdk/core/azure-core-amqp/src/amqp/link.cpp +++ b/sdk/core/azure-core-amqp/src/amqp/link.cpp @@ -4,7 +4,9 @@ #include "azure/core/amqp/internal/link.hpp" #include "../models/private/error_impl.hpp" +#include "../models/private/performatives/transfer_impl.hpp" #include "../models/private/value_impl.hpp" +#include "azure/core/amqp/internal/common/completion_operation.hpp" #include "azure/core/amqp/internal/message_receiver.hpp" #include "azure/core/amqp/internal/message_sender.hpp" #include "azure/core/amqp/internal/models/message_source.hpp" @@ -25,9 +27,10 @@ namespace Azure { namespace Core { namespace Amqp { namespace _detail { std::string const& name, Azure::Core::Amqp::_internal::SessionRole role, Models::_internal::MessageSource const& source, - Models::_internal::MessageTarget const& target) - : m_impl{ - std::make_shared(SessionFactory::GetImpl(session), name, role, source, target)} + Models::_internal::MessageTarget const& target, + LinkEvents* linkEvents) + : m_impl{std::make_shared< + LinkImpl>(SessionFactory::GetImpl(session), name, role, source, target, linkEvents)} { } @@ -37,9 +40,16 @@ namespace Azure { namespace Core { namespace Amqp { namespace _detail { std::string const& name, _internal::SessionRole role, Models::_internal::MessageSource const& source, - Models::_internal::MessageTarget const& target) - : m_impl{std::make_shared< - LinkImpl>(SessionFactory::GetImpl(session), linkEndpoint, name, role, source, target)} + Models::_internal::MessageTarget const& target, + LinkEvents* linkEvents) + : m_impl{std::make_shared( + SessionFactory::GetImpl(session), + linkEndpoint, + name, + role, + source, + target, + linkEvents)} { } @@ -74,10 +84,24 @@ namespace Azure { namespace Core { namespace Amqp { namespace _detail { } uint64_t Link::GetMaxMessageSize() const { return m_impl->GetMaxMessageSize(); } uint64_t Link::GetPeerMaxMessageSize() const { return m_impl->GetPeerMaxMessageSize(); } - void Link::SetAttachProperties(Models::AmqpValue attachProperties) + void Link::SetAttachProperties(Models::AmqpValue const& attachProperties) { m_impl->SetAttachProperties(attachProperties); } + + Models::AmqpValue Link::GetDesiredCapabilities() const + { + return m_impl->GetDesiredCapabilities(); + } + void Link::SetDesiredCapabilities(Models::AmqpValue const& desiredCapabilities) + { + m_impl->SetDesiredCapabilities(desiredCapabilities); + } + + void Link::ResetLinkCredit(std::uint32_t linkCredit, bool drain) + { + m_impl->ResetLinkCredit(linkCredit, drain); + } void Link::SetMaxLinkCredit(uint32_t credit) { m_impl->SetMaxLinkCredit(credit); } std::string Link::GetName() const { return m_impl->GetName(); } @@ -85,16 +109,53 @@ namespace Azure { namespace Core { namespace Amqp { namespace _detail { void Link::Attach() { return m_impl->Attach(); } + std::tuple Link::Transfer( + std::vector const& payload, + Azure::Core::Context const& context) + { + return m_impl->Transfer(payload, context); + } + void Link::Detach( bool close, std::string const& errorCondition, std::string const& errorDescription, - Models::AmqpValue& info) + Models::AmqpValue const& info) { return m_impl->Detach(close, errorCondition, errorDescription, info); } #endif + std::ostream& operator<<(std::ostream& os, LinkState const& linkState) + { + switch (linkState) + { + case LinkState::Attached: + os << "Attached"; + break; + case LinkState::Detached: + os << "Detached"; + break; + case LinkState::Error: + os << "Error"; + break; + case LinkState::HalfAttachedAttachReceived: + os << "HalfAttachedAttachReceived"; + break; + case LinkState::HalfAttachedAttachSent: + os << "HalfAttachedAttachSent"; + break; + case LinkState::Invalid: + os << "Invalid"; + break; + default: + os << "Unknown"; + break; + } + + return os; + } + /****/ /* LINK Implementation */ @@ -103,8 +164,9 @@ namespace Azure { namespace Core { namespace Amqp { namespace _detail { std::string const& name, _internal::SessionRole role, Models::_internal::MessageSource const& source, - Models::_internal::MessageTarget const& target) - : m_session{session}, m_source(source), m_target(target) + Models::_internal::MessageTarget const& target, + LinkEvents* events) + : m_session{session}, m_source(source), m_target(target), m_eventHandler{events} { Models::AmqpValue sourceValue{source.AsAmqpValue()}; Models::AmqpValue targetValue(target.AsAmqpValue()); @@ -122,8 +184,9 @@ namespace Azure { namespace Core { namespace Amqp { namespace _detail { std::string const& name, _internal::SessionRole role, Models::_internal::MessageSource const& source, - Models::_internal::MessageTarget const& target) - : m_session{session}, m_source(source), m_target(target) + Models::_internal::MessageTarget const& target, + LinkEvents* events) + : m_session{session}, m_source(source), m_target(target), m_eventHandler{events} { Models::AmqpValue sourceValue(source.AsAmqpValue()); Models::AmqpValue targetValue(target.AsAmqpValue()); @@ -358,6 +421,78 @@ namespace Azure { namespace Core { namespace Amqp { namespace _detail { } } + void LinkImpl::OnLinkFlowOnFn(void* context) + { + LinkImpl* link = static_cast(context); + if (link->m_eventHandler) + { +#if defined(BUILD_TESTING) + link->m_eventHandler->OnLinkFlowOn(Link{link->shared_from_this()}); +#else + link->m_eventHandler->OnLinkFlowOn(link->shared_from_this()); +#endif + } + } + namespace { + LinkState LinkStateFromLINK_STATE(LINK_STATE state) + { + switch (state) + { + case LINK_STATE_ATTACHED: + return LinkState::Attached; + case LINK_STATE_DETACHED: + return LinkState::Detached; + case LINK_STATE_ERROR: + return LinkState::Error; + case LINK_STATE_HALF_ATTACHED_ATTACH_RECEIVED: + return LinkState::HalfAttachedAttachReceived; + case LINK_STATE_HALF_ATTACHED_ATTACH_SENT: + return LinkState::HalfAttachedAttachSent; + case LINK_STATE_INVALID: + default: + return LinkState::Invalid; + } + } + } // namespace + void LinkImpl::OnLinkStateChangedFn(void* context, LINK_STATE newState, LINK_STATE oldState) + { + LinkImpl* link = static_cast(context); + if (link->m_eventHandler) + { + link->m_eventHandler->OnLinkStateChanged( +#if defined(BUILD_TESTING) + Link{link->shared_from_this()}, +#else + link->shared_from_this(), +#endif + LinkStateFromLINK_STATE(newState), + LinkStateFromLINK_STATE(oldState)); + } + } + + AMQP_VALUE LinkImpl::OnTransferReceivedFn( + void* context, + TRANSFER_HANDLE transfer, + uint32_t payload_size, + const unsigned char* payload_bytes) + { + LinkImpl* link = static_cast(context); + if (link->m_eventHandler) + { + + return Models::_detail::AmqpValueFactory::ToUamqp(link->m_eventHandler->OnTransferReceived( +#if defined(TESTING_BUILD) + Link{link->shared_from_this()}, +#else + link->shared_from_this(), +#endif + Models::_detail::AmqpTransferFactory::FromUamqp(transfer), + payload_size, + payload_bytes)); + } + return nullptr; + } + void LinkImpl::Poll() { // Ensure that the connection hierarchy's state is not modified while polling on the link. @@ -375,25 +510,132 @@ namespace Azure { namespace Core { namespace Amqp { namespace _detail { void LinkImpl::Attach() { - if (link_attach(m_link, nullptr, nullptr, nullptr, this)) { - throw std::runtime_error("Could not set attach properties."); + auto lock{m_session->GetConnection()->Lock()}; + if (m_eventHandler) + { + if (link_attach(m_link, OnTransferReceivedFn, OnLinkStateChangedFn, OnLinkFlowOnFn, this)) + { + throw std::runtime_error("Could not set attach properties."); + } + } + else + { + if (link_attach(m_link, nullptr, nullptr, nullptr, this)) + { + throw std::runtime_error("Could not set attach properties."); + } + } } + // Mark the connection as async so that we can use the async APIs. + m_session->GetConnection()->EnableAsyncOperation(true); } void LinkImpl::Detach( bool close, std::string const& condition, std::string const& description, - Models::AmqpValue& info) + Models::AmqpValue const& info) { - if (link_detach( - m_link, - close, - (condition.empty() ? nullptr : condition.c_str()), - (description.empty() ? nullptr : description.c_str()), - Models::_detail::AmqpValueFactory::ToUamqp(info))) { - throw std::runtime_error("Could not set attach properties."); + auto lock{m_session->GetConnection()->Lock()}; + if (link_detach( + m_link, + close, + (condition.empty() ? nullptr : condition.c_str()), + (description.empty() ? nullptr : description.c_str()), + Models::_detail::AmqpValueFactory::ToUamqp(info))) + { + throw std::runtime_error("Could not set attach properties."); + } + } + m_session->GetConnection()->EnableAsyncOperation(false); + } + + template struct RewriteTransferComplete + { + static void OnOperation( + CompleteFn onComplete, + delivery_number deliveryId, + LINK_DELIVERY_SETTLE_REASON reason, + AMQP_VALUE disposition) + { + LinkDeliverySettleReason result{}; + switch (reason) + { + case LINK_DELIVERY_SETTLE_REASON_CANCELLED: + result = LinkDeliverySettleReason::Cancelled; + break; + case LINK_DELIVERY_SETTLE_REASON_INVALID: + result = LinkDeliverySettleReason::Invalid; + break; + case LINK_DELIVERY_SETTLE_REASON_NOT_DELIVERED: + result = LinkDeliverySettleReason::NotDelivered; + break; + case LINK_DELIVERY_SETTLE_REASON_DISPOSITION_RECEIVED: + result = LinkDeliverySettleReason::DispositionReceived; + break; + case LINK_DELIVERY_SETTLE_REASON_SETTLED: + result = LinkDeliverySettleReason::Settled; + break; + case LINK_DELIVERY_SETTLE_REASON_TIMEOUT: + result = LinkDeliverySettleReason::Timeout; + break; + } + + // Reference disposition so that we don't over-release when the AmqpValue passed to OnComplete + // is destroyed. + onComplete( + static_cast(deliveryId), + result, + Models::_detail::AmqpValueFactory::FromUamqp( + Models::_detail::UniqueAmqpValueHandle{amqpvalue_clone(disposition)})); + } + }; + + std::tuple LinkImpl::Transfer( + std::vector const& payload, + Azure::Core::Context const& context) + { + { + + auto lock{m_session->GetConnection()->Lock()}; + + auto onTransferComplete = + [this]( + uint32_t deliveryId, LinkDeliverySettleReason settleReason, Models::AmqpValue value) { + m_transferCompleteQueue.CompleteOperation(deliveryId, settleReason, value); + }; + using MessageSendCompleteCallback = std::function; + + auto operation( + std::make_unique>>(onTransferComplete)); + PAYLOAD payloadToSend{payload.data(), payload.size()}; + LINK_TRANSFER_RESULT transferResult; + auto asyncResult = link_transfer_async( + m_link, + 0, + &payloadToSend, + 1, + std::remove_pointer::type::OnOperationFn, + operation.release(), + &transferResult, + 0 /*timeout*/); + if (asyncResult == nullptr) + { + throw std::runtime_error("Could not send message"); + } + } + + auto result = m_transferCompleteQueue.WaitForResult(context); + if (result) + { + return std::move(*result); } + throw std::runtime_error("Error transferring data"); } }}}} // namespace Azure::Core::Amqp::_detail diff --git a/sdk/core/azure-core-amqp/src/amqp/management.cpp b/sdk/core/azure-core-amqp/src/amqp/management.cpp index f92abcf932..2774126517 100644 --- a/sdk/core/azure-core-amqp/src/amqp/management.cpp +++ b/sdk/core/azure-core-amqp/src/amqp/management.cpp @@ -204,13 +204,19 @@ namespace Azure { namespace Core { namespace Amqp { namespace _detail { SetState(ManagementState::Closing); if (m_messageSender && m_messageSenderOpen) { - Log::Stream(Logger::Level::Verbose) << "ManagementClient::Close Sender" << std::endl; + if (m_options.EnableTrace) + { + Log::Stream(Logger::Level::Verbose) << "ManagementClient::Close Sender" << std::endl; + } m_messageSender->Close(context); m_messageSenderOpen = false; } if (m_messageReceiver && m_messageReceiverOpen) { - Log::Stream(Logger::Level::Verbose) << "ManagementClient::Close Receiver" << std::endl; + if (m_options.EnableTrace) + { + Log::Stream(Logger::Level::Verbose) << "ManagementClient::Close Receiver" << std::endl; + } m_messageReceiver->Close(context); m_messageReceiverOpen = false; } @@ -224,8 +230,11 @@ namespace Azure { namespace Core { namespace Amqp { namespace _detail { { if (newState == oldState) { - Log::Stream(Logger::Level::Verbose) - << "ManagementClient::OnMessageSenderStateChanged: newState == oldState" << std::endl; + if (m_options.EnableTrace) + { + Log::Stream(Logger::Level::Verbose) + << "ManagementClient::OnMessageSenderStateChanged: newState == oldState" << std::endl; + } return; } @@ -319,7 +328,9 @@ namespace Azure { namespace Core { namespace Amqp { namespace _detail { } } - void ManagementClientImpl::OnMessageSenderDisconnected(Models::_internal::AmqpError const& error) + void ManagementClientImpl::OnMessageSenderDisconnected( + _internal::MessageSender const&, + Models::_internal::AmqpError const& error) { if (error) { @@ -553,6 +564,7 @@ namespace Azure { namespace Core { namespace Amqp { namespace _detail { } void ManagementClientImpl::OnMessageReceiverDisconnected( + _internal::MessageReceiver const&, Models::_internal::AmqpError const& error) { if (error) diff --git a/sdk/core/azure-core-amqp/src/amqp/message_receiver.cpp b/sdk/core/azure-core-amqp/src/amqp/message_receiver.cpp index 3a4618f76c..e24e97dbfb 100644 --- a/sdk/core/azure-core-amqp/src/amqp/message_receiver.cpp +++ b/sdk/core/azure-core-amqp/src/amqp/message_receiver.cpp @@ -41,6 +41,20 @@ namespace Azure { namespace Core { namespace Amqp { namespace _detail { namespace Azure { namespace Core { namespace Amqp { namespace _internal { + std::ostream& operator<<(std::ostream& stream, ReceiverSettleMode const& settleMode) + { + switch (settleMode) + { + case ReceiverSettleMode::First: + stream << "First"; + break; + case ReceiverSettleMode::Second: + stream << "Second"; + break; + } + return stream; + } + MessageReceiver::~MessageReceiver() noexcept {} void MessageReceiver::Open(Context const& context) @@ -117,8 +131,6 @@ namespace Azure { namespace Core { namespace Amqp { namespace _internal { case _internal::MessageReceiverState::Error: stream << "Error"; break; - default: - throw std::runtime_error("Unknown message sender state operation type."); } return stream; } @@ -166,7 +178,8 @@ namespace Azure { namespace Core { namespace Amqp { namespace _detail { m_options.Name, SessionRole::Sender, // This is the role of the link, not the endpoint. m_source, - m_options.MessageTarget); + m_options.MessageTarget, + nullptr); PopulateLinkProperties(); Log::Stream(Logger::Level::Verbose) @@ -179,7 +192,12 @@ namespace Azure { namespace Core { namespace Amqp { namespace _detail { void MessageReceiverImpl::CreateLink() { m_link = std::make_shared<_detail::LinkImpl>( - m_session, m_options.Name, SessionRole::Receiver, m_source, m_options.MessageTarget); + m_session, + m_options.Name, + SessionRole::Receiver, + m_source, + m_options.MessageTarget, + nullptr); PopulateLinkProperties(); Log::Stream(Logger::Level::Verbose) @@ -253,7 +271,8 @@ namespace Azure { namespace Core { namespace Amqp { namespace _detail { { if (m_eventHandler) { - m_eventHandler->OnMessageReceiverDisconnected(error); + m_eventHandler->OnMessageReceiverDisconnected( + MessageReceiverFactory::CreateFromInternal(shared_from_this()), error); } // Log that an error occurred. @@ -382,7 +401,14 @@ namespace Azure { namespace Core { namespace Amqp { namespace _detail { std::ostream& operator<<(std::ostream& stream, MESSAGE_RECEIVER_STATE const& state) { - stream << MESSAGE_RECEIVER_STATEStrings[static_cast(state)]; + if (state < sizeof(MESSAGE_RECEIVER_STATEStrings) / sizeof(MESSAGE_RECEIVER_STATEStrings[0])) + { + stream << MESSAGE_RECEIVER_STATEStrings[static_cast(state)]; + } + else + { + stream << "Unknown MESSAGE_RECEIVER_STATE value: " << state; + } return stream; } diff --git a/sdk/core/azure-core-amqp/src/amqp/message_sender.cpp b/sdk/core/azure-core-amqp/src/amqp/message_sender.cpp index 570eed0f7e..de4d8226d5 100644 --- a/sdk/core/azure-core-amqp/src/amqp/message_sender.cpp +++ b/sdk/core/azure-core-amqp/src/amqp/message_sender.cpp @@ -37,6 +37,22 @@ namespace Azure { namespace Core { namespace Amqp { namespace _detail { }}}} // namespace Azure::Core::Amqp::_detail namespace Azure { namespace Core { namespace Amqp { namespace _internal { + std::ostream& operator<<(std::ostream& stream, SenderSettleMode const& settleMode) + { + switch (settleMode) + { + case SenderSettleMode::Settled: + stream << "Settled"; + break; + case SenderSettleMode::Unsettled: + stream << "Unsettled"; + break; + case SenderSettleMode::Mixed: + stream << "Mixed"; + break; + } + return stream; + } void MessageSender::Open(Context const& context) { m_impl->Open(context); } void MessageSender::Close(Context const& context) { m_impl->Close(context); } @@ -48,7 +64,7 @@ namespace Azure { namespace Core { namespace Amqp { namespace _internal { } std::uint64_t MessageSender::GetMaxMessageSize() const { return m_impl->GetMaxMessageSize(); } - + std::string MessageSender::GetLinkName() const { return m_impl->GetLinkName(); } MessageSender::~MessageSender() noexcept {} std::ostream& operator<<(std::ostream& stream, _internal::MessageSenderState const& state) { @@ -72,8 +88,6 @@ namespace Azure { namespace Core { namespace Amqp { namespace _internal { case _internal::MessageSenderState::Error: stream << "Error"; break; - default: - throw std::runtime_error("Unknown message sender state operation type."); } return stream; } @@ -144,7 +158,8 @@ namespace Azure { namespace Core { namespace Amqp { namespace _detail { m_options.Name, _internal::SessionRole::Receiver, // This is the role of the link, not the endpoint. m_options.MessageSource, - m_target); + m_target, + nullptr); PopulateLinkProperties(); m_link->SubscribeToDetachEvent( @@ -158,7 +173,8 @@ namespace Azure { namespace Core { namespace Amqp { namespace _detail { m_options.Name, _internal::SessionRole::Sender, // This is the role of the link, not the endpoint. m_options.MessageSource, - m_target); + m_target, + nullptr); PopulateLinkProperties(); m_link->SubscribeToDetachEvent( @@ -386,12 +402,15 @@ namespace Azure { namespace Core { namespace Amqp { namespace _detail { { if (m_events) { - m_events->OnMessageSenderDisconnected(error); + m_events->OnMessageSenderDisconnected( + MessageSenderFactory::CreateFromInternal(shared_from_this()), error); + } + + if (m_options.EnableTrace) + { + // Log that an error occurred. + Log::Stream(Logger::Level::Warning) << "Message sender link detached: " << error; } - // Log that an error occurred. - Log::Stream(Logger::Level::Warning) - << "Message sender link detached: " << error.Condition.ToString() << ": " - << error.Description; // Cache the error we received in the OnDetach notification so we can return it to the user // on the next send which fails. @@ -521,4 +540,7 @@ namespace Azure { namespace Core { namespace Amqp { namespace _detail { } throw std::runtime_error("Error sending message"); } + + std::string MessageSenderImpl::GetLinkName() const { return m_link->GetName(); } + }}}} // namespace Azure::Core::Amqp::_detail diff --git a/sdk/core/azure-core-amqp/src/amqp/private/connection_impl.hpp b/sdk/core/azure-core-amqp/src/amqp/private/connection_impl.hpp index dbbc72ae0e..56fe61ea5d 100644 --- a/sdk/core/azure-core-amqp/src/amqp/private/connection_impl.hpp +++ b/sdk/core/azure-core-amqp/src/amqp/private/connection_impl.hpp @@ -58,7 +58,8 @@ namespace Azure { namespace Core { namespace Amqp { namespace _detail { ConnectionImpl( std::shared_ptr transport, _internal::ConnectionOptions const& options, - _internal::ConnectionEvents* eventHandler); + _internal::ConnectionEvents* eventHandler, + _internal::ConnectionEndpointEvents* endpointEvents); ConnectionImpl( std::string const& hostName, @@ -91,8 +92,6 @@ namespace Azure { namespace Core { namespace Amqp { namespace _detail { void Open(); void Listen(); - void Close(); - void Close( std::string const& condition = {}, std::string const& description = {}, @@ -144,6 +143,7 @@ namespace Azure { namespace Core { namespace Amqp { namespace _detail { Azure::Core::Amqp::Common::_internal::AsyncOperationQueue> m_newSessionQueue; _internal::ConnectionEvents* m_eventHandler{}; + _internal::ConnectionEndpointEvents* m_endpointEvents{}; _internal::ConnectionState m_connectionState = _internal::ConnectionState::Start; LockType m_amqpMutex; diff --git a/sdk/core/azure-core-amqp/src/amqp/private/link_impl.hpp b/sdk/core/azure-core-amqp/src/amqp/private/link_impl.hpp index 4a3a51bcb2..1a1d81fb68 100644 --- a/sdk/core/azure-core-amqp/src/amqp/private/link_impl.hpp +++ b/sdk/core/azure-core-amqp/src/amqp/private/link_impl.hpp @@ -3,7 +3,9 @@ #pragma once +#include "../private/session_impl.hpp" #include "azure/core/amqp/internal/common/global_state.hpp" +#include "azure/core/amqp/internal/link.hpp" #include "azure/core/amqp/internal/models/amqp_error.hpp" #include "azure/core/amqp/internal/models/message_source.hpp" #include "azure/core/amqp/internal/models/message_target.hpp" @@ -29,14 +31,16 @@ namespace Azure { namespace Core { namespace Amqp { namespace _detail { std::string const& name, _internal::SessionRole role, Models::_internal::MessageSource const& source, - Models::_internal::MessageTarget const& target); + Models::_internal::MessageTarget const& target, + _detail::LinkEvents* events); LinkImpl( std::shared_ptr<_detail::SessionImpl> session, _internal::LinkEndpoint& linkEndpoint, std::string const& name, _internal::SessionRole role, Models::_internal::MessageSource const& source, - Models::_internal::MessageTarget const& target); + Models::_internal::MessageTarget const& target, + _detail::LinkEvents* events); ~LinkImpl() noexcept; LinkImpl(LinkImpl const&) = delete; @@ -89,17 +93,31 @@ namespace Azure { namespace Core { namespace Amqp { namespace _detail { bool close, std::string const& errorCondition, std::string const& errorDescription, - Models::AmqpValue& info); + Models::AmqpValue const& info); + + std::tuple Transfer( + std::vector const& payload, + Azure::Core::Context const& context); private: LINK_HANDLE m_link; std::shared_ptr<_detail::SessionImpl> m_session; Models::_internal::MessageSource m_source; Models::_internal::MessageTarget m_target; + Common::_internal::AsyncOperationQueue + m_transferCompleteQueue; OnLinkDetachEvent m_onLinkDetachEvent; + LinkEvents* m_eventHandler; ON_LINK_DETACH_EVENT_SUBSCRIPTION_HANDLE m_linkSubscriptionHandle{}; static void OnLinkDetachEventFn(void* context, ERROR_HANDLE error); + static void OnLinkFlowOnFn(void* context); + static void OnLinkStateChangedFn(void* context, LINK_STATE newState, LINK_STATE oldState); + static AMQP_VALUE OnTransferReceivedFn( + void* context, + TRANSFER_HANDLE transfer, + uint32_t payload_size, + const unsigned char* payload_bytes); // Inherited via Pollable void Poll() override; diff --git a/sdk/core/azure-core-amqp/src/amqp/private/management_impl.hpp b/sdk/core/azure-core-amqp/src/amqp/private/management_impl.hpp index 91cf737266..29aa3e6a0a 100644 --- a/sdk/core/azure-core-amqp/src/amqp/private/management_impl.hpp +++ b/sdk/core/azure-core-amqp/src/amqp/private/management_impl.hpp @@ -118,7 +118,9 @@ namespace Azure { namespace Core { namespace Amqp { namespace _detail { _internal::MessageSender const& sender, _internal::MessageSenderState newState, _internal::MessageSenderState oldState) override; - void OnMessageSenderDisconnected(Models::_internal::AmqpError const& error) override; + void OnMessageSenderDisconnected( + _internal::MessageSender const&, + Models::_internal::AmqpError const& error) override; // Inherited via MessageReceiverEvents void OnMessageReceiverStateChanged( @@ -128,6 +130,8 @@ namespace Azure { namespace Core { namespace Amqp { namespace _detail { Models::AmqpValue OnMessageReceived( _internal::MessageReceiver const& receiver, std::shared_ptr const& message) override; - void OnMessageReceiverDisconnected(Models::_internal::AmqpError const& error) override; + void OnMessageReceiverDisconnected( + _internal::MessageReceiver const&, + Models::_internal::AmqpError const& error) override; }; }}}} // namespace Azure::Core::Amqp::_detail diff --git a/sdk/core/azure-core-amqp/src/amqp/private/message_sender_impl.hpp b/sdk/core/azure-core-amqp/src/amqp/private/message_sender_impl.hpp index d1775c6024..c61f91904e 100644 --- a/sdk/core/azure-core-amqp/src/amqp/private/message_sender_impl.hpp +++ b/sdk/core/azure-core-amqp/src/amqp/private/message_sender_impl.hpp @@ -61,6 +61,8 @@ namespace Azure { namespace Core { namespace Amqp { namespace _detail { std::uint64_t GetMaxMessageSize() const; + std::string GetLinkName() const; + private: static void OnMessageSenderStateChangedFn( void* context, diff --git a/sdk/core/azure-core-amqp/src/amqp/private/session_impl.hpp b/sdk/core/azure-core-amqp/src/amqp/private/session_impl.hpp index 9f8c82b254..4ff8440e26 100644 --- a/sdk/core/azure-core-amqp/src/amqp/private/session_impl.hpp +++ b/sdk/core/azure-core-amqp/src/amqp/private/session_impl.hpp @@ -3,6 +3,7 @@ #pragma once +#include "azure/core/amqp/internal/models/amqp_error.hpp" #include "azure/core/amqp/internal/session.hpp" #include "connection_impl.hpp" #include "unique_handle.hpp" @@ -48,6 +49,7 @@ namespace Azure { namespace Core { namespace Amqp { namespace _detail { _internal::Endpoint& newEndpoint, _internal::SessionOptions const& options, _internal::SessionEvents* eventHandler); + SessionImpl( std::shared_ptr<_detail::ConnectionImpl> parentConnection, _internal::SessionOptions const& options, @@ -69,8 +71,15 @@ namespace Azure { namespace Core { namespace Amqp { namespace _detail { void Begin(); void End(std::string const& condition_value, std::string const& description); + void SendDetach( + _internal::LinkEndpoint const& linkEndpoint, + bool closeLink, + Models::_internal::AmqpError const& error) const; + private: SessionImpl(); + bool m_connectionAsyncStarted{false}; + bool m_isBegun{false}; std::shared_ptr<_detail::ConnectionImpl> m_connectionToPoll; UniqueAmqpSession m_session; _internal::SessionOptions m_options; diff --git a/sdk/core/azure-core-amqp/src/amqp/session.cpp b/sdk/core/azure-core-amqp/src/amqp/session.cpp index 9ccfb73027..5705e21a30 100644 --- a/sdk/core/azure-core-amqp/src/amqp/session.cpp +++ b/sdk/core/azure-core-amqp/src/amqp/session.cpp @@ -3,9 +3,11 @@ #include "azure/core/amqp/internal/session.hpp" +#include "../models/private/performatives/detach_impl.hpp" #include "../models/private/value_impl.hpp" #include "azure/core/amqp/internal/connection.hpp" #include "azure/core/amqp/internal/link.hpp" +#include "azure/core/amqp/internal/models/performatives/amqp_detach.hpp" #include "private/claims_based_security_impl.hpp" #include "private/connection_impl.hpp" #include "private/management_impl.hpp" @@ -48,6 +50,13 @@ namespace Azure { namespace Core { namespace Amqp { namespace _internal { { m_impl->End(condition_value, description); } + void Session::SendDetach( + _internal::LinkEndpoint const& linkEndpoint, + bool closeLink, + Models::_internal::AmqpError const& error) const + { + m_impl->SendDetach(linkEndpoint, closeLink, error); + } MessageSender Session::CreateMessageSender( Models::_internal::MessageTarget const& target, @@ -102,6 +111,18 @@ namespace Azure { namespace Core { namespace Amqp { namespace _detail { SessionImpl::~SessionImpl() noexcept { + if (m_isBegun) + { + AZURE_ASSERT_MSG(false, "Session was not ended before destruction."); + } + + // If we have a mismatched begin/end pair, we need to stop polling on the connection so it + // gets cleaned up properly. + if (m_connectionAsyncStarted) + { + m_connectionToPoll->EnableAsyncOperation(false); + } + auto lock{m_connectionToPoll->Lock()}; m_session.reset(); } @@ -209,9 +230,19 @@ namespace Azure { namespace Core { namespace Amqp { namespace _detail { { throw std::runtime_error("Could not begin session"); } + + m_isBegun = true; + + // Mark the connection as async so that we can use the async APIs. + GetConnection()->EnableAsyncOperation(true); + m_connectionAsyncStarted = true; } void SessionImpl::End(const std::string& condition, const std::string& description) { + if (!m_isBegun) + { + throw std::runtime_error("Session End without corresponding Begin."); + } // When we end the session, it clears all the links, so we need to ensure that the // m_newLinkAttachedQueue.Clear(); if (session_end( @@ -221,6 +252,27 @@ namespace Azure { namespace Core { namespace Amqp { namespace _detail { { throw std::runtime_error("Could not begin session"); } + // Mark the connection as async so that we can use the async APIs. + GetConnection()->EnableAsyncOperation(false); + m_connectionAsyncStarted = false; + m_isBegun = false; + } + + void SessionImpl::SendDetach( + _internal::LinkEndpoint const& linkEndpoint, + bool closeLink, + Models::_internal::AmqpError const& error) const + { + Models::_internal::Performatives::AmqpDetach detach; + + detach.Closed = closeLink; + detach.Error = error; + + if (session_send_detach( + linkEndpoint.Get(), Models::_detail::AmqpDetachFactory::ToAmqpDetach(detach).get())) + { + throw std::runtime_error("Failed to send detach performative."); + } } bool SessionImpl::OnLinkAttachedFn( diff --git a/sdk/core/azure-core-amqp/src/models/amqp_detach.cpp b/sdk/core/azure-core-amqp/src/models/amqp_detach.cpp new file mode 100644 index 0000000000..fe54455bf5 --- /dev/null +++ b/sdk/core/azure-core-amqp/src/models/amqp_detach.cpp @@ -0,0 +1,89 @@ +// Copyright(c) Microsoft Corporation. +// Licensed under the MIT License. + +#include "azure/core/amqp/internal/models/performatives/amqp_detach.hpp" + +#include "../amqp/private/unique_handle.hpp" +#include "azure/core/amqp/models/amqp_value.hpp" +#include "private/error_impl.hpp" +#include "private/performatives/detach_impl.hpp" +#include "private/value_impl.hpp" + +#include + +#include +#include +#include + +#include + +namespace Azure { namespace Core { namespace Amqp { namespace _detail { + // @cond + void UniqueHandleHelper::FreeAmqpDetach(DETACH_HANDLE handle) + { + detach_destroy(handle); + } + // @endcond +}}}} // namespace Azure::Core::Amqp::_detail + +namespace Azure { namespace Core { namespace Amqp { namespace Models { namespace _detail { + + /* + * Note that this does not take a unique handle to an AMQP Error - that is because the AMQP + * code will NOT take ownership of the underlying ERROR_HANDLE object. + */ + _internal::Performatives::AmqpDetach AmqpDetachFactory::FromUamqp(DETACH_HANDLE detachHandle) + { + _internal::Performatives::AmqpDetach rv; + handle handle_value; + if (!detach_get_handle(detachHandle, &handle_value)) + { + rv.Handle = handle_value; + } + bool boolValue; + if (!detach_get_closed(detachHandle, &boolValue)) + { + rv.Closed = boolValue; + } + + { + ERROR_HANDLE amqpErrorHandle; + if (!detach_get_error(detachHandle, &amqpErrorHandle)) + { + UniqueAmqpErrorHandle error{amqpErrorHandle}; + amqpErrorHandle = nullptr; + rv.Error = _detail::AmqpErrorFactory::FromUamqp(error.get()); + } + } + return rv; + } + + _detail::UniqueAmqpDetachHandle AmqpDetachFactory::ToAmqpDetach( + _internal::Performatives::AmqpDetach const& detach) + { + _detail::UniqueAmqpDetachHandle detachHandle(detach_create(detach.Handle)); + if (detach_set_closed(detachHandle.get(), detach.Closed)) + { + throw std::runtime_error("Could not set closed state on detach item."); + } + + if (detach_set_error(detachHandle.get(), AmqpErrorFactory::ToAmqpError(detach.Error).get())) + { + throw std::runtime_error("Could not set error on detach item."); + } + return detachHandle; + } +}}}}} // namespace Azure::Core::Amqp::Models::_detail + +namespace Azure { namespace Core { namespace Amqp { namespace Models { namespace _internal { + namespace Performatives { + std::ostream& operator<<(std::ostream& os, AmqpDetach const& detach) + { + os << "Detach {"; + os << "Handle =" << detach.Handle; + os << ", Closed: " << detach.Closed; + os << ", Error: " << detach.Error; + os << "}"; + return os; + } +}}}}}} // namespace Azure::Core::Amqp::Models::_internal::Performatives diff --git a/sdk/core/azure-core-amqp/src/models/amqp_error.cpp b/sdk/core/azure-core-amqp/src/models/amqp_error.cpp index de457dd3de..f18afc183d 100644 --- a/sdk/core/azure-core-amqp/src/models/amqp_error.cpp +++ b/sdk/core/azure-core-amqp/src/models/amqp_error.cpp @@ -10,9 +10,10 @@ #include -#include #include +#include + #include namespace Azure { namespace Core { namespace Amqp { namespace _detail { @@ -57,7 +58,7 @@ namespace Azure { namespace Core { namespace Amqp { namespace Models { namespace return rv; } - AmqpValue AmqpErrorFactory::ToAmqp(_internal::AmqpError const& error) + UniqueAmqpErrorHandle AmqpErrorFactory::ToAmqpError(_internal::AmqpError const& error) { _detail::UniqueAmqpErrorHandle errorHandle(error_create(error.Condition.ToString().data())); if (!error.Description.empty()) @@ -69,6 +70,13 @@ namespace Azure { namespace Core { namespace Amqp { namespace Models { namespace AmqpValue infoValue(error.Info.AsAmqpValue()); error_set_info(errorHandle.get(), _detail::AmqpValueFactory::ToUamqp(infoValue)); } + return errorHandle; + } + + AmqpValue AmqpErrorFactory::ToAmqp(_internal::AmqpError const& error) + { + _detail::UniqueAmqpErrorHandle errorHandle(ToAmqpError(error)); + // amqpvalue_create_error clones the error handle, so we remember it separately. _detail::UniqueAmqpValueHandle handleAsValue{amqpvalue_create_error(errorHandle.get())}; diff --git a/sdk/core/azure-core-amqp/src/models/amqp_transfer.cpp b/sdk/core/azure-core-amqp/src/models/amqp_transfer.cpp new file mode 100644 index 0000000000..484a00b8a0 --- /dev/null +++ b/sdk/core/azure-core-amqp/src/models/amqp_transfer.cpp @@ -0,0 +1,147 @@ +// Copyright(c) Microsoft Corporation. +// Licensed under the MIT License. + +#include "azure/core/amqp/internal/models/performatives/amqp_transfer.hpp" + +#include "../amqp/private/unique_handle.hpp" +#include "azure/core/amqp/models/amqp_value.hpp" +#include "private/performatives/transfer_impl.hpp" +#include "private/value_impl.hpp" + +#include + +#include +#include +#include + +#include + +namespace Azure { namespace Core { namespace Amqp { namespace _detail { + // @cond + void UniqueHandleHelper::FreeAmqpTransfer(TRANSFER_HANDLE handle) + { + transfer_destroy(handle); + } + // @endcond +}}}} // namespace Azure::Core::Amqp::_detail + +namespace Azure { namespace Core { namespace Amqp { namespace Models { namespace _detail { + + /* + * Note that this does not take a unique handle to an AMQP Error - that is because the AMQP + * code will NOT take ownership of the underlying ERROR_HANDLE object. + */ + _internal::Performatives::AmqpTransfer AmqpTransferFactory::FromUamqp( + TRANSFER_HANDLE transferHandle) + { + _internal::Performatives::AmqpTransfer rv; + handle handle_value; + if (!transfer_get_handle(transferHandle, &handle_value)) + { + rv.Handle = handle_value; + } + uint32_t uint32val; + if (!transfer_get_delivery_id(transferHandle, &uint32val)) + { + rv.DeliveryId = uint32val; + } + amqp_binary binaryValue; + if (!transfer_get_delivery_tag(transferHandle, &binaryValue)) + { + std::vector binaryData( + reinterpret_cast(binaryValue.bytes), + reinterpret_cast(binaryValue.bytes) + binaryValue.length); + rv.DeliveryTag = AmqpBinaryData{binaryData}; + } + if (!transfer_get_message_format(transferHandle, &uint32val)) + { + rv.MessageFormat = uint32val; + } + bool boolValue; + if (!transfer_get_settled(transferHandle, &boolValue)) + { + rv.Settled = boolValue; + } + if (!transfer_get_more(transferHandle, &boolValue)) + { + rv.More = boolValue; + } + receiver_settle_mode settleMode; + if (!transfer_get_rcv_settle_mode(transferHandle, &settleMode)) + { + switch (settleMode) + { + case receiver_settle_mode_first: + rv.SettleMode = Azure::Core::Amqp::_internal::ReceiverSettleMode::First; + break; + case receiver_settle_mode_second: + rv.SettleMode = Azure::Core::Amqp::_internal::ReceiverSettleMode::Second; + } + } + AMQP_VALUE amqpValue; + if (!transfer_get_state(transferHandle, &amqpValue)) + { + rv.State = _detail::AmqpValueFactory::FromUamqp( + _detail::UniqueAmqpValueHandle{amqpvalue_clone(amqpValue)}); + } + if (!transfer_get_resume(transferHandle, &boolValue)) + { + rv.Resume = boolValue; + } + if (!transfer_get_aborted(transferHandle, &boolValue)) + + { + rv.Aborted = boolValue; + } + if (!transfer_get_batchable(transferHandle, &boolValue)) + { + rv.Batchable = boolValue; + } + return rv; + } + + AmqpValue AmqpTransferFactory::ToAmqp(_internal::Performatives::AmqpTransfer const& transfer) + { + _detail::UniqueAmqpTransferHandle transferHandle(transfer_create(transfer.Handle)); + + // amqpvalue_create_error clones the error handle, so we remember it separately. + _detail::UniqueAmqpValueHandle handleAsValue{amqpvalue_create_transfer(transferHandle.get())}; + + // The AmqpValue constructor will clone the handle passed into it. + // The UniqueAmqpValueHandle will take care of freeing the cloned handle. + return _detail::AmqpValueFactory::FromUamqp(handleAsValue); + } +}}}}} // namespace Azure::Core::Amqp::Models::_detail + +namespace Azure { namespace Core { namespace Amqp { namespace Models { namespace _internal { + namespace Performatives { + std::ostream& operator<<(std::ostream& os, AmqpTransfer const& transfer) + { + os << "Transfer {"; + os << "Handle: " << transfer.Handle; + if (transfer.DeliveryId) + { + os << ", DeliveryId: " << transfer.DeliveryId.Value(); + } + if (transfer.DeliveryTag) + { + os << ", DeliveryTag: " << transfer.DeliveryTag.Value(); + } + os << ", MessageFormat: " << transfer.MessageFormat; + if (transfer.Settled) + { + os << ", Settled, " << transfer.Settled.Value(); + } + os << ", More: " << transfer.More; + if (transfer.SettleMode) + { + os << ", RcvSettleMode=" << transfer.SettleMode.Value(); + } + os << ", State=" << transfer.State; + os << ", Resume=" << transfer.Resume; + os << ", Aborted=" << transfer.Aborted; + os << ", Batchable=" << transfer.Batchable; + os << "}"; + return os; + } +}}}}}} // namespace Azure::Core::Amqp::Models::_internal::Performatives diff --git a/sdk/core/azure-core-amqp/src/models/amqp_value.cpp b/sdk/core/azure-core-amqp/src/models/amqp_value.cpp index c16a95d0c9..15962ec650 100644 --- a/sdk/core/azure-core-amqp/src/models/amqp_value.cpp +++ b/sdk/core/azure-core-amqp/src/models/amqp_value.cpp @@ -128,7 +128,7 @@ namespace Azure { namespace Core { namespace Amqp { namespace Models { } return os; } - std::ostream& operator<<(std::ostream& os, AMQP_VALUE_DATA_TAG* const value) + std::ostream& operator<<(std::ostream& os, AMQP_VALUE const value) { if (value != nullptr) { diff --git a/sdk/core/azure-core-amqp/src/models/message_source.cpp b/sdk/core/azure-core-amqp/src/models/message_source.cpp index 25854f773a..b9c8d6ebc7 100644 --- a/sdk/core/azure-core-amqp/src/models/message_source.cpp +++ b/sdk/core/azure-core-amqp/src/models/message_source.cpp @@ -7,6 +7,7 @@ #include "private/value_impl.hpp" #include + #include #include diff --git a/sdk/core/azure-core-amqp/src/models/message_target.cpp b/sdk/core/azure-core-amqp/src/models/message_target.cpp index 0860e6275d..2c936b9678 100644 --- a/sdk/core/azure-core-amqp/src/models/message_target.cpp +++ b/sdk/core/azure-core-amqp/src/models/message_target.cpp @@ -9,6 +9,7 @@ #include #include + #include #include diff --git a/sdk/core/azure-core-amqp/src/models/private/error_impl.hpp b/sdk/core/azure-core-amqp/src/models/private/error_impl.hpp index 84465f0600..f17cb9711b 100644 --- a/sdk/core/azure-core-amqp/src/models/private/error_impl.hpp +++ b/sdk/core/azure-core-amqp/src/models/private/error_impl.hpp @@ -37,6 +37,7 @@ namespace Azure { namespace Core { namespace Amqp { namespace Models { namespace { static _internal::AmqpError FromUamqp(ERROR_HANDLE error); static AmqpValue ToAmqp(_internal::AmqpError const& error); + static UniqueAmqpErrorHandle ToAmqpError(_internal::AmqpError const& error); AmqpErrorFactory() = delete; }; }}}}} // namespace Azure::Core::Amqp::Models::_detail diff --git a/sdk/core/azure-core-amqp/src/models/private/performatives/detach_impl.hpp b/sdk/core/azure-core-amqp/src/models/private/performatives/detach_impl.hpp new file mode 100644 index 0000000000..c7a9667139 --- /dev/null +++ b/sdk/core/azure-core-amqp/src/models/private/performatives/detach_impl.hpp @@ -0,0 +1,46 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#pragma once + +#include "../../../amqp/private/unique_handle.hpp" +#include "azure/core/amqp/internal/models/performatives/amqp_detach.hpp" + +#include + +#include +#include +#include + +#include + +#include + +namespace Azure { namespace Core { namespace Amqp { namespace _detail { + template <> struct UniqueHandleHelper::type> + { + static void FreeAmqpDetach(DETACH_HANDLE obj); + + using type = Core::_internal:: + BasicUniqueHandle::type, FreeAmqpDetach>; + }; +}}}} // namespace Azure::Core::Amqp::_detail + +namespace Azure { namespace Core { namespace Amqp { namespace Models { namespace _detail { + using UniqueAmqpDetachHandle + = Amqp::_detail::UniqueHandle::type>; + + /** + * @brief uAMQP interoperability functions to convert an AmqpTransfer to a uAMQP AMQP_TRANSFER + * and back. + * + * @remarks This class should not be used directly. It is used by the uAMQP interoperability + * layer. + */ + struct AmqpDetachFactory + { + static _internal::Performatives::AmqpDetach FromUamqp(DETACH_HANDLE error); + static UniqueAmqpDetachHandle ToAmqpDetach(_internal::Performatives::AmqpDetach const& error); + AmqpDetachFactory() = delete; + }; +}}}}} // namespace Azure::Core::Amqp::Models::_detail diff --git a/sdk/core/azure-core-amqp/src/models/private/performatives/transfer_impl.hpp b/sdk/core/azure-core-amqp/src/models/private/performatives/transfer_impl.hpp new file mode 100644 index 0000000000..672ff20af6 --- /dev/null +++ b/sdk/core/azure-core-amqp/src/models/private/performatives/transfer_impl.hpp @@ -0,0 +1,49 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#pragma once + +#include "../../../amqp/private/unique_handle.hpp" +#include "azure/core/amqp/internal/models/performatives/amqp_transfer.hpp" + +#include + +#include +#include + +#include +#include +#include +#include +#include + +#include + +namespace Azure { namespace Core { namespace Amqp { namespace _detail { + template <> struct UniqueHandleHelper::type> + { + static void FreeAmqpTransfer(TRANSFER_HANDLE obj); + + using type = Core::_internal:: + BasicUniqueHandle::type, FreeAmqpTransfer>; + }; +}}}} // namespace Azure::Core::Amqp::_detail + +namespace Azure { namespace Core { namespace Amqp { namespace Models { namespace _detail { + using UniqueAmqpTransferHandle + = Amqp::_detail::UniqueHandle::type>; + + /** + * @brief uAMQP interoperability functions to convert an AmqpTransfer to a uAMQP AMQP_TRANSFER + * and back. + * + * @remarks This class should not be used directly. It is used by the uAMQP interoperability + * layer. + */ + struct AmqpTransferFactory + { + static _internal::Performatives::AmqpTransfer FromUamqp(TRANSFER_HANDLE error); + static AmqpValue ToAmqp(_internal::Performatives::AmqpTransfer const& error); + AmqpTransferFactory() = delete; + }; +}}}}} // namespace Azure::Core::Amqp::Models::_detail diff --git a/sdk/core/azure-core-amqp/src/models/private/source_impl.hpp b/sdk/core/azure-core-amqp/src/models/private/source_impl.hpp index 28dceb955f..dfb56f4a88 100644 --- a/sdk/core/azure-core-amqp/src/models/private/source_impl.hpp +++ b/sdk/core/azure-core-amqp/src/models/private/source_impl.hpp @@ -7,6 +7,7 @@ #include "azure/core/amqp/internal/models/message_source.hpp" #include + #include #include diff --git a/sdk/core/azure-core-amqp/src/models/private/target_impl.hpp b/sdk/core/azure-core-amqp/src/models/private/target_impl.hpp index e70f6969be..24dce9d81c 100644 --- a/sdk/core/azure-core-amqp/src/models/private/target_impl.hpp +++ b/sdk/core/azure-core-amqp/src/models/private/target_impl.hpp @@ -7,6 +7,7 @@ #include "azure/core/amqp/internal/models/message_target.hpp" #include + #include #include diff --git a/sdk/core/azure-core-amqp/src/models/private/value_impl.hpp b/sdk/core/azure-core-amqp/src/models/private/value_impl.hpp index 52d039bcc1..b081326086 100644 --- a/sdk/core/azure-core-amqp/src/models/private/value_impl.hpp +++ b/sdk/core/azure-core-amqp/src/models/private/value_impl.hpp @@ -4,6 +4,7 @@ #pragma once #include "../../amqp/private/unique_handle.hpp" +#include "azure/core/amqp/models/amqp_value.hpp" #include @@ -25,6 +26,7 @@ namespace Azure { namespace Core { namespace Amqp { namespace _detail { BasicUniqueHandle::type, FreeAmqpDecoder>; // @endcond }; + }}}} // namespace Azure::Core::Amqp::_detail namespace Azure { namespace Core { namespace Amqp { namespace Models { namespace _detail { @@ -56,5 +58,7 @@ namespace Azure { namespace Core { namespace Amqp { namespace Models { namespace private: UniqueAmqpValueHandle m_value; }; + std::ostream& operator<<(std::ostream& os, AMQP_TYPE const value); + std::ostream& operator<<(std::ostream& os, AMQP_VALUE const value); }}}}} // namespace Azure::Core::Amqp::Models::_detail diff --git a/sdk/core/azure-core-amqp/test/ut/CMakeLists.txt b/sdk/core/azure-core-amqp/test/ut/CMakeLists.txt index eea684614e..21cc74ce38 100644 --- a/sdk/core/azure-core-amqp/test/ut/CMakeLists.txt +++ b/sdk/core/azure-core-amqp/test/ut/CMakeLists.txt @@ -30,7 +30,7 @@ add_executable(azure-core-amqp-tests mock_amqp_server.hpp session_tests.cpp transport_tests.cpp -) + "amqp_performative_tests.cpp") add_dependencies(azure-core-amqp-tests Azure::azure-core-amqp) if (MSVC) diff --git a/sdk/core/azure-core-amqp/test/ut/amqp_performative_tests.cpp b/sdk/core/azure-core-amqp/test/ut/amqp_performative_tests.cpp new file mode 100644 index 0000000000..7fb7c8136a --- /dev/null +++ b/sdk/core/azure-core-amqp/test/ut/amqp_performative_tests.cpp @@ -0,0 +1,23 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#include "azure/core/amqp/internal/models/performatives/amqp_detach.hpp" +#include "azure/core/amqp/internal/models/performatives/amqp_transfer.hpp" + +#include + +using namespace Azure::Core::Amqp::Models::_internal; + +class TestPerformatives : public testing::Test { +protected: + void SetUp() override {} + void TearDown() override {} +}; + +TEST_F(TestPerformatives, SimpleCreate) +{ + { + Performatives::AmqpDetach detach; + Performatives::AmqpTransfer transfer; + } +} diff --git a/sdk/core/azure-core-amqp/test/ut/connection_tests.cpp b/sdk/core/azure-core-amqp/test/ut/connection_tests.cpp index e74f73ed1f..e981e6594d 100644 --- a/sdk/core/azure-core-amqp/test/ut/connection_tests.cpp +++ b/sdk/core/azure-core-amqp/test/ut/connection_tests.cpp @@ -51,7 +51,8 @@ namespace Azure { namespace Core { namespace Amqp { namespace Tests { auto socketTransport{Azure::Core::Amqp::Network::_internal::SocketTransportFactory::Create( "localhost", Azure::Core::Amqp::_internal::AmqpPort)}; - Azure::Core::Amqp::_internal::Connection connection(socketTransport, options); + Azure::Core::Amqp::_internal::Connection connection( + socketTransport, options, nullptr, nullptr); } } diff --git a/sdk/core/azure-core-amqp/test/ut/link_tests.cpp b/sdk/core/azure-core-amqp/test/ut/link_tests.cpp index 0a4fef7f99..fea904c267 100644 --- a/sdk/core/azure-core-amqp/test/ut/link_tests.cpp +++ b/sdk/core/azure-core-amqp/test/ut/link_tests.cpp @@ -6,14 +6,17 @@ #include "azure/core/amqp/internal/message_receiver.hpp" #include "azure/core/amqp/internal/message_sender.hpp" #include "azure/core/amqp/internal/models/messaging_values.hpp" +#include "azure/core/amqp/internal/models/performatives/amqp_transfer.hpp" #include "azure/core/amqp/internal/network/amqp_header_detect_transport.hpp" #include "azure/core/amqp/internal/network/socket_listener.hpp" #include "azure/core/amqp/internal/network/socket_transport.hpp" #include "azure/core/amqp/internal/session.hpp" +#include "mock_amqp_server.hpp" #include #include +#include #include #include @@ -50,6 +53,9 @@ namespace Azure { namespace Core { namespace Amqp { namespace Tests { Link link1(session, "MySession", SessionRole::Sender, "Source1", "Target1"); Link link2(session, "MySession", SessionRole::Sender, "Source2", "Target2"); } + + GTEST_LOG_(INFO) << LinkState::Error << LinkState::Invalid << static_cast(92) + << LinkState::HalfAttachedAttachReceived; } TEST_F(TestLinks, LinkProperties) @@ -83,20 +89,28 @@ namespace Azure { namespace Core { namespace Amqp { namespace Tests { link.SetReceiverSettleMode(ReceiverSettleMode::First); EXPECT_EQ(ReceiverSettleMode::First, link.GetReceiverSettleMode()); + EXPECT_ANY_THROW(link.SetReceiverSettleMode(static_cast(023))); + link.SetSenderSettleMode(SenderSettleMode::Settled); EXPECT_EQ(SenderSettleMode::Settled, link.GetSenderSettleMode()); link.SetSenderSettleMode(SenderSettleMode::Unsettled); EXPECT_EQ(SenderSettleMode::Unsettled, link.GetSenderSettleMode()); link.SetSenderSettleMode(SenderSettleMode::Mixed); EXPECT_EQ(SenderSettleMode::Mixed, link.GetSenderSettleMode()); + EXPECT_ANY_THROW(link.SetSenderSettleMode(static_cast(023))); link.SetMaxLinkCredit(95); link.SetAttachProperties("Attach Properties"); + + link.SetDesiredCapabilities("DesiredCapabilities"); + auto val = link.GetDesiredCapabilities(); + + EXPECT_ANY_THROW(link.ResetLinkCredit(92, true)); } { - Link link(session, "MySession", SessionRole::Sender, "MySource", "MyTarget"); + Link link(session, "MySession", SessionRole::Receiver, "MySource", "MyTarget"); Link link2(link); EXPECT_EQ(link.GetInitialDeliveryCount(), link2.GetInitialDeliveryCount()); @@ -108,20 +122,21 @@ namespace Azure { namespace Core { namespace Amqp { namespace Tests { EXPECT_EQ(Azure::Core::Amqp::Models::AmqpValue{"MySource"}, link.GetSource().GetAddress()); EXPECT_EQ(Azure::Core::Amqp::Models::AmqpValue{"MyTarget"}, link.GetTarget().GetAddress()); + + EXPECT_ANY_THROW(link.ResetLinkCredit(92, true)); } } - class LinkSocketListenerEvents : public Azure::Core::Amqp::Network::_detail::SocketListenerEvents, - public Azure::Core::Amqp::_internal::ConnectionEvents, - public Azure::Core::Amqp::_internal::SessionEvents { - Azure::Core::Amqp::Common::_internal::AsyncOperationQueue< - std::shared_ptr> - m_listeningQueue; - Azure::Core::Amqp::Common::_internal::AsyncOperationQueue< - std::unique_ptr> - m_listeningSessionQueue; - Azure::Core::Amqp::Common::_internal::AsyncOperationQueue> - m_receiveLinkQueue; + class LinkSocketListenerEvents + : public Azure::Core::Amqp::Network::_detail::SocketListenerEvents, + public Azure::Core::Amqp::_internal::ConnectionEvents, + public Azure ::Core ::Amqp ::_internal ::ConnectionEndpointEvents, + public Azure::Core::Amqp::_internal::SessionEvents { + Azure::Core::Amqp::Common::_internal::AsyncOperationQueue m_listeningQueue; + Azure::Core::Amqp::Common::_internal::AsyncOperationQueue m_listeningSessionQueue; + Azure::Core::Amqp::Common::_internal::AsyncOperationQueue m_receiveLinkQueue; + std::shared_ptr m_link; + std::unique_ptr m_session; std::shared_ptr m_connection; virtual void OnSocketAccepted( @@ -135,9 +150,9 @@ namespace Azure { namespace Core { namespace Amqp { namespace Tests { options.ContainerId = "connectionId"; options.EnableTrace = true; m_connection = std::make_shared( - amqpTransport, options, this); + amqpTransport, options, this, this); m_connection->Listen(); - m_listeningQueue.CompleteOperation(m_connection); + m_listeningQueue.CompleteOperation(true); } virtual void OnConnectionStateChanged( @@ -158,8 +173,9 @@ namespace Azure { namespace Core { namespace Amqp { namespace Tests { auto listeningSession = std::make_unique( connection.CreateSession(endpoint, sessionOptions, this)); listeningSession->Begin(); + m_session = std::move(listeningSession); - m_listeningSessionQueue.CompleteOperation(std::move(listeningSession)); + m_listeningSessionQueue.CompleteOperation(true); return true; } @@ -169,44 +185,75 @@ namespace Azure { namespace Core { namespace Amqp { namespace Tests { Azure::Core::Amqp::_internal::Session const& session, Azure::Core::Amqp::_internal::LinkEndpoint& newLinkInstance, std::string const& name, - Azure::Core::Amqp::_internal::SessionRole, + Azure::Core::Amqp::_internal::SessionRole sessionRole, Azure::Core::Amqp::Models::AmqpValue const& source, Azure::Core::Amqp::Models::AmqpValue const& target, Azure::Core::Amqp::Models::AmqpValue const&) override { GTEST_LOG_(INFO) << "OnLinkAttached - Link attached to session."; - auto newLink = std::make_unique( - session, - newLinkInstance, - name, - Azure::Core::Amqp::_internal::SessionRole::Receiver, - static_cast(source), - static_cast(target)); - // newLink->SetReceiverSettleMode(Azure::Core::Amqp::ReceiverSettleMode::First); - m_receiveLinkQueue.CompleteOperation(std::move(newLink)); + std::unique_ptr newLink; + if (sessionRole == SessionRole::Sender) + { + newLink = std::make_unique( + session, + newLinkInstance, + name, + Azure::Core::Amqp::_internal::SessionRole::Receiver, + source, + target); + } + else + { + newLink = std::make_unique( + session, + newLinkInstance, + name, + Azure::Core::Amqp::_internal::SessionRole::Sender, + source, + target); + } + m_link = std::move(newLink); + m_receiveLinkQueue.CompleteOperation(true); return true; } public: LinkSocketListenerEvents() {} - std::shared_ptr WaitForConnection( + bool WaitForConnection( Azure::Core::Amqp::Network::_detail::SocketListener const& listener, Azure::Core::Context const& context) { auto result = m_listeningQueue.WaitForPolledResult(context, listener); - return std::move(std::get<0>(*result)); + return std::get<0>(*result); } - std::unique_ptr WaitForSession(Azure::Core::Context const& context) + bool WaitForSession(Azure::Core::Context const& context) { - auto result = m_listeningSessionQueue.WaitForPolledResult(context, *m_connection); - return std::move(std::get<0>(*result)); + auto result = m_listeningSessionQueue.WaitForResult(context); + return std::get<0>(*result); } - std::unique_ptr WaitForLink( - Azure::Core::Context const& context) + bool WaitForLink(Azure::Core::Context const& context) { - auto result = m_receiveLinkQueue.WaitForPolledResult(context, *m_connection); - return std::move(std::get<0>(*result)); + auto result = m_receiveLinkQueue.WaitForResult(context); + return std::get<0>(*result); + } + + void Cleanup() + { + if (m_link) + { + m_link.reset(); + } + if (m_session) + { + m_session->End(); + m_session.reset(); + } + if (m_connection) + { + m_connection->Close(); + m_connection.reset(); + } } }; @@ -229,14 +276,183 @@ namespace Azure { namespace Core { namespace Amqp { namespace Tests { Link link(session, "MySession", SessionRole::Sender, "MySource", "MyTarget"); link.Attach(); - Azure::Core::Amqp::Models::AmqpValue data; - link.Detach(false, {}, {}, data); + EXPECT_TRUE(events.WaitForConnection(listener, {})); + EXPECT_TRUE(events.WaitForSession({})); - // auto listeningConnection = listener.WaitForConnection(); - // auto listeningSession = listeningConnection->WaitForSession(); - // auto listeningLink = listeningSession->WaitForLink(); + EXPECT_TRUE(events.WaitForLink({})); + link.Detach(false, {}, {}, {}); } + events.Cleanup(); listener.Stop(); } + + TEST_F(TestLinks, LinkAttachDetachMultipleOneSession) + { + class MySessionListener final : public MessageTests::MockServiceEndpoint { + public: + MySessionListener(MessageTests::MockServiceEndpointOptions const& options) + : MockServiceEndpoint("MyTarget", options) + { + } + void MessageReceived( + std::string const& linkName, + std::shared_ptr const& message) override + { + GTEST_LOG_(INFO) << "Message received on link " << linkName << ": " << *message; + } + }; + + MessageTests::AmqpServerMock server; + auto sessionListener{ + std::make_shared(MessageTests::MockServiceEndpointOptions{})}; + server.AddServiceEndpoint(sessionListener); + server.EnableTrace(false); + + // Create a connection + ConnectionOptions connectionOptions; + connectionOptions.Port = server.GetPort(); + connectionOptions.EnableTrace = true; + Connection connection("localhost", nullptr, connectionOptions); + server.StartListening(); + + Session session{connection.CreateSession()}; + + class ClientLinkEvents : public Azure::Core::Amqp::_detail::LinkEvents { + public: + LinkState WaitForLink(Azure::Core::Context const& context) + { + auto result = m_linkStateQueue.WaitForResult(context); + if (!result) + { + throw Azure::Core::OperationCancelledException("Canceled link wait."); + } + return std::get<0>(*result); + } + + private: + void OnLinkFlowOn(Link const& link) override + { + GTEST_LOG_(INFO) << "Link Flow On on link " << link.GetName(); + } + Models::AmqpValue OnTransferReceived( + Link const& link, + Azure::Core::Amqp::Models::_internal::Performatives::AmqpTransfer transfer, + uint32_t payloadSize, + const unsigned char*) override + { + GTEST_LOG_(INFO) << "OnTransferReceived(" << link.GetName() << "). Transfer : " << transfer + << "Payload size: " << payloadSize; + return Azure::Core::Amqp::Models::AmqpValue{}; + } + void OnLinkStateChanged( + Azure::Core::Amqp::_detail::Link const& link, + LinkState newLinkState, + LinkState previousLinkState) override + { + GTEST_LOG_(INFO) << "Link " << link.GetName() + << ", State Changed. OldState: " << previousLinkState + << " NewState: " << newLinkState; + m_linkStateQueue.CompleteOperation(newLinkState); + } + + Azure::Core::Amqp::Common::_internal::AsyncOperationQueue m_linkStateQueue; + }; + + Link keepAliveLink{ + session, "KeepConnectionAlive", SessionRole::Receiver, "MyTarget", "TestReceiver"}; + keepAliveLink.Attach(); + + { + ClientLinkEvents linkEvents; + Link link(session, "MySession", SessionRole::Sender, "MySource", "MyTarget", &linkEvents); + link.Attach(); + + LinkState linkState; + + // Iterate until the state changes to Attached. + do + { + linkState = linkEvents.WaitForLink({}); + } while (linkState != LinkState::Attached); + + Models::AmqpMessage message; + message.SetBody("Hello"); + + link.Transfer(Models::AmqpMessage::Serialize(message), {}); + + Azure::Core::Amqp::Models::AmqpValue data; + link.Detach(true, {}, {}, data); + + // Iterate until the state changes to Detached. + do + { + linkState = linkEvents.WaitForLink({}); + } while (linkState != LinkState::Detached); + } + { + ClientLinkEvents linkEvents; + Link link(session, "MySession2", SessionRole::Sender, "MySource", "MyTarget", &linkEvents); + link.Attach(); + + LinkState linkState; + + // Iterate until the state changes to Attached. + do + { + linkState = linkEvents.WaitForLink({}); + } while (linkState != LinkState::Attached); + + Azure::Core::Amqp::Models::AmqpValue data; + link.Detach(true, {}, {}, data); + // Iterate until the state changes to Attached. + do + { + linkState = linkEvents.WaitForLink({}); + } while (linkState != LinkState::Detached); + } + { + constexpr const size_t linkCount = 20; + + std::vector links; + std::vector> linkEvents; + for (size_t i = 0; i < linkCount; i += 1) + { + // Create 100 links on the session. + linkEvents.push_back(std::make_unique()); + links.push_back(Link{ + session, + "MySession " + std::to_string(i), + SessionRole::Sender, + "MySource", + "MyTarget", + linkEvents.back().get()}); + } + for (size_t i = 0; i < linkCount; i += 1) + { + links[i].Attach(); // Iterate until the state changes to Attached. + LinkState linkState; + + // Wait for the links to attach. + do + { + linkState = linkEvents[i]->WaitForLink({}); + } while (linkState != LinkState::Attached); + } + for (size_t i = 0; i < linkCount; i += 1) + { + links[i].Detach(true, "", "", Models::AmqpValue{}); + // Iterate until the state changes to Detached. + LinkState linkState; + do + { + linkState = linkEvents[i]->WaitForLink({}); + } while (linkState != LinkState::Detached); + } + } + + keepAliveLink.Detach(true, "", "", {}); + + server.StopListening(); + } #endif // defined(AZ_PLATFORM_MAC) }}}} // namespace Azure::Core::Amqp::Tests diff --git a/sdk/core/azure-core-amqp/test/ut/management_tests.cpp b/sdk/core/azure-core-amqp/test/ut/management_tests.cpp index 101aaedf71..922c02c9fa 100644 --- a/sdk/core/azure-core-amqp/test/ut/management_tests.cpp +++ b/sdk/core/azure-core-amqp/test/ut/management_tests.cpp @@ -103,7 +103,8 @@ namespace Azure { namespace Core { namespace Amqp { namespace Tests { } return MockServiceEndpoint::OnMessageReceived(receiver, incomingMessage); } - void MessageReceived(std::shared_ptr const& incomingMessage) override + void MessageReceived(std::string const&, std::shared_ptr const& incomingMessage) + override { if (incomingMessage->ApplicationProperties.at("operation") == "Test") { @@ -234,11 +235,13 @@ namespace Azure { namespace Core { namespace Amqp { namespace Tests { } private: - void MessageReceived(std::shared_ptr const& incomingMessage) override + void MessageReceived( + std::string const& linkName, + std::shared_ptr const& message) override { - // Do nothing. - GTEST_LOG_(INFO) << "NullResponseManagementServiceEndpoint::MessageReceived(" - << incomingMessage << ") called."; + GTEST_LOG_(INFO) + << "NullResponseManagementServiceEndpoint::MessageReceived received on link " + << linkName << ": " << *message; } }; } // namespace diff --git a/sdk/core/azure-core-amqp/test/ut/message_sender_receiver.cpp b/sdk/core/azure-core-amqp/test/ut/message_sender_receiver.cpp index 6b17ec50f9..97f6439e78 100644 --- a/sdk/core/azure-core-amqp/test/ut/message_sender_receiver.cpp +++ b/sdk/core/azure-core-amqp/test/ut/message_sender_receiver.cpp @@ -57,7 +57,7 @@ namespace Azure { namespace Core { namespace Amqp { namespace Tests { << _internal::MessageReceiverState::Opening << _internal::MessageReceiverState::Open << _internal::MessageReceiverState::Error; - EXPECT_ANY_THROW(GTEST_LOG_(INFO) << static_cast<_internal::MessageReceiverState>(5993)); + GTEST_LOG_(INFO) << static_cast<_internal::MessageReceiverState>(5993); } TEST_F(TestMessageSendReceive, ReceiverProperties) { // Create a connection @@ -100,7 +100,7 @@ namespace Azure { namespace Core { namespace Amqp { namespace Tests { << _internal::MessageSenderState::Idle << _internal::MessageSenderState::Opening << _internal::MessageSenderState::Open << _internal::MessageSenderState::Error; - EXPECT_ANY_THROW(GTEST_LOG_(INFO) << static_cast<_internal::MessageSenderState>(5993)); + GTEST_LOG_(INFO) << static_cast<_internal::MessageSenderState>(5993); } TEST_F(TestMessageSendReceive, SenderProperties) { // Create a connection @@ -137,7 +137,8 @@ namespace Azure { namespace Core { namespace Amqp { namespace Tests { MessageReceiverState newState, MessageReceiverState oldState) override { - GTEST_LOG_(INFO) << "MessageReceiverEvents::OnMessageReceiverSTateChanged."; + GTEST_LOG_(INFO) << "MessageReceiverEvents::OnMessageReceiverStateChanged: " << newState + << "->" << oldState; (void)receiver; (void)newState; (void)oldState; @@ -150,6 +151,7 @@ namespace Azure { namespace Core { namespace Amqp { namespace Tests { return Models::AmqpValue(); } void OnMessageReceiverDisconnected( + Azure::Core::Amqp::_internal::MessageReceiver const&, Azure::Core::Amqp::Models::_internal::AmqpError const& error) override { GTEST_LOG_(INFO) << "Message receiver disconnected: " << error; @@ -222,7 +224,9 @@ namespace Azure { namespace Core { namespace Amqp { namespace Tests { << " NewState: " << std::to_string(static_cast(newState)); (void)sender; } - virtual void OnMessageSenderDisconnected(Models::_internal::AmqpError const& error) override + virtual void OnMessageSenderDisconnected( + MessageSender const&, + Models::_internal::AmqpError const& error) override { GTEST_LOG_(INFO) << "MessageSenderEvents::OnMessageSenderDisconnected. Error: " << error; }; @@ -281,9 +285,10 @@ namespace Azure { namespace Core { namespace Amqp { namespace Tests { private: void MessageReceived( + std::string const& linkName, std::shared_ptr const& message) override { - GTEST_LOG_(INFO) << "Message received: " << *message; + GTEST_LOG_(INFO) << "Message received on link " << linkName << ": " << *message; } }; @@ -321,7 +326,9 @@ namespace Azure { namespace Core { namespace Amqp { namespace Tests { GTEST_LOG_(INFO) << "MessageSenderEvents::OnMessageSenderStateChanged. Old State: " << oldState << " New State: " << newState; } - virtual void OnMessageSenderDisconnected(Models::_internal::AmqpError const& error) override + virtual void OnMessageSenderDisconnected( + MessageSender const&, + Models::_internal::AmqpError const& error) override { GTEST_LOG_(INFO) << "MessageSenderEvents::OnMessageSenderDisconnected. Error: " << error; }; @@ -365,9 +372,10 @@ namespace Azure { namespace Core { namespace Amqp { namespace Tests { private: void MessageReceived( + std::string const& linkName, std::shared_ptr const& message) override { - GTEST_LOG_(INFO) << "Message received: " << *message; + GTEST_LOG_(INFO) << "Message received on link " << linkName << ": " << *message; } }; @@ -432,9 +440,10 @@ namespace Azure { namespace Core { namespace Amqp { namespace Tests { private: void MessageReceived( + std::string const& linkName, std::shared_ptr const& message) override { - GTEST_LOG_(INFO) << "Message received: " << *message; + GTEST_LOG_(INFO) << "Message received on link " << linkName << ": " << *message; } }; MessageTests::AmqpServerMock server; @@ -511,9 +520,10 @@ namespace Azure { namespace Core { namespace Amqp { namespace Tests { private: void MessageReceived( + std::string const& linkName, std::shared_ptr const& message) override { - GTEST_LOG_(INFO) << "Message received: " << *message; + GTEST_LOG_(INFO) << "Message received on link " << linkName << ": " << *message; } }; MessageTests::AmqpServerMock server; @@ -599,9 +609,10 @@ namespace Azure { namespace Core { namespace Amqp { namespace Tests { } void MessageReceived( + std::string const& linkName, std::shared_ptr const& message) override { - GTEST_LOG_(INFO) << "Message received: " << *message; + GTEST_LOG_(INFO) << "Message received on link " << linkName << ": " << *message; } }; @@ -718,9 +729,10 @@ namespace Azure { namespace Core { namespace Amqp { namespace Tests { } } void MessageReceived( + std::string const& linkName, std::shared_ptr const& message) override { - GTEST_LOG_(INFO) << "Message received: " << *message; + GTEST_LOG_(INFO) << "Message received on link " << linkName << ": " << *message; } }; @@ -845,9 +857,10 @@ namespace Azure { namespace Core { namespace Amqp { namespace Tests { } } void MessageReceived( + std::string const& linkName, std::shared_ptr const& message) override { - GTEST_LOG_(INFO) << "Message received: " << *message; + GTEST_LOG_(INFO) << "Message received on link " << linkName << ": " << *message; } }; diff --git a/sdk/core/azure-core-amqp/test/ut/mock_amqp_server.hpp b/sdk/core/azure-core-amqp/test/ut/mock_amqp_server.hpp index c43f13bd07..6ead71ed81 100644 --- a/sdk/core/azure-core-amqp/test/ut/mock_amqp_server.hpp +++ b/sdk/core/azure-core-amqp/test/ut/mock_amqp_server.hpp @@ -29,7 +29,6 @@ namespace Azure { namespace Core { namespace Amqp { namespace Tests { extern uint16_t FindAvailableSocket(); namespace MessageTests { -#if NEW_MOCK_SERVER static std::ostream& operator<<( std::ostream& os, Azure::Core::Amqp::_internal::SessionRole role) @@ -82,41 +81,44 @@ namespace Azure { namespace Core { namespace Amqp { namespace Tests { if (role == Azure::Core::Amqp::_internal::SessionRole::Receiver) { GTEST_LOG_(INFO) << "Role is receiver, create sender."; - if (!m_sender) + if (!HasMessageSender(linkName)) { - GTEST_LOG_(INFO) << "No sender found, create new."; + GTEST_LOG_(INFO) << "No sender found, create new sender for " << linkName; Azure::Core::Amqp::_internal::MessageSenderOptions senderOptions; senderOptions.EnableTrace = m_enableTrace; senderOptions.Name = linkName; senderOptions.MessageSource = source; senderOptions.InitialDeliveryCount = 0; - m_sender = std::make_unique( + m_sender[linkName] = std::make_unique( session.CreateMessageSender(linkEndpoint, target, senderOptions, this)); - m_sender->Open(); + m_sender[linkName]->Open(); } else { - GTEST_LOG_(INFO) << "Sender already created for target " << target; - throw std::runtime_error( - "Sender already created for target " - + static_cast(target.GetAddress())); + GTEST_LOG_(INFO) << "Sender already created for link name " << linkName << " on target " + << target; + Models::_internal::AmqpError error; + error.Condition = Models::_internal::AmqpErrorCondition::EntityAlreadyExists; + error.Description = "Link already exists."; + session.SendDetach(linkEndpoint, true, error); + return false; } } else if (role == Azure::Core::Amqp::_internal::SessionRole::Sender) { GTEST_LOG_(INFO) << "Role is sender, create receiver."; - if (!m_receiver) + if (!HasMessageReceiver(linkName)) { - GTEST_LOG_(INFO) << "No receiver found, create new."; + GTEST_LOG_(INFO) << "No receiver found, create new receiver for " << linkName; Azure::Core::Amqp::_internal::MessageReceiverOptions receiverOptions; receiverOptions.EnableTrace = m_enableTrace; receiverOptions.Name = linkName; receiverOptions.MessageTarget = target; receiverOptions.InitialDeliveryCount = 0; - m_receiver = std::make_unique( + m_receiver[linkName] = std::make_unique( session.CreateMessageReceiver(linkEndpoint, source, receiverOptions, this)); GTEST_LOG_(INFO) << "Open new message receiver."; - m_receiver->Open(); + m_receiver[linkName]->Open(); } else { @@ -137,47 +139,108 @@ namespace Azure { namespace Core { namespace Amqp { namespace Tests { // Spin up a thread to process incoming messages. if (!m_serverThread.joinable()) { - m_serverThread = std::thread([this]() { MessageLoop(); }); + m_serverThread = std::thread([this]() { + MessageLoop(); + GTEST_LOG_(INFO) << "Exiting message loop for " << m_name; + }); } return true; } void StopProcessing() { + GTEST_LOG_(INFO) << "Stop processing for " << m_name; if (m_serverThread.joinable()) { m_listenerContext.Cancel(); m_serverThread.join(); } - if (m_receiver) + if (!m_receiver.empty()) { - m_receiver->Close(); + for (const auto& receiver : m_receiver) + { + receiver.second->Close(); + } } - if (m_sender) + if (!m_sender.empty()) { - m_sender->Close(); + for (const auto& sender : m_sender) + { + sender.second->Close(); + } } } protected: - bool HasMessageSender() const { return static_cast(m_sender); } - Azure::Core::Amqp::_internal::MessageSender& GetMessageSender() const + bool HasMessageSender(std::string const& linkName = {}) const + { + if (linkName.empty()) + { + if (m_sender.size() == 0) + { + return false; + } + if (m_sender.size() != 1) + { + throw std::runtime_error("Ambiguous sender link name."); + } + return true; + } + return (m_sender.find(linkName) != m_sender.end()); + } + Azure::Core::Amqp::_internal::MessageSender& GetMessageSender( + std::string const& linkName = {}) const { - if (!m_sender) + if (linkName.empty()) + { + if (m_sender.size() != 1) + { + throw std::runtime_error("Ambiguous sender link name."); + } + return *m_sender.begin()->second; + } + auto sender = m_sender.find(linkName); + if (sender == m_sender.end()) { throw std::runtime_error("Message sender not created."); } - return *m_sender; + return *(sender->second); } - Azure::Core::Amqp::_internal::MessageReceiver& GetMessageReceiver() const + bool HasMessageReceiver(std::string const& linkName = {}) const { - if (!m_receiver) + if (linkName.empty()) + { + if (m_receiver.size() == 0) + { + return false; + } + if (m_receiver.size() != 1) + { + throw std::runtime_error("Ambiguous sender link name."); + } + return true; + } + return (m_receiver.find(linkName) != m_receiver.end()); + } + Azure::Core::Amqp::_internal::MessageReceiver& GetMessageReceiver( + std::string const& linkName = {}) const + { + if (linkName.empty()) + { + if (m_receiver.size() != 1) + { + throw std::runtime_error("Ambiguous receiver link name."); + } + return *m_receiver.begin()->second; + } + auto receiver = m_receiver.find(linkName); + if (receiver == m_receiver.end()) { throw std::runtime_error("Message receiver not created."); } - return *m_receiver; + return *(receiver->second); } virtual void Poll() const {} @@ -189,18 +252,20 @@ namespace Azure { namespace Core { namespace Amqp { namespace Tests { bool m_enableTrace{true}; std::string m_name; std::thread m_serverThread; - std::unique_ptr m_sender; - std::unique_ptr m_receiver; - - Azure::Core::Amqp::Common::_internal::AsyncOperationQueue< - std::shared_ptr> - m_messageQueue; - Azure::Core::Amqp::Common::_internal::AsyncOperationQueue + std::map> m_sender; + std::map> + m_receiver; + + Azure::Core::Amqp::Common::_internal:: + AsyncOperationQueue> + m_messageQueue; + Azure::Core::Amqp::Common::_internal::AsyncOperationQueue m_messageSenderDisconnectedQueue; - Azure::Core::Amqp::Common::_internal::AsyncOperationQueue + Azure::Core::Amqp::Common::_internal::AsyncOperationQueue m_messageReceiverDisconnectedQueue; virtual void MessageReceived( + std::string const& linkName, std::shared_ptr const& message) = 0; @@ -213,27 +278,34 @@ namespace Azure { namespace Core { namespace Amqp { namespace Tests { if (message) { - GTEST_LOG_(INFO) << "Received message: " << *std::get<0>(*message); + GTEST_LOG_(INFO) << "Received message on link " << std::get<0>(*message) << ": " + << *std::get<1>(*message); - MessageReceived(std::get<0>(*message)); + MessageReceived(std::get<0>(*message), std::get<1>(*message)); } auto senderDisconnected = m_messageSenderDisconnectedQueue.TryWaitForResult(); if (senderDisconnected) { - GTEST_LOG_(INFO) << "Sender disconnected: " << std::get<0>(*senderDisconnected); - m_sender->Close(m_listenerContext); - m_sender.reset(); + auto senderName = std::get<0>(*senderDisconnected); + GTEST_LOG_(INFO) << "Sender disconnected: " << senderName; + std::unique_ptr sender{ + m_sender[senderName].release()}; + m_sender.erase(senderName); + sender->Close(m_listenerContext); } auto receiverDisconnected = m_messageReceiverDisconnectedQueue.TryWaitForResult(); if (receiverDisconnected) { - GTEST_LOG_(INFO) << "Receiver disconnected: " << std::get<0>(*receiverDisconnected); - m_receiver->Close(m_listenerContext); - m_receiver.reset(); + auto receiverName = std::get<0>(*receiverDisconnected); + GTEST_LOG_(INFO) << "Receiver disconnected: " << receiverName; + std::unique_ptr receiver{ + m_receiver[receiverName].release()}; + m_receiver.erase(receiverName); + receiver->Close(m_listenerContext); } - if (!m_receiver && !m_sender) + if (m_receiver.empty() && m_sender.empty()) { GTEST_LOG_(INFO) << "No more links, exiting message loop."; break; @@ -256,20 +328,21 @@ namespace Azure { namespace Core { namespace Amqp { namespace Tests { protected: Azure::Core::Amqp::Models::AmqpValue OnMessageReceived( - Azure::Core::Amqp::_internal::MessageReceiver const&, + Azure::Core::Amqp::_internal::MessageReceiver const& receiver, std::shared_ptr const& message) override { GTEST_LOG_(INFO) << "MockServiceEndpoint(" << m_name << ") Received a message " << *message; - m_messageQueue.CompleteOperation(message); + m_messageQueue.CompleteOperation(receiver.GetLinkName(), message); return Azure::Core::Amqp::Models::_internal::Messaging::DeliveryAccepted(); } private: virtual void OnMessageReceiverDisconnected( + Azure::Core::Amqp::_internal::MessageReceiver const& receiver, Azure::Core::Amqp::Models::_internal::AmqpError const& error) override { GTEST_LOG_(INFO) << "Message receiver disconnected: " << error << std::endl; - m_messageReceiverDisconnectedQueue.CompleteOperation(true); + m_messageReceiverDisconnectedQueue.CompleteOperation(receiver.GetLinkName()); } // Inherited via MessageSenderEvents @@ -284,10 +357,11 @@ namespace Azure { namespace Core { namespace Amqp { namespace Tests { } void OnMessageSenderDisconnected( + Azure::Core::Amqp::_internal::MessageSender const& sender, Azure::Core::Amqp::Models::_internal::AmqpError const& error) override { GTEST_LOG_(INFO) << "Message Sender Disconnected: Error: " << error; - m_messageSenderDisconnectedQueue.CompleteOperation(true); + m_messageSenderDisconnectedQueue.CompleteOperation(sender.GetLinkName()); } }; @@ -305,6 +379,7 @@ namespace Azure { namespace Core { namespace Amqp { namespace Tests { bool m_forceCbsError{false}; void MessageReceived( + std::string const&, std::shared_ptr const& message) override { if (IsCbsMessage(message)) @@ -440,6 +515,7 @@ namespace Azure { namespace Core { namespace Amqp { namespace Tests { class AmqpServerMock : public Azure::Core::Amqp::Network::_detail::SocketListenerEvents, public Azure::Core::Amqp::_internal::ConnectionEvents, + public Azure::Core::Amqp::_internal::ConnectionEndpointEvents, public Azure::Core::Amqp::_internal::SessionEvents { public: AmqpServerMock( @@ -523,7 +599,10 @@ namespace Azure { namespace Core { namespace Amqp { namespace Tests { // Cancel the listener context, which will cause any WaitForXxx calls to exit. m_listenerContext.Cancel(); - m_serverThread.join(); + if (m_serverThread.joinable()) + { + m_serverThread.join(); + } for (auto& serviceEndpoint : m_serviceEndpoints) { @@ -532,6 +611,10 @@ namespace Azure { namespace Core { namespace Amqp { namespace Tests { if (!m_sessions.empty()) { + for (const auto& session : m_sessions) + { + session->End(); + } // Note: clearing the sessions list destroys the session and calls session_end always, so // it does not need to be called here. m_sessions.clear(); @@ -590,7 +673,7 @@ namespace Azure { namespace Core { namespace Amqp { namespace Tests { options.IdleTimeout = std::chrono::minutes(2); options.EnableTrace = m_enableTrace; auto newConnection = std::make_shared( - amqpTransport, options, this); + amqpTransport, options, this, this); m_connections.push_back(newConnection); newConnection->Listen(); } @@ -670,543 +753,5 @@ namespace Azure { namespace Core { namespace Amqp { namespace Tests { return false; } }; -#else - class AmqpServerMock : public Azure::Core::Amqp::Network::_detail::SocketListenerEvents, - public Azure::Core::Amqp::_internal::ConnectionEvents, - public Azure::Core::Amqp::_internal::SessionEvents, - public Azure::Core::Amqp::_internal::MessageReceiverEvents, - public Azure::Core::Amqp::_internal::MessageSenderEvents { - public: - struct MessageLinkComponents - { - std::unique_ptr LinkSender; - std::unique_ptr LinkReceiver; - Azure::Core::Amqp::Common::_internal::AsyncOperationQueue< - std::shared_ptr> - MessageQueue; - Azure::Core::Amqp::Common::_internal::AsyncOperationQueue MessageReceiverPresentQueue; - Azure::Core::Amqp::Common::_internal::AsyncOperationQueue MessageSenderPresentQueue; - }; - - AmqpServerMock( - std::string name = testing::UnitTest::GetInstance()->current_test_info()->name()) - : m_connectionId{"Mock Server for " + name}, m_testPort{FindAvailableSocket()} - { - } - AmqpServerMock( - uint16_t listeningPort, - std::string name = testing::UnitTest::GetInstance()->current_test_info()->name()) - : m_connectionId{"Mock Server for " + name}, m_testPort{listeningPort} - { - } - - virtual void Poll() const - { - if (!m_connectionValid) - { - throw std::runtime_error("Polling with invalid connection."); - } - } - - bool WaitForConnection(Azure::Core::Context const& context = {}) - { - GTEST_LOG_(INFO) << "Wait for connection to be established on Mock Server."; - auto result = m_externalConnectionQueue.WaitForResult(context); - if (!result) - { - throw std::runtime_error("Connection not received"); - } - GTEST_LOG_(INFO) << "Connection has been established."; - return result != nullptr; - } - - private: - bool WaitForConnection( - Azure::Core::Amqp::Network::_detail::SocketListener const& listener, - Azure::Core::Context const& context = {}) - { - auto result = m_connectionQueue.WaitForPolledResult(context, listener); - if (result) - { - m_connectionValid = true; - m_externalConnectionQueue.CompleteOperation(true); - } - return result != nullptr; - } - bool WaitForMessageReceiver( - std::string const& nodeName, - Azure::Core::Context const& context = {}) - { - auto result - = m_linkMessageQueues[nodeName].MessageReceiverPresentQueue.WaitForResult(context); - return result != nullptr; - } - bool WaitForMessageSender( - std::string const& nodeName, - Azure::Core::Context const& context = {}) - { - auto result - = m_linkMessageQueues[nodeName].MessageSenderPresentQueue.WaitForResult(context); - return result != nullptr; - } - - std::shared_ptr TryWaitForMessage( - std::string const& nodeName) - { - // Poll for completion on both the mock server and the connection, that ensures that - // we can implement unsolicited sends from the Poll function. - auto result = m_linkMessageQueues[nodeName].MessageQueue.TryWaitForResult(); - if (result) - { - return std::move(std::get<0>(*result)); - } - else - { - Poll(); - return nullptr; - } - } - - std::shared_ptr WaitForMessage( - std::string const& nodeName) - { - // Poll for completion on both the mock server and the connection, that ensures that - // we can implement unsolicited sends from the Poll function. - auto result - = m_linkMessageQueues[nodeName].MessageQueue.WaitForResult(m_listenerContext, *this); - if (result) - { - return std::move(std::get<0>(*result)); - } - else - { - return nullptr; - } - } - - /** @brief Override for non CBS message receive operations which allows a specialization - * to customize the behavior for received messages. - */ - virtual void MessageReceived( - std::string const&, - MessageLinkComponents const&, - std::shared_ptr const&) const {}; - - virtual void MessageLoop( - std::string const& nodeName, - MessageLinkComponents const& linkComponents) - { - auto message = TryWaitForMessage(nodeName); - if (message) - { - GTEST_LOG_(INFO) << "Received message: " << *message; - if (nodeName == "$cbs" && IsCbsMessage(message)) - { - ProcessCbsMessage(linkComponents, message); - } - else - { - MessageReceived(nodeName, linkComponents, message); - } - } - std::this_thread::yield(); - }; - - public: - uint16_t GetPort() const { return m_testPort; } - Azure::Core::Context& GetListenerContext() { return m_listenerContext; } - - void StartListening() - { - // Start the mock AMQP server which will be used to receive the connect open. - // Ensure that the thread is started before we start using the message sender. - std::mutex threadRunningMutex; - std::condition_variable threadStarted; - bool running = false; - - m_serverThread = std::thread([this, &threadStarted, &running]() { - Azure::Core::Amqp::Network::_detail::SocketListener listener(GetPort(), this); - try - { - GTEST_LOG_(INFO) << "Start test listener on port " << GetPort(); - listener.Start(); - GTEST_LOG_(INFO) << "listener started"; - running = true; - threadStarted.notify_one(); - - GTEST_LOG_(INFO) << "Wait for connection on listener."; - if (!WaitForConnection(listener, m_listenerContext)) - { - GTEST_LOG_(INFO) << "Cancelling thread."; - return; - } - while (!m_listenerContext.IsCancelled()) - { - std::this_thread::yield(); - for (const auto& val : m_linkMessageQueues) - { - MessageLoop(val.first, val.second); - } - } - } - catch (std::exception& ex) - { - GTEST_LOG_(ERROR) << "Exception " << ex.what() << " thrown in listener thread."; - } - listener.Stop(); - }); - - // Wait until our running thread is actually listening before we return. - GTEST_LOG_(INFO) << "Wait 10 seconds for listener to start."; - std::unique_lock waitForThreadStart(threadRunningMutex); - threadStarted.wait_until( - waitForThreadStart, - std::chrono::system_clock::now() + std::chrono::seconds(10), - [&running]() { return running == true; }); - GTEST_LOG_(INFO) << "Listener running."; - } - - void StopListening() - { - GTEST_LOG_(INFO) << "Stop listening"; - // Cancel the listener context, which will cause any WaitForXxx calls to exit. - - m_listenerContext.Cancel(); - m_serverThread.join(); - for (auto& val : m_linkMessageQueues) - { - if (val.second.LinkSender) - { - val.second.LinkSender->Close(); - val.second.LinkSender.reset(); - } - if (val.second.LinkReceiver) - { - val.second.LinkReceiver->Close(); - val.second.LinkReceiver.reset(); - } - } - if (m_session) - { - // Note: resetting the m_session calls session_end always, so it does not need to be - // called here. - m_session.reset(); - } - if (m_connection) - { - m_connection->Close(); - m_connection.reset(); - } - } - - void ForceCbsError(bool forceError) { m_forceCbsError = forceError; } - - void EnableTrace(bool enableTrace) { m_enableTrace = enableTrace; } - - private: - std::shared_ptr m_connection; - bool m_connectionValid{false}; - bool m_enableTrace{true}; - std::shared_ptr m_session; - - Azure::Core::Amqp::Common::_internal::AsyncOperationQueue m_connectionQueue; - Azure::Core::Amqp::Common::_internal::AsyncOperationQueue m_externalConnectionQueue; - - std::string m_connectionId; - std::thread m_serverThread; - std::uint16_t m_testPort; - bool m_forceCbsError{false}; - - protected: - // For each incoming message source, we create a queue of messages intended for that - // message source. - // - // Each message queue is keyed by the message-id. - std::map m_linkMessageQueues; - Azure::Core::Context m_listenerContext; // Used to cancel the listener if necessary. - - bool IsCbsMessage(std::shared_ptr const& message) - { - if (!message->ApplicationProperties.empty()) - { - Azure::Core::Amqp::Models::AmqpValue operation - = message->ApplicationProperties.at("operation"); - Azure::Core::Amqp::Models::AmqpValue type = message->ApplicationProperties.at("type"); - - // If we're processing a put-token message, then we should get a "type" and "name" - // value. - EXPECT_EQ(operation.GetType(), Azure::Core::Amqp::Models::AmqpValueType::String); - if (static_cast(operation) == "put-token") - { - return true; - } - else if (static_cast(operation) == "delete-token") - { - return true; - } - } - return false; - } - - void ProcessCbsMessage( - MessageLinkComponents const& linkComponents, - std::shared_ptr const& message) - { - Azure::Core::Amqp::Models::AmqpValue operation - = message->ApplicationProperties.at("operation"); - Azure::Core::Amqp::Models::AmqpValue type = message->ApplicationProperties.at("type"); - Azure::Core::Amqp::Models::AmqpValue name = message->ApplicationProperties.at("name"); - // If we're processing a put-token message, then we should get a "type" and "name" - // value. - EXPECT_EQ(operation.GetType(), Azure::Core::Amqp::Models::AmqpValueType::String); - if (static_cast(operation) == "put-token") - { - EXPECT_EQ(type.GetType(), Azure::Core::Amqp::Models::AmqpValueType::String); - EXPECT_EQ(name.GetType(), Azure::Core::Amqp::Models::AmqpValueType::String); - // The body of a put-token operation MUST be an AMQP AmqpValue. - EXPECT_EQ(message->BodyType, Azure::Core::Amqp::Models::MessageBodyType::Value); - - // Respond to the operation. - Azure::Core::Amqp::Models::AmqpMessage response; - Azure::Core::Amqp::Models::MessageProperties responseProperties; - - // Management specification section 3.2: The correlation-id of the response message - // MUST be the correlation-id from the request message (if present), else the - // message-id from the request message. - Azure::Nullable requestCorrelationId - = message->Properties.CorrelationId; - if (!message->Properties.CorrelationId.HasValue()) - { - requestCorrelationId = message->Properties.MessageId.Value(); - } - response.Properties.CorrelationId = requestCorrelationId; - - // Populate the response application properties. - - if (m_forceCbsError) - { - response.ApplicationProperties["status-code"] = 500; - response.ApplicationProperties["status-description"] = "Internal Server Error"; - } - else - { - response.ApplicationProperties["status-code"] = 200; - response.ApplicationProperties["status-description"] = "OK-put"; - } - - response.SetBody(Azure::Core::Amqp::Models::AmqpValue()); - - // Set the response body type to an empty AMQP value. - if (m_listenerContext.IsCancelled()) - { - return; - } - try - { - auto result = linkComponents.LinkSender->Send(response, m_listenerContext); - if (std::get<0>(result) != Azure::Core::Amqp::_internal::MessageSendStatus::Ok) - { - GTEST_LOG_(INFO) << "Failed to send CBS response: " << std::get<1>(result); - return; - } - } - catch (std::exception& ex) - { - GTEST_LOG_(INFO) << "Exception thrown sending CBS response: " << ex.what(); - return; - } - } - else if (static_cast(operation) == "delete-token") - { - Azure::Core::Amqp::Models::AmqpMessage response; - Azure::Core::Amqp::Models::MessageProperties responseProperties; - - // Management specification section 3.2: The correlation-id of the response message - // MUST be the correlation-id from the request message (if present), else the - // message-id from the request message. - Azure::Nullable requestCorrelationId - = message->Properties.CorrelationId; - if (!message->Properties.CorrelationId.HasValue()) - { - requestCorrelationId = message->Properties.MessageId; - } - response.Properties.CorrelationId = requestCorrelationId; - response.ApplicationProperties["status-code"] = 200; - response.ApplicationProperties["status-description"] = "OK-delete"; - - response.SetBody(Azure::Core::Amqp::Models::AmqpValue()); - - // Set the response body type to an empty AMQP value. - if (m_listenerContext.IsCancelled()) - { - return; - } - - auto sendResult = linkComponents.LinkSender->Send(response, m_listenerContext); - if (std::get<0>(sendResult) != Azure::Core::Amqp::_internal::MessageSendStatus::Ok) - { - GTEST_LOG_(INFO) << "Failed to send CBS response: " << std::get<1>(sendResult); - return; - } - } - } - - virtual void OnSocketAccepted( - std::shared_ptr transport) override - { - GTEST_LOG_(INFO) << "OnSocketAccepted - Socket connection received."; - auto amqpTransport{ - Azure::Core::Amqp::Network::_internal::AmqpHeaderDetectTransportFactory::Create( - transport, nullptr)}; - Azure::Core::Amqp::_internal::ConnectionOptions options; - options.ContainerId = m_connectionId; - options.IdleTimeout = std::chrono::minutes(2); - options.EnableTrace = m_enableTrace; - m_connection = std::make_shared( - amqpTransport, options, this); - m_connection->Listen(); - m_connectionQueue.CompleteOperation(true); - } - - virtual void OnConnectionStateChanged( - Azure::Core::Amqp::_internal::Connection const&, - Azure::Core::Amqp::_internal::ConnectionState newState, - Azure::Core::Amqp::_internal::ConnectionState oldState) override - { - GTEST_LOG_(INFO) << "Connection State changed. Connection: " << m_connectionId - << " Old state : " << oldState << " New state: " << newState; - if (newState == Azure::Core::Amqp::_internal::ConnectionState::End - || newState == Azure::Core::Amqp::_internal::ConnectionState::Error) - { - // If the connection is closed, then we should close the connection. - m_connectionValid = false; - m_listenerContext.Cancel(); - } - } - virtual bool OnNewEndpoint( - Azure::Core::Amqp::_internal::Connection const& connection, - Azure::Core::Amqp::_internal::Endpoint& endpoint) override - { - GTEST_LOG_(INFO) << "OnNewEndpoint - Incoming endpoint created, create session."; - Azure::Core::Amqp::_internal::SessionOptions options; - options.InitialIncomingWindowSize = 10000; - - m_session = std::make_shared( - connection.CreateSession(endpoint, options, this)); - m_session->Begin(); - return true; - } - virtual void OnIOError(Azure::Core::Amqp::_internal::Connection const&) override - { - GTEST_LOG_(INFO) << "On I/O Error - connection closed."; - } - - // Inherited via Session - virtual bool OnLinkAttached( - Azure::Core::Amqp::_internal::Session const& session, - Azure::Core::Amqp::_internal::LinkEndpoint& newLinkInstance, - std::string const& name, - Azure::Core::Amqp::_internal::SessionRole role, - Azure::Core::Amqp::Models::AmqpValue const& source, - Azure::Core::Amqp::Models::AmqpValue const& target, - Azure::Core::Amqp::Models::AmqpValue const&) override - { - Azure::Core::Amqp::Models::_internal::MessageSource msgSource(source); - Azure::Core::Amqp::Models::_internal::MessageTarget msgTarget(target); - - GTEST_LOG_(INFO) << "OnLinkAttached. Source: " << msgSource << " Target: " << msgTarget - << " Role: " << static_cast(role); - - // If the incoming role is receiver, then we want to create a sender to talk to it. - // Similarly, if the incoming role is sender, we want to create a receiver to receive - // from it. - if (role == Azure::Core::Amqp::_internal::SessionRole::Receiver) - { - GTEST_LOG_(INFO) << "Role is receiver, create sender."; - std::string targetAddress = static_cast(msgTarget.GetAddress()); - MessageLinkComponents& linkComponents - = m_linkMessageQueues[static_cast(msgSource.GetAddress())]; - - if (!linkComponents.LinkSender) - { - GTEST_LOG_(INFO) << "No sender found, create new."; - Azure::Core::Amqp::_internal::MessageSenderOptions senderOptions; - senderOptions.EnableTrace = m_enableTrace; - senderOptions.Name = name; - senderOptions.MessageSource = msgSource; - senderOptions.InitialDeliveryCount = 0; - linkComponents.LinkSender - = std::make_unique( - session.CreateMessageSender( - newLinkInstance, targetAddress, senderOptions, this)); - linkComponents.LinkSender->Open(); - linkComponents.MessageSenderPresentQueue.CompleteOperation(true); - } - } - else if (role == Azure::Core::Amqp::_internal::SessionRole::Sender) - { - GTEST_LOG_(INFO) << "Role is sender, create receiver."; - MessageLinkComponents& linkComponents - = m_linkMessageQueues[static_cast(msgTarget.GetAddress())]; - if (!linkComponents.LinkReceiver) - { - GTEST_LOG_(INFO) << "No receiver found, create new."; - Azure::Core::Amqp::_internal::MessageReceiverOptions receiverOptions; - receiverOptions.EnableTrace = m_enableTrace; - receiverOptions.Name = name; - receiverOptions.MessageTarget = msgTarget; - receiverOptions.InitialDeliveryCount = 0; - std::string sourceAddress = static_cast(msgSource.GetAddress()); - linkComponents.LinkReceiver - = std::make_unique( - session.CreateMessageReceiver( - newLinkInstance, sourceAddress, receiverOptions, this)); - linkComponents.LinkReceiver->Open(); - linkComponents.MessageReceiverPresentQueue.CompleteOperation(true); - } - } - return true; - } - - // Inherited via MessageReceiverEvents - void OnMessageReceiverStateChanged( - Azure::Core::Amqp::_internal::MessageReceiver const&, - Azure::Core::Amqp::_internal::MessageReceiverState newState, - Azure::Core::Amqp::_internal::MessageReceiverState oldState) override - { - GTEST_LOG_(INFO) << "Message Receiver State changed. Old state: " << oldState - << " New state: " << newState; - } - Azure::Core::Amqp::Models::AmqpValue OnMessageReceived( - Azure::Core::Amqp::_internal::MessageReceiver const& receiver, - std::shared_ptr const& message) override - { - GTEST_LOG_(INFO) << "Received a message " << message; - m_linkMessageQueues[receiver.GetSourceName()].MessageQueue.CompleteOperation(message); - return Azure::Core::Amqp::Models::_internal::Messaging::DeliveryAccepted(); - } - - // Inherited via MessageSenderEvents - void OnMessageSenderStateChanged( - Azure::Core::Amqp::_internal::MessageSender const&, - Azure::Core::Amqp::_internal::MessageSenderState newState, - Azure::Core::Amqp::_internal::MessageSenderState oldState) override - { - GTEST_LOG_(INFO) << "Message Sender State changed. Old state: " << oldState - << " New state: " << newState; - } - - void OnMessageSenderDisconnected( - Azure::Core::Amqp::Models::_internal::AmqpError const& error) override - { - GTEST_LOG_(INFO) << "Message Sender Disconnected: Error: " << error; - } - virtual void OnMessageReceiverDisconnected( - Azure::Core::Amqp::Models::_internal::AmqpError const& error) override - { - GTEST_LOG_(INFO) << "Message receiver disconnected: " << error << std::endl; - } - }; -#endif } // namespace MessageTests }}}} // namespace Azure::Core::Amqp::Tests diff --git a/sdk/core/azure-core-amqp/test/ut/session_tests.cpp b/sdk/core/azure-core-amqp/test/ut/session_tests.cpp index 1c937a272f..806a126c65 100644 --- a/sdk/core/azure-core-amqp/test/ut/session_tests.cpp +++ b/sdk/core/azure-core-amqp/test/ut/session_tests.cpp @@ -10,6 +10,7 @@ #include "azure/core/amqp/internal/network/socket_listener.hpp" #include "azure/core/amqp/internal/network/socket_transport.hpp" #include "azure/core/amqp/internal/session.hpp" +#include "mock_amqp_server.hpp" #include #include @@ -56,7 +57,7 @@ namespace Azure { namespace Core { namespace Amqp { namespace Tests { Session session1{connection.CreateSession({})}; Session session2{connection.CreateSession({})}; - session1.End("", ""); + EXPECT_ANY_THROW(session1.End("", "")); } } @@ -201,6 +202,7 @@ namespace Azure { namespace Core { namespace Amqp { namespace Tests { Session session{connection.CreateSession()}; session.Begin(); + session.End(); } { @@ -212,5 +214,69 @@ namespace Azure { namespace Core { namespace Amqp { namespace Tests { listener.Stop(); } + + TEST_F(TestSessions, MultipleSessionBeginEnd) + { + + MessageTests::AmqpServerMock mockServer; + mockServer.EnableTrace(false); + mockServer.StartListening(); + + // Create a connection + Azure::Core::Amqp::_internal::ConnectionOptions connectionOptions; + connectionOptions.Port = mockServer.GetPort(); + connectionOptions.EnableTrace = true; + + class OutgoingConnectionEvents : public ConnectionEvents { + /** @brief Called when the connection state changes. + * + * @param newState The new state of the connection. + * @param oldState The previous state of the connection. + */ + void OnConnectionStateChanged( + Connection const&, + ConnectionState newState, + ConnectionState oldState) override + { + GTEST_LOG_(INFO) << "Connection state changed. OldState: " << oldState << " -> " + << newState; + }; + + /** @brief called when an I/O error has occurred on the connection. + * + */ + void OnIOError(Connection const&) override { GTEST_LOG_(INFO) << "Connection IO Error."; }; + }; + + OutgoingConnectionEvents connectionEvents; + Azure::Core::Amqp::_internal::Connection connection( + "localhost", nullptr, connectionOptions, &connectionEvents); + + connection.Open(); + + { + constexpr const size_t sessionCount = 10; + GTEST_LOG_(INFO) << "Opening " << sessionCount << " sessions."; + std::vector sessions; + for (size_t i = 0; i < sessionCount; i += 1) + { + sessions.push_back(connection.CreateSession()); + sessions.back().Begin(); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + } + + std::this_thread::sleep_for(std::chrono::milliseconds(500)); + + GTEST_LOG_(INFO) << "Closing " << sessionCount << " sessions."; + for (auto& session : sessions) + { + session.End(); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + } + } + connection.Close(); + + mockServer.StopListening(); + } #endif // !AZ_PLATFORM_MAC }}}} // namespace Azure::Core::Amqp::Tests diff --git a/sdk/core/azure-core-amqp/test/ut_uamqp/CMakeLists.txt b/sdk/core/azure-core-amqp/test/ut_uamqp/CMakeLists.txt index 0179f88c56..8468471562 100644 --- a/sdk/core/azure-core-amqp/test/ut_uamqp/CMakeLists.txt +++ b/sdk/core/azure-core-amqp/test/ut_uamqp/CMakeLists.txt @@ -30,7 +30,10 @@ add_executable(azure-core-amqp-uamqp-tests uamqp_header_tests.cpp uamqp_insertion_tests.cpp uamqp_message_tests.cpp - "uamqp_properties_tests.cpp") + uamqp_properties_tests.cpp + uamqp_performatives_tests.cpp + uamqp_value_tests.cpp +) add_dependencies(azure-core-amqp-uamqp-tests Azure::azure-core-amqp uamqp) diff --git a/sdk/core/azure-core-amqp/test/ut_uamqp/uamqp_insertion_tests.cpp b/sdk/core/azure-core-amqp/test/ut_uamqp/uamqp_insertion_tests.cpp index 49069940a5..5d11138465 100644 --- a/sdk/core/azure-core-amqp/test/ut_uamqp/uamqp_insertion_tests.cpp +++ b/sdk/core/azure-core-amqp/test/ut_uamqp/uamqp_insertion_tests.cpp @@ -6,6 +6,7 @@ #include #include + #include #include diff --git a/sdk/core/azure-core-amqp/test/ut_uamqp/uamqp_performatives_tests.cpp b/sdk/core/azure-core-amqp/test/ut_uamqp/uamqp_performatives_tests.cpp new file mode 100644 index 0000000000..00c20b50e1 --- /dev/null +++ b/sdk/core/azure-core-amqp/test/ut_uamqp/uamqp_performatives_tests.cpp @@ -0,0 +1,392 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#include "../../src/models/private/error_impl.hpp" +#include "../../src/models/private/performatives/detach_impl.hpp" +#include "../../src/models/private/performatives/transfer_impl.hpp" +#include "../../src/models/private/value_impl.hpp" +#include "azure/core/amqp/internal/models/performatives/amqp_detach.hpp" +#include "azure/core/amqp/internal/models/performatives/amqp_transfer.hpp" + +#include + +using namespace Azure::Core::Amqp::Models::_internal; +using namespace Azure::Core::Amqp::Models; + +class TestPerformativesUamqp : public testing::Test { +protected: + void SetUp() override {} + void TearDown() override {} +}; + +TEST_F(TestPerformativesUamqp, SimpleCreate) +{ + { + Performatives::AmqpDetach detach; + detach.Closed = true; + detach.Handle = 23; + + auto detachHandle{Azure::Core::Amqp::Models::_detail::AmqpDetachFactory::ToAmqpDetach(detach)}; + + ASSERT_TRUE(detachHandle); + bool closed; + ASSERT_EQ(0, detach_get_closed(detachHandle.get(), &closed)); + ASSERT_TRUE(closed); + } + { + Performatives::AmqpTransfer transfer; + + transfer.Handle = 17; + transfer.DeliveryId = 92; + transfer.Aborted = true; + + auto transferHandle{Azure::Core::Amqp::Models::_detail::AmqpTransferFactory::ToAmqp(transfer)}; + } +} + +TEST_F(TestPerformativesUamqp, AmqpTransferFactory) +{ + Azure::Core::Amqp::Models::_detail::UniqueAmqpTransferHandle amqpTransfer{transfer_create(92)}; + + { + Performatives::AmqpTransfer transfer{ + Azure::Core::Amqp::Models::_detail::AmqpTransferFactory::FromUamqp(amqpTransfer.get())}; + + GTEST_LOG_(INFO) << "Transfer: " << transfer; + + ASSERT_EQ(92, transfer.Handle); + ASSERT_FALSE(transfer.DeliveryId); + ASSERT_FALSE(transfer.DeliveryTag); + ASSERT_EQ(Azure::Core::Amqp::Models::AmqpDefaultMessageFormatValue, transfer.MessageFormat); + ASSERT_FALSE(transfer.Settled); + ASSERT_EQ(false, transfer.More); + ASSERT_FALSE(transfer.SettleMode); + ASSERT_TRUE(transfer.State.IsNull()); + ASSERT_FALSE(transfer.Resume); + ASSERT_FALSE(transfer.Aborted); + ASSERT_FALSE(transfer.Batchable); + } + + { + ASSERT_EQ(0, transfer_set_delivery_id(amqpTransfer.get(), 17)); + Performatives::AmqpTransfer transfer{ + Azure::Core::Amqp::Models::_detail::AmqpTransferFactory::FromUamqp(amqpTransfer.get())}; + + GTEST_LOG_(INFO) << "Transfer: " << transfer; + + ASSERT_EQ(92, transfer.Handle); + ASSERT_TRUE(transfer.DeliveryId); + ASSERT_EQ(17, transfer.DeliveryId.Value()); + ASSERT_FALSE(transfer.DeliveryTag); + ASSERT_EQ(Azure::Core::Amqp::Models::AmqpDefaultMessageFormatValue, transfer.MessageFormat); + ASSERT_FALSE(transfer.Settled); + ASSERT_EQ(false, transfer.More); + ASSERT_FALSE(transfer.SettleMode); + ASSERT_TRUE(transfer.State.IsNull()); + ASSERT_FALSE(transfer.Resume); + ASSERT_FALSE(transfer.Aborted); + ASSERT_FALSE(transfer.Batchable); + } + { + delivery_tag tag; + uint8_t bytes[] = {1, 2, 3, 4, 5}; + tag.bytes = bytes; + tag.length = 5; + ASSERT_EQ(0, transfer_set_delivery_tag(amqpTransfer.get(), tag)); + + Performatives::AmqpTransfer transfer{ + Azure::Core::Amqp::Models::_detail::AmqpTransferFactory::FromUamqp(amqpTransfer.get())}; + + GTEST_LOG_(INFO) << "Transfer: " << transfer; + + ASSERT_EQ(92, transfer.Handle); + ASSERT_TRUE(transfer.DeliveryId); + ASSERT_EQ(17, transfer.DeliveryId.Value()); + ASSERT_TRUE(transfer.DeliveryTag); + ASSERT_EQ(5, transfer.DeliveryTag.Value().size()); + ASSERT_EQ(Azure::Core::Amqp::Models::AmqpDefaultMessageFormatValue, transfer.MessageFormat); + ASSERT_FALSE(transfer.Settled); + ASSERT_EQ(false, transfer.More); + ASSERT_FALSE(transfer.SettleMode); + ASSERT_TRUE(transfer.State.IsNull()); + ASSERT_FALSE(transfer.Resume); + ASSERT_FALSE(transfer.Aborted); + ASSERT_FALSE(transfer.Batchable); + } + + // Message Format. + { + ASSERT_EQ(0, transfer_set_message_format(amqpTransfer.get(), 95525)); + Performatives::AmqpTransfer transfer{ + Azure::Core::Amqp::Models::_detail::AmqpTransferFactory::FromUamqp(amqpTransfer.get())}; + + GTEST_LOG_(INFO) << "Transfer: " << transfer; + + ASSERT_EQ(92, transfer.Handle); + ASSERT_TRUE(transfer.DeliveryId); + ASSERT_EQ(17, transfer.DeliveryId.Value()); + ASSERT_TRUE(transfer.DeliveryTag); + ASSERT_EQ(5, transfer.DeliveryTag.Value().size()); + ASSERT_EQ(95525, transfer.MessageFormat); + ASSERT_FALSE(transfer.Settled); + ASSERT_EQ(false, transfer.More); + ASSERT_FALSE(transfer.SettleMode); + ASSERT_TRUE(transfer.State.IsNull()); + ASSERT_FALSE(transfer.Resume); + ASSERT_FALSE(transfer.Aborted); + ASSERT_FALSE(transfer.Batchable); + } + + // Settled. + { + ASSERT_EQ(0, transfer_set_settled(amqpTransfer.get(), true)); + Performatives::AmqpTransfer transfer{ + Azure::Core::Amqp::Models::_detail::AmqpTransferFactory::FromUamqp(amqpTransfer.get())}; + + GTEST_LOG_(INFO) << "Transfer: " << transfer; + + ASSERT_EQ(92, transfer.Handle); + ASSERT_TRUE(transfer.DeliveryId); + ASSERT_EQ(17, transfer.DeliveryId.Value()); + ASSERT_TRUE(transfer.DeliveryTag); + ASSERT_EQ(5, transfer.DeliveryTag.Value().size()); + ASSERT_EQ(95525, transfer.MessageFormat); + ASSERT_TRUE(transfer.Settled); + ASSERT_TRUE(transfer.Settled.Value()); + ASSERT_EQ(false, transfer.More); + ASSERT_FALSE(transfer.SettleMode); + ASSERT_TRUE(transfer.State.IsNull()); + ASSERT_FALSE(transfer.Resume); + ASSERT_FALSE(transfer.Aborted); + ASSERT_FALSE(transfer.Batchable); + } + + // More. + { + ASSERT_EQ(0, transfer_set_more(amqpTransfer.get(), true)); + Performatives::AmqpTransfer transfer{ + Azure::Core::Amqp::Models::_detail::AmqpTransferFactory::FromUamqp(amqpTransfer.get())}; + + GTEST_LOG_(INFO) << "Transfer: " << transfer; + + ASSERT_EQ(92, transfer.Handle); + ASSERT_TRUE(transfer.DeliveryId); + ASSERT_EQ(17, transfer.DeliveryId.Value()); + ASSERT_TRUE(transfer.DeliveryTag); + ASSERT_EQ(5, transfer.DeliveryTag.Value().size()); + ASSERT_EQ(95525, transfer.MessageFormat); + ASSERT_TRUE(transfer.Settled); + ASSERT_TRUE(transfer.Settled.Value()); + ASSERT_EQ(true, transfer.More); + ASSERT_FALSE(transfer.SettleMode); + ASSERT_TRUE(transfer.State.IsNull()); + ASSERT_FALSE(transfer.Resume); + ASSERT_FALSE(transfer.Aborted); + ASSERT_FALSE(transfer.Batchable); + } + + // Receiver Settle Mode first + { + ASSERT_EQ(0, transfer_set_rcv_settle_mode(amqpTransfer.get(), receiver_settle_mode_first)); + Performatives::AmqpTransfer transfer{ + Azure::Core::Amqp::Models::_detail::AmqpTransferFactory::FromUamqp(amqpTransfer.get())}; + + GTEST_LOG_(INFO) << "Transfer: " << transfer; + + ASSERT_EQ(92, transfer.Handle); + ASSERT_TRUE(transfer.DeliveryId); + ASSERT_EQ(17, transfer.DeliveryId.Value()); + ASSERT_TRUE(transfer.DeliveryTag); + ASSERT_EQ(5, transfer.DeliveryTag.Value().size()); + ASSERT_EQ(95525, transfer.MessageFormat); + ASSERT_TRUE(transfer.Settled); + ASSERT_TRUE(transfer.Settled.Value()); + ASSERT_EQ(true, transfer.More); + ASSERT_TRUE(transfer.SettleMode); + ASSERT_EQ(Azure::Core::Amqp::_internal::ReceiverSettleMode::First, transfer.SettleMode.Value()); + ASSERT_TRUE(transfer.State.IsNull()); + ASSERT_FALSE(transfer.Resume); + ASSERT_FALSE(transfer.Aborted); + ASSERT_FALSE(transfer.Batchable); + } + + // Receiver Settle Mode second + { + ASSERT_EQ(0, transfer_set_rcv_settle_mode(amqpTransfer.get(), receiver_settle_mode_second)); + Performatives::AmqpTransfer transfer{ + Azure::Core::Amqp::Models::_detail::AmqpTransferFactory::FromUamqp(amqpTransfer.get())}; + + GTEST_LOG_(INFO) << "Transfer: " << transfer; + + ASSERT_EQ(92, transfer.Handle); + ASSERT_TRUE(transfer.DeliveryId); + ASSERT_EQ(17, transfer.DeliveryId.Value()); + ASSERT_TRUE(transfer.DeliveryTag); + ASSERT_EQ(5, transfer.DeliveryTag.Value().size()); + ASSERT_EQ(95525, transfer.MessageFormat); + ASSERT_TRUE(transfer.Settled); + ASSERT_TRUE(transfer.Settled.Value()); + ASSERT_EQ(true, transfer.More); + ASSERT_TRUE(transfer.SettleMode); + ASSERT_EQ( + Azure::Core::Amqp::_internal::ReceiverSettleMode::Second, transfer.SettleMode.Value()); + ASSERT_TRUE(transfer.State.IsNull()); + ASSERT_FALSE(transfer.Resume); + ASSERT_FALSE(transfer.Aborted); + ASSERT_FALSE(transfer.Batchable); + } + + // State + { + AmqpValue value{"This is a string value"}; + + ASSERT_EQ(0, transfer_set_state(amqpTransfer.get(), _detail::AmqpValueFactory::ToUamqp(value))); + Performatives::AmqpTransfer transfer{ + Azure::Core::Amqp::Models::_detail::AmqpTransferFactory::FromUamqp(amqpTransfer.get())}; + + GTEST_LOG_(INFO) << "Transfer: " << transfer; + + ASSERT_EQ(92, transfer.Handle); + ASSERT_TRUE(transfer.DeliveryId); + ASSERT_EQ(17, transfer.DeliveryId.Value()); + ASSERT_TRUE(transfer.DeliveryTag); + ASSERT_EQ(5, transfer.DeliveryTag.Value().size()); + ASSERT_EQ(95525, transfer.MessageFormat); + ASSERT_TRUE(transfer.Settled); + ASSERT_TRUE(transfer.Settled.Value()); + ASSERT_EQ(true, transfer.More); + ASSERT_TRUE(transfer.SettleMode); + ASSERT_EQ( + Azure::Core::Amqp::_internal::ReceiverSettleMode::Second, transfer.SettleMode.Value()); + ASSERT_FALSE(transfer.State.IsNull()); + ASSERT_EQ("This is a string value", static_cast(transfer.State)); + ASSERT_FALSE(transfer.Resume); + ASSERT_FALSE(transfer.Aborted); + ASSERT_FALSE(transfer.Batchable); + } + + // Resume + { + ASSERT_EQ(0, transfer_set_resume(amqpTransfer.get(), true)); + Performatives::AmqpTransfer transfer{ + Azure::Core::Amqp::Models::_detail::AmqpTransferFactory::FromUamqp(amqpTransfer.get())}; + + GTEST_LOG_(INFO) << "Transfer: " << transfer; + + ASSERT_EQ(92, transfer.Handle); + ASSERT_TRUE(transfer.DeliveryId); + ASSERT_EQ(17, transfer.DeliveryId.Value()); + ASSERT_TRUE(transfer.DeliveryTag); + ASSERT_EQ(5, transfer.DeliveryTag.Value().size()); + ASSERT_EQ(95525, transfer.MessageFormat); + ASSERT_TRUE(transfer.Settled); + ASSERT_TRUE(transfer.Settled.Value()); + ASSERT_EQ(true, transfer.More); + ASSERT_TRUE(transfer.SettleMode); + ASSERT_EQ( + Azure::Core::Amqp::_internal::ReceiverSettleMode::Second, transfer.SettleMode.Value()); + ASSERT_FALSE(transfer.State.IsNull()); + ASSERT_EQ("This is a string value", static_cast(transfer.State)); + ASSERT_TRUE(transfer.Resume); + ASSERT_FALSE(transfer.Aborted); + ASSERT_FALSE(transfer.Batchable); + } + + // Aborted + { + transfer_set_aborted(amqpTransfer.get(), true); + Performatives::AmqpTransfer transfer{ + Azure::Core::Amqp::Models::_detail::AmqpTransferFactory::FromUamqp(amqpTransfer.get())}; + + GTEST_LOG_(INFO) << "Transfer: " << transfer; + + ASSERT_EQ(92, transfer.Handle); + ASSERT_TRUE(transfer.DeliveryId); + ASSERT_EQ(17, transfer.DeliveryId.Value()); + ASSERT_TRUE(transfer.DeliveryTag); + ASSERT_EQ(5, transfer.DeliveryTag.Value().size()); + ASSERT_EQ(95525, transfer.MessageFormat); + ASSERT_TRUE(transfer.Settled); + ASSERT_TRUE(transfer.Settled.Value()); + ASSERT_EQ(true, transfer.More); + ASSERT_TRUE(transfer.SettleMode); + ASSERT_EQ( + Azure::Core::Amqp::_internal::ReceiverSettleMode::Second, transfer.SettleMode.Value()); + ASSERT_FALSE(transfer.State.IsNull()); + ASSERT_EQ("This is a string value", static_cast(transfer.State)); + ASSERT_TRUE(transfer.Resume); + ASSERT_TRUE(transfer.Aborted); + ASSERT_FALSE(transfer.Batchable); + } + + // Batchable + { + transfer_set_batchable(amqpTransfer.get(), false); + Performatives::AmqpTransfer transfer{ + Azure::Core::Amqp::Models::_detail::AmqpTransferFactory::FromUamqp(amqpTransfer.get())}; + + GTEST_LOG_(INFO) << "Transfer: " << transfer; + + ASSERT_EQ(92, transfer.Handle); + ASSERT_TRUE(transfer.DeliveryId); + ASSERT_EQ(17, transfer.DeliveryId.Value()); + ASSERT_TRUE(transfer.DeliveryTag); + ASSERT_EQ(5, transfer.DeliveryTag.Value().size()); + ASSERT_EQ(95525, transfer.MessageFormat); + ASSERT_TRUE(transfer.Settled); + ASSERT_TRUE(transfer.Settled.Value()); + ASSERT_EQ(true, transfer.More); + ASSERT_TRUE(transfer.SettleMode); + ASSERT_EQ( + Azure::Core::Amqp::_internal::ReceiverSettleMode::Second, transfer.SettleMode.Value()); + ASSERT_FALSE(transfer.State.IsNull()); + ASSERT_EQ("This is a string value", static_cast(transfer.State)); + ASSERT_TRUE(transfer.Resume); + ASSERT_TRUE(transfer.Aborted); + ASSERT_FALSE(transfer.Batchable); + } +} + +TEST_F(TestPerformativesUamqp, AmqpDetachFactory) +{ + Azure::Core::Amqp::Models::_detail::UniqueAmqpDetachHandle amqpDetach{detach_create(343)}; + + { + Performatives::AmqpDetach detach{ + Azure::Core::Amqp::Models::_detail::AmqpDetachFactory::FromUamqp(amqpDetach.get())}; + GTEST_LOG_(INFO) << "Detach: " << detach; + + ASSERT_EQ(343, detach.Handle); + ASSERT_FALSE(detach.Closed); + ASSERT_FALSE(detach.Error); + } + + { + ASSERT_EQ(0, detach_set_closed(amqpDetach.get(), true)); + Performatives::AmqpDetach detach{ + Azure::Core::Amqp::Models::_detail::AmqpDetachFactory::FromUamqp(amqpDetach.get())}; + GTEST_LOG_(INFO) << "Detach: " << detach; + + ASSERT_EQ(343, detach.Handle); + ASSERT_TRUE(detach.Closed); + ASSERT_FALSE(detach.Error); + } + + { + _internal::AmqpError error; + + error.Condition = AmqpErrorCondition::DecodeError; + error.Description = "A Description of the error"; + + ASSERT_EQ( + 0, detach_set_error(amqpDetach.get(), _detail::AmqpErrorFactory::ToAmqpError(error).get())); + Performatives::AmqpDetach detach{ + Azure::Core::Amqp::Models::_detail::AmqpDetachFactory::FromUamqp(amqpDetach.get())}; + GTEST_LOG_(INFO) << "Detach: " << detach; + + ASSERT_EQ(343, detach.Handle); + ASSERT_TRUE(detach.Closed); + ASSERT_TRUE(detach.Error); + ASSERT_EQ(AmqpErrorCondition::DecodeError, detach.Error.Condition); + } +} diff --git a/sdk/core/azure-core-amqp/test/ut_uamqp/uamqp_value_tests.cpp b/sdk/core/azure-core-amqp/test/ut_uamqp/uamqp_value_tests.cpp new file mode 100644 index 0000000000..c61a99bce5 --- /dev/null +++ b/sdk/core/azure-core-amqp/test/ut_uamqp/uamqp_value_tests.cpp @@ -0,0 +1,218 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#include "../src/models/private/value_impl.hpp" +#include "azure/core/amqp/models/amqp_value.hpp" + +#include + +#include + +using namespace Azure::Core::Amqp::Models::_internal; +using namespace Azure::Core::Amqp::Models::_detail; +using namespace Azure::Core::Amqp::Models; + +class TestValue : public testing::Test { +protected: + void SetUp() override {} + void TearDown() override {} +}; + +TEST_F(TestValue, SimpleCreate) +{ + { + AmqpValue value; + } + + { + _detail::UniqueAmqpValueHandle handle{amqpvalue_create_null()}; + + AmqpValue value{_detail::AmqpValueFactory::FromUamqp(handle)}; + GTEST_LOG_(INFO) << "Handle Type: " << amqpvalue_get_type(handle.get()) + << " Value: " << static_cast(handle.get()); + } + + { + _detail::UniqueAmqpValueHandle handle{amqpvalue_create_byte('q')}; + + AmqpValue value{_detail::AmqpValueFactory::FromUamqp(handle)}; + GTEST_LOG_(INFO) << "Handle Type: " << amqpvalue_get_type(handle.get()) + << " Value: " << static_cast(handle.get()); + } + + // MOCKABLE_FUNCTION(, AMQP_VALUE, amqpvalue_create_boolean, bool, bool_value); + { + _detail::UniqueAmqpValueHandle handle{amqpvalue_create_boolean(true)}; + + AmqpValue value{_detail::AmqpValueFactory::FromUamqp(handle)}; + GTEST_LOG_(INFO) << "Handle Type: " << amqpvalue_get_type(handle.get()) + << " Value: " << static_cast(handle.get()); + } + { + _detail::UniqueAmqpValueHandle handle{amqpvalue_create_boolean(false)}; + + AmqpValue value{_detail::AmqpValueFactory::FromUamqp(handle)}; + GTEST_LOG_(INFO) << "Handle Type: " << amqpvalue_get_type(handle.get()) + << " Value: " << static_cast(handle.get()); + } + + // MOCKABLE_FUNCTION(, AMQP_VALUE, amqpvalue_create_ubyte, unsigned char, ubyte_value); + { + _detail::UniqueAmqpValueHandle handle{amqpvalue_create_ubyte(225)}; + + AmqpValue value{_detail::AmqpValueFactory::FromUamqp(handle)}; + GTEST_LOG_(INFO) << "Handle Type: " << amqpvalue_get_type(handle.get()) + << " Value: " << static_cast(handle.get()); + } + + // MOCKABLE_FUNCTION(, AMQP_VALUE, amqpvalue_create_ushort, uint16_t, ushort_value); + { + _detail::UniqueAmqpValueHandle handle{amqpvalue_create_ushort(32769)}; + + AmqpValue value{_detail::AmqpValueFactory::FromUamqp(handle)}; + GTEST_LOG_(INFO) << "Handle Type: " << amqpvalue_get_type(handle.get()) + << " Value: " << static_cast(handle.get()); + } + // MOCKABLE_FUNCTION(, AMQP_VALUE, amqpvalue_create_uint, uint32_t, uint_value); + { + _detail::UniqueAmqpValueHandle handle{amqpvalue_create_uint(1235125)}; + + AmqpValue value{_detail::AmqpValueFactory::FromUamqp(handle)}; + GTEST_LOG_(INFO) << "Handle Type: " << amqpvalue_get_type(handle.get()) + << " Value: " << static_cast(handle.get()); + } + // MOCKABLE_FUNCTION(, AMQP_VALUE, amqpvalue_create_ulong, uint64_t, ulong_value); + { + _detail::UniqueAmqpValueHandle handle{amqpvalue_create_ulong(13421266651)}; + + AmqpValue value{_detail::AmqpValueFactory::FromUamqp(handle)}; + GTEST_LOG_(INFO) << "Handle Type: " << amqpvalue_get_type(handle.get()) + << " Value: " << static_cast(handle.get()); + } + // MOCKABLE_FUNCTION(, AMQP_VALUE, amqpvalue_create_byte, char, byte_value); + { + _detail::UniqueAmqpValueHandle handle{amqpvalue_create_byte('q')}; + + AmqpValue value{_detail::AmqpValueFactory::FromUamqp(handle)}; + GTEST_LOG_(INFO) << "Handle Type: " << amqpvalue_get_type(handle.get()) + << " Value: " << static_cast(handle.get()); + } + + // MOCKABLE_FUNCTION(, AMQP_VALUE, amqpvalue_create_short, int16_t, short_value); + { + _detail::UniqueAmqpValueHandle handle{amqpvalue_create_short(225)}; + + AmqpValue value{_detail::AmqpValueFactory::FromUamqp(handle)}; + GTEST_LOG_(INFO) << "Handle Type: " << amqpvalue_get_type(handle.get()) + << " Value: " << static_cast(handle.get()); + } + // MOCKABLE_FUNCTION(, AMQP_VALUE, amqpvalue_create_int, int32_t, int_value); + { + _detail::UniqueAmqpValueHandle handle{amqpvalue_create_int(1151551)}; + + AmqpValue value{_detail::AmqpValueFactory::FromUamqp(handle)}; + GTEST_LOG_(INFO) << "Handle Type: " << amqpvalue_get_type(handle.get()) + << " Value: " << static_cast(handle.get()); + } + // MOCKABLE_FUNCTION(, AMQP_VALUE, amqpvalue_create_long, int64_t, long_value); + { + _detail::UniqueAmqpValueHandle handle{amqpvalue_create_long(1551516661161)}; + + AmqpValue value{_detail::AmqpValueFactory::FromUamqp(handle)}; + GTEST_LOG_(INFO) << "Handle Type: " << amqpvalue_get_type(handle.get()) + << " Value: " << static_cast(handle.get()); + } + // MOCKABLE_FUNCTION(, AMQP_VALUE, amqpvalue_create_float, float, float_value); + { + _detail::UniqueAmqpValueHandle handle{amqpvalue_create_float(16.5)}; + + AmqpValue value{_detail::AmqpValueFactory::FromUamqp(handle)}; + GTEST_LOG_(INFO) << "Handle Type: " << amqpvalue_get_type(handle.get()) + << " Value: " << static_cast(handle.get()); + } + // MOCKABLE_FUNCTION(, AMQP_VALUE, amqpvalue_create_double, double, double_value); + { + _detail::UniqueAmqpValueHandle handle{amqpvalue_create_double(100515.021)}; + + AmqpValue value{_detail::AmqpValueFactory::FromUamqp(handle)}; + GTEST_LOG_(INFO) << "Handle Type: " << amqpvalue_get_type(handle.get()) + << " Value: " << static_cast(handle.get()); + } + // MOCKABLE_FUNCTION(, AMQP_VALUE, amqpvalue_create_char, uint32_t, char_value); + { + _detail::UniqueAmqpValueHandle handle{amqpvalue_create_char('9')}; + + AmqpValue value{_detail::AmqpValueFactory::FromUamqp(handle)}; + GTEST_LOG_(INFO) << "Handle Type: " << amqpvalue_get_type(handle.get()) + << " Value: " << static_cast(handle.get()); + } + // MOCKABLE_FUNCTION(, AMQP_VALUE, amqpvalue_create_timestamp, int64_t, timestamp_value); + { + _detail::UniqueAmqpValueHandle handle{amqpvalue_create_timestamp(1569)}; + + AmqpValue value{_detail::AmqpValueFactory::FromUamqp(handle)}; + GTEST_LOG_(INFO) << "Handle Type: " << amqpvalue_get_type(handle.get()) + << " Value: " << static_cast(handle.get()); + } + // MOCKABLE_FUNCTION(, AMQP_VALUE, amqpvalue_create_uuid, uuid, uuid_value); + { + Azure::Core::Uuid uuid = Azure::Core::Uuid::CreateUuid(); + _detail::UniqueAmqpValueHandle handle{amqpvalue_create_uuid( + reinterpret_cast(const_cast(uuid.AsArray().data())))}; + + AmqpValue value{_detail::AmqpValueFactory::FromUamqp(handle)}; + GTEST_LOG_(INFO) << "Handle Type: " << amqpvalue_get_type(handle.get()) + << " Value: " << static_cast(handle.get()); + } + // MOCKABLE_FUNCTION(, AMQP_VALUE, amqpvalue_create_binary, amqp_binary, binary_value); + { + amqp_binary binary; + binary.bytes = reinterpret_cast("Hello World"); + binary.length = 11; + _detail::UniqueAmqpValueHandle handle{amqpvalue_create_binary(binary)}; + + AmqpValue value{_detail::AmqpValueFactory::FromUamqp(handle)}; + GTEST_LOG_(INFO) << "Handle Type: " << amqpvalue_get_type(handle.get()) + << " Value: " << static_cast(handle.get()); + } + // MOCKABLE_FUNCTION(, AMQP_VALUE, amqpvalue_create_string, const char*, string_value); + { + _detail::UniqueAmqpValueHandle handle{amqpvalue_create_string("binary")}; + + AmqpValue value{_detail::AmqpValueFactory::FromUamqp(handle)}; + GTEST_LOG_(INFO) << "Handle Type: " << amqpvalue_get_type(handle.get()) + << " Value: " << static_cast(handle.get()); + } + // MOCKABLE_FUNCTION(, AMQP_VALUE, amqpvalue_create_symbol, const char*, symbol_value); + { + _detail::UniqueAmqpValueHandle handle{amqpvalue_create_symbol("binary")}; + + AmqpValue value{_detail::AmqpValueFactory::FromUamqp(handle)}; + GTEST_LOG_(INFO) << "Handle Type: " << amqpvalue_get_type(handle.get()) + << " Value: " << static_cast(handle.get()); + } + // MOCKABLE_FUNCTION(, AMQP_VALUE, amqpvalue_create_list); + { + _detail::UniqueAmqpValueHandle handle{amqpvalue_create_list()}; + + AmqpValue value{_detail::AmqpValueFactory::FromUamqp(handle)}; + GTEST_LOG_(INFO) << "Handle Type: " << amqpvalue_get_type(handle.get()) + << " Value: " << static_cast(handle.get()); + } + // MOCKABLE_FUNCTION(, AMQP_VALUE, amqpvalue_create_map); + { + _detail::UniqueAmqpValueHandle handle{amqpvalue_create_map()}; + + AmqpValue value{_detail::AmqpValueFactory::FromUamqp(handle)}; + GTEST_LOG_(INFO) << "Handle Type: " << amqpvalue_get_type(handle.get()) + << " Value: " << static_cast(handle.get()); + } + // MOCKABLE_FUNCTION(, AMQP_VALUE, amqpvalue_create_array); + { + _detail::UniqueAmqpValueHandle handle{amqpvalue_create_array()}; + + AmqpValue value{_detail::AmqpValueFactory::FromUamqp(handle)}; + GTEST_LOG_(INFO) << "Handle Type: " << amqpvalue_get_type(handle.get()) + << " Value: " << static_cast(handle.get()); + } +} diff --git a/sdk/core/azure-core-amqp/vendor/azure-uamqp-c/src/connection.c b/sdk/core/azure-core-amqp/vendor/azure-uamqp-c/src/connection.c index 699d7e57c3..7f096ff70d 100644 --- a/sdk/core/azure-core-amqp/vendor/azure-uamqp-c/src/connection.c +++ b/sdk/core/azure-core-amqp/vendor/azure-uamqp-c/src/connection.c @@ -9,6 +9,7 @@ #include "azure_c_shared_utility/xio.h" #include "azure_c_shared_utility/xlogging.h" #include "azure_c_shared_utility/tickcounter.h" +#include "azure_c_shared_utility/safe_math.h" #include "azure_uamqp_c/connection.h" #include "azure_uamqp_c/frame_codec.h" @@ -36,6 +37,7 @@ typedef struct ON_CONNECTION_CLOSED_EVENT_SUBSCRIPTION_TAG void* context; } ON_CONNECTION_CLOSED_EVENT_SUBSCRIPTION; +#define ENDPOINT_UNUSED_CHANNEL 0xffff typedef struct ENDPOINT_INSTANCE_TAG { uint16_t incoming_channel; @@ -46,6 +48,27 @@ typedef struct ENDPOINT_INSTANCE_TAG CONNECTION_HANDLE connection; } ENDPOINT_INSTANCE; + +typedef struct CHANNEL_TABLE_ENTRY_TAG +{ + uint16_t outgoing_channel; + uint16_t incoming_channel; + bool is_endpoint_live; +} CHANNEL_TABLE_ENTRY; + +typedef struct CHANNEL_TABLE_TABLE_TAG +{ + // The entries in the channels map are initialized to ENDPOINT_UNUSED_CHANNEL. If a channel is in use, the entry is set to the channel number. + // When a remote BEGIN message is received (when the ENDPOINT_INSTANCE incoming_channel is set), the entry is set to the *incoming* + // channel number, this allows us to free the channel when an + // incoming END is received. + // When a channel is freed, the entry is set back to ENDPOINT_UNUSED_CHANNEL. + // + CHANNEL_TABLE_ENTRY* channels; + uint16_t capacity; + uint16_t size; +} CHANNEL_TABLE; + typedef struct CONNECTION_INSTANCE_TAG { XIO_HANDLE io; @@ -53,6 +76,7 @@ typedef struct CONNECTION_INSTANCE_TAG CONNECTION_STATE connection_state; FRAME_CODEC_HANDLE frame_codec; AMQP_FRAME_CODEC_HANDLE amqp_frame_codec; + CHANNEL_TABLE channel_table; ENDPOINT_INSTANCE** endpoints; uint32_t endpoint_count; char* host_name; @@ -90,6 +114,229 @@ typedef struct CONNECTION_INSTANCE_TAG unsigned int is_trace_on : 1; } CONNECTION_INSTANCE; +#define INITIAL_CHANNEL_TABLE_CAPACITY 16 + +static int channel_table_initialize(CHANNEL_TABLE* channel_table) +{ + int result; + + // Codes_S_R_S_CONNECTION_01_253: [The channel_table shall be initialized with a capacity of INITIAL_CHANNEL_TABLE_CAPACITY handles.] + channel_table->channels = (CHANNEL_TABLE_ENTRY*)malloc(sizeof(CHANNEL_TABLE_ENTRY) * INITIAL_CHANNEL_TABLE_CAPACITY); + if (channel_table->channels == NULL) + { + LogError("Cannot allocate memory for handle table"); + result = MU_FAILURE; + } + else + { + for (uint16_t i = 0; i < INITIAL_CHANNEL_TABLE_CAPACITY; i++) + { + channel_table->channels[i].outgoing_channel = ENDPOINT_UNUSED_CHANNEL; + channel_table->channels[i].incoming_channel = ENDPOINT_UNUSED_CHANNEL; + channel_table->channels[i].is_endpoint_live = false; + } + channel_table->capacity = 16; + channel_table->size = 0; + result = 0; + } + + return result; +} + +static int channel_table_destroy(CHANNEL_TABLE* channel_table) +{ + int result; + + if (channel_table->channels!= NULL) + { + free(channel_table->channels); + } + + channel_table->capacity = 0; + channel_table->size = 0; + channel_table->channels= NULL; + + result = 0; + + return result; +} + +static int channel_table_allocate(CHANNEL_TABLE* channel_table, uint16_t* h) +{ + int result=0; + + if (h == NULL) + { + LogError("Null handle in handle_table_allocate"); + result = MU_FAILURE; + } + else + { + *h = 0; + + if (channel_table->size == channel_table->capacity) + { + CHANNEL_TABLE_ENTRY* new_handles = (CHANNEL_TABLE_ENTRY*)realloc( + channel_table->channels, sizeof(CHANNEL_TABLE_ENTRY) * channel_table->capacity * 2); + if (new_handles == NULL) + { + LogError("Cannot reallocate memory for handle table"); + result = MU_FAILURE; + } + else + { + channel_table->channels = new_handles; + // Ensure the newly resized handle table is empty. + for (uint16_t i = channel_table->size; i < channel_table->capacity * 2; i++) + { + channel_table->channels[i].outgoing_channel = ENDPOINT_UNUSED_CHANNEL; + channel_table->channels[i].incoming_channel = ENDPOINT_UNUSED_CHANNEL; + channel_table->channels[i].is_endpoint_live = false; + } + channel_table->capacity *= 2; + } + } + if (!result) + { + uint16_t i = 0; + for (; i < channel_table->size; i++) + { + if (channel_table->channels[i].outgoing_channel == ENDPOINT_UNUSED_CHANNEL) + { + *h = i; + channel_table->channels[i].outgoing_channel = i; + break; + } + } + // If we didn't find a hole, add this to the end of the table. + if (i == channel_table->size) + { + *h = channel_table->size; + channel_table->channels[channel_table->size].outgoing_channel = channel_table->size; + channel_table->channels[channel_table->size].is_endpoint_live = true; + channel_table->size++; + } + } + } + + return result; +} + + +/* Set the incoming channel for the outgoing channel. +* This is used when a BEGIN frame is received. +* The incoming channel is the channel number used by the remote peer. +* The outgoing channel is the channel number used by the local peer. +*/ +static int channel_table_set_incoming_channel(CHANNEL_TABLE* channel_table, uint16_t outgoing_channel, uint16_t incoming_channel) +{ + int result = 0; + + // Ensure that the outgoing channel is "reasonable". + if (outgoing_channel >= channel_table->size) + { + LogError("Incoming channel out of range"); + result = MU_FAILURE; + } + // Ensure that the outgoing channel is allocated in the table. + else if (channel_table->channels[outgoing_channel].outgoing_channel != outgoing_channel) + { + LogError("Outgoing Channel is not allocated."); + result = MU_FAILURE; + } + //else if (channel_table->channels[outgoing_channel].is_endpoint_live == false) + //{ + // LogError("Outgoing Channel does not have an endpoint."); + // result = MU_FAILURE; + //} + else + { + channel_table->channels[outgoing_channel].incoming_channel = incoming_channel; + result = 0; + } + + return result; +} + +static int channel_table_release_endpoint(CHANNEL_TABLE* channel_table, uint16_t outgoing_channel) +{ + int result = 0; + + // Ensure that the outgoing channel is "reasonable". + if (outgoing_channel >= channel_table->size) + { + LogError("Incoming channel out of range"); + result = MU_FAILURE; + } + // Ensure that the outgoing channel is allocated in the table. + else if (channel_table->channels[outgoing_channel].outgoing_channel != outgoing_channel) + { + LogError("Outgoing Channel is not allocated."); + result = MU_FAILURE; + } + else if (channel_table->channels[outgoing_channel].is_endpoint_live == false) + { + LogError("Outgoing Channel does not have an endpoint."); + result = MU_FAILURE; + } + else + { + channel_table->channels[outgoing_channel].is_endpoint_live = false; + result = 0; + } + return result; +} + + +static int channel_table_find_outgoing_channel_from_incoming_channel(CHANNEL_TABLE*channel_table, uint16_t incoming_channel, uint16_t *outgoing_channel, bool*endpoint_is_live) +{ + int result = 0; + + size_t i = 0; + for (; i < channel_table->size; i++) + { + if (channel_table->channels[i].incoming_channel == incoming_channel) + { + *outgoing_channel = channel_table->channels[i].outgoing_channel; + *endpoint_is_live = channel_table->channels[i].is_endpoint_live; + result = 0; + break; + } + } + if (i == channel_table->size) + { + LogError("Could not find incoming channel %d", incoming_channel); + result = MU_FAILURE; + } + + return result; +} + +static int channel_table_free(CHANNEL_TABLE* channel_table, uint16_t channel) +{ + int result=0; + + if (channel > channel_table->size) + { + LogError("Channel out of range"); + result = MU_FAILURE; + } + else if (channel_table->channels[channel].outgoing_channel == ENDPOINT_UNUSED_CHANNEL) + { + LogError("Channel is already free"); + result = MU_FAILURE; + } + else + { + channel_table->channels[channel].outgoing_channel = ENDPOINT_UNUSED_CHANNEL; + channel_table->channels[channel].incoming_channel = ENDPOINT_UNUSED_CHANNEL; + channel_table->channels[channel].is_endpoint_live = false; + result = 0; + } + + return result; +} + /* Codes_S_R_S_CONNECTION_01_258: [on_connection_state_changed shall be invoked whenever the connection state changes.]*/ static void connection_set_state(CONNECTION_HANDLE connection, CONNECTION_STATE connection_state) { @@ -208,7 +455,7 @@ static const char* get_frame_type_as_string(AMQP_VALUE descriptor) } #endif // NO_LOGGING -static void log_incoming_frame(AMQP_VALUE performative) +static void log_incoming_frame(uint16_t channel, AMQP_VALUE performative) { #ifdef NO_LOGGING UNUSED(performative); @@ -221,6 +468,7 @@ static void log_incoming_frame(AMQP_VALUE performative) else { char* performative_as_string; + LOG(AZ_LOG_TRACE, 0, "%d:", channel) LOG(AZ_LOG_TRACE, 0, "<- "); LOG(AZ_LOG_TRACE, 0, "%s", (char*)get_frame_type_as_string(descriptor)); performative_as_string = NULL; @@ -233,7 +481,7 @@ static void log_incoming_frame(AMQP_VALUE performative) #endif } -static void log_outgoing_frame(AMQP_VALUE performative) +static void log_outgoing_frame(uint16_t channel, AMQP_VALUE performative) { #ifdef NO_LOGGING UNUSED(performative); @@ -246,6 +494,7 @@ static void log_outgoing_frame(AMQP_VALUE performative) else { char* performative_as_string; + LOG(AZ_LOG_TRACE, 0, "%d:", channel) LOG(AZ_LOG_TRACE, 0, "-> "); LOG(AZ_LOG_TRACE, 0, "%s", (char*)get_frame_type_as_string(descriptor)); performative_as_string = NULL; @@ -429,7 +678,7 @@ static int send_open_frame(CONNECTION_HANDLE connection) { if (connection->is_trace_on == 1) { - log_outgoing_frame(open_performative_value); + log_outgoing_frame(0xffff, open_performative_value); } /* Codes_S_R_S_CONNECTION_01_046: [OPEN SENT In this state the connection headers have been exchanged. An open frame has been sent to the peer but no open frame has yet been received.] */ @@ -492,7 +741,7 @@ static int send_close_frame(CONNECTION_HANDLE connection, ERROR_HANDLE error_han { if (connection->is_trace_on == 1) { - log_outgoing_frame(close_performative_value); + log_outgoing_frame(0xffff, close_performative_value); } result = 0; @@ -840,7 +1089,7 @@ static void on_amqp_frame_received(void* context, uint16_t channel, AMQP_VALUE p if (connection->is_trace_on == 1) { - log_incoming_frame(performative); + log_incoming_frame(channel, performative); } if (is_open_type_by_descriptor(descriptor)) @@ -1013,16 +1262,21 @@ static void on_amqp_frame_received(void* context, uint16_t channel, AMQP_VALUE p } else { - uint16_t remote_channel; + uint16_t remote_channel = 0xffff; ENDPOINT_HANDLE new_endpoint = NULL; bool remote_begin = false; + // If there is no remote channel in the begin, this is an incoming session BEGIN operation on a listening + // connection. if (begin_get_remote_channel(begin, &remote_channel) != 0) { remote_begin = true; + if (connection->on_new_endpoint != NULL) { new_endpoint = connection_create_endpoint(connection); + new_endpoint->incoming_channel = channel; + channel_table_set_incoming_channel(&connection->channel_table, new_endpoint->outgoing_channel, channel); if (!connection->on_new_endpoint(connection->on_new_endpoint_callback_context, new_endpoint)) { connection_destroy_endpoint(new_endpoint); @@ -1036,10 +1290,12 @@ static void on_amqp_frame_received(void* context, uint16_t channel, AMQP_VALUE p ENDPOINT_INSTANCE* session_endpoint = find_session_endpoint_by_outgoing_channel(connection, remote_channel); if (session_endpoint == NULL) { - LogError("Cannot create session endpoint"); + LogError("Cannot find session endpoint corresponding to remote channel %d", remote_channel); } else { + // Update the channel table to reflect the incoming channel number. + channel_table_set_incoming_channel(&connection->channel_table, session_endpoint->outgoing_channel, channel); session_endpoint->incoming_channel = channel; session_endpoint->on_endpoint_frame_received(session_endpoint->callback_context, performative, payload_size, payload_bytes); } @@ -1048,6 +1304,8 @@ static void on_amqp_frame_received(void* context, uint16_t channel, AMQP_VALUE p { if (new_endpoint != NULL) { + // Update the channel table to reflect the incoming channel number. + channel_table_set_incoming_channel(&connection->channel_table, new_endpoint->outgoing_channel, channel); new_endpoint->incoming_channel = channel; new_endpoint->on_endpoint_frame_received(new_endpoint->callback_context, performative, payload_size, payload_bytes); } @@ -1059,10 +1317,31 @@ static void on_amqp_frame_received(void* context, uint16_t channel, AMQP_VALUE p break; } + case AMQP_END: { + // When we receive an "end" frame, we need to find the session endpoint that corresponds to the incoming channel number. + // Once we find it, we need to update the channel table to reflect that the incoming and outgoing channel numbers is no + // longer in use. + uint16_t outgoing_channel; + bool endpoint_is_live = false; + if (channel_table_find_outgoing_channel_from_incoming_channel(&connection->channel_table, channel, &outgoing_channel, &endpoint_is_live) != 0) + { + LogError("Cannot find outgoing channel for incoming channel %u", (unsigned int)channel); + } + else + { + channel_table_free(&connection->channel_table, outgoing_channel); + // If the endpoint associated with the incoming endpoint is no longer live, we should ignore the END frame. + if (!endpoint_is_live) + { + LogInfo("END received, but endpoint is not live. Ignoring."); + break; + } + } + } + // fallthrough case AMQP_FLOW: case AMQP_TRANSFER: case AMQP_DISPOSITION: - case AMQP_END: case AMQP_ATTACH: case AMQP_DETACH: { @@ -1228,48 +1507,10 @@ CONNECTION_HANDLE connection_create2(XIO_HANDLE xio, const char* hostname, const } else { - (void)memcpy(connection->container_id, container_id, container_id_length + 1); - - /* Codes_S_R_S_CONNECTION_01_173: [] */ - connection->max_frame_size = 4294967295u; - /* Codes: [] */ - connection->channel_max = 65535; - - /* Codes_S_R_S_CONNECTION_01_175: [] */ - /* Codes_S_R_S_CONNECTION_01_192: [A value of zero is the same as if it was not set (null).] */ - connection->idle_timeout = 0; - connection->remote_idle_timeout = 0; - connection->remote_idle_timeout_send_frame_millisecond = 0; - connection->idle_timeout_empty_frame_send_ratio = 0.5; - - connection->endpoint_count = 0; - connection->endpoints = NULL; - connection->header_bytes_received = 0; - connection->is_remote_frame_received = 0; - connection->properties = NULL; - - connection->is_underlying_io_open = 0; - connection->remote_max_frame_size = 512; - connection->is_trace_on = 0; - - /* Mark that settings have not yet been set by the user */ - connection->idle_timeout_specified = 0; - - connection->on_new_endpoint = on_new_endpoint; - connection->on_new_endpoint_callback_context = callback_context; - - connection->on_connection_close_received_event_subscription.on_connection_close_received = NULL; - connection->on_connection_close_received_event_subscription.context = NULL; - - connection->on_io_error = on_io_error; - connection->on_io_error_callback_context = on_io_error_context; - connection->on_connection_state_changed = on_connection_state_changed; - connection->on_connection_state_changed_callback_context = on_connection_state_changed_context; - - if (tickcounter_get_current_ms(connection->tick_counter, &connection->last_frame_received_time) != 0) + if (channel_table_initialize(&connection->channel_table) + != 0) { - LogError("Could not retrieve time for last frame received time"); - tickcounter_destroy(connection->tick_counter); + LogError("Cannot initialize endpoint handle table"); free(connection->container_id); free(connection->host_name); amqp_frame_codec_destroy(connection->amqp_frame_codec); @@ -1279,10 +1520,81 @@ CONNECTION_HANDLE connection_create2(XIO_HANDLE xio, const char* hostname, const } else { - connection->last_frame_sent_time = connection->last_frame_received_time; + (void)memcpy( + connection->container_id, + container_id, + container_id_length + 1); + + /* Codes_S_R_S_CONNECTION_01_173: [] */ + connection->max_frame_size = 4294967295u; + /* Codes: [] */ + connection->channel_max = 65535; + + /* Codes_S_R_S_CONNECTION_01_175: [] */ + /* Codes_S_R_S_CONNECTION_01_192: [A value of zero is the same as if + * it was not set (null).] */ + connection->idle_timeout = 0; + connection->remote_idle_timeout = 0; + connection->remote_idle_timeout_send_frame_millisecond = 0; + connection->idle_timeout_empty_frame_send_ratio = 0.5; + + connection->endpoint_count = 0; + connection->endpoints = NULL; + connection->header_bytes_received = 0; + connection->is_remote_frame_received = 0; + connection->properties = NULL; + + connection->is_underlying_io_open = 0; + connection->remote_max_frame_size = 512; + connection->is_trace_on = 0; + + /* Mark that settings have not yet been set by the user */ + connection->idle_timeout_specified = 0; + + connection->on_new_endpoint = on_new_endpoint; + connection->on_new_endpoint_callback_context = callback_context; + + connection->on_connection_close_received_event_subscription + .on_connection_close_received + = NULL; + connection->on_connection_close_received_event_subscription.context + = NULL; + + connection->on_io_error = on_io_error; + connection->on_io_error_callback_context = on_io_error_context; + connection->on_connection_state_changed + = on_connection_state_changed; + connection->on_connection_state_changed_callback_context + = on_connection_state_changed_context; + + if (tickcounter_get_current_ms( + connection->tick_counter, + &connection->last_frame_received_time) + != 0) + { + LogError( + "Could not retrieve time for last frame received time"); + tickcounter_destroy(connection->tick_counter); + free(connection->container_id); + free(connection->host_name); + amqp_frame_codec_destroy(connection->amqp_frame_codec); + frame_codec_destroy(connection->frame_codec); + free(connection); + connection = NULL; + } + else + { + connection->last_frame_sent_time + = connection->last_frame_received_time; - /* Codes_S_R_S_CONNECTION_01_072: [When connection_create succeeds, the state of the connection shall be CONNECTION_STATE_START.] */ - connection_set_state(connection, CONNECTION_STATE_START); + /* Codes_S_R_S_CONNECTION_01_072: [When connection_create + * succeeds, the state of the connection shall be + * CONNECTION_STATE_START.] */ + connection_set_state(connection, CONNECTION_STATE_START); + } } } } @@ -1313,6 +1625,7 @@ void connection_destroy(CONNECTION_HANDLE connection) amqp_frame_codec_destroy(connection->amqp_frame_codec); frame_codec_destroy(connection->frame_codec); tickcounter_destroy(connection->tick_counter); + channel_table_destroy(&connection->channel_table); if (connection->properties != NULL) { amqpvalue_destroy(connection->properties); @@ -1867,57 +2180,60 @@ ENDPOINT_HANDLE connection_create_endpoint(CONNECTION_HANDLE connection) } else { - uint32_t i = 0; - /* Codes_S_R_S_CONNECTION_01_128: [The lowest number outgoing channel shall be associated with the newly created endpoint.] */ - for (i = 0; i < connection->endpoint_count; i++) + uint16_t outgoing_channel = 0; + if (channel_table_allocate(&connection->channel_table, &outgoing_channel)) { - if (connection->endpoints[i]->outgoing_channel > i) - { - /* found a gap in the sorted endpoint array */ - break; - } - } - - /* Codes_S_R_S_CONNECTION_01_127: [On success, connection_create_endpoint shall return a non-NULL handle to the newly created endpoint.] */ - result = (ENDPOINT_HANDLE)calloc(1, sizeof(ENDPOINT_INSTANCE)); - /* Codes_S_R_S_CONNECTION_01_196: [If memory cannot be allocated for the new endpoint, connection_create_endpoint shall fail and return NULL.] */ - if (result == NULL) - { - LogError("Cannot allocate memory for endpoint"); + LogError("Unable to allocate outgoing channel"); + result = NULL; } else { - ENDPOINT_HANDLE* new_endpoints; - - result->on_endpoint_frame_received = NULL; - result->on_connection_state_changed = NULL; - result->callback_context = NULL; - result->outgoing_channel = (uint16_t)i; - result->connection = connection; - - /* Codes_S_R_S_CONNECTION_01_197: [The newly created endpoint shall be added to the endpoints list, so that it can be tracked.] */ - new_endpoints = (ENDPOINT_HANDLE*)realloc(connection->endpoints, sizeof(ENDPOINT_HANDLE) * ((size_t)connection->endpoint_count + 1)); - if (new_endpoints == NULL) + /* Codes_S_R_S_CONNECTION_01_127: [On success, connection_create_endpoint shall + * return a non-NULL handle to the newly created endpoint.] */ + result = (ENDPOINT_HANDLE)calloc(1, sizeof(ENDPOINT_INSTANCE)); + /* Codes_S_R_S_CONNECTION_01_196: [If memory cannot be allocated for the new + * endpoint, connection_create_endpoint shall fail and return NULL.] */ + if (result == NULL) { - /* Tests_S_R_S_CONNECTION_01_198: [If adding the endpoint to the endpoints list tracked by the connection fails, connection_create_endpoint shall fail and return NULL.] */ - LogError("Cannot reallocate memory for connection endpoints"); - free(result); - result = NULL; + LogError("Cannot allocate memory for endpoint"); } else { - connection->endpoints = new_endpoints; - - if (i < connection->endpoint_count) + ENDPOINT_HANDLE* new_endpoints; + + result->on_endpoint_frame_received = NULL; + result->on_connection_state_changed = NULL; + result->callback_context = NULL; + result->outgoing_channel = outgoing_channel; + result->incoming_channel = ENDPOINT_UNUSED_CHANNEL; + result->connection = connection; + + /* Codes_S_R_S_CONNECTION_01_197: [The newly created endpoint shall be added to + * the endpoints list, so that it can be tracked.] */ + new_endpoints = (ENDPOINT_HANDLE*)realloc( + connection->endpoints, + sizeof(ENDPOINT_HANDLE) * ((size_t)connection->endpoint_count + 1)); + if (new_endpoints == NULL) { - (void)memmove(&connection->endpoints[i + 1], &connection->endpoints[i], sizeof(ENDPOINT_INSTANCE*) * (connection->endpoint_count - i)); + /* Tests_S_R_S_CONNECTION_01_198: [If adding the endpoint to the endpoints + * list tracked by the connection fails, connection_create_endpoint shall + * fail and return NULL.] */ + LogError("Cannot reallocate memory for connection endpoints"); + channel_table_free(&connection->channel_table, outgoing_channel); + free(result); + result = NULL; } + else + { + // Insert the new endpoint at the end of the set of endpoints. + connection->endpoints = new_endpoints; + connection->endpoints[connection->endpoint_count] = result; + connection->endpoint_count++; - connection->endpoints[i] = result; - connection->endpoint_count++; - - /* Codes_S_R_S_CONNECTION_01_112: [connection_create_endpoint shall create a new endpoint that can be used by a session.] */ + /* Codes_S_R_S_CONNECTION_01_112: [connection_create_endpoint shall create a + * new endpoint that can be used by a session.] */ + } } } } @@ -1944,6 +2260,15 @@ int connection_start_endpoint(ENDPOINT_HANDLE endpoint, ON_ENDPOINT_FRAME_RECEIV endpoint->on_connection_state_changed = on_connection_state_changed; endpoint->callback_context = context; + /* If the connection is currently opened, tell the endpoint that the connection is open so that it can start processing the session. */ + if (endpoint->connection->connection_state == CONNECTION_STATE_OPENED) + { + endpoint->on_connection_state_changed( + endpoint->callback_context, + endpoint->connection->connection_state, + CONNECTION_STATE_OPEN_SENT); // Fake the transition between "Open Sent" and "Opened" because session is only triggered on state changes. + } + result = 0; } @@ -1970,6 +2295,24 @@ int connection_endpoint_get_incoming_channel(ENDPOINT_HANDLE endpoint, uint16_t* return result; } +int connection_endpoint_get_outgoing_channel(ENDPOINT_HANDLE endpoint, uint16_t* outgoing_channel) +{ + int result; + + if ((endpoint == NULL) || (outgoing_channel == NULL)) + { + LogError("Bad arguments: endpoint = %p, outgoing_channel = %p", endpoint, outgoing_channel); + result = MU_FAILURE; + } + else + { + *outgoing_channel = endpoint->outgoing_channel; + result = 0; + } + + return result; +} + /* Codes_S_R_S_CONNECTION_01_129: [connection_destroy_endpoint shall free all resources associated with an endpoint created by connection_create_endpoint.] */ void connection_destroy_endpoint(ENDPOINT_HANDLE endpoint) { @@ -1995,6 +2338,9 @@ void connection_destroy_endpoint(ENDPOINT_HANDLE endpoint) if (i < connection->endpoint_count) { // endpoint found + + // Let the channel table know that the endpoint is gone. We keep it allocated until an END frame is received, or the connection is destroyed. + channel_table_release_endpoint(&connection->channel_table, connection->endpoints[i]->outgoing_channel); if (connection->endpoint_count == 1) { free(connection->endpoints); @@ -2009,7 +2355,6 @@ void connection_destroy_endpoint(ENDPOINT_HANDLE endpoint) { (void)memmove(connection->endpoints + i, connection->endpoints + i + 1, sizeof(ENDPOINT_HANDLE) * (connection->endpoint_count - i - 1)); } - new_endpoints = (ENDPOINT_HANDLE*)realloc(connection->endpoints, (connection->endpoint_count - 1) * sizeof(ENDPOINT_HANDLE)); if (new_endpoints != NULL) { @@ -2066,7 +2411,7 @@ int connection_encode_frame(ENDPOINT_HANDLE endpoint, AMQP_VALUE performative, P { if (connection->is_trace_on == 1) { - log_outgoing_frame(performative); + log_outgoing_frame(endpoint->outgoing_channel, performative); } if (tickcounter_get_current_ms(connection->tick_counter, &connection->last_frame_sent_time) != 0) diff --git a/sdk/core/azure-core-amqp/vendor/azure-uamqp-c/src/session.c b/sdk/core/azure-core-amqp/vendor/azure-uamqp-c/src/session.c index eac5d448d2..b6556e4e3a 100644 --- a/sdk/core/azure-core-amqp/vendor/azure-uamqp-c/src/session.c +++ b/sdk/core/azure-core-amqp/vendor/azure-uamqp-c/src/session.c @@ -218,7 +218,7 @@ static void end_session_with_error(SESSION_INSTANCE* session_instance, const cha static int send_begin(SESSION_INSTANCE* session_instance) { - int result; + int result = 0; BEGIN_HANDLE begin = begin_create(session_instance->next_outgoing_id, session_instance->incoming_window, session_instance->outgoing_window); if (begin == NULL) @@ -228,6 +228,7 @@ static int send_begin(SESSION_INSTANCE* session_instance) else { uint16_t remote_channel; + #if 0 if (begin_set_handle_max(begin, session_instance->handle_max) != 0) { result = MU_FAILURE; @@ -238,28 +239,72 @@ static int send_begin(SESSION_INSTANCE* session_instance) { result = MU_FAILURE; } - else + else + { + AMQP_VALUE begin_performative_value = amqpvalue_create_begin(begin); + if (begin_performative_value == NULL) + { + result = MU_FAILURE; + } + else + { + if (connection_encode_frame( + session_instance->endpoint, + begin_performative_value, + NULL, + 0, + NULL, + NULL) + != 0) + { + result = MU_FAILURE; + } + else + { + result = 0; + } + + amqpvalue_destroy(begin_performative_value); + } + } +#else + if (begin_set_handle_max(begin, session_instance->handle_max) != 0) { - AMQP_VALUE begin_performative_value = amqpvalue_create_begin(begin); - if (begin_performative_value == NULL) + result = MU_FAILURE; + } + else + { + if ((connection_endpoint_get_incoming_channel(session_instance->endpoint, &remote_channel) == 0) + && (remote_channel != 0xffff)) { - result = MU_FAILURE; + if (begin_set_remote_channel(begin, remote_channel) != 0) + { + result = MU_FAILURE; + } } - else + if (result != MU_FAILURE) { - if (connection_encode_frame(session_instance->endpoint, begin_performative_value, NULL, 0, NULL, NULL) != 0) + AMQP_VALUE begin_performative_value = amqpvalue_create_begin(begin); + if (begin_performative_value == NULL) { result = MU_FAILURE; } else { - result = 0; - } + if (connection_encode_frame(session_instance->endpoint, begin_performative_value, NULL, 0, NULL, NULL) != 0) + { + result = MU_FAILURE; + } + else + { + result = 0; + } - amqpvalue_destroy(begin_performative_value); + amqpvalue_destroy(begin_performative_value); + } } } - +#endif begin_destroy(begin); } @@ -874,7 +919,8 @@ int session_begin(SESSION_HANDLE session) } else { - if (!session_instance->is_underlying_connection_open) + // If we don't think the connection is already open, open the connection. We'll send the begin message as soon as the connection is open. + if (session_instance->is_underlying_connection_open == UNDERLYING_CONNECTION_NOT_OPEN) { if (connection_open(session_instance->connection) != 0) { diff --git a/sdk/core/ci.yml b/sdk/core/ci.yml index 4dffe8da83..d8c59326d2 100644 --- a/sdk/core/ci.yml +++ b/sdk/core/ci.yml @@ -51,7 +51,7 @@ stages: CtestRegex: azure-core.|json-test LiveTestCtestRegex: azure-core.|json-test LiveTestTimeoutInMinutes: 90 # default is 60 min. We need a little longer on worst case for Win+jsonTests - LineCoverageTarget: 87 + LineCoverageTarget: 88 BranchCoverageTarget: 50 # PreTestSteps: # - pwsh: | diff --git a/sdk/eventhubs/azure-messaging-eventhubs/src/private/eventhubs_utilities.hpp b/sdk/eventhubs/azure-messaging-eventhubs/src/private/eventhubs_utilities.hpp index dde48a0c6b..af34e29190 100644 --- a/sdk/eventhubs/azure-messaging-eventhubs/src/private/eventhubs_utilities.hpp +++ b/sdk/eventhubs/azure-messaging-eventhubs/src/private/eventhubs_utilities.hpp @@ -20,7 +20,7 @@ namespace Azure { namespace Messaging { namespace EventHubs { namespace _detail { - constexpr bool EnableAmqpTrace = true; + constexpr bool EnableAmqpTrace = false; class EventHubsExceptionFactory { public: diff --git a/sdk/eventhubs/azure-messaging-eventhubs/test/ut/processor_test.cpp b/sdk/eventhubs/azure-messaging-eventhubs/test/ut/processor_test.cpp index b38792af3e..3dbc184526 100644 --- a/sdk/eventhubs/azure-messaging-eventhubs/test/ut/processor_test.cpp +++ b/sdk/eventhubs/azure-messaging-eventhubs/test/ut/processor_test.cpp @@ -164,7 +164,6 @@ namespace Azure { namespace Messaging { namespace EventHubs { namespace Test { // Run the processor on a background thread and the test on the foreground. processor.Start(context); -#if ASYNC_PROCESS_EVENTS std::set partitionsAcquired; std::vector processEventsThreads; // When we exit the process thread, cancel the context to unblock the processor. @@ -195,8 +194,93 @@ namespace Azure { namespace Messaging { namespace EventHubs { namespace Test { thread.join(); } } + // Stop the processor, we're done with the test. + processor.Stop(); +#endif + } + + void TestWithLoadBalancerSingleThreaded(Models::ProcessorStrategy processorStrategy) + { + Azure::Core::Context context = Azure::Core::Context::ApplicationContext.WithDeadline( + Azure::DateTime::clock::now() + std::chrono::minutes(5)); + + std::string eventHubName{GetEnv("EVENTHUB_NAME")}; + std::string consumerGroup = GetEnv("EVENTHUB_CONSUMER_GROUP"); + + std::string const connectionString = GetEnv("EVENTHUB_CONNECTION_STRING"); + Azure::Messaging::EventHubs::ConsumerClientOptions consumerClientOptions; + consumerClientOptions.ApplicationID + = testing::UnitTest::GetInstance()->current_test_info()->name(); + consumerClientOptions.Name = testing::UnitTest::GetInstance()->current_test_info()->name(); + + std::string containerName{GetRandomName("proctest")}; + + // Create the checkpoint store + std::shared_ptr checkpointStore{std::make_shared()}; + GTEST_LOG_(INFO) << "Checkpoint store created"; + + std::shared_ptr consumerClient{std::make_shared( + connectionString, eventHubName, consumerGroup, consumerClientOptions)}; + GTEST_LOG_(INFO) << "Consumer Client created"; + + ProcessorOptions processorOptions; + processorOptions.LoadBalancingStrategy = processorStrategy; + processorOptions.UpdateInterval = std::chrono::milliseconds(1000); + + Processor processor{consumerClient, checkpointStore, processorOptions}; + + // Warm up the consumer client - establish connection to the server, etc. + auto eventHubProperties = consumerClient->GetEventHubProperties(context); + + ProducerClientOptions producerOptions; + producerOptions.Name = "Producer for LoadBalancerTest"; + ProducerClient producerClient{connectionString, eventHubName, producerOptions}; + +#if PROCESSOR_ON_TEST_THREAD + std::thread processEventsThread([&]() { + std::set partitionsAcquired; + std::vector processEventsThreads; + // When we exit the process thread, cancel the context to unblock the processor. + scope_guard onExit([&context] { context.Cancel(); }); + + WaitGroup waitGroup; + for (auto const& partitionId : eventHubProperties.PartitionIds) + { + std::shared_ptr partitionClient + = processor.NextPartitionClient(context); + waitGroup.AddWaiter(); + ASSERT_EQ(partitionsAcquired.find(partitionId), partitionsAcquired.end()) + << "No previous client for " << partitionClient->PartitionId(); + processEventsThreads.push_back( + std::thread([&waitGroup, &producerClient, partitionClient, &context, this] { + scope_guard onExit([&] { waitGroup.CompleteWaiter(); }); + ProcessEventsForLoadBalancerTest(producerClient, partitionClient, context); + })); + } + // Block until all the events have been processed. + waitGroup.Wait(); + + // And wait until all the threads have completed. + for (auto& thread : processEventsThreads) + { + if (thread.joinable()) + { + thread.join(); + } + } + // Stop the processor, we're done with the test. + processor.Stop(); + }); + + processor.Run(context); + + processEventsThread.join(); #else + // Run the processor on a background thread and the test on the foreground. + processor.Start(context); + std::set partitionsAcquired; + // Iterate over the partitions, processing events until we've received all of them. for (auto const& partitionId : eventHubProperties.PartitionIds) { std::shared_ptr partitionClient @@ -204,9 +288,8 @@ namespace Azure { namespace Messaging { namespace EventHubs { namespace Test { ASSERT_EQ(partitionsAcquired.find(partitionId), partitionsAcquired.end()) << "No previous client for " << partitionClient->PartitionId(); - ProcessEventsForLoadBalancerTest(producerClient, partitionClient, context); + ProcessEventsForLoadBalancerTestSingleThreaded(producerClient, partitionClient, context); } -#endif // Stop the processor, we're done with the test. processor.Stop(); #endif @@ -274,7 +357,6 @@ namespace Azure { namespace Messaging { namespace EventHubs { namespace Test { const int32_t batchSize = 1000; EXPECT_EQ(0, expectedEventsCount % batchSize) << "Keep the math simple - even # of messages for each batch"; -#if ASYNC_GENERATE_EVENTS std::thread produceEvents([&, partitionClient]() { // Wait for 10 seconds for all of the consumer clients to be spun up. GTEST_LOG_(INFO) @@ -307,7 +389,56 @@ namespace Azure { namespace Messaging { namespace EventHubs { namespace Test { GTEST_LOG_(FATAL) << "Exception thrown sending messages" << ex.what(); } }); -#else + std::vector> allEvents; + while (!context.IsCancelled()) + { + auto receiveContext + = context.WithDeadline(Azure::DateTime::clock::now() + std::chrono::seconds(50)); + GTEST_LOG_(INFO) << "Receive up to 100 events with a 50 second timeout on partition " + << partitionClient->PartitionId(); + auto events = partitionClient->ReceiveEvents(100, receiveContext); + if (events.size() != 0) + { + GTEST_LOG_(INFO) << "Processing " << events.size() << " events for partition " + << partitionClient->PartitionId(); + allEvents.insert(allEvents.end(), events.begin(), events.end()); + GTEST_LOG_(INFO) << "Updating checkpoint for partition " + << partitionClient->PartitionId(); + partitionClient->UpdateCheckpoint(events.back(), context); + if (allEvents.size() == expectedEventsCount) + { + GTEST_LOG_(INFO) << "Received all expected events; returning."; + if (produceEvents.joinable()) + { + produceEvents.join(); + } + return; + } + } + } + } + catch (std::runtime_error const& ex) + { + GTEST_LOG_(FATAL) << "Exception thrown receiving messages." << ex.what(); + producerContext.Cancel(); + } + } + + void ProcessEventsForLoadBalancerTestSingleThreaded( + ProducerClient& producerClient, + std::shared_ptr partitionClient, + Azure::Core::Context const& context) + { + Azure::Core::Context producerContext{context}; + try + { + // initialize any resources needed to process the partition + // This is the equivalent to PartitionOpen + GTEST_LOG_(INFO) << "Started processing partition " << partitionClient->PartitionId(); + const int32_t expectedEventsCount = 1000; + const int32_t batchSize = 1000; + EXPECT_EQ(0, expectedEventsCount % batchSize) + << "Keep the math simple - even # of messages for each batch"; try { GTEST_LOG_(INFO) << "Generate " << std::dec << expectedEventsCount << " events in " @@ -334,7 +465,7 @@ namespace Azure { namespace Messaging { namespace EventHubs { namespace Test { { GTEST_LOG_(FATAL) << "Exception thrown sending messages" << ex.what(); } -#endif + std::vector> allEvents; while (!context.IsCancelled()) { @@ -354,9 +485,6 @@ namespace Azure { namespace Messaging { namespace EventHubs { namespace Test { if (allEvents.size() == expectedEventsCount) { GTEST_LOG_(INFO) << "Received all expected events; returning."; -#if ASYNC_GENERATE_EVENTS - produceEvents.join(); -#endif return; } } @@ -597,6 +725,15 @@ namespace Azure { namespace Messaging { namespace EventHubs { namespace Test { processor.Stop(); } + TEST_F(ProcessorTest, Processor_SingleThreaded_Balanced_LIVEONLY_) + { + TestWithLoadBalancerSingleThreaded(Models::ProcessorStrategy::ProcessorStrategyBalanced); + } + TEST_F(ProcessorTest, Processor_SingleThreaded_Greedy_LIVEONLY_) + { + TestWithLoadBalancerSingleThreaded(Models::ProcessorStrategy::ProcessorStrategyGreedy); + } + TEST_F(ProcessorTest, Processor_Balanced_LIVEONLY_) { TestWithLoadBalancer(Models::ProcessorStrategy::ProcessorStrategyBalanced);