Skip to content

Commit

Permalink
feat(repeater): establish bridges integration
Browse files Browse the repository at this point in the history
closes #170
  • Loading branch information
ostridm committed May 24, 2024
1 parent b1b9264 commit 472e126
Show file tree
Hide file tree
Showing 17 changed files with 366 additions and 39 deletions.
6 changes: 6 additions & 0 deletions src/SecTester.Core/Bus/Message.cs
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
using System;
using System.Runtime.Serialization;
using SecTester.Core.Utils;

namespace SecTester.Core.Bus;

public abstract record Message
{
[IgnoreDataMember]
public string CorrelationId { get; protected init; }

[IgnoreDataMember]
public DateTime CreatedAt { get; protected init; }

[IgnoreDataMember]
public string Type { get; protected init; }

protected Message()
Expand Down
2 changes: 1 addition & 1 deletion src/SecTester.Core/Configuration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ private void ResolveUrls(Uri uri)
if (_loopbackAddresses.Any(address => address == host))
{
Bus = $"amqp://{host}:5672";
Api = $"http://{host}:8000";
Api = $"http://{host}:8090";
}
else
{
Expand Down
3 changes: 2 additions & 1 deletion src/SecTester.Repeater/Bus/DefaultRepeaterBusFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using SecTester.Core.Utils;
using SocketIO.Serializer.MessagePack;
using SocketIOClient;
using SocketIOClient.Transport;

namespace SecTester.Repeater.Bus;

Expand Down Expand Up @@ -37,7 +38,7 @@ public IRepeaterBus Create(string repeaterId)
ReconnectionAttempts = options.ReconnectionAttempts,
ReconnectionDelayMax = options.ReconnectionDelayMax,
ConnectionTimeout = options.ConnectionTimeout,
AutoUpgrade = false,
Transport = TransportProtocol.WebSocket,
Auth = new { token = _config.Credentials.Token, domain = repeaterId }
})
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using MessagePack;
using MessagePack.Formatters;

namespace SecTester.Repeater.Bus.Formatters;

// Headers formatter is to be support javascript `undefined` which is treated as null (0xC0)
// https://www.npmjs.com/package/@msgpack/msgpack#messagepack-mapping-table
// https://github.com/msgpack/msgpack/blob/master/spec.md#nil-format

