diff --git a/src/FirebirdSql.Data.FirebirdClient/Client/Managed/DataProviderStreamWrapper.cs b/src/FirebirdSql.Data.FirebirdClient/Client/Managed/DataProviderStreamWrapper.cs index 35de2a9c4..a611e270a 100644 --- a/src/FirebirdSql.Data.FirebirdClient/Client/Managed/DataProviderStreamWrapper.cs +++ b/src/FirebirdSql.Data.FirebirdClient/Client/Managed/DataProviderStreamWrapper.cs @@ -15,8 +15,10 @@ //$Authors = Jiri Cincura (jiri@cincura.net) +using System; using System.IO; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; using System.Threading; using System.Threading.Tasks; @@ -36,12 +38,31 @@ public int Read(byte[] buffer, int offset, int count) { return _stream.Read(buffer, offset, count); } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int Read(Span buffer, int offset, int count) + { + return _stream.Read(buffer[offset..(offset+count)]); + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public ValueTask ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken = default) { return new ValueTask(_stream.ReadAsync(buffer, offset, count, cancellationToken)); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ValueTask ReadAsync(Memory buffer, int offset, int count, CancellationToken cancellationToken = default) + { + return _stream.ReadAsync(buffer.Slice(offset, count), cancellationToken); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Write(ReadOnlySpan buffer) + { + _stream.Write(buffer); + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Write(byte[] buffer, int offset, int count) { @@ -53,6 +74,12 @@ public ValueTask WriteAsync(byte[] buffer, int offset, int count, CancellationTo return new ValueTask(_stream.WriteAsync(buffer, offset, count, cancellationToken)); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ValueTask WriteAsync(ReadOnlyMemory buffer, int offset, int count, CancellationToken cancellationToken = default) + { + return _stream.WriteAsync(buffer.Slice(offset, count), cancellationToken); + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Flush() { diff --git a/src/FirebirdSql.Data.FirebirdClient/Client/Managed/FirebirdNetworkHandlingWrapper.cs b/src/FirebirdSql.Data.FirebirdClient/Client/Managed/FirebirdNetworkHandlingWrapper.cs index 17f068a30..8028da2e5 100644 --- a/src/FirebirdSql.Data.FirebirdClient/Client/Managed/FirebirdNetworkHandlingWrapper.cs +++ b/src/FirebirdSql.Data.FirebirdClient/Client/Managed/FirebirdNetworkHandlingWrapper.cs @@ -16,25 +16,27 @@ //$Authors = Jiri Cincura (jiri@cincura.net) using System; -using System.Collections.Generic; +using System.Buffers; using System.IO; +using System.Runtime.InteropServices; using System.Threading; using System.Threading.Tasks; using FirebirdSql.Data.Common; namespace FirebirdSql.Data.Client.Managed; -sealed class FirebirdNetworkHandlingWrapper : IDataProvider, ITracksIOFailure +sealed class FirebirdNetworkHandlingWrapper : IDataProvider, ITracksIOFailure, IDisposable { public const string CompressionName = "zlib"; public const string EncryptionName = "Arc4"; const int PreferredBufferSize = 32 * 1024; + const int DirectReadWriteThreshold = 8 * 1024; readonly IDataProvider _dataProvider; - readonly Queue _outputBuffer; - readonly Queue _inputBuffer; + readonly ByteRingBuffer _outputBuffer; + readonly ByteRingBuffer _inputBuffer; readonly byte[] _readBuffer; byte[] _compressionBuffer; @@ -44,111 +46,267 @@ sealed class FirebirdNetworkHandlingWrapper : IDataProvider, ITracksIOFailure Org.BouncyCastle.Crypto.Engines.RC4Engine _decryptor; Org.BouncyCastle.Crypto.Engines.RC4Engine _encryptor; + bool _disposed; + public FirebirdNetworkHandlingWrapper(IDataProvider dataProvider) { _dataProvider = dataProvider; - _outputBuffer = new Queue(PreferredBufferSize); - _inputBuffer = new Queue(PreferredBufferSize); + _outputBuffer = new ByteRingBuffer(PreferredBufferSize); + _inputBuffer = new ByteRingBuffer(PreferredBufferSize); _readBuffer = new byte[PreferredBufferSize]; + _disposed = false; } public bool IOFailed { get; set; } public int Read(byte[] buffer, int offset, int count) { - if (_inputBuffer.Count < count) + EnsureNotDisposed(); + if (count <= 0) + return 0; + + if (_inputBuffer.Count == 0 && _decompressor == null && count >= DirectReadWriteThreshold) { - var readBuffer = _readBuffer; int read; try { - read = _dataProvider.Read(readBuffer, 0, readBuffer.Length); + read = _dataProvider.Read(buffer, offset, count); } catch (IOException) { IOFailed = true; throw; } - if (read != 0) + + if (read > 0 && _decryptor != null) { - if (_decryptor != null) - { - _decryptor.ProcessBytes(readBuffer, 0, read, readBuffer, 0); - } - if (_decompressor != null) - { - read = HandleDecompression(readBuffer, read); - readBuffer = _compressionBuffer; - } - WriteToInputBuffer(readBuffer, read); + _decryptor.ProcessBytes(buffer, offset, read, buffer, offset); + } + return read; + } + + if (_inputBuffer.Count < count) + { + FillInputBuffer(); + } + + return _inputBuffer.CopyTo(buffer.AsSpan(offset, count)); + } + + public int Read(Span buffer, int offset, int count) + { + EnsureNotDisposed(); + if (count <= 0) + return 0; + + // Cannot decrypt into arbitrary spans (BouncyCastle API is byte[] based), + // so bypass only when no transforms are active. + if (_inputBuffer.Count == 0 && _decompressor == null && _decryptor == null && count >= DirectReadWriteThreshold) + { + try + { + return _dataProvider.Read(buffer, offset, count); + } + catch (IOException) { + IOFailed = true; + throw; } } - var dataLength = ReadFromInputBuffer(buffer, offset, count); - return dataLength; + + if (_inputBuffer.Count < count) + { + FillInputBuffer(); + } + + return _inputBuffer.CopyTo(buffer.Slice(offset, count)); } + public async ValueTask ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken = default) { + EnsureNotDisposed(); + if (count <= 0) + return 0; + + if (_inputBuffer.Count == 0 && _decompressor == null && count >= DirectReadWriteThreshold) + { + int read; + try + { + read = await _dataProvider.ReadAsync(buffer, offset, count, cancellationToken).ConfigureAwait(false); + } + catch (IOException) + { + IOFailed = true; + throw; + } + + if (read > 0 && _decryptor != null) + { + _decryptor.ProcessBytes(buffer, offset, read, buffer, offset); + } + return read; + } + if (_inputBuffer.Count < count) { - var readBuffer = _readBuffer; + await FillInputBufferAsync(cancellationToken).ConfigureAwait(false); + } + + return _inputBuffer.CopyTo(buffer.AsSpan(offset, count)); + } + + public async ValueTask ReadAsync(Memory buffer, int offset, int count, CancellationToken cancellationToken = default) + { + EnsureNotDisposed(); + if (count <= 0) + return 0; + + var destination = buffer.Slice(offset, count); + if (_inputBuffer.Count == 0 && _decompressor == null && count >= DirectReadWriteThreshold + && MemoryMarshal.TryGetArray(destination, out ArraySegment segment)) + { int read; try { - read = await _dataProvider.ReadAsync(readBuffer, 0, readBuffer.Length, cancellationToken).ConfigureAwait(false); + read = await _dataProvider.ReadAsync(segment.Array, segment.Offset, segment.Count, cancellationToken).ConfigureAwait(false); } catch (IOException) { IOFailed = true; throw; } - if (read != 0) + + if (read > 0 && _decryptor != null) { - if (_decryptor != null) - { - _decryptor.ProcessBytes(readBuffer, 0, read, readBuffer, 0); - } - if (_decompressor != null) - { - read = HandleDecompression(readBuffer, read); - readBuffer = _compressionBuffer; - } - WriteToInputBuffer(readBuffer, read); + _decryptor.ProcessBytes(segment.Array, segment.Offset, read, segment.Array, segment.Offset); + } + return read; + } + + if (_inputBuffer.Count < count) + { + await FillInputBufferAsync(cancellationToken).ConfigureAwait(false); + } + + return _inputBuffer.CopyTo(destination.Span); + } + + public void Write(ReadOnlySpan buffer) + { + EnsureNotDisposed(); + if (buffer.IsEmpty) + return; + + if (_compressor == null && _encryptor == null && _outputBuffer.Count == 0 && buffer.Length >= DirectReadWriteThreshold) + { + try + { + _dataProvider.Write(buffer); + } + catch (IOException) + { + IOFailed = true; + throw; } + return; } - var dataLength = ReadFromInputBuffer(buffer, offset, count); - return dataLength; + + _outputBuffer.Write(buffer); } public void Write(byte[] buffer, int offset, int count) { - for (var i = 0; i < count; i++) - _outputBuffer.Enqueue(buffer[offset + i]); + EnsureNotDisposed(); + if (buffer == null || count <= 0) + return; + + if (_compressor == null && _encryptor == null && _outputBuffer.Count == 0 && count >= DirectReadWriteThreshold) + { + try + { + _dataProvider.Write(buffer, offset, count); + } + catch (IOException) + { + IOFailed = true; + throw; + } + return; + } + + _outputBuffer.Write(buffer.AsSpan(offset, count)); } public ValueTask WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken = default) { - for (var i = 0; i < count; i++) - _outputBuffer.Enqueue(buffer[offset + i]); + EnsureNotDisposed(); + if (buffer == null || count <= 0) + return ValueTask.CompletedTask; + + if (_compressor == null && _encryptor == null && _outputBuffer.Count == 0 && count >= DirectReadWriteThreshold) + { + return WriteDirectAsync(buffer, offset, count, cancellationToken); + } + + _outputBuffer.Write(buffer.AsSpan(offset, count)); return ValueTask.CompletedTask; + + async ValueTask WriteDirectAsync(byte[] directBuffer, int directOffset, int directCount, CancellationToken directCancellationToken) + { + try + { + await _dataProvider.WriteAsync(directBuffer, directOffset, directCount, directCancellationToken).ConfigureAwait(false); + } + catch (IOException) + { + IOFailed = true; + throw; + } + } } - public void Flush() + public ValueTask WriteAsync(ReadOnlyMemory buffer, int offset, int count, CancellationToken cancellationToken = default) { - var buffer = _outputBuffer.ToArray(); - _outputBuffer.Clear(); - var count = buffer.Length; - if (_compressor != null) + EnsureNotDisposed(); + if (count <= 0) + return ValueTask.CompletedTask; + + if (_compressor == null && _encryptor == null && _outputBuffer.Count == 0 && count >= DirectReadWriteThreshold) { - count = HandleCompression(buffer, count); - buffer = _compressionBuffer; + return WriteDirectAsync(buffer, offset, count, cancellationToken); } - if (_encryptor != null) + + _outputBuffer.Write(buffer.Span.Slice(offset, count)); + return ValueTask.CompletedTask; + + async ValueTask WriteDirectAsync(ReadOnlyMemory directBuffer, int directOffset, int directCount, CancellationToken directCancellationToken) { - _encryptor.ProcessBytes(buffer, 0, count, buffer, 0); + try + { + await _dataProvider.WriteAsync(directBuffer, directOffset, directCount, directCancellationToken).ConfigureAwait(false); + } + catch (IOException) + { + IOFailed = true; + throw; + } } + } + + public void Flush() + { + EnsureNotDisposed(); try { - _dataProvider.Write(buffer, 0, count); + if (_compressor != null) + { + FlushCompressed(); + } + else if (_outputBuffer.Count > 0) + { + FlushPlain(); + } + _dataProvider.Flush(); } catch (IOException) @@ -159,21 +317,18 @@ public void Flush() } public async ValueTask FlushAsync(CancellationToken cancellationToken = default) { - var buffer = _outputBuffer.ToArray(); - _outputBuffer.Clear(); - var count = buffer.Length; - if (_compressor != null) - { - count = HandleCompression(buffer, count); - buffer = _compressionBuffer; - } - if (_encryptor != null) - { - _encryptor.ProcessBytes(buffer, 0, count, buffer, 0); - } + EnsureNotDisposed(); try { - await _dataProvider.WriteAsync(buffer, 0, count, cancellationToken).ConfigureAwait(false); + if (_compressor != null) + { + await FlushCompressedAsync(cancellationToken).ConfigureAwait(false); + } + else if (_outputBuffer.Count > 0) + { + await FlushPlainAsync(cancellationToken).ConfigureAwait(false); + } + await _dataProvider.FlushAsync(cancellationToken).ConfigureAwait(false); } catch (IOException) @@ -185,6 +340,7 @@ public async ValueTask FlushAsync(CancellationToken cancellationToken = default) public void StartCompression() { + EnsureNotDisposed(); _compressionBuffer = new byte[PreferredBufferSize]; _compressor = new Ionic.Zlib.ZlibCodec(Ionic.Zlib.CompressionMode.Compress); _decompressor = new Ionic.Zlib.ZlibCodec(Ionic.Zlib.CompressionMode.Decompress); @@ -192,30 +348,96 @@ public void StartCompression() public void StartEncryption(byte[] key) { + EnsureNotDisposed(); _encryptor = CreateCipher(key); _decryptor = CreateCipher(key); } - int ReadFromInputBuffer(byte[] buffer, int offset, int count) + void FillInputBuffer() { - var read = Math.Min(count, _inputBuffer.Count); - for (var i = 0; i < read; i++) + EnsureNotDisposed(); + try { - buffer[offset + i] = _inputBuffer.Dequeue(); + if (_decompressor == null) + { + _inputBuffer.EnsureFree(1); + _inputBuffer.GetWriteSegment(out var writeOffset, out var writeLength); + var toRead = Math.Min(writeLength, _readBuffer.Length); + var read = _dataProvider.Read(_inputBuffer.Buffer, writeOffset, toRead); + if (read <= 0) + return; + + if (_decryptor != null) + { + _decryptor.ProcessBytes(_inputBuffer.Buffer, writeOffset, read, _inputBuffer.Buffer, writeOffset); + } + _inputBuffer.AdvanceWrite(read); + } + else + { + var read = _dataProvider.Read(_readBuffer, 0, _readBuffer.Length); + if (read <= 0) + return; + + if (_decryptor != null) + { + _decryptor.ProcessBytes(_readBuffer, 0, read, _readBuffer, 0); + } + read = HandleDecompression(_readBuffer, read); + _inputBuffer.Write(_compressionBuffer.AsSpan(0, read)); + } + } + catch (IOException) + { + IOFailed = true; + throw; } - return read; } - void WriteToInputBuffer(byte[] data, int count) + async ValueTask FillInputBufferAsync(CancellationToken cancellationToken) { - for (var i = 0; i < count; i++) + EnsureNotDisposed(); + try { - _inputBuffer.Enqueue(data[i]); + if (_decompressor == null) + { + _inputBuffer.EnsureFree(1); + _inputBuffer.GetWriteSegment(out var writeOffset, out var writeLength); + var toRead = Math.Min(writeLength, _readBuffer.Length); + var read = await _dataProvider.ReadAsync(_inputBuffer.Buffer, writeOffset, toRead, cancellationToken).ConfigureAwait(false); + if (read <= 0) + return; + + if (_decryptor != null) + { + _decryptor.ProcessBytes(_inputBuffer.Buffer, writeOffset, read, _inputBuffer.Buffer, writeOffset); + } + _inputBuffer.AdvanceWrite(read); + } + else + { + var read = await _dataProvider.ReadAsync(_readBuffer, 0, _readBuffer.Length, cancellationToken).ConfigureAwait(false); + if (read <= 0) + return; + + if (_decryptor != null) + { + _decryptor.ProcessBytes(_readBuffer, 0, read, _readBuffer, 0); + } + read = HandleDecompression(_readBuffer, read); + _inputBuffer.Write(_compressionBuffer.AsSpan(0, read)); + } + } + catch (IOException) + { + IOFailed = true; + throw; } } int HandleDecompression(byte[] buffer, int count) { + EnsureNotDisposed(); _decompressor.InputBuffer = buffer; _decompressor.NextOut = 0; _decompressor.NextIn = 0; @@ -237,46 +459,189 @@ int HandleDecompression(byte[] buffer, int count) return _decompressor.NextOut; } - int HandleCompression(byte[] buffer, int count) + static void ResizeBuffer(ref byte[] buffer) + { + Array.Resize(ref buffer, buffer.Length * 2); + } + + void FlushPlain() + { + EnsureNotDisposed(); + _outputBuffer.GetReadSegments(out var off1, out var len1, out var off2, out var len2); + + try + { + if (_encryptor != null) + { + if (len1 > 0) + { + _encryptor.ProcessBytes(_outputBuffer.Buffer, off1, len1, _outputBuffer.Buffer, off1); + } + if (len2 > 0) + { + _encryptor.ProcessBytes(_outputBuffer.Buffer, off2, len2, _outputBuffer.Buffer, off2); + } + } + + if (len1 > 0) + { + _dataProvider.Write(_outputBuffer.Buffer, off1, len1); + } + if (len2 > 0) + { + _dataProvider.Write(_outputBuffer.Buffer, off2, len2); + } + } + finally + { + _outputBuffer.Consume(len1 + len2); + } + } + + async ValueTask FlushPlainAsync(CancellationToken cancellationToken) + { + EnsureNotDisposed(); + _outputBuffer.GetReadSegments(out var off1, out var len1, out var off2, out var len2); + + try + { + if (_encryptor != null) + { + if (len1 > 0) + { + _encryptor.ProcessBytes(_outputBuffer.Buffer, off1, len1, _outputBuffer.Buffer, off1); + } + if (len2 > 0) + { + _encryptor.ProcessBytes(_outputBuffer.Buffer, off2, len2, _outputBuffer.Buffer, off2); + } + } + + if (len1 > 0) + { + await _dataProvider.WriteAsync(_outputBuffer.Buffer, off1, len1, cancellationToken).ConfigureAwait(false); + } + if (len2 > 0) + { + await _dataProvider.WriteAsync(_outputBuffer.Buffer, off2, len2, cancellationToken).ConfigureAwait(false); + } + } + finally + { + _outputBuffer.Consume(len1 + len2); + } + } + + void FlushCompressed() + { + EnsureNotDisposed(); + _outputBuffer.GetReadSegments(out var off1, out var len1, out var off2, out var len2); + try + { + if (len1 > 0) + { + DeflateAndWrite(_outputBuffer.Buffer, off1, len1, Ionic.Zlib.FlushType.None); + } + if (len2 > 0) + { + DeflateAndWrite(_outputBuffer.Buffer, off2, len2, Ionic.Zlib.FlushType.None); + } + DeflateAndWrite(Array.Empty(), 0, 0, Ionic.Zlib.FlushType.Sync); + } + finally + { + _outputBuffer.Consume(len1 + len2); + } + } + + async ValueTask FlushCompressedAsync(CancellationToken cancellationToken) + { + EnsureNotDisposed(); + _outputBuffer.GetReadSegments(out var off1, out var len1, out var off2, out var len2); + try + { + if (len1 > 0) + { + await DeflateAndWriteAsync(_outputBuffer.Buffer, off1, len1, Ionic.Zlib.FlushType.None, cancellationToken).ConfigureAwait(false); + } + if (len2 > 0) + { + await DeflateAndWriteAsync(_outputBuffer.Buffer, off2, len2, Ionic.Zlib.FlushType.None, cancellationToken).ConfigureAwait(false); + } + await DeflateAndWriteAsync(Array.Empty(), 0, 0, Ionic.Zlib.FlushType.Sync, cancellationToken).ConfigureAwait(false); + } + finally + { + _outputBuffer.Consume(len1 + len2); + } + } + + void DeflateAndWrite(byte[] input, int offset, int count, Ionic.Zlib.FlushType flushType) { - _compressor.InputBuffer = buffer; - _compressor.NextOut = 0; - _compressor.NextIn = 0; + EnsureNotDisposed(); + _compressor.InputBuffer = input; + _compressor.NextIn = offset; _compressor.AvailableBytesIn = count; + while (true) { _compressor.OutputBuffer = _compressionBuffer; - _compressor.AvailableBytesOut = _compressionBuffer.Length - _compressor.NextOut; - var rc = _compressor.Deflate(Ionic.Zlib.FlushType.None); + _compressor.NextOut = 0; + _compressor.AvailableBytesOut = _compressionBuffer.Length; + var rc = _compressor.Deflate(flushType); if (rc != Ionic.Zlib.ZlibConstants.Z_OK) throw new IOException($"Error '{rc}' while compressing the data."); + + var produced = _compressor.NextOut; + if (produced > 0) + { + if (_encryptor != null) + { + _encryptor.ProcessBytes(_compressionBuffer, 0, produced, _compressionBuffer, 0); + } + _dataProvider.Write(_compressionBuffer, 0, produced); + } + if (_compressor.AvailableBytesIn > 0 || _compressor.AvailableBytesOut == 0) { - ResizeBuffer(ref _compressionBuffer); continue; } break; } + } + + async ValueTask DeflateAndWriteAsync(byte[] input, int offset, int count, Ionic.Zlib.FlushType flushType, CancellationToken cancellationToken) + { + EnsureNotDisposed(); + _compressor.InputBuffer = input; + _compressor.NextIn = offset; + _compressor.AvailableBytesIn = count; + while (true) { _compressor.OutputBuffer = _compressionBuffer; - _compressor.AvailableBytesOut = _compressionBuffer.Length - _compressor.NextOut; - var rc = _compressor.Deflate(Ionic.Zlib.FlushType.Sync); + _compressor.NextOut = 0; + _compressor.AvailableBytesOut = _compressionBuffer.Length; + var rc = _compressor.Deflate(flushType); if (rc != Ionic.Zlib.ZlibConstants.Z_OK) throw new IOException($"Error '{rc}' while compressing the data."); + + var produced = _compressor.NextOut; + if (produced > 0) + { + if (_encryptor != null) + { + _encryptor.ProcessBytes(_compressionBuffer, 0, produced, _compressionBuffer, 0); + } + await _dataProvider.WriteAsync(_compressionBuffer, 0, produced, cancellationToken).ConfigureAwait(false); + } + if (_compressor.AvailableBytesIn > 0 || _compressor.AvailableBytesOut == 0) { - ResizeBuffer(ref _compressionBuffer); continue; } break; } - return _compressor.NextOut; - } - - static void ResizeBuffer(ref byte[] buffer) - { - Array.Resize(ref buffer, buffer.Length * 2); } static Org.BouncyCastle.Crypto.Engines.RC4Engine CreateCipher(byte[] key) @@ -285,4 +650,205 @@ static Org.BouncyCastle.Crypto.Engines.RC4Engine CreateCipher(byte[] key) cipher.Init(default, new Org.BouncyCastle.Crypto.Parameters.KeyParameter(key)); return cipher; } + + public void Dispose() + { + if (_disposed) + return; + _disposed = true; + + _inputBuffer.Dispose(); + _outputBuffer.Dispose(); + + _compressionBuffer = null; + _compressor = null; + _decompressor = null; + _decryptor = null; + _encryptor = null; + } + + void EnsureNotDisposed() + { + if (_disposed) + throw new ObjectDisposedException(nameof(FirebirdNetworkHandlingWrapper)); + } + + sealed class ByteRingBuffer : IDisposable + { + byte[] _buffer; + int _head; + int _count; + bool _disposed; + + public byte[] Buffer => _buffer; + public int Count => _count; + + public ByteRingBuffer(int initialCapacity) + { + _buffer = ArrayPool.Shared.Rent(initialCapacity); + _head = 0; + _count = 0; + _disposed = false; + } + + public void Dispose() + { + if (_disposed) + return; + _disposed = true; + + var buffer = _buffer; + _buffer = Array.Empty(); + _head = 0; + _count = 0; + + if (buffer.Length > 0) + { + ArrayPool.Shared.Return(buffer); + } + } + + void EnsureNotDisposed() + { + if (_disposed) + throw new ObjectDisposedException(nameof(ByteRingBuffer)); + } + + public void EnsureFree(int bytes) + { + EnsureNotDisposed(); + if (bytes <= 0) + return; + + var free = _buffer.Length - _count; + if (free >= bytes) + return; + + Grow(_count + bytes); + } + + void Grow(int requiredCapacity) + { + EnsureNotDisposed(); + var newCapacity = _buffer.Length; + while (newCapacity < requiredCapacity) + { + newCapacity *= 2; + } + + var newBuffer = ArrayPool.Shared.Rent(newCapacity); + + GetReadSegments(out var off1, out var len1, out var off2, out var len2); + if (len1 > 0) + { + System.Buffer.BlockCopy(_buffer, off1, newBuffer, 0, len1); + } + if (len2 > 0) + { + System.Buffer.BlockCopy(_buffer, off2, newBuffer, len1, len2); + } + + ArrayPool.Shared.Return(_buffer); + _buffer = newBuffer; + _head = 0; + } + + public void Write(ReadOnlySpan src) + { + EnsureNotDisposed(); + if (src.IsEmpty) + return; + + EnsureFree(src.Length); + + var tail = (_head + _count) % _buffer.Length; + var len1 = Math.Min(src.Length, _buffer.Length - tail); + src.Slice(0, len1).CopyTo(_buffer.AsSpan(tail, len1)); + + var len2 = src.Length - len1; + if (len2 > 0) + { + src.Slice(len1, len2).CopyTo(_buffer.AsSpan(0, len2)); + } + + _count += src.Length; + } + + public int CopyTo(Span dst) + { + EnsureNotDisposed(); + if (dst.IsEmpty || _count == 0) + return 0; + + var toCopy = Math.Min(dst.Length, _count); + var len1 = Math.Min(toCopy, _buffer.Length - _head); + _buffer.AsSpan(_head, len1).CopyTo(dst); + var len2 = toCopy - len1; + if (len2 > 0) + { + _buffer.AsSpan(0, len2).CopyTo(dst.Slice(len1, len2)); + } + Consume(toCopy); + return toCopy; + } + + public void Consume(int bytes) + { + EnsureNotDisposed(); + if (bytes <= 0) + return; + + if (bytes > _count) + throw new ArgumentOutOfRangeException(nameof(bytes)); + + _head = (_head + bytes) % _buffer.Length; + _count -= bytes; + if (_count == 0) + { + _head = 0; + } + } + + public void GetReadSegments(out int offset1, out int length1, out int offset2, out int length2) + { + EnsureNotDisposed(); + if (_count == 0) + { + offset1 = offset2 = length1 = length2 = 0; + return; + } + + offset1 = _head; + length1 = Math.Min(_count, _buffer.Length - _head); + offset2 = 0; + length2 = _count - length1; + } + + public void GetWriteSegment(out int offset, out int length) + { + EnsureNotDisposed(); + if (_count == _buffer.Length) + { + offset = 0; + length = 0; + return; + } + + var tail = (_head + _count) % _buffer.Length; + offset = tail; + length = tail >= _head ? _buffer.Length - tail : _head - tail; + } + + public void AdvanceWrite(int bytes) + { + EnsureNotDisposed(); + if (bytes <= 0) + return; + + if (bytes > _buffer.Length - _count) + throw new ArgumentOutOfRangeException(nameof(bytes)); + + _count += bytes; + } + } } diff --git a/src/FirebirdSql.Data.FirebirdClient/Client/Managed/GdsConnection.cs b/src/FirebirdSql.Data.FirebirdClient/Client/Managed/GdsConnection.cs index cc39ba6b2..cb4804a1a 100644 --- a/src/FirebirdSql.Data.FirebirdClient/Client/Managed/GdsConnection.cs +++ b/src/FirebirdSql.Data.FirebirdClient/Client/Managed/GdsConnection.cs @@ -359,19 +359,29 @@ await Xdr.ReadBooleanAsync(cancellationToken).ConfigureAwait(false), public void Disconnect() { + _firebirdNetworkHandlingWrapper?.Dispose(); + _firebirdNetworkHandlingWrapper = null; + if (_networkStream != null) { _networkStream.Dispose(); _networkStream = null; } + + Xdr = null; } public async ValueTask DisconnectAsync(CancellationToken cancellationToken = default) { + _firebirdNetworkHandlingWrapper?.Dispose(); + _firebirdNetworkHandlingWrapper = null; + if (_networkStream != null) { await _networkStream.DisposeAsync().ConfigureAwait(false); _networkStream = null; } + + Xdr = null; } internal IResponse ProcessOperation(int operation) diff --git a/src/FirebirdSql.Data.FirebirdClient/Client/Managed/IDataProvider.cs b/src/FirebirdSql.Data.FirebirdClient/Client/Managed/IDataProvider.cs index f15ca14d4..b97b2383b 100644 --- a/src/FirebirdSql.Data.FirebirdClient/Client/Managed/IDataProvider.cs +++ b/src/FirebirdSql.Data.FirebirdClient/Client/Managed/IDataProvider.cs @@ -15,6 +15,7 @@ //$Authors = Jiri Cincura (jiri@cincura.net) +using System; using System.Threading; using System.Threading.Tasks; @@ -23,10 +24,14 @@ namespace FirebirdSql.Data.Client.Managed; interface IDataProvider { int Read(byte[] buffer, int offset, int count); + int Read(Span buffer, int offset, int count); ValueTask ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken = default); + ValueTask ReadAsync(Memory buffer, int offset, int count, CancellationToken cancellationToken = default); + void Write(ReadOnlySpan buffer); void Write(byte[] buffer, int offset, int count); ValueTask WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken = default); + ValueTask WriteAsync(ReadOnlyMemory buffer, int offset, int count, CancellationToken cancellationToken = default); void Flush(); ValueTask FlushAsync(CancellationToken cancellationToken = default); diff --git a/src/FirebirdSql.Data.FirebirdClient/Client/Managed/IXdrReader.cs b/src/FirebirdSql.Data.FirebirdClient/Client/Managed/IXdrReader.cs index 6fcbd0974..3eab8465d 100644 --- a/src/FirebirdSql.Data.FirebirdClient/Client/Managed/IXdrReader.cs +++ b/src/FirebirdSql.Data.FirebirdClient/Client/Managed/IXdrReader.cs @@ -27,12 +27,18 @@ namespace FirebirdSql.Data.Client.Managed; interface IXdrReader { byte[] ReadBytes(byte[] buffer, int count); + void ReadBytes(Span buffer, int count); + ValueTask ReadBytesAsync(Memory buffer, int count, CancellationToken cancellationToken = default); ValueTask ReadBytesAsync(byte[] buffer, int count, CancellationToken cancellationToken = default); byte[] ReadOpaque(int length); + void ReadOpaque(Span buffer, int length); + ValueTask ReadOpaqueAsync(Memory buffer, int length, CancellationToken cancellationToken = default); ValueTask ReadOpaqueAsync(int length, CancellationToken cancellationToken = default); byte[] ReadBuffer(); + void ReadBuffer(Span buffer); + ValueTask ReadBufferAsync(Memory buffer, CancellationToken cancellationToken = default); ValueTask ReadBufferAsync(CancellationToken cancellationToken = default); string ReadString(); diff --git a/src/FirebirdSql.Data.FirebirdClient/Client/Managed/IXdrWriter.cs b/src/FirebirdSql.Data.FirebirdClient/Client/Managed/IXdrWriter.cs index 3bb14f041..b8fb74ff9 100644 --- a/src/FirebirdSql.Data.FirebirdClient/Client/Managed/IXdrWriter.cs +++ b/src/FirebirdSql.Data.FirebirdClient/Client/Managed/IXdrWriter.cs @@ -28,25 +28,36 @@ interface IXdrWriter void Flush(); ValueTask FlushAsync(CancellationToken cancellationToken = default); + void WriteBytes(ReadOnlySpan buffer); void WriteBytes(byte[] buffer, int count); ValueTask WriteBytesAsync(byte[] buffer, int count, CancellationToken cancellationToken = default); void WriteOpaque(byte[] buffer); + void WriteOpaque(ReadOnlySpan buffer); + ValueTask WriteOpaqueAsync(ReadOnlyMemory buffer, CancellationToken cancellationToken = default); ValueTask WriteOpaqueAsync(byte[] buffer, CancellationToken cancellationToken = default); void WriteOpaque(byte[] buffer, int length); + void WriteOpaque(ReadOnlySpan buffer, int length); ValueTask WriteOpaqueAsync(byte[] buffer, int length, CancellationToken cancellationToken = default); + ValueTask WriteOpaqueAsync(ReadOnlyMemory buffer, int length, CancellationToken cancellationToken = default); void WriteBuffer(byte[] buffer); + void WriteBuffer(ReadOnlySpan buffer); + ValueTask WriteBufferAsync(ReadOnlyMemory buffer, CancellationToken cancellationToken = default); ValueTask WriteBufferAsync(byte[] buffer, CancellationToken cancellationToken = default); void WriteBuffer(byte[] buffer, int length); ValueTask WriteBufferAsync(byte[] buffer, int length, CancellationToken cancellationToken = default); void WriteBlobBuffer(byte[] buffer); + void WriteBlobBuffer(ReadOnlySpan buffer); + ValueTask WriteBlobBufferAsync(ReadOnlyMemory buffer, CancellationToken cancellationToken = default); ValueTask WriteBlobBufferAsync(byte[] buffer, CancellationToken cancellationToken = default); void WriteTyped(int type, byte[] buffer); + void WriteTyped(int type, ReadOnlySpan buffer); + ValueTask WriteTypedAsync(int type, ReadOnlyMemory buffer, CancellationToken cancellationToken = default); ValueTask WriteTypedAsync(int type, byte[] buffer, CancellationToken cancellationToken = default); void Write(string value); diff --git a/src/FirebirdSql.Data.FirebirdClient/Client/Managed/XdrReaderWriter.cs b/src/FirebirdSql.Data.FirebirdClient/Client/Managed/XdrReaderWriter.cs index eeb4c322c..f000cb5e3 100644 --- a/src/FirebirdSql.Data.FirebirdClient/Client/Managed/XdrReaderWriter.cs +++ b/src/FirebirdSql.Data.FirebirdClient/Client/Managed/XdrReaderWriter.cs @@ -20,6 +20,7 @@ using System.IO; using System.Linq; using System.Numerics; +using System.Buffers; using System.Threading; using System.Threading.Tasks; using FirebirdSql.Data.Common; @@ -33,13 +34,16 @@ sealed class XdrReaderWriter : IXdrReader, IXdrWriter readonly Charset _charset; byte[] _smallBuffer; + byte[] _smallBuffer8; + const int StackallocThreshold = 1024; public XdrReaderWriter(IDataProvider dataProvider, Charset charset) { _dataProvider = dataProvider; _charset = charset; - _smallBuffer = new byte[8]; + _smallBuffer = new byte[16]; + _smallBuffer8 = new byte[8]; } public XdrReaderWriter(IDataProvider dataProvider) @@ -69,6 +73,27 @@ public byte[] ReadBytes(byte[] buffer, int count) } return buffer; } + + public void ReadBytes(Span dst, int count) + { + if (count > 0) + { + var toRead = count; + var currentlyRead = -1; + while (toRead > 0 && currentlyRead != 0) + { + toRead -= (currentlyRead = _dataProvider.Read(dst, count - toRead, toRead)); + } + if (currentlyRead == 0) + { + if (_dataProvider is ITracksIOFailure tracksIOFailure) + { + tracksIOFailure.IOFailed = true; + } + throw new IOException($"Missing {toRead} bytes to fill total {count}."); + } + } + } public async ValueTask ReadBytesAsync(byte[] buffer, int count, CancellationToken cancellationToken = default) { if (count > 0) @@ -91,30 +116,81 @@ public async ValueTask ReadBytesAsync(byte[] buffer, int count, Cancella return buffer; } + public async ValueTask ReadBytesAsync(Memory buffer, int count, CancellationToken cancellationToken = default) + { + if (count <= 0) + return; + var toRead = count; + var offset = 0; + while (toRead > 0) + { + var chunk = await _dataProvider.ReadAsync(buffer.Slice(offset, toRead), 0, toRead, cancellationToken).ConfigureAwait(false); + if (chunk == 0) + { + if (_dataProvider is ITracksIOFailure tracksIOFailure) + { + tracksIOFailure.IOFailed = true; + } + throw new IOException($"Missing {toRead} bytes to fill total {count}."); + } + offset += chunk; + toRead -= chunk; + } + } + public byte[] ReadOpaque(int length) { - var buffer = new byte[length]; + var buffer = length > 0 ? new byte[length] : Array.Empty(); ReadBytes(buffer, length); ReadPad((4 - length) & 3); return buffer; } + + public void ReadOpaque(Span dst, int length) + { + ReadBytes(dst, length); + ReadPad((4 - length) & 3); + } + public async ValueTask ReadOpaqueAsync(int length, CancellationToken cancellationToken = default) { - var buffer = new byte[length]; + var buffer = length > 0 ? new byte[length] : Array.Empty(); await ReadBytesAsync(buffer, length, cancellationToken).ConfigureAwait(false); await ReadPadAsync((4 - length) & 3, cancellationToken).ConfigureAwait(false); return buffer; } + public async ValueTask ReadOpaqueAsync(Memory buffer, int length, CancellationToken cancellationToken = default) + { + if (length <= 0) + return; + await ReadBytesAsync(buffer, length, cancellationToken).ConfigureAwait(false); + await ReadPadAsync((4 - length) & 3, cancellationToken).ConfigureAwait(false); + } + public byte[] ReadBuffer() { return ReadOpaque((ushort)ReadInt32()); } + + public void ReadBuffer(Span dst) + { + ReadOpaque(dst, (ushort)ReadInt32()); + } + public async ValueTask ReadBufferAsync(CancellationToken cancellationToken = default) { return await ReadOpaqueAsync((ushort)await ReadInt32Async(cancellationToken).ConfigureAwait(false), cancellationToken).ConfigureAwait(false); } + public async ValueTask ReadBufferAsync(Memory dst, CancellationToken cancellationToken = default) + { + var length = (ushort)await ReadInt32Async(cancellationToken).ConfigureAwait(false); + if (dst.Length < length) + throw new IOException($"Destination too small. Need {length}, have {dst.Length}."); + await ReadOpaqueAsync(dst, length, cancellationToken).ConfigureAwait(false); + } + public string ReadString() { return ReadString(_charset); @@ -144,13 +220,44 @@ public async ValueTask ReadStringAsync(Charset charset, CancellationToke public string ReadString(Charset charset, int length) { - var buffer = ReadOpaque(length); - return charset.GetString(buffer, 0, buffer.Length); + if (length <= 0) + return string.Empty; + if (length <= StackallocThreshold) + { + Span buffer = stackalloc byte[length]; + ReadOpaque(buffer, length); + return charset.GetString(buffer); + } + else + { + var rented = ArrayPool.Shared.Rent(length); + try + { + ReadBytes(rented, length); + ReadPad((4 - length) & 3); + return charset.GetString(rented.AsSpan(0, length)); + } + finally + { + ArrayPool.Shared.Return(rented); + } + } } public async ValueTask ReadStringAsync(Charset charset, int length, CancellationToken cancellationToken = default) { - var buffer = await ReadOpaqueAsync(length, cancellationToken).ConfigureAwait(false); - return charset.GetString(buffer, 0, buffer.Length); + if (length <= 0) + return string.Empty; + var rented = ArrayPool.Shared.Rent(length); + try + { + await ReadBytesAsync(rented, length, cancellationToken).ConfigureAwait(false); + await ReadPadAsync((4 - length) & 3, cancellationToken).ConfigureAwait(false); + return charset.GetString(rented.AsSpan(0, length)); + } + finally + { + ArrayPool.Shared.Return(rented); + } } public short ReadInt16() @@ -164,8 +271,9 @@ public async ValueTask ReadInt16Async(CancellationToken cancellationToken public int ReadInt32() { - ReadBytes(_smallBuffer, 4); - return TypeDecoder.DecodeInt32(_smallBuffer); + Span bytes = stackalloc byte[4]; + ReadBytes(bytes, 4); + return TypeDecoder.DecodeInt32(bytes); } public async ValueTask ReadInt32Async(CancellationToken cancellationToken = default) { @@ -175,8 +283,9 @@ public async ValueTask ReadInt32Async(CancellationToken cancellationToken = public long ReadInt64() { - ReadBytes(_smallBuffer, 8); - return TypeDecoder.DecodeInt64(_smallBuffer); + Span bytes = stackalloc byte[8]; + ReadBytes(bytes, 8); + return TypeDecoder.DecodeInt64(bytes); } public async ValueTask ReadInt64Async(CancellationToken cancellationToken = default) { @@ -192,7 +301,9 @@ public Guid ReadGuid(int sqlType) } else { - return TypeDecoder.DecodeGuid(ReadOpaque(16)); + Span buf = stackalloc byte[16]; + ReadOpaque(buf, 16); + return TypeDecoder.DecodeGuidSpan(buf); } } public async ValueTask ReadGuidAsync(int sqlType, CancellationToken cancellationToken = default) @@ -201,28 +312,29 @@ public async ValueTask ReadGuidAsync(int sqlType, CancellationToken cancel { return TypeDecoder.DecodeGuid(await ReadBufferAsync(cancellationToken).ConfigureAwait(false)); } - else - { - return TypeDecoder.DecodeGuid(await ReadOpaqueAsync(16, cancellationToken).ConfigureAwait(false)); + else + { + await ReadBytesAsync(_smallBuffer, 16, cancellationToken).ConfigureAwait(false); + return TypeDecoder.DecodeGuid(_smallBuffer); } } public float ReadSingle() { - return BitConverter.ToSingle(BitConverter.GetBytes(ReadInt32()), 0); + return BitConverter.Int32BitsToSingle(ReadInt32()); } public async ValueTask ReadSingleAsync(CancellationToken cancellationToken = default) { - return BitConverter.ToSingle(BitConverter.GetBytes(await ReadInt32Async(cancellationToken).ConfigureAwait(false)), 0); + return BitConverter.Int32BitsToSingle(await ReadInt32Async(cancellationToken).ConfigureAwait(false)); } public double ReadDouble() { - return BitConverter.ToDouble(BitConverter.GetBytes(ReadInt64()), 0); + return BitConverter.Int64BitsToDouble(ReadInt64()); } public async ValueTask ReadDoubleAsync(CancellationToken cancellationToken = default) { - return BitConverter.ToDouble(BitConverter.GetBytes(await ReadInt64Async(cancellationToken).ConfigureAwait(false)), 0); + return BitConverter.Int64BitsToDouble(await ReadInt64Async(cancellationToken).ConfigureAwait(false)); } public DateTime ReadDateTime() @@ -299,11 +411,14 @@ public async ValueTask ReadDecimalAsync(int type, int scale, Cancellati public bool ReadBoolean() { - return TypeDecoder.DecodeBoolean(ReadOpaque(1)); + Span bytes = stackalloc byte[4]; + ReadBytes(bytes, 4); + return TypeDecoder.DecodeBoolean(bytes); } public async ValueTask ReadBooleanAsync(CancellationToken cancellationToken = default) { - return TypeDecoder.DecodeBoolean(await ReadOpaqueAsync(1, cancellationToken).ConfigureAwait(false)); + await ReadBytesAsync(_smallBuffer, 4, cancellationToken).ConfigureAwait(false); + return TypeDecoder.DecodeBoolean(_smallBuffer); } public FbZonedDateTime ReadZonedDateTime(bool isExtended) @@ -330,29 +445,35 @@ public async ValueTask ReadZonedTimeAsync(bool isExtended, Cancella public FbDecFloat ReadDec16() { - return TypeDecoder.DecodeDec16(ReadOpaque(8)); + ReadBytes(_smallBuffer8, 8); + return TypeDecoder.DecodeDec16(_smallBuffer8); } public async ValueTask ReadDec16Async(CancellationToken cancellationToken = default) { - return TypeDecoder.DecodeDec16(await ReadOpaqueAsync(8, cancellationToken).ConfigureAwait(false)); + await ReadBytesAsync(_smallBuffer8, 8, cancellationToken).ConfigureAwait(false); + return TypeDecoder.DecodeDec16(_smallBuffer8); } public FbDecFloat ReadDec34() { - return TypeDecoder.DecodeDec34(ReadOpaque(16)); + ReadBytes(_smallBuffer, 16); + return TypeDecoder.DecodeDec34(_smallBuffer); } public async ValueTask ReadDec34Async(CancellationToken cancellationToken = default) { - return TypeDecoder.DecodeDec34(await ReadOpaqueAsync(16, cancellationToken).ConfigureAwait(false)); + await ReadBytesAsync(_smallBuffer, 16, cancellationToken).ConfigureAwait(false); + return TypeDecoder.DecodeDec34(_smallBuffer); } public BigInteger ReadInt128() { - return TypeDecoder.DecodeInt128(ReadOpaque(16)); + ReadBytes(_smallBuffer, 16); + return TypeDecoder.DecodeInt128(_smallBuffer); } public async ValueTask ReadInt128Async(CancellationToken cancellationToken = default) { - return TypeDecoder.DecodeInt128(await ReadOpaqueAsync(16, cancellationToken).ConfigureAwait(false)); + await ReadBytesAsync(_smallBuffer, 16, cancellationToken).ConfigureAwait(false); + return TypeDecoder.DecodeInt128(_smallBuffer); } public IscException ReadStatusVector() @@ -480,6 +601,11 @@ public ValueTask FlushAsync(CancellationToken cancellationToken = default) return _dataProvider.FlushAsync(cancellationToken); } + public void WriteBytes(ReadOnlySpan buffer) + { + _dataProvider.Write(buffer); + } + public void WriteBytes(byte[] buffer, int count) { _dataProvider.Write(buffer, 0, count); @@ -493,29 +619,75 @@ public void WriteOpaque(byte[] buffer) { WriteOpaque(buffer, buffer.Length); } + public ValueTask WriteOpaqueAsync(byte[] buffer, CancellationToken cancellationToken = default) { return WriteOpaqueAsync(buffer, buffer.Length, cancellationToken); } - public void WriteOpaque(byte[] buffer, int length) - { - if (buffer != null && length > 0) - { - _dataProvider.Write(buffer, 0, buffer.Length); - WriteFill(length - buffer.Length); + public void WriteOpaque(byte[] buffer, int length) + { + if (buffer != null && length > 0) + { + _dataProvider.Write(buffer, 0, buffer.Length); + WriteFill(length - buffer.Length); + WritePad((4 - length) & 3); + } + } + + public void WriteOpaque(ReadOnlySpan buffer, int length) + { + if (length > 0) + { + if (!buffer.IsEmpty) + { + _dataProvider.Write(buffer); + } + WriteFill(length - buffer.Length); + WritePad((4 - length) & 3); + } + } + + public void WriteOpaque(ReadOnlySpan buffer) + { + var length = buffer.Length; + if (length > 0) { + _dataProvider.Write(buffer); WritePad((4 - length) & 3); } } - public async ValueTask WriteOpaqueAsync(byte[] buffer, int length, CancellationToken cancellationToken = default) - { - if (buffer != null && length > 0) - { - await _dataProvider.WriteAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false); - await WriteFillAsync(length - buffer.Length, cancellationToken).ConfigureAwait(false); - await WritePadAsync((4 - length) & 3, cancellationToken).ConfigureAwait(false); - } - } + public async ValueTask WriteOpaqueAsync(byte[] buffer, int length, CancellationToken cancellationToken = default) + { + if (buffer != null && length > 0) + { + await _dataProvider.WriteAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false); + await WriteFillAsync(length - buffer.Length, cancellationToken).ConfigureAwait(false); + await WritePadAsync((4 - length) & 3, cancellationToken).ConfigureAwait(false); + } + } + + public async ValueTask WriteOpaqueAsync(ReadOnlyMemory buffer, int length, CancellationToken cancellationToken = default) + { + if (length > 0) + { + if (buffer.Length > 0) + { + await _dataProvider.WriteAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false); + } + await WriteFillAsync(length - buffer.Length, cancellationToken).ConfigureAwait(false); + await WritePadAsync((4 - length) & 3, cancellationToken).ConfigureAwait(false); + } + } + + public async ValueTask WriteOpaqueAsync(ReadOnlyMemory buffer, CancellationToken cancellationToken = default) + { + var length = buffer.Length; + if (length > 0) + { + await _dataProvider.WriteAsync(buffer, 0, length, cancellationToken).ConfigureAwait(false); + await WritePadAsync((4 - length) & 3, cancellationToken).ConfigureAwait(false); + } + } public void WriteBuffer(byte[] buffer) { @@ -526,6 +698,17 @@ public ValueTask WriteBufferAsync(byte[] buffer, CancellationToken cancellationT return WriteBufferAsync(buffer, buffer?.Length ?? 0, cancellationToken); } + public async ValueTask WriteBufferAsync(ReadOnlyMemory buffer, CancellationToken cancellationToken = default) + { + var length = buffer.Length; + await WriteAsync(length, cancellationToken).ConfigureAwait(false); + if (length > 0) + { + await _dataProvider.WriteAsync(buffer, 0, length, cancellationToken).ConfigureAwait(false); + await WritePadAsync((4 - length) & 3, cancellationToken).ConfigureAwait(false); + } + } + public void WriteBuffer(byte[] buffer, int length) { Write(length); @@ -535,6 +718,17 @@ public void WriteBuffer(byte[] buffer, int length) WritePad((4 - length) & 3); } } + + public void WriteBuffer(ReadOnlySpan buffer) + { + var length = buffer.Length; + Write(length); + if (length > 0) + { + _dataProvider.Write(buffer); + WritePad((4 - length) & 3); + } + } public async ValueTask WriteBufferAsync(byte[] buffer, int length, CancellationToken cancellationToken = default) { await WriteAsync(length, cancellationToken).ConfigureAwait(false); @@ -552,10 +746,26 @@ public void WriteBlobBuffer(byte[] buffer) throw new IOException("Blob buffer too big."); Write(length + 2); Write(length + 2); //bizarre but true! three copies of the length - _dataProvider.Write(new[] { (byte)((length >> 0) & 0xff), (byte)((length >> 8) & 0xff) }, 0, 2); + Span lengthBytes = stackalloc byte[2]; + lengthBytes[0] = (byte)((length >> 0) & 0xff); + lengthBytes[1] = (byte)((length >> 8) & 0xff); + _dataProvider.Write(lengthBytes); _dataProvider.Write(buffer, 0, length); WritePad((4 - length + 2) & 3); } + + public void WriteBlobBuffer(ReadOnlySpan buffer) + { + var length = buffer.Length; // 2 for short for buffer length + if (length > short.MaxValue) + throw new IOException("Blob buffer too big."); + Write(length + 2); + Write(length + 2); //bizarre but true! three copies of the length + Span lengthBytes = [(byte)((length >> 0) & 0xff), (byte)((length >> 8) & 0xff)]; + _dataProvider.Write(lengthBytes); + _dataProvider.Write(buffer); + WritePad((4 - length + 2) & 3); + } public async ValueTask WriteBlobBufferAsync(byte[] buffer, CancellationToken cancellationToken = default) { var length = buffer.Length; // 2 for short for buffer length @@ -563,57 +773,192 @@ public async ValueTask WriteBlobBufferAsync(byte[] buffer, CancellationToken can throw new IOException("Blob buffer too big."); await WriteAsync(length + 2, cancellationToken).ConfigureAwait(false); await WriteAsync(length + 2, cancellationToken).ConfigureAwait(false); //bizarre but true! three copies of the length - await _dataProvider.WriteAsync(new[] { (byte)((length >> 0) & 0xff), (byte)((length >> 8) & 0xff) }, 0, 2, cancellationToken).ConfigureAwait(false); + var rented = ArrayPool.Shared.Rent(2); + try + { + rented[0] = (byte)((length >> 0) & 0xff); + rented[1] = (byte)((length >> 8) & 0xff); + await _dataProvider.WriteAsync(rented, 0, 2, cancellationToken).ConfigureAwait(false); + } + finally + { + ArrayPool.Shared.Return(rented); + } await _dataProvider.WriteAsync(buffer, 0, length, cancellationToken).ConfigureAwait(false); await WritePadAsync((4 - length + 2) & 3, cancellationToken).ConfigureAwait(false); } + public async ValueTask WriteBlobBufferAsync(ReadOnlyMemory buffer, CancellationToken cancellationToken = default) + { + var length = buffer.Length; // 2 for short for buffer length + if (length > short.MaxValue) + throw new IOException("Blob buffer too big."); + await WriteAsync(length + 2, cancellationToken).ConfigureAwait(false); + await WriteAsync(length + 2, cancellationToken).ConfigureAwait(false); // three copies of the length + Span lengthBytes = stackalloc byte[2]; + lengthBytes[0] = (byte)((length >> 0) & 0xff); + lengthBytes[1] = (byte)((length >> 8) & 0xff); + _dataProvider.Write(lengthBytes); + await _dataProvider.WriteAsync(buffer, 0, length, cancellationToken).ConfigureAwait(false); + await WritePadAsync((4 - length + 2) & 3, cancellationToken).ConfigureAwait(false); + } + public void WriteTyped(int type, byte[] buffer) { + Span typeByte = stackalloc byte[1]; int length; if (buffer == null) { Write(1); - _dataProvider.Write(new[] { (byte)type }, 0, 1); + typeByte[0] = (byte)type; + _dataProvider.Write(typeByte); length = 1; } else { length = buffer.Length + 1; Write(length); - _dataProvider.Write(new[] { (byte)type }, 0, 1); + typeByte[0] = (byte)type; + _dataProvider.Write(typeByte); _dataProvider.Write(buffer, 0, buffer.Length); } WritePad((4 - length) & 3); } + + public void WriteTyped(int type, ReadOnlySpan buffer) + { + Span typeByte = stackalloc byte[1]; + var length = buffer.Length + 1; + Write(length); + typeByte[0] = (byte)type; + _dataProvider.Write(typeByte); + if (!buffer.IsEmpty) + { + _dataProvider.Write(buffer); + } + WritePad((4 - length) & 3); + } public async ValueTask WriteTypedAsync(int type, byte[] buffer, CancellationToken cancellationToken = default) { int length; if (buffer == null) { await WriteAsync(1, cancellationToken).ConfigureAwait(false); - await _dataProvider.WriteAsync(new[] { (byte)type }, 0, 1, cancellationToken).ConfigureAwait(false); + var rented = ArrayPool.Shared.Rent(1); + try + { + rented[0] = (byte)type; + await _dataProvider.WriteAsync(rented, 0, 1, cancellationToken).ConfigureAwait(false); + } + finally + { + ArrayPool.Shared.Return(rented); + } length = 1; } else { length = buffer.Length + 1; await WriteAsync(length, cancellationToken).ConfigureAwait(false); - await _dataProvider.WriteAsync(new[] { (byte)type }, 0, 1, cancellationToken).ConfigureAwait(false); + var rented = ArrayPool.Shared.Rent(1); + try + { + rented[0] = (byte)type; + await _dataProvider.WriteAsync(rented, 0, 1, cancellationToken).ConfigureAwait(false); + } + finally + { + ArrayPool.Shared.Return(rented); + } await _dataProvider.WriteAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false); } await WritePadAsync((4 - length) & 3, cancellationToken).ConfigureAwait(false); } + public async ValueTask WriteTypedAsync(int type, ReadOnlyMemory buffer, CancellationToken cancellationToken = default) + { + int length; + if (buffer.Length == 0) + { + await WriteAsync(1, cancellationToken).ConfigureAwait(false); + var rented = ArrayPool.Shared.Rent(1); + try + { + rented[0] = (byte)type; + await _dataProvider.WriteAsync(rented, 0, 1, cancellationToken).ConfigureAwait(false); + } + finally + { + ArrayPool.Shared.Return(rented); + } + length = 1; + } + else + { + length = buffer.Length + 1; + await WriteAsync(length, cancellationToken).ConfigureAwait(false); + var rented = ArrayPool.Shared.Rent(1); + try + { + rented[0] = (byte)type; + await _dataProvider.WriteAsync(rented, 0, 1, cancellationToken).ConfigureAwait(false); + } + finally + { + ArrayPool.Shared.Return(rented); + } + await _dataProvider.WriteAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false); + } + await WritePadAsync((4 - length) & 3, cancellationToken).ConfigureAwait(false); + } + public void Write(string value) { - var buffer = _charset.GetBytes(value); - WriteBuffer(buffer, buffer.Length); + if (string.IsNullOrEmpty(value)) + { + WriteBuffer(ReadOnlySpan.Empty); + return; + } + var encoding = _charset.Encoding; + var maxBytes = encoding.GetMaxByteCount(value.Length); + if (maxBytes <= StackallocThreshold) + { + Span span = stackalloc byte[maxBytes]; + var written = encoding.GetBytes(value.AsSpan(), span); + WriteBuffer(span[..written]); + } + else + { + var rented = ArrayPool.Shared.Rent(maxBytes); + try + { + var written = encoding.GetBytes(value.AsSpan(), rented.AsSpan()); + WriteBuffer(rented.AsSpan(0, written)); + } + finally + { + ArrayPool.Shared.Return(rented); + } + } } public ValueTask WriteAsync(string value, CancellationToken cancellationToken = default) { - var buffer = _charset.GetBytes(value); - return WriteBufferAsync(buffer, buffer.Length, cancellationToken); + if (string.IsNullOrEmpty(value)) + { + return WriteBufferAsync(Array.Empty(), 0, cancellationToken); + } + var encoding = _charset.Encoding; + var byteCount = encoding.GetByteCount(value); + var rented = ArrayPool.Shared.Rent(byteCount); + var written = encoding.GetBytes(value, 0, value.Length, rented, 0); + var task = WriteBufferAsync(rented, written, cancellationToken); + return ReturnAfter(task, rented); + } + + static async ValueTask ReturnAfter(ValueTask writeTask, byte[] rented) + { + try { await writeTask.ConfigureAwait(false); } + finally { ArrayPool.Shared.Return(rented); } } public void Write(short value) @@ -627,42 +972,50 @@ public ValueTask WriteAsync(short value, CancellationToken cancellationToken = d public void Write(int value) { - _dataProvider.Write(TypeEncoder.EncodeInt32(value), 0, 4); + Span bytes = stackalloc byte[4]; + TypeEncoder.EncodeInt32(value, bytes); + _dataProvider.Write(bytes); } public ValueTask WriteAsync(int value, CancellationToken cancellationToken = default) { - return _dataProvider.WriteAsync(TypeEncoder.EncodeInt32(value), 0, 4, cancellationToken); + var rented = ArrayPool.Shared.Rent(4); + Span span = rented; + TypeEncoder.EncodeInt32(value, span); + var task = _dataProvider.WriteAsync(rented, 0, 4, cancellationToken); + return ReturnAfter(task, rented); } public void Write(long value) { - _dataProvider.Write(TypeEncoder.EncodeInt64(value), 0, 8); + Span bytes = stackalloc byte[8]; + TypeEncoder.EncodeInt64(value, bytes); + _dataProvider.Write(bytes); } public ValueTask WriteAsync(long value, CancellationToken cancellationToken = default) { - return _dataProvider.WriteAsync(TypeEncoder.EncodeInt64(value), 0, 8, cancellationToken); + var rented = ArrayPool.Shared.Rent(8); + Span span = rented; + TypeEncoder.EncodeInt64(value, span); + var task = _dataProvider.WriteAsync(rented, 0, 8, cancellationToken); + return ReturnAfter(task, rented); } public void Write(float value) { - var buffer = BitConverter.GetBytes(value); - Write(BitConverter.ToInt32(buffer, 0)); + Write(BitConverter.SingleToInt32Bits(value)); } public ValueTask WriteAsync(float value, CancellationToken cancellationToken = default) { - var buffer = BitConverter.GetBytes(value); - return WriteAsync(BitConverter.ToInt32(buffer, 0), cancellationToken); + return WriteAsync(BitConverter.SingleToInt32Bits(value), cancellationToken); } public void Write(double value) { - var buffer = BitConverter.GetBytes(value); - Write(BitConverter.ToInt64(buffer, 0)); + Write(BitConverter.DoubleToInt64Bits(value)); } public ValueTask WriteAsync(double value, CancellationToken cancellationToken = default) { - var buffer = BitConverter.GetBytes(value); - return WriteAsync(BitConverter.ToInt64(buffer, 0), cancellationToken); + return WriteAsync(BitConverter.DoubleToInt64Bits(value), cancellationToken); } public void Write(decimal value, int type, int scale) @@ -715,11 +1068,16 @@ public ValueTask WriteAsync(decimal value, int type, int scale, CancellationToke public void Write(bool value) { - WriteOpaque(TypeEncoder.EncodeBoolean(value)); + Span buffer = stackalloc byte[1]; + TypeEncoder.EncodeBoolean(value, buffer); + WriteOpaque(buffer); } public ValueTask WriteAsync(bool value, CancellationToken cancellationToken = default) { - return WriteOpaqueAsync(TypeEncoder.EncodeBoolean(value), cancellationToken); + var rented = ArrayPool.Shared.Rent(1); + TypeEncoder.EncodeBoolean(value, rented.AsSpan()); + var task = WriteOpaqueAsync(rented, 1, cancellationToken); + return ReturnAfter(task, rented); } public void Write(DateTime value) @@ -735,7 +1093,8 @@ public async ValueTask WriteAsync(DateTime value, CancellationToken cancellation public void Write(Guid value, int sqlType) { - var bytes = TypeEncoder.EncodeGuid(value); + Span bytes = stackalloc byte[16]; + TypeEncoder.EncodeGuid(value, bytes); if (sqlType == IscCodes.SQL_VARYING) { WriteBuffer(bytes); @@ -747,14 +1106,18 @@ public void Write(Guid value, int sqlType) } public ValueTask WriteAsync(Guid value, int sqlType, CancellationToken cancellationToken = default) { - var bytes = TypeEncoder.EncodeGuid(value); + var rented = ArrayPool.Shared.Rent(16); + Span span = rented; + TypeEncoder.EncodeGuid(value, span); if (sqlType == IscCodes.SQL_VARYING) { - return WriteBufferAsync(bytes, cancellationToken); + var task = WriteBufferAsync(rented, 16, cancellationToken); + return ReturnAfter(task, rented); } else { - return WriteOpaqueAsync(bytes, cancellationToken); + var task = WriteOpaqueAsync(rented, 16, cancellationToken); + return ReturnAfter(task, rented); } } @@ -779,7 +1142,7 @@ public ValueTask WriteAsync(FbDecFloat value, int size, CancellationToken cancel public void Write(BigInteger value) { - WriteOpaqueAsync(TypeEncoder.EncodeInt128(value)); + WriteOpaque(TypeEncoder.EncodeInt128(value)); } public ValueTask WriteAsync(BigInteger value, CancellationToken cancellationToken = default) { diff --git a/src/FirebirdSql.Data.FirebirdClient/Common/Charset.cs b/src/FirebirdSql.Data.FirebirdClient/Common/Charset.cs index c8fb48659..6a8e54df2 100644 --- a/src/FirebirdSql.Data.FirebirdClient/Common/Charset.cs +++ b/src/FirebirdSql.Data.FirebirdClient/Common/Charset.cs @@ -141,6 +141,11 @@ public string GetString(byte[] buffer) return Encoding.GetString(buffer); } + public string GetString(ReadOnlySpan buffer) + { + return Encoding.GetString(buffer); + } + public string GetString(byte[] buffer, int index, int count) { return Encoding.GetString(buffer, index, count); diff --git a/src/FirebirdSql.Data.FirebirdClient/Common/IscHelper.cs b/src/FirebirdSql.Data.FirebirdClient/Common/IscHelper.cs index 578895f7a..b25a86b12 100644 --- a/src/FirebirdSql.Data.FirebirdClient/Common/IscHelper.cs +++ b/src/FirebirdSql.Data.FirebirdClient/Common/IscHelper.cs @@ -232,4 +232,16 @@ public static long VaxInteger(byte[] buffer, int index, int length) } return value; } + + public static long VaxInteger(ReadOnlySpan buffer, int index, int length) + { + var value = 0L; + var shift = 0; + var i = index; + while(--length >= 0) { + value += (buffer[i++] & 0xffL) << shift; + shift += 8; + } + return value; + } } diff --git a/src/FirebirdSql.Data.FirebirdClient/Common/ParameterBuffer.cs b/src/FirebirdSql.Data.FirebirdClient/Common/ParameterBuffer.cs index bbd6283d0..9d726f22d 100644 --- a/src/FirebirdSql.Data.FirebirdClient/Common/ParameterBuffer.cs +++ b/src/FirebirdSql.Data.FirebirdClient/Common/ParameterBuffer.cs @@ -63,7 +63,11 @@ protected void Write(short value) { value = IPAddress.NetworkToHostOrder(value); } - var buffer = BitConverter.GetBytes(value); + Span buffer = stackalloc byte[2]; + if (!BitConverter.TryWriteBytes(buffer, value)) + { + throw new InvalidOperationException("Failed to write Int16 bytes."); + } Write(buffer); } @@ -73,7 +77,11 @@ protected void Write(int value) { value = IPAddress.NetworkToHostOrder(value); } - var buffer = BitConverter.GetBytes(value); + Span buffer = stackalloc byte[4]; + if (!BitConverter.TryWriteBytes(buffer, value)) + { + throw new InvalidOperationException("Failed to write Int32 bytes."); + } Write(buffer); } @@ -83,7 +91,11 @@ protected void Write(long value) { value = IPAddress.NetworkToHostOrder(value); } - var buffer = BitConverter.GetBytes(value); + Span buffer = stackalloc byte[8]; + if (!BitConverter.TryWriteBytes(buffer, value)) + { + throw new InvalidOperationException("Failed to write Int64 bytes."); + } Write(buffer); } @@ -92,6 +104,11 @@ protected void Write(byte[] buffer) Write(buffer, 0, buffer.Length); } + protected void Write(ReadOnlySpan buffer) + { + _data.AddRange(buffer); + } + protected void Write(byte[] buffer, int offset, int count) { _data.AddRange(new ArraySegment(buffer, offset, count)); diff --git a/src/FirebirdSql.Data.FirebirdClient/Common/TypeDecoder.cs b/src/FirebirdSql.Data.FirebirdClient/Common/TypeDecoder.cs index f7d058520..4c25799c3 100644 --- a/src/FirebirdSql.Data.FirebirdClient/Common/TypeDecoder.cs +++ b/src/FirebirdSql.Data.FirebirdClient/Common/TypeDecoder.cs @@ -16,6 +16,7 @@ //$Authors = Carlos Guzman Alvarez, Jiri Cincura (jiri@cincura.net) using System; +using System.Buffers.Binary; using System.Net; using System.Numerics; using FirebirdSql.Data.Types; @@ -98,23 +99,43 @@ public static bool DecodeBoolean(byte[] value) return value[0] != 0; } + public static bool DecodeBoolean(ReadOnlySpan value) + { + return value[0] != 0; + } + public static Guid DecodeGuid(byte[] value) { var a = IPAddress.HostToNetworkOrder(BitConverter.ToInt32(value, 0)); var b = IPAddress.HostToNetworkOrder(BitConverter.ToInt16(value, 4)); var c = IPAddress.HostToNetworkOrder(BitConverter.ToInt16(value, 6)); - var d = new[] { value[8], value[9], value[10], value[11], value[12], value[13], value[14], value[15] }; - return new Guid(a, b, c, d); + return new Guid(a, b, c, value[8], value[9], value[10], value[11], value[12], value[13], value[14], value[15]); + } + + public static Guid DecodeGuidSpan(Span value) + { + var a = IPAddress.HostToNetworkOrder(BitConverter.ToInt32(value[..4])); + var b = IPAddress.HostToNetworkOrder(BitConverter.ToInt16(value[4..6])); + var c = IPAddress.HostToNetworkOrder(BitConverter.ToInt16(value[6..8])); + return new Guid(a, b, c, value[8], value[9], value[10], value[11], value[12], value[13], value[14], value[15]); } public static int DecodeInt32(byte[] value) { - return IPAddress.HostToNetworkOrder(BitConverter.ToInt32(value, 0)); + return BinaryPrimitives.ReadInt32BigEndian(value); + } + + public static int DecodeInt32(Span value) { + return BinaryPrimitives.ReadInt32BigEndian(value); } public static long DecodeInt64(byte[] value) { - return IPAddress.HostToNetworkOrder(BitConverter.ToInt64(value, 0)); + return BinaryPrimitives.ReadInt64BigEndian(value); + } + + public static long DecodeInt64(Span value) { + return BinaryPrimitives.ReadInt64BigEndian(value); } public static FbDecFloat DecodeDec16(byte[] value) diff --git a/src/FirebirdSql.Data.FirebirdClient/Common/TypeEncoder.cs b/src/FirebirdSql.Data.FirebirdClient/Common/TypeEncoder.cs index eda0a8058..bd05314ce 100644 --- a/src/FirebirdSql.Data.FirebirdClient/Common/TypeEncoder.cs +++ b/src/FirebirdSql.Data.FirebirdClient/Common/TypeEncoder.cs @@ -16,7 +16,7 @@ //$Authors = Carlos Guzman Alvarez, Jiri Cincura (jiri@cincura.net) using System; -using System.Globalization; +using System.Buffers.Binary; using System.Net; using System.Numerics; using FirebirdSql.Data.Types; @@ -64,11 +64,7 @@ public static int EncodeTime(TimeOnly t) public static int EncodeDate(DateTime d) { - var calendar = new GregorianCalendar(); - var day = calendar.GetDayOfMonth(d); - var month = calendar.GetMonth(d); - var year = calendar.GetYear(d); - return EncodeDateImpl(year, month, day); + return EncodeDateImpl(d.Year, d.Month, d.Day); } public static int EncodeDate(DateOnly d) { @@ -97,6 +93,11 @@ public static byte[] EncodeBoolean(bool value) return new[] { (byte)(value ? 1 : 0) }; } + public static void EncodeBoolean(bool value, Span destination) + { + destination[0] = (byte)(value ? 1 : 0); + } + public static byte[] EncodeGuid(Guid value) { var data = value.ToByteArray(); @@ -109,17 +110,62 @@ public static byte[] EncodeGuid(Guid value) b[0], b[1], c[0], c[1], data[8], data[9], data[10], data[11], data[12], data[13], data[14], data[15] - }; + }; + } + + public static void EncodeGuid(Guid value, Span destination) + { + Span data = stackalloc byte[16]; + if (!value.TryWriteBytes(data)) + { + throw new InvalidOperationException("Failed to write Guid bytes."); + } + + Span a = stackalloc byte[4]; + Span b = stackalloc byte[2]; + Span c = stackalloc byte[2]; + + if (!BitConverter.TryWriteBytes(a, IPAddress.NetworkToHostOrder(BitConverter.ToInt32(data[..4])))) + { + throw new InvalidOperationException("Failed to write Guid bytes."); + } + if (!BitConverter.TryWriteBytes(b, IPAddress.NetworkToHostOrder(BitConverter.ToInt16(data.Slice(4, 2))))) + { + throw new InvalidOperationException("Failed to write Guid bytes."); + } + if (!BitConverter.TryWriteBytes(c, IPAddress.NetworkToHostOrder(BitConverter.ToInt16(data.Slice(6, 2))))) + { + throw new InvalidOperationException("Failed to write Guid bytes."); + } + + a.CopyTo(destination[..4]); + b.CopyTo(destination.Slice(4, 2)); + c.CopyTo(destination.Slice(6, 2)); + data.Slice(8, 8).CopyTo(destination[8..]); } public static byte[] EncodeInt32(int value) { - return BitConverter.GetBytes(IPAddress.NetworkToHostOrder(value)); + var result = new byte[4]; + EncodeInt32(value, result); + return result; + } + + public static void EncodeInt32(int value, Span destination) + { + BinaryPrimitives.WriteInt32BigEndian(destination, value); } public static byte[] EncodeInt64(long value) { - return BitConverter.GetBytes(IPAddress.NetworkToHostOrder(value)); + var result = new byte[8]; + EncodeInt64(value, result); + return result; + } + + public static void EncodeInt64(long value, Span destination) + { + BinaryPrimitives.WriteInt64BigEndian(destination, value); } public static byte[] EncodeDec16(FbDecFloat value)