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+ }
0 commit comments