Beispiel #1
0
        /// <summary>
        ///   Creates a fully populated message with a consistent set of
        ///   test data.
        /// </summary>
        ///
        /// <returns>The populated message.</returns>
        ///
        private static AmqpAnnotatedMessage CreateEmptydDataBodyMessage()
        {
            var body    = AmqpMessageBody.FromData(new[] { (ReadOnlyMemory <byte>)Array.Empty <byte>() });
            var message = new AmqpAnnotatedMessage(body);

            return(message);
        }
Beispiel #2
0
        public void MessageIsPopulatedFromEventHubProperties()
        {
            var properties = new Dictionary <string, object>
            {
                { "Test", 1 },
                { "Second", "2" },
                { "Third", TimeSpan.FromSeconds(99) }
            };

            var sequenceNumber = 123L;
            var offset         = 456L;
            var enqueueTime    = new DateTimeOffset(2015, 10, 27, 00, 00, 00, TimeSpan.Zero);
            var partitionKey   = "fake-key";
            var lastSequence   = 321L;
            var lastOffset     = 654L;
            var lastEnqueue    = new DateTimeOffset(2012, 03, 04, 08, 00, 00, TimeSpan.Zero);
            var lastRetrieve   = new DateTimeOffset(2020, 01, 01, 05, 15, 37, TimeSpan.Zero);

            var message = new AmqpAnnotatedMessage(AmqpMessageBody.FromData(new[] { (ReadOnlyMemory <byte>)Array.Empty <byte>() }));

            message.PopulateFromEventProperties(properties, sequenceNumber, offset, enqueueTime, partitionKey, lastSequence, lastOffset, lastEnqueue, lastRetrieve);

            Assert.That(message.ApplicationProperties, Is.EquivalentTo(properties), "The application properties should match.");
            Assert.That(message.GetSequenceNumber(), Is.EqualTo(sequenceNumber), "The sequence number should match.");
            Assert.That(message.GetOffset(), Is.EqualTo(offset), "The offset should match.");
            Assert.That(message.GetEnqueuedTime(), Is.EqualTo(enqueueTime), "The enqueue time should match.");
            Assert.That(message.GetPartitionKey(), Is.EqualTo(partitionKey), "The partition key should match.");
            Assert.That(message.GetLastPartitionSequenceNumber(), Is.EqualTo(lastSequence), "The last sequence number should match.");
            Assert.That(message.GetLastPartitionOffset(), Is.EqualTo(lastOffset), "The last offset should match.");
            Assert.That(message.GetLastPartitionEnqueuedTime(), Is.EqualTo(lastEnqueue), "The last enqueue time should match.");
            Assert.That(message.GetLastPartitionPropertiesRetrievalTime(), Is.EqualTo(lastRetrieve), "The last retrieve time should match.");
        }
