-
Notifications
You must be signed in to change notification settings - Fork 20
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
When not wrapping fallback to base64 encoding in case invalid SQS cha…
…rs are being used (#2646) * When not wrapping fallback to base64 encoding in case invalid SQS chars are being used (most straightforward impl ignoring perf) * If only I would understand bools * Better test * Better assert * Documentation * Acceptance test * Copy body * Cleanup * Header value with invalid chars works too --------- Co-authored-by: danielmarbach <[email protected]>
- Loading branch information
1 parent
c9e2e9e
commit 291c8cc
Showing
9 changed files
with
190 additions
and
130 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
143 changes: 143 additions & 0 deletions
143
...Bus.Transport.SQS.AcceptanceTests/Sending/When_sending_messages_with_invalid_sqs_chars.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,143 @@ | ||
namespace NServiceBus.AcceptanceTests.Sending | ||
{ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
using System.Text; | ||
using System.Threading; | ||
using System.Threading.Tasks; | ||
using AcceptanceTesting; | ||
using EndpointTemplates; | ||
using Features; | ||
using Microsoft.Extensions.DependencyInjection; | ||
using NServiceBus.Pipeline; | ||
using NServiceBus.Routing; | ||
using NUnit.Framework; | ||
using Transport; | ||
|
||
class When_sending_messages_with_invalid_sqs_chars : NServiceBusAcceptanceTest | ||
{ | ||
[Test] | ||
public async Task Can_be_sent_and_processed() | ||
{ | ||
var context = await Scenario.Define<MyContext>(ctx => | ||
{ | ||
ctx.DestinationQueueName = TestNameHelper.GetSqsQueueName("SendingMessagesWithInvalidSqsChars.Receiver", SetupFixture.NamePrefix); | ||
ctx.ControlMessageId = Guid.NewGuid().ToString(); | ||
}) | ||
.WithEndpoint<Sender>() | ||
.WithEndpoint<Receiver>() | ||
.Done(ctx => ctx.ControlMessageReceived) | ||
.Run(); | ||
|
||
Assert.That(context.ControlMessageBody, Is.Not.Empty); | ||
} | ||
|
||
class Sender : EndpointConfigurationBuilder | ||
{ | ||
public Sender() => EndpointSetup<DefaultServer>(cfg => cfg.ConfigureSqsTransport().DoNotWrapOutgoingMessages = true); | ||
|
||
class DispatchControlMessageAtStartup : Feature | ||
{ | ||
public DispatchControlMessageAtStartup() => EnableByDefault(); | ||
|
||
protected override void Setup(FeatureConfigurationContext context) => | ||
context.RegisterStartupTask(sp => new Startup( | ||
sp.GetRequiredService<IMessageDispatcher>(), | ||
sp.GetRequiredService<MyContext>()) | ||
); | ||
|
||
class Startup(IMessageDispatcher dispatcher, MyContext context) : FeatureStartupTask | ||
{ | ||
protected override Task OnStart(IMessageSession session, | ||
CancellationToken cancellationToken = default) | ||
{ | ||
var transportOperations = new TransportOperations( | ||
new TransportOperation( | ||
new OutgoingMessage( | ||
context.ControlMessageId, | ||
new Dictionary<string, string> | ||
{ | ||
[Headers.MessageId] = context.ControlMessageId | ||
}, | ||
CreateBodyWithDisallowedCharacters() | ||
), | ||
new UnicastAddressTag(context.DestinationQueueName) | ||
) | ||
); | ||
var transportTransaction = new TransportTransaction(); | ||
return dispatcher.Dispatch(transportOperations, transportTransaction, cancellationToken); | ||
} | ||
|
||
protected override Task OnStop(IMessageSession session, | ||
CancellationToken cancellationToken = default) => Task.CompletedTask; | ||
} | ||
} | ||
|
||
// See https://docs.aws.amazon.com/AWSSimpleQueueService/latest/APIReference/API_SendMessage.html | ||
static byte[] CreateBodyWithDisallowedCharacters() | ||
{ | ||
var disallowed = new List<int>(16559); | ||
|
||
// Characters below #x9 | ||
disallowed.AddRange(Enumerable.Range(0x0, 0x9)); | ||
|
||
// Characters between #xB and #xC | ||
disallowed.AddRange(Enumerable.Range(0xB, 2)); // #xB, #xC | ||
|
||
// Characters between #xE and #x1F | ||
disallowed.AddRange(Enumerable.Range(0xE, 0x20 - 0xE)); | ||
|
||
// Surrogate pairs (from #xD800 to #xDFFF) cannot be added because ConvertFromUtf32 throws | ||
// disallowed.AddRange(Enumerable.Range(0xD800, 0xE000 - 0xD800)); | ||
|
||
// Characters greater than #x10FFFF | ||
for (int i = 0x110000; i <= 0x10FFFF; i++) | ||
{ | ||
disallowed.Add(i); | ||
} | ||
|
||
var byteList = new List<byte>(disallowed.Count * 4); | ||
foreach (var codePoint in disallowed) | ||
{ | ||
if (codePoint <= 0x10FFFF) | ||
{ | ||
string charAsString = char.ConvertFromUtf32(codePoint); | ||
byte[] utf8Bytes = Encoding.UTF8.GetBytes(charAsString); | ||
byteList.AddRange(utf8Bytes); | ||
} | ||
} | ||
|
||
return [.. byteList]; | ||
} | ||
} | ||
|
||
class Receiver : EndpointConfigurationBuilder | ||
{ | ||
public Receiver() => EndpointSetup<DefaultServer>(c => c.Pipeline.Register("CatchControlMessage", typeof(CatchControlMessageBehavior), "Catches control message")); | ||
|
||
class CatchControlMessageBehavior(MyContext myContext) : Behavior<IIncomingPhysicalMessageContext> | ||
{ | ||
public override Task Invoke(IIncomingPhysicalMessageContext context, Func<Task> next) | ||
{ | ||
if (context.MessageId == myContext.ControlMessageId) | ||
{ | ||
myContext.ControlMessageBody = context.Message.Body.ToString(); | ||
myContext.ControlMessageReceived = true; | ||
return Task.CompletedTask; | ||
} | ||
|
||
return next(); | ||
} | ||
} | ||
} | ||
|
||
class MyContext : ScenarioContext | ||
{ | ||
public string DestinationQueueName { get; set; } | ||
public string ControlMessageId { get; set; } | ||
public bool ControlMessageReceived { get; set; } | ||
public string ControlMessageBody { get; set; } | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
36 changes: 0 additions & 36 deletions
36
...eBus.Transport.SQS.TransportTests.DoNotWrapOutgoingMessages/Receiving_metrics_messages.cs
This file was deleted.
Oops, something went wrong.
25 changes: 17 additions & 8 deletions
25
...goingMessages/Sending_metrics_messages.cs → ...ending_messages_with_invalid_sqs_chars.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,38 +1,47 @@ | ||
namespace NServiceBus.TransportTests | ||
{ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Threading.Tasks; | ||
using NUnit.Framework; | ||
using Transport; | ||
|
||
public class Sending_metrics_messages : NServiceBusTransportTest | ||
public class Sending_messages_with_invalid_sqs_chars : NServiceBusTransportTest | ||
{ | ||
[TestCase(TransportTransactionMode.None)] | ||
[TestCase(TransportTransactionMode.ReceiveOnly)] | ||
public async Task Should_not_fail_when_using_do_not_wrap( | ||
public async Task Should_receive_message( | ||
TransportTransactionMode transactionMode) | ||
{ | ||
var messageProcessed = CreateTaskCompletionSource<MessageContext>(); | ||
byte[] copyOfTheBody = null; | ||
|
||
await StartPump( | ||
(context, _) => messageProcessed.SetCompleted(context), | ||
(context, _) => | ||
{ | ||
// This is crucial due to internal buffer pooling in SQS transport | ||
copyOfTheBody = context.Body.ToArray(); | ||
return messageProcessed.SetCompleted(context); | ||
}, | ||
(_, __) => Task.FromResult(ErrorHandleResult.Handled), | ||
TransportTransactionMode.None); | ||
|
||
var headers = new Dictionary<string, string> | ||
{ | ||
{ Transport.SQS.Constants.MetricsMessageMetricTypeHeaderKey, "doesn't matter" }, | ||
{ Headers.ContentType, Transport.SQS.Constants.MetricsMessageContentTypeHeaderValue } | ||
{ "SomeHeader", "header value with invalid chars: \0" }, | ||
}; | ||
var body = Guid.NewGuid().ToByteArray(); | ||
|
||
var body = "body with invalid chars: \0"u8.ToArray(); | ||
|
||
await SendMessage(InputQueueName, headers, body: body); | ||
|
||
var messageContext = await messageProcessed.Task; | ||
|
||
Assert.That(messageContext.Headers, Is.Not.Empty); | ||
Assert.That(messageContext.Headers, Is.SupersetOf(headers)); | ||
Assert.Multiple(() => | ||
{ | ||
Assert.That(messageContext.Headers, Is.SupersetOf(headers)); | ||
Assert.That(copyOfTheBody, Is.EquivalentTo(body)); | ||
}); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
Oops, something went wrong.