public class MessagePackHttpHeadersFormatter: IMessagePackFormatter<
IEnumerable<KeyValuePair<string, IEnumerable<string>>>
>
{
public MessagePackHttpHeadersFormatter()
{
// noop
}

public void Serialize(ref MessagePackWriter writer, IEnumerable<KeyValuePair<string, IEnumerable<string>>> value,
MessagePackSerializerOptions options)
{

if (value == null)
{
writer.WriteNil();
}
else
{
var count = value.Count();

writer.WriteMapHeader(count);

SerializeMap(ref writer, value);
}
}



public IEnumerable<KeyValuePair<string, IEnumerable<string>>> Deserialize(ref MessagePackReader reader,
MessagePackSerializerOptions options)
{
switch (reader.NextMessagePackType)
{
case MessagePackType.Nil:
return null;
case MessagePackType.Map:
break;
default:
throw new MessagePackSerializationException(string.Format(CultureInfo.InvariantCulture,
"Unrecognized code: 0x{0:X2} but expected to be a map or null", reader.NextCode));
}

var length = reader.ReadMapHeader();

options.Security.DepthStep(ref reader);

try
{
return DeserializeMap(ref reader, length, options);
}
finally
{
reader.Depth--;
}
}

private static void SerializeMap(ref MessagePackWriter writer, IEnumerable<KeyValuePair<string, IEnumerable<string>>> value)
{

foreach (var item in value)
{
writer.Write(item.Key);

var headersCount = item.Value.Count();

if (headersCount == 1)
{
writer.Write(item.Value.First());
}
else
{
writer.WriteArrayHeader(headersCount);

foreach (var subItem in item.Value)
{
writer.Write(subItem);
}
}
};
}


private static List<KeyValuePair<string, IEnumerable<string>>> DeserializeMap(ref MessagePackReader reader, int length, MessagePackSerializerOptions options)
{
var result = new List<KeyValuePair<string, IEnumerable<string>>>(length);

for ( int i = 0 ; i < length ; i++ )
{
var key = DeserializeString(ref reader);

switch (reader.NextMessagePackType)
{
case MessagePackType.String:
result.Add(new KeyValuePair<string, IEnumerable<string>>(key, new List<string>{DeserializeString(ref reader)}));
break;
case MessagePackType.Array:
result.Add(new KeyValuePair<string, IEnumerable<string>>(key, DeserializeArray(ref reader, reader.ReadArrayHeader(), options)));
break;
default:
throw new MessagePackSerializationException(string.Format(CultureInfo.InvariantCulture, "Unrecognized code: 0x{0:X2} but expected to be either a string or an array.", reader.NextCode));
}
}


return result;
}

private static IEnumerable<string> DeserializeArray(ref MessagePackReader reader, int length, MessagePackSerializerOptions options)
{
var result = new List<string>(length);

if (length == 0)
{
return result;
}

options.Security.DepthStep(ref reader);
try
{
for ( int i = 0 ; i < length ; i++ )
{
result.Add(DeserializeString(ref reader));
}
}
finally
{
reader.Depth--;
}

return result;
}

private static string DeserializeString(ref MessagePackReader reader)
{
if (reader.NextMessagePackType != MessagePackType.String)
{
throw new MessagePackSerializationException(string.Format(CultureInfo.InvariantCulture, "Unrecognized code: 0x{0:X2} but expected to be a string.", reader.NextCode));
}

var value = reader.ReadString();

if (null == value)
{
throw new MessagePackSerializationException(string.Format(CultureInfo.InvariantCulture, "Nulls are not allowed."));
}

return value;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
using System.Globalization;
using System.Net.Http;
using MessagePack;
using MessagePack.Formatters;

namespace SecTester.Repeater.Bus.Formatters;

public class MessagePackHttpMethodFormatter : IMessagePackFormatter<HttpMethod?>
{
public void Serialize(ref MessagePackWriter writer, HttpMethod? value, MessagePackSerializerOptions options)
{
if (null == value)
{
writer.WriteNil();
}
else
{
writer.Write(value.Method);
}
}

public HttpMethod? Deserialize(ref MessagePackReader reader, MessagePackSerializerOptions options)
{
switch (reader.NextMessagePackType)
{
case MessagePackType.Nil:
return null;
case MessagePackType.String:
var method = reader.ReadString();
return string.IsNullOrWhiteSpace(method) ? null : new HttpMethod(method);
default:
throw new MessagePackSerializationException(string.Format(CultureInfo.InvariantCulture,
"Unrecognized code: 0x{0:X2} but expected to be either a string or null.", reader.NextCode));
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Http;

namespace SecTester.Repeater.Bus.Formatters;

public class SocketIOIncomingRequestMapper
{
private const string Protocol = "protocol";
private const string Url = "url";
private const string Method = "method";
private const string Body = "body";
private const string Headers = "headers";

public static IncomingRequest ToRequest(Dictionary<object, object> dictionary)
{
var protocol = dictionary.TryGetValue(Protocol, out var p) && p is string && Enum.TryParse<Protocol>(p.ToString(), out var e)
? e
: SecTester.Repeater.Protocol.Http;

var uri = dictionary.TryGetValue(Url, out var u) && u is string
? new Uri(u.ToString())
: throw new InvalidDataException(FormatPropertyError(Url));

var method = dictionary.TryGetValue(Method, out var m) && m is string
? new HttpMethod(m.ToString())
: HttpMethod.Get;

var body = dictionary.TryGetValue(Body, out var b) && b is string ? b.ToString() : null;

dictionary.TryGetValue(Headers, out var headers);

return new IncomingRequest(uri)
{
Protocol = protocol,
Body = body,
Method = method,
Headers = MapHeaders(headers as Dictionary<object, object>)
};
}

private static IEnumerable<KeyValuePair<string, IEnumerable<string>>> MapHeaders(Dictionary<object, object>? headers)
{
var result = new List<KeyValuePair<string, IEnumerable<string>>>(headers?.Count ?? 0);

if (null == headers)
{
return result;
}

foreach (var kvp in headers)
{
var key = kvp.Key.ToString();

if (null == kvp.Value)
{
result.Add(new KeyValuePair<string, IEnumerable<string>>(key, new List<string>()));
continue;
}

if (kvp.Value is string)
{
result.Add(new KeyValuePair<string, IEnumerable<string>>(key, new List<string>
{ kvp.Value.ToString() }));
continue;
}

if (kvp.Value is not object[] objects)
{
continue;
}

var values = objects.OfType<string>().Select(value => value.ToString()).ToList();

result.Add(new KeyValuePair<string, IEnumerable<string>>(key, values));
}

return result;
}

private static string FormatPropertyError(string propName) => $"{propName} is either null or has an invalid data type";
}
14 changes: 13 additions & 1 deletion src/SecTester.Repeater/Bus/IncomingRequest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,29 @@
using System.Net.Http;
using MessagePack;
using SecTester.Core.Bus;
using SecTester.Repeater.Bus.Formatters;
using SecTester.Repeater.Runners;

namespace SecTester.Repeater.Bus;

[MessagePackObject(true)]
[MessagePackObject]
public record IncomingRequest(Uri Url) : Event, IRequest
{
[Key("body")]
public string? Body { get; set; }

[Key("method")]
[MessagePackFormatter(typeof(MessagePackHttpMethodFormatter))]
public HttpMethod Method { get; set; } = HttpMethod.Get;

[Key("protocol")]
public Protocol Protocol { get; set; } = Protocol.Http;

[Key("url")]
public Uri Url { get; set; } = Url ?? throw new ArgumentNullException(nameof(Url));

[Key("headers")]
[MessagePackFormatter(typeof(MessagePackHttpHeadersFormatter))]
public IEnumerable<KeyValuePair<string, IEnumerable<string>>> Headers { get; set; } =
new List<KeyValuePair<string, IEnumerable<string>>>();
}
15 changes: 14 additions & 1 deletion src/SecTester.Repeater/Bus/OutgoingResponse.cs
Original file line number Diff line number Diff line change
@@ -1,17 +1,30 @@
using System.Collections.Generic;
using MessagePack;
using SecTester.Repeater.Bus.Formatters;
using SecTester.Repeater.Runners;

namespace SecTester.Repeater.Bus;

[MessagePackObject(true)]
[MessagePackObject]
public record OutgoingResponse : IResponse
{
[Key("statusCode")]
public int? StatusCode { get; set; }

[Key("body")]
public string? Body { get; set; }

[Key("message")]
public string? Message { get; set; }

[Key("errorCode")]
public string? ErrorCode { get; set; }

[Key("protocol")]
public Protocol Protocol { get; set; } = Protocol.Http;

[Key("headers")]
[MessagePackFormatter(typeof(MessagePackHttpHeadersFormatter))]
public IEnumerable<KeyValuePair<string, IEnumerable<string>>> Headers { get; set; } =
new List<KeyValuePair<string, IEnumerable<string>>>();
}
1 change: 1 addition & 0 deletions src/SecTester.Repeater/Bus/RepeaterError.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,6 @@ namespace SecTester.Repeater.Bus;
[MessagePackObject(true)]
public sealed record RepeaterError
{
[Key("message")]
public string Message { get; set; } = null!;
}
Loading

0 comments on commit 472e126

Please sign in to comment.