SmtpServer
SmtpServer copied to clipboard
Pipereader example require internal "unsafe" class
Hi @cosullivan , I am currently upgrading to v9.0 and had to rebuild my ListenFactory with PIPE as design is now.
In your example: https://github.com/cosullivan/SmtpServer/blob/2b455e652bb451dd3f38a6b7b84fb1b06e4dbfb3/Examples/SampleApp/Examples/CustomEndpointListenerExample.cs#L133 The StringUtil are "unsafe" because of the fixed buffers and pointers in the internal class. (I'm an old c++ programmer, so that is why I write "unsafe". You know what you are doing, so it is not unsafe... :))
By using your internal class StringUtil in an example would bind you to implement the class or SmtpProject to get it compiled using the Nuget. Wouldn't be enough to use Streamreader/Streamwriter to handle PIPEs in your SmtpServer?
I played with your example to get the full communication between server -> client. Here is my suggestion to achieve that:
public sealed class CustomSecurableDuplexPipe : ISecurableDuplexPipe
{
readonly ISecurableDuplexPipe _securableDuplexPipe;
public CustomSecurableDuplexPipe(ISecurableDuplexPipe securableDuplexPipe)
{
_securableDuplexPipe = securableDuplexPipe;
}
public Task UpgradeAsync(X509Certificate certificate, SslProtocols protocols, CancellationToken cancellationToken = default)
{
return _securableDuplexPipe.UpgradeAsync(certificate, protocols, cancellationToken);
}
public void Dispose()
{
Console.WriteLine("Raw transcript of last connection:");
foreach (var logLine in LogArray)
Console.WriteLine(logLine);
_securableDuplexPipe.Dispose();
}
//Actual variable that holds the log
private List<String> LogArray = new List<String>();
public PipeReader Input => new LoggingPipeReader(_securableDuplexPipe.Input, LogArray);
public PipeWriter Output => new LoggingPipeWriter(_securableDuplexPipe.Output, LogArray);
public bool IsSecure => _securableDuplexPipe.IsSecure;
}
//Quite simular to LoggingPipeReader and as simple as possible
public sealed class LoggingPipeWriter : PipeWriter
{
readonly PipeWriter _delegate;
readonly List<String> _logArray; //Store data until EOF
public LoggingPipeWriter(PipeWriter @delegate, List<String> @logArray)
{
_delegate = @delegate;
_logArray = @logArray;
}
public override void Advance(int bytes)
{
var spandata = GetSpan().Slice(0, bytes);
String str = Encoding.UTF8.GetString(spandata);
_logArray.Add(str);
_delegate.Advance(bytes);
}
//Unused, but required overrides
public override void CancelPendingFlush()
=> _delegate.CancelPendingFlush();
public override void Complete(Exception exception = null)
=> _delegate.Complete(exception);
public override ValueTask<FlushResult> FlushAsync(CancellationToken cancellationToken = default)
=> _delegate.FlushAsync(cancellationToken);
public override Memory<byte> GetMemory(int sizeHint = 0)
=> _delegate.GetMemory(sizeHint);
public override Span<byte> GetSpan(int sizeHint = 0)
=> _delegate.GetSpan(sizeHint);
}
//LoggingPipeReader with helper for getting data after each CRLF
public sealed class LoggingPipeReader : PipeReader
{
readonly PipeReader _delegate;
readonly List<String> _logArray;
public LoggingPipeReader(PipeReader @delegate, List<String> @logArray)
{
_delegate = @delegate;
_logArray = @logArray;
}
public override async ValueTask<ReadResult> ReadAsync(CancellationToken cancellationToken = default(CancellationToken))
{
ReadResult readResult = await _delegate.ReadAsync(cancellationToken);
ReadOnlySequence<byte> buffer = readResult.Buffer;
string tmp = ProcessLine(ref buffer); ;
if (tmp != null)
_logArray.Add(tmp);
return readResult;
}
//Unused, but required overrides
public override void AdvanceTo(SequencePosition consumed) => _delegate.AdvanceTo(consumed);
public override void AdvanceTo(SequencePosition consumed, SequencePosition examined)
=> _delegate.AdvanceTo(consumed, examined);
public override void CancelPendingRead() => _delegate.CancelPendingRead();
public override void Complete(Exception exception = null) => _delegate.Complete(exception);
public override bool TryRead(out ReadResult result) => _delegate.TryRead(out result);
//Helper class, raw copy from: https://dev.to/joni2nja/evaluating-readline-using-system-io-pipelines-performance-in-c-part-2-pmf
private static ReadOnlySpan<byte> NewLine => new[] { (byte)'\r', (byte)'\n' };
private static string ProcessLine(ref ReadOnlySequence<byte> buffer)
{
string str = null;
if (buffer.IsSingleSegment)
{
var span = buffer.FirstSpan;
int consumed;
while (span.Length > 0)
{
var newLine = span.IndexOf(NewLine);
if (newLine == -1) break;
var line = span.Slice(0, newLine);
str = Encoding.UTF8.GetString(line);
// simulate string processing
//str = str.AsSpan().Slice(0, 5).ToString();
consumed = line.Length + NewLine.Length;
span = span.Slice(consumed);
buffer = buffer.Slice(consumed);
}
}
else
{
var sequenceReader = new SequenceReader<byte>(buffer);
while (!sequenceReader.End)
{
while (sequenceReader.TryReadTo(out ReadOnlySequence<byte> line, NewLine))
{
str = Encoding.UTF8.GetString(line);
// simulate string processing
//str = str.AsSpan().Slice(0, 5).ToString();
}
buffer = buffer.Slice(sequenceReader.Position);
sequenceReader.Advance(buffer.Length);
}
}
return str;
}
}
The helper for the reader here seems to be quite good. Since data is short it never triggers SequenceReader though, so that is probably useless.