Skip to content

Commit d109e7e

Browse files
committed
feat: implement PacketLogHandlerService
This service will handle the formatting of intercepted packets from all directions, and convert them into PacketLog objects. These PacketLog objects carry 'Color' chunks (length-based) that allow consumers to highlight portions of the log in different colors.
1 parent f45ba40 commit d109e7e

2 files changed

Lines changed: 131 additions & 1 deletion

File tree

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
using System.Buffers;
2+
using System.Threading.Channels;
3+
using System.Runtime.CompilerServices;
4+
5+
using Microsoft.Extensions.Logging;
6+
using Microsoft.Extensions.Options;
7+
8+
using Tanji.Core.Net.Formats;
9+
using Tanji.Core.Net.Messages;
10+
11+
using Tanji.Core.Infrastructure.Models;
12+
using Tanji.Core.Infrastructure.Configuration;
13+
14+
namespace Tanji.Core.Infrastructure.Services.Implementations;
15+
16+
public sealed class PacketLogHandlerService : IPacketLogHandlerService
17+
{
18+
private readonly Lock _writeSync;
19+
private readonly Channel<PacketLog> _packetLogs;
20+
21+
private readonly IClientHandlerService _clientHandler;
22+
private readonly ILogger<PacketLogHandlerService> _logger;
23+
private readonly PacketLoggingOptions _packetLoggingOptions;
24+
25+
private byte[]? _lastPacketBuffer;
26+
private PacketLog? _lastPacketLogWritten;
27+
28+
public PacketLogHandlerService(ILogger<PacketLogHandlerService> logger,
29+
IOptions<TanjiOptions> options,
30+
IClientHandlerService clientHandler)
31+
{
32+
_logger = logger;
33+
_clientHandler = clientHandler;
34+
_packetLoggingOptions = options.Value.PacketLoggingOptions;
35+
36+
_writeSync = new Lock();
37+
_packetLogs = Channel.CreateUnbounded<PacketLog>(new()
38+
{
39+
SingleWriter = true,
40+
SingleReader = true
41+
});
42+
}
43+
44+
public ValueTask<PacketLog> ReadPacketLogAsync(CancellationToken cancellationToken = default)
45+
{
46+
return _packetLogs.Reader.ReadAsync(cancellationToken);
47+
}
48+
public ValueTask<bool> WaitForPacketLogsAsync(CancellationToken cancellationToken = default) => _packetLogs.Reader.WaitToReadAsync(cancellationToken);
49+
50+
public PacketLog? WritePacketLog(ReadOnlySpan<byte> packetBufferSpan, in HMessage message)
51+
{
52+
lock (_writeSync)
53+
{
54+
PacketLog? pLog = null;
55+
if (_packetLoggingOptions.IsCompactingRepetitions)
56+
{
57+
if (_lastPacketBuffer?.Length < packetBufferSpan.Length)
58+
{
59+
ArrayPool<byte>.Shared.Return(_lastPacketBuffer);
60+
_lastPacketBuffer = null;
61+
}
62+
63+
_lastPacketBuffer ??= ArrayPool<byte>.Shared.Rent(packetBufferSpan.Length + 1);
64+
Span<byte> lastPacketBufferSpan = _lastPacketBuffer.AsSpan(); // Do not trim, could be shorter than current packet.
65+
66+
if (_lastPacketLogWritten != null && IsPacketRepeated(message.IsOutgoing, packetBufferSpan, lastPacketBufferSpan))
67+
{
68+
/*
69+
* The original packet may not be there by the time we update the Repetitions property.
70+
* We need to add it back to the queue as quickly as possible (lock), so that consumers may act on it.
71+
*/
72+
_lastPacketLogWritten.Repetitions++;
73+
pLog = _lastPacketLogWritten;
74+
}
75+
else
76+
{
77+
packetBufferSpan.CopyTo(lastPacketBufferSpan);
78+
lastPacketBufferSpan[^1] = (byte)(message.IsOutgoing ? 1 : 0);
79+
}
80+
}
81+
else if (_lastPacketBuffer != null)
82+
{
83+
ArrayPool<byte>.Shared.Return(_lastPacketBuffer);
84+
_lastPacketBuffer = null;
85+
}
86+
87+
pLog ??= PacketLog.Create(packetBufferSpan, message, _packetLoggingOptions);
88+
if (_packetLogs.Writer.TryWrite(pLog))
89+
{
90+
_lastPacketLogWritten = pLog;
91+
return _lastPacketLogWritten;
92+
}
93+
94+
return null;
95+
}
96+
}
97+
public PacketLog? WritePacketLog(ReadOnlySpan<byte> packetBufferSpan, bool isOutgoing, IHFormat format, string? revision)
98+
{
99+
bool hasRevisionMessages = _clientHandler.TryGetIdentifiers(
100+
revision, out Outgoing? outgoing, out Incoming? incoming);
101+
102+
_ = format.TryReadHeader(packetBufferSpan, out int length, out short id, out _);
103+
if (hasRevisionMessages)
104+
{
105+
// Reduce the number of copies created per packet interception by pulling the message as a reference.
106+
ref readonly HMessage refMessage = ref (isOutgoing
107+
? ref outgoing![id]
108+
: ref incoming![id]);
109+
110+
// Message could be null reference. (Unresolved)
111+
if (!Unsafe.IsNullRef(in refMessage))
112+
{
113+
return WritePacketLog(packetBufferSpan, in refMessage);
114+
}
115+
}
116+
117+
var message = new HMessage(id, isOutgoing);
118+
return WritePacketLog(packetBufferSpan, in message);
119+
}
120+
121+
private static bool IsPacketRepeated(bool isOutgoing, in ReadOnlySpan<byte> current, in Span<byte> last)
122+
{
123+
// 0 = Incoming, Non-Zero = Outgoing
124+
if (isOutgoing && last[^1] == 0) return false;
125+
if (!isOutgoing && last[^1] != 0) return false;
126+
127+
// If current packet is larger than last packet (excluding direction byte), they can't be equal.
128+
return current.Length <= (last.Length - 1) && current.SequenceEqual(last[..current.Length]);
129+
}
130+
}

Tanji.Core.Infrastructure/Services/ServiceCollectionExtensions.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,14 @@ public static class ServiceCollectionExtensions
1212
{
1313
public static IServiceCollection AddTanjiCore(this IServiceCollection services)
1414
{
15-
1615
// Add Configuration
1716
services.AddOptions();
1817
services.AddSingleton<IPostConfigureOptions<TanjiOptions>, PostConfigureTanjiOptions>();
1918

2019
// Add Singleton Services
2120
services.AddSingleton<IHotelStateService, HotelStateService>();
2221
services.AddSingleton<IClientHandlerService, ClientHandlerService>();
22+
services.AddSingleton<IPacketLogHandlerService, PacketLogHandlerService>();
2323
services.AddSingleton<IConnectionHandlerService, ConnectionHandlerService>();
2424
services.AddSingleton<IWebInterceptionService, EavesdropInterceptionService>();
2525
services.AddSingleton<IRemoteEndPointResolverService<HotelEndPoint>, RemoteHotelEndPointResolverService>();

0 commit comments

Comments
 (0)