Beispiel #3
0
        /// <summary>
        ///   Creates a fully populated message with a consistent set of
        ///   test data and the specified set of system properties.
        /// </summary>
        ///
        /// <param name="sequenceNumber">The sequence number of the event; if null, this will not be populated.</param>
        /// <param name="lastSequenceNumber">The sequence number that was last enqueued in the partition; if null, this will not be populated.</param>
        /// <param name="offset">The offset of the event; if null, this will not be populated.</param>
        /// <param name="lastOffset">The offset that was last enqueued in the partition; if null, this will not be populated.</param>
        /// <param name="partitionKey">The partition key of the event; if null, this will not be populated.</param>
        /// <param name="enqueueTime">The time that the event was enqueued in the partition; if null, this will not be populated.</param>
        /// <param name="lastEnqueueTime">The time that an event was last enqueued in the partition; if null, this will not be populated.</param>
        /// <param name="lastRetrieveTime">The time that the information about the last event enqueued in the partition was reported; if null, this will not be populated.</param>
        ///
        /// <returns>The populated message.</returns>
        ///
        private static AmqpAnnotatedMessage CreateFullyPopulatedAmqpMessage(long sequenceNumber,
                                                                            long lastSequenceNumber,
                                                                            long offset,
                                                                            long lastOffset,
                                                                            string partitionKey,
                                                                            DateTimeOffset enqueueTime,
                                                                            DateTimeOffset lastEnqueueTime,
                                                                            DateTimeOffset lastRetrieveTime)
        {
            var body    = AmqpMessageBody.FromData(new ReadOnlyMemory <byte>[] { new byte[] { 0x65, 0x66, 0x67, 0x68 } });
            var message = new AmqpAnnotatedMessage(body);

            // Header

            message.Header.DeliveryCount = 99;
            message.Header.Durable       = true;
            message.Header.FirstAcquirer = true;
            message.Header.Priority      = 123;
            message.Header.TimeToLive    = TimeSpan.FromSeconds(10);

            // Properties

            message.Properties.AbsoluteExpiryTime = new DateTimeOffset(2015, 10, 27, 00, 00, 00, TimeSpan.Zero);
            message.Properties.ContentEncoding    = "fake";
            message.Properties.ContentType        = "test/unit";
            message.Properties.CorrelationId      = new AmqpMessageId("red-5");
            message.Properties.CreationTime       = new DateTimeOffset(2012, 03, 04, 08, 00, 00, 00, TimeSpan.Zero);
            message.Properties.GroupId            = "mine!";
            message.Properties.GroupSequence      = 555;
            message.Properties.MessageId          = new AmqpMessageId("red-leader");
            message.Properties.ReplyTo            = new AmqpAddress("amqps://some.namespace.com");
            message.Properties.ReplyToGroupId     = "not-mine!";
            message.Properties.Subject            = "We tried to copy an AMQP message.  You won't believe what happened next!";
            message.Properties.To     = new AmqpAddress("https://some.url.com");
            message.Properties.UserId = new byte[] { 0x11, 0x22 };

            // Application Properties

            message.ApplicationProperties.Add("first", 1);
            message.ApplicationProperties.Add("second", "two");

            // Message Annotations

            message.MessageAnnotations.Add(AmqpProperty.SequenceNumber.ToString(), sequenceNumber);
            message.MessageAnnotations.Add(AmqpProperty.Offset.ToString(), offset);
            message.MessageAnnotations.Add(AmqpProperty.EnqueuedTime.ToString(), enqueueTime);
            message.MessageAnnotations.Add(AmqpProperty.PartitionKey.ToString(), partitionKey);

            // Delivery annotations

            message.DeliveryAnnotations.Add(AmqpProperty.PartitionLastEnqueuedSequenceNumber.ToString(), lastSequenceNumber);
            message.DeliveryAnnotations.Add(AmqpProperty.PartitionLastEnqueuedOffset.ToString(), lastOffset);
            message.DeliveryAnnotations.Add(AmqpProperty.PartitionLastEnqueuedTimeUtc.ToString(), lastEnqueueTime);
            message.DeliveryAnnotations.Add(AmqpProperty.LastPartitionPropertiesRetrievalTimeUtc.ToString(), lastRetrieveTime);

            return(message);
        }
Beispiel #4
0
        public void GetEventBodyRetrievesTheDataBody()
        {
            var payload = new byte[] { 0x10, 0x20, 0x30 };
            var body    = AmqpMessageBody.FromData(new ReadOnlyMemory <byte>[1] {
                payload
            });
            var message   = new AmqpAnnotatedMessage(body);
            var eventBody = message.GetEventBody();

            Assert.That(eventBody, Is.Not.Null, "The event body should be populated.");
            Assert.That(eventBody.ToArray(), Is.EquivalentTo(payload), "The event body should match the original payload.");
        }
Beispiel #5
0
        /// <summary>
        ///   Creates a fully populated message with a consistent set of
        ///   test data.
        /// </summary>
        ///
        /// <returns>The populated message.</returns>
        ///
        private static AmqpAnnotatedMessage CreatePopulatedDataBodyMessage()
        {
            var body    = AmqpMessageBody.FromData(new[] { (ReadOnlyMemory <byte>)Array.Empty <byte>() });
            var message = new AmqpAnnotatedMessage(body);

            // Header

            message.Header.DeliveryCount = 99;
            message.Header.Durable       = true;
            message.Header.FirstAcquirer = true;
            message.Header.Priority      = 123;
            message.Header.TimeToLive    = TimeSpan.FromSeconds(10);

            // Properties

            message.Properties.AbsoluteExpiryTime = new DateTimeOffset(2015, 10, 27, 00, 00, 00, TimeSpan.Zero);
            message.Properties.ContentEncoding    = "fake";
            message.Properties.ContentType        = "test/unit";
            message.Properties.CorrelationId      = new AmqpMessageId("red-5");
            message.Properties.CreationTime       = new DateTimeOffset(2012, 03, 04, 08, 00, 00, 00, TimeSpan.Zero);
            message.Properties.GroupId            = "mine!";
            message.Properties.GroupSequence      = 555;
            message.Properties.MessageId          = new AmqpMessageId("red-leader");
            message.Properties.ReplyTo            = new AmqpAddress("amqps://some.namespace.com");
            message.Properties.ReplyToGroupId     = "not-mine!";
            message.Properties.Subject            = "We tried to copy an AMQP message.  You won't believe what happened next!";
            message.Properties.To     = new AmqpAddress("https://some.url.com");
            message.Properties.UserId = new byte[] { 0x11, 0x22 };

            // Footer

            message.Footer.Add("one", "1111");
            message.Footer.Add("two", "2222");

            // Delivery Annotations

            message.DeliveryAnnotations.Add("three", "3333");

            // Message Annotations

            message.MessageAnnotations.Add("four", "4444");
            message.MessageAnnotations.Add("five", "5555");
            message.MessageAnnotations.Add("six", "6666");

            // Application Properties

            message.ApplicationProperties.Add("seven", "7777");

            return(message);
        }
Beispiel #6
0
        public void ReadingUnpopulatedMutablePropertiesDoesNotCreateTheSection()
        {
            var amqpMessage = new AmqpAnnotatedMessage(AmqpMessageBody.FromData(new[] { ReadOnlyMemory <byte> .Empty }));
            var eventData   = new EventData(amqpMessage);

            Assert.That(eventData.MessageId, Is.Null, "The message identifier should not be populated.");
            Assert.That(amqpMessage.HasSection(AmqpMessageSection.Properties), Is.False, "Reading the message identifier should not create the section.");

            Assert.That(eventData.CorrelationId, Is.Null, "The correlation identifier should not be populated.");
            Assert.That(amqpMessage.HasSection(AmqpMessageSection.Properties), Is.False, "Reading the correlation identifier should not create the section.");

            Assert.That(eventData.ContentType, Is.Null, "The content type should not be populated.");
            Assert.That(amqpMessage.HasSection(AmqpMessageSection.Properties), Is.False, "Reading the content type should not create the section.");
        }
Beispiel #7
0
        public void CannotCreateFromNullBody()
        {
            Assert.That(
                () => AmqpMessageBody.FromData(data: null),
                Throws.InstanceOf <ArgumentNullException>());

            Assert.That(
                () => AmqpMessageBody.FromValue(value: null),
                Throws.InstanceOf <ArgumentNullException>());

            Assert.That(
                () => AmqpMessageBody.FromSequence(sequence: null),
                Throws.InstanceOf <ArgumentNullException>());
        }
Beispiel #8
0
        public void CanCreateDataBodyFactory()
        {
            var body = AmqpMessageBody.FromData(Array.Empty <ReadOnlyMemory <byte> >());

            Assert.AreEqual(AmqpMessageBodyType.Data, body.BodyType);
            Assert.IsTrue(body.TryGetData(out var data));
            Assert.NotNull(data);

            Assert.IsFalse(body.TryGetValue(out var value));
            Assert.IsNull(value);

            Assert.IsFalse(body.TryGetSequence(out var sequence));
            Assert.IsNull(sequence);
        }
Beispiel #9
0
        public void MutablePropertySettersPopulateTheAmqpMessage()
        {
            var messageId     = "message-id-value";
            var correlationId = "correlation-id-value";
            var contentType   = "text/content-type";
            var amqpMessage   = new AmqpAnnotatedMessage(AmqpMessageBody.FromData(new[] { ReadOnlyMemory <byte> .Empty }));
            var eventData     = new EventData(amqpMessage);

            eventData.MessageId = messageId;
            Assert.That(amqpMessage.Properties.MessageId.ToString(), Is.EqualTo(messageId), "The AMQP message identifier should match.");

            eventData.CorrelationId = correlationId;
            Assert.That(amqpMessage.Properties.CorrelationId.ToString(), Is.EqualTo(correlationId), "The AMQP message correlation identifier should match.");

            eventData.ContentType = contentType;
            Assert.That(amqpMessage.Properties.ContentType, Is.EqualTo(contentType), "The AMQP message content type should match.");
        }
Beispiel #10
0
        /// <summary>
        ///   Initializes a new instance of the <see cref="EventData"/> class.
        /// </summary>
        ///
        /// <param name="eventBody">The raw data as binary to use as the body of the event.</param>
        /// <param name="properties">The set of free-form event properties to send with the event.</param>
        /// <param name="systemProperties">The set of system properties received from the Event Hubs service.</param>
        /// <param name="sequenceNumber">The sequence number assigned to the event when it was enqueued in the associated Event Hub partition.</param>
        /// <param name="offset">The offset of the event when it was received from the associated Event Hub partition.</param>
        /// <param name="enqueuedTime">The date and time, in UTC, of when the event was enqueued in the Event Hub partition.</param>
        /// <param name="partitionKey">The partition hashing key applied to the batch that the associated <see cref="EventData"/>, was sent with.</param>
        /// <param name="lastPartitionSequenceNumber">The sequence number that was last enqueued into the Event Hub partition.</param>
        /// <param name="lastPartitionOffset">The offset that was last enqueued into the Event Hub partition.</param>
        /// <param name="lastPartitionEnqueuedTime">The date and time, in UTC, of the event that was last enqueued into the Event Hub partition.</param>
        /// <param name="lastPartitionPropertiesRetrievalTime">The date and time, in UTC, that the last event information for the Event Hub partition was retrieved from the service.</param>
        /// <param name="publishedSequenceNumber">The publishing sequence number assigned to the event at the time it was successfully published.</param>
        /// <param name="pendingPublishSequenceNumber">The publishing sequence number assigned to the event as part of a publishing operation.</param>
        /// <param name="pendingProducerGroupId">The producer group identifier assigned to the event as part of a publishing operation.</param>
        /// <param name="pendingOwnerLevel">The producer owner level assigned to the event as part of a publishing operation.</param>
        ///
        internal EventData(BinaryData eventBody,
                           IDictionary <string, object> properties = null,
                           IReadOnlyDictionary <string, object> systemProperties = null,
                           long?sequenceNumber                                 = null,
                           long?offset                                         = null,
                           DateTimeOffset?enqueuedTime                         = null,
                           string partitionKey                                 = null,
                           long?lastPartitionSequenceNumber                    = null,
                           long?lastPartitionOffset                            = null,
                           DateTimeOffset?lastPartitionEnqueuedTime            = null,
                           DateTimeOffset?lastPartitionPropertiesRetrievalTime = null,
                           int?publishedSequenceNumber                         = null,
                           int?pendingPublishSequenceNumber                    = null,
                           long?pendingProducerGroupId                         = null,
                           short?pendingOwnerLevel                             = null)
        {
            Argument.AssertNotNull(eventBody, nameof(eventBody));
            _amqpMessage = new AmqpAnnotatedMessage(AmqpMessageBody.FromData(MessageBody.FromReadOnlyMemorySegment(eventBody.ToMemory())));

            _amqpMessage.PopulateFromEventProperties(
                properties,
                sequenceNumber,
                offset,
                enqueuedTime,
                partitionKey,
                lastPartitionSequenceNumber,
                lastPartitionOffset,
                lastPartitionEnqueuedTime,
                lastPartitionPropertiesRetrievalTime);

            // If there was a set of system properties explicitly provided, then
            // override the default projection with them.

            if (systemProperties != null)
            {
                _systemProperties = systemProperties;
            }

            // Set the idempotent publishing state.

            PublishedSequenceNumber      = publishedSequenceNumber;
            PendingPublishSequenceNumber = pendingPublishSequenceNumber;
            PendingProducerGroupId       = pendingProducerGroupId;
            PendingProducerOwnerLevel    = pendingOwnerLevel;
        }
Beispiel #11
0
        public async Task CanSendMultipleDataSections()
        {
            await using (var scope = await ServiceBusScope.CreateWithQueue(enablePartitioning: false, enableSession: false))
            {
                var client = new ServiceBusClient(TestEnvironment.ServiceBusConnectionString);
                var sender = client.CreateSender(scope.QueueName);

                var msg  = new ServiceBusMessage();
                var amqp = new AmqpAnnotatedMessage(
                    AmqpMessageBody.FromData(
                        new ReadOnlyMemory <byte>[]
                {
                    new ReadOnlyMemory <byte>(GetRandomBuffer(100)),
                    new ReadOnlyMemory <byte>(GetRandomBuffer(100))
                }));
                msg.AmqpMessage = amqp;

                await sender.SendMessageAsync(msg);

                var receiver = client.CreateReceiver(scope.QueueName);
                var received = await receiver.ReceiveMessageAsync();

                received.GetRawAmqpMessage().Body.TryGetData(out var receivedData);
                var bodyEnum = receivedData.GetEnumerator();
                int ct       = 0;
                msg.GetRawAmqpMessage().Body.TryGetData(out var sentData);

                foreach (ReadOnlyMemory <byte> data in sentData)
                {
                    bodyEnum.MoveNext();
                    var bytes = data.ToArray();
                    Assert.AreEqual(bytes, bodyEnum.Current.ToArray());
                    if (ct++ == 0)
                    {
                        Assert.AreEqual(bytes, received.Body.ToMemory().Slice(0, 100).ToArray());
                    }
                    else
                    {
                        Assert.AreEqual(bytes, received.Body.ToMemory().Slice(100, 100).ToArray());
                    }
                }
            }
        }
Beispiel #12
0
        /// <summary>
        /// Creates a new message from the specified received message by copying the properties.
        /// </summary>
        /// <param name="receivedMessage">The received message to copy the data from.</param>
        public ServiceBusMessage(ServiceBusReceivedMessage receivedMessage)
        {
            Argument.AssertNotNull(receivedMessage, nameof(receivedMessage));

            AmqpMessageBody body = null;

            if (receivedMessage.AmqpMessage.Body.TryGetData(out IEnumerable <ReadOnlyMemory <byte> > dataBody))
            {
                body = AmqpMessageBody.FromData(MessageBody.FromReadOnlyMemorySegments(dataBody));
            }
            else if (receivedMessage.AmqpMessage.Body.TryGetValue(out object valueBody))
            {
                body = AmqpMessageBody.FromValue(valueBody);
            }
            else if (receivedMessage.AmqpMessage.Body.TryGetSequence(out IEnumerable <IList <object> > sequenceBody))
            {
                body = AmqpMessageBody.FromSequence(sequenceBody);
            }
            else
            {
                throw new NotSupportedException($"{receivedMessage.AmqpMessage.Body.BodyType} is not a supported message body type.");
            }

            AmqpMessage = new AmqpAnnotatedMessage(body);

            // copy properties
            AmqpMessageProperties properties         = AmqpMessage.Properties;
            AmqpMessageProperties receivedProperties = receivedMessage.AmqpMessage.Properties;

            properties.MessageId          = receivedProperties.MessageId;
            properties.UserId             = receivedProperties.UserId;
            properties.To                 = receivedProperties.To;
            properties.Subject            = receivedProperties.Subject;
            properties.ReplyTo            = receivedProperties.ReplyTo;
            properties.CorrelationId      = receivedProperties.CorrelationId;
            properties.ContentType        = receivedProperties.ContentType;
            properties.ContentEncoding    = receivedProperties.ContentEncoding;
            properties.AbsoluteExpiryTime = receivedProperties.AbsoluteExpiryTime;
            properties.CreationTime       = receivedProperties.CreationTime;
            properties.GroupId            = receivedProperties.GroupId;
            properties.GroupSequence      = receivedProperties.GroupSequence;
            properties.ReplyToGroupId     = receivedProperties.ReplyToGroupId;

            // copy header except for delivery count which should be set to null
            AmqpMessageHeader header         = AmqpMessage.Header;
            AmqpMessageHeader receivedHeader = receivedMessage.AmqpMessage.Header;

            header.DeliveryCount = null;
            header.Durable       = receivedHeader.Durable;
            header.Priority      = receivedHeader.Priority;
            header.TimeToLive    = receivedHeader.TimeToLive;
            header.FirstAcquirer = receivedHeader.FirstAcquirer;

            // copy message annotations except for broker set ones
            foreach (KeyValuePair <string, object> kvp in receivedMessage.AmqpMessage.MessageAnnotations)
            {
                if (kvp.Key == AmqpMessageConstants.LockedUntilName || kvp.Key == AmqpMessageConstants.SequenceNumberName ||
                    kvp.Key == AmqpMessageConstants.DeadLetterSourceName || kvp.Key == AmqpMessageConstants.EnqueueSequenceNumberName ||
                    kvp.Key == AmqpMessageConstants.EnqueuedTimeUtcName || kvp.Key == AmqpMessageConstants.MessageStateName)
                {
                    continue;
                }
                AmqpMessage.MessageAnnotations.Add(kvp.Key, kvp.Value);
            }

            // copy delivery annotations
            foreach (KeyValuePair <string, object> kvp in receivedMessage.AmqpMessage.DeliveryAnnotations)
            {
                AmqpMessage.DeliveryAnnotations.Add(kvp.Key, kvp.Value);
            }

            // copy footer
            foreach (KeyValuePair <string, object> kvp in receivedMessage.AmqpMessage.Footer)
            {
                AmqpMessage.Footer.Add(kvp.Key, kvp.Value);
            }

            // copy application properties except for broker set ones
            foreach (KeyValuePair <string, object> kvp in receivedMessage.AmqpMessage.ApplicationProperties)
            {
                if (kvp.Key == AmqpMessageConstants.DeadLetterReasonHeader || kvp.Key == AmqpMessageConstants.DeadLetterErrorDescriptionHeader)
                {
                    continue;
                }
                AmqpMessage.ApplicationProperties.Add(kvp.Key, kvp.Value);
            }
        }
Beispiel #13
0
        public static ServiceBusReceivedMessage AmqpMessageToSBMessage(AmqpMessage amqpMessage, bool isPeeked = false)
        {
            Argument.AssertNotNull(amqpMessage, nameof(amqpMessage));
            AmqpAnnotatedMessage annotatedMessage;

            if ((amqpMessage.BodyType & SectionFlag.Data) != 0 && amqpMessage.DataBody != null)
            {
                annotatedMessage = new AmqpAnnotatedMessage(AmqpMessageBody.FromData(BodyMemory.FromAmqpData(amqpMessage.DataBody)));
            }
            else if ((amqpMessage.BodyType & SectionFlag.AmqpValue) != 0 && amqpMessage.ValueBody?.Value != null)
            {
                if (TryGetNetObjectFromAmqpObject(amqpMessage.ValueBody.Value, MappingType.MessageBody, out object netObject))
                {
                    annotatedMessage = new AmqpAnnotatedMessage(AmqpMessageBody.FromValue(netObject));
                }
                else
                {
                    throw new NotSupportedException(Resources.InvalidAmqpMessageValueBody.FormatForUser(amqpMessage.ValueBody.Value.GetType()));
                }
            }
            else if ((amqpMessage.BodyType & SectionFlag.AmqpSequence) != 0)
            {
                annotatedMessage = new AmqpAnnotatedMessage(
                    AmqpMessageBody.FromSequence(amqpMessage.SequenceBody.Select(s => (IList <object>)s.List).ToList()));
            }
            // default to using an empty Data section if no data
            else
            {
                annotatedMessage = new AmqpAnnotatedMessage(new AmqpMessageBody(Enumerable.Empty <ReadOnlyMemory <byte> >()));
            }
            ServiceBusReceivedMessage sbMessage = new ServiceBusReceivedMessage(annotatedMessage);

            SectionFlag sections = amqpMessage.Sections;

            if ((sections & SectionFlag.Header) != 0)
            {
                if (amqpMessage.Header.Ttl != null)
                {
                    annotatedMessage.Header.TimeToLive = TimeSpan.FromMilliseconds(amqpMessage.Header.Ttl.Value);
                }

                if (amqpMessage.Header.DeliveryCount != null)
                {
                    annotatedMessage.Header.DeliveryCount = isPeeked ? (amqpMessage.Header.DeliveryCount.Value) : (amqpMessage.Header.DeliveryCount.Value + 1);
                }
            }

            if ((sections & SectionFlag.Properties) != 0)
            {
                if (amqpMessage.Properties.MessageId != null)
                {
                    annotatedMessage.Properties.MessageId = new AmqpMessageId(amqpMessage.Properties.MessageId.ToString());
                }

                if (amqpMessage.Properties.CorrelationId != null)
                {
                    annotatedMessage.Properties.CorrelationId = new AmqpMessageId(amqpMessage.Properties.CorrelationId.ToString());
                }

                if (amqpMessage.Properties.ContentType.Value != null)
                {
                    annotatedMessage.Properties.ContentType = amqpMessage.Properties.ContentType.Value;
                }

                if (amqpMessage.Properties.Subject != null)
                {
                    annotatedMessage.Properties.Subject = amqpMessage.Properties.Subject;
                }

                if (amqpMessage.Properties.To != null)
                {
                    annotatedMessage.Properties.To = new AmqpAddress(amqpMessage.Properties.To.ToString());
                }

                if (amqpMessage.Properties.ReplyTo != null)
                {
                    annotatedMessage.Properties.ReplyTo = new AmqpAddress(amqpMessage.Properties.ReplyTo.ToString());
                }

                if (amqpMessage.Properties.GroupId != null)
                {
                    annotatedMessage.Properties.GroupId = amqpMessage.Properties.GroupId;
                }

                if (amqpMessage.Properties.ReplyToGroupId != null)
                {
                    annotatedMessage.Properties.ReplyToGroupId = amqpMessage.Properties.ReplyToGroupId;
                }
            }

            // Do application properties before message annotations, because the application properties
            // can be updated by entries from message annotation.
            if ((sections & SectionFlag.ApplicationProperties) != 0)
            {
                foreach (var pair in amqpMessage.ApplicationProperties.Map)
                {
                    if (TryGetNetObjectFromAmqpObject(pair.Value, MappingType.ApplicationProperty, out var netObject))
                    {
                        annotatedMessage.ApplicationProperties[pair.Key.ToString()] = netObject;
                    }
                }
            }

            if ((sections & SectionFlag.MessageAnnotations) != 0)
            {
                foreach (var pair in amqpMessage.MessageAnnotations.Map)
                {
                    var key = pair.Key.ToString();
                    switch (key)
                    {
                    case AmqpMessageConstants.EnqueuedTimeUtcName:
                        annotatedMessage.MessageAnnotations[AmqpMessageConstants.EnqueuedTimeUtcName] = pair.Value;
                        break;

                    case AmqpMessageConstants.ScheduledEnqueueTimeUtcName:
                        annotatedMessage.MessageAnnotations[AmqpMessageConstants.ScheduledEnqueueTimeUtcName] = pair.Value;
                        break;

                    case AmqpMessageConstants.SequenceNumberName:
                        annotatedMessage.MessageAnnotations[AmqpMessageConstants.SequenceNumberName] = pair.Value;
                        break;

                    case AmqpMessageConstants.EnqueueSequenceNumberName:
                        annotatedMessage.MessageAnnotations[AmqpMessageConstants.EnqueueSequenceNumberName] = pair.Value;
                        break;

                    case AmqpMessageConstants.LockedUntilName:
                        DateTimeOffset lockedUntil = (DateTime)pair.Value >= DateTimeOffset.MaxValue.UtcDateTime ?
                                                     DateTimeOffset.MaxValue : (DateTime)pair.Value;
                        annotatedMessage.MessageAnnotations[AmqpMessageConstants.LockedUntilName] = lockedUntil.UtcDateTime;
                        break;

                    case AmqpMessageConstants.PartitionKeyName:
                        annotatedMessage.MessageAnnotations[AmqpMessageConstants.PartitionKeyName] = pair.Value;
                        break;

                    case AmqpMessageConstants.PartitionIdName:
                        annotatedMessage.MessageAnnotations[AmqpMessageConstants.PartitionIdName] = pair.Value;
                        break;

                    case AmqpMessageConstants.ViaPartitionKeyName:
                        annotatedMessage.MessageAnnotations[AmqpMessageConstants.ViaPartitionKeyName] = pair.Value;
                        break;

                    case AmqpMessageConstants.DeadLetterSourceName:
                        annotatedMessage.MessageAnnotations[AmqpMessageConstants.DeadLetterSourceName] = pair.Value;
                        break;

                    default:
                        if (TryGetNetObjectFromAmqpObject(pair.Value, MappingType.ApplicationProperty, out var netObject))
                        {
                            annotatedMessage.MessageAnnotations[key] = netObject;
                        }
                        break;
                    }
                }
            }

            if (amqpMessage.DeliveryTag.Count == GuidSizeInBytes)
            {
                Span <byte> guidBytes = stackalloc byte[GuidSizeInBytes];
                amqpMessage.DeliveryTag.AsSpan().CopyTo(guidBytes);
                if (!MemoryMarshal.TryRead <Guid>(guidBytes, out var lockTokenGuid))
                {
                    lockTokenGuid = new Guid(guidBytes.ToArray());
                }
                sbMessage.LockTokenGuid = lockTokenGuid;
            }

            amqpMessage.Dispose();

            return(sbMessage);
        }