From 55564825bca71fb8ba4501a0b0da01b5fa62e799 Mon Sep 17 00:00:00 2001 From: pl752 Date: Thu, 11 Dec 2025 10:57:52 +0500 Subject: [PATCH 01/11] Optimized memory allocations using stackalloc, spans and pooled arrays --- .../Client/Managed/AuthBlock.cs | 192 ++++-- .../Managed/DataProviderStreamWrapper.cs | 27 + .../Managed/FirebirdNetworkHandlingWrapper.cs | 62 ++ .../Client/Managed/IDataProvider.cs | 5 + .../Client/Managed/IXdrReader.cs | 6 + .../Client/Managed/IXdrWriter.cs | 11 + .../Client/Managed/Srp/SrpClientBase.cs | 41 +- .../Client/Managed/Version10/GdsArray.cs | 129 ++-- .../Client/Managed/Version10/GdsBlob.cs | 66 +- .../Client/Managed/Version10/GdsDatabase.cs | 37 +- .../Managed/Version10/GdsServiceManager.cs | 15 +- .../Client/Managed/Version10/GdsStatement.cs | 385 +++++++++--- .../Managed/Version10/GdsTransaction.cs | 4 +- .../Client/Managed/Version11/GdsDatabase.cs | 6 +- .../Client/Managed/Version13/GdsStatement.cs | 139 +++-- .../Client/Managed/XdrReaderWriter.cs | 588 ++++++++++++++++-- .../Common/Charset.cs | 5 + .../Common/IscHelper.cs | 12 + .../Common/ParameterBuffer.cs | 23 +- .../Common/TypeDecoder.cs | 8 +- .../Common/TypeEncoder.cs | 54 +- 21 files changed, 1442 insertions(+), 373 deletions(-) diff --git a/src/FirebirdSql.Data.FirebirdClient/Client/Managed/AuthBlock.cs b/src/FirebirdSql.Data.FirebirdClient/Client/Managed/AuthBlock.cs index 0a8a14513..55ebdf4b9 100644 --- a/src/FirebirdSql.Data.FirebirdClient/Client/Managed/AuthBlock.cs +++ b/src/FirebirdSql.Data.FirebirdClient/Client/Managed/AuthBlock.cs @@ -52,6 +52,8 @@ sealed class AuthBlock public bool WireCryptInitialized { get; private set; } + private const int STACKALLOC_LIMIT = 512; + public AuthBlock(GdsConnection connection, string user, string password, WireCryptOption wireCrypt) { _srp256 = new Srp256Client(); @@ -64,64 +66,164 @@ public AuthBlock(GdsConnection connection, string user, string password, WireCry WireCrypt = wireCrypt; } - public byte[] UserIdentificationData() + public byte[] UserIdentificationData() { using (var result = new MemoryStream(256)) { - var userString = Environment.GetEnvironmentVariable("USERNAME") ?? Environment.GetEnvironmentVariable("USER") ?? string.Empty; - var user = Encoding.UTF8.GetBytes(userString); - result.WriteByte(IscCodes.CNCT_user); - result.WriteByte((byte)user.Length); - result.Write(user, 0, user.Length); + { + var userString = Environment.GetEnvironmentVariable("USERNAME") ?? Environment.GetEnvironmentVariable("USER") ?? string.Empty; + var slen = Encoding.UTF8.GetByteCount(userString); + byte[] rented = null; + Span user = slen > STACKALLOC_LIMIT + ? (rented = System.Buffers.ArrayPool.Shared.Rent(slen)).AsSpan(0, slen) + : stackalloc byte[slen]; + int real_len = Encoding.UTF8.GetBytes(userString, user); + result.WriteByte(IscCodes.CNCT_user); + result.WriteByte((byte)real_len); + result.Write(user); + if (rented != null) + { + System.Buffers.ArrayPool.Shared.Return(rented, clearArray: true); + } + } - var host = Encoding.UTF8.GetBytes(Dns.GetHostName()); - result.WriteByte(IscCodes.CNCT_host); - result.WriteByte((byte)host.Length); - result.Write(host, 0, host.Length); + { + var hostName = Dns.GetHostName(); + var slen = Encoding.UTF8.GetByteCount(hostName); + byte[] rented = null; + Span host = slen > STACKALLOC_LIMIT + ? (rented = System.Buffers.ArrayPool.Shared.Rent(slen)).AsSpan(0, slen) + : stackalloc byte[slen]; + int real_len = Encoding.UTF8.GetBytes(hostName, host); + result.WriteByte(IscCodes.CNCT_host); + result.WriteByte((byte)real_len); + result.Write(host); + if (rented != null) + { + System.Buffers.ArrayPool.Shared.Return(rented, clearArray: true); + } + } result.WriteByte(IscCodes.CNCT_user_verification); result.WriteByte(0); if (!string.IsNullOrEmpty(User)) { - var login = Encoding.UTF8.GetBytes(User); - result.WriteByte(IscCodes.CNCT_login); - result.WriteByte((byte)login.Length); - result.Write(login, 0, login.Length); - - var pluginNameBytes = Encoding.UTF8.GetBytes(_srp256.Name); - result.WriteByte(IscCodes.CNCT_plugin_name); - result.WriteByte((byte)pluginNameBytes.Length); - result.Write(pluginNameBytes, 0, pluginNameBytes.Length); - var specificData = Encoding.UTF8.GetBytes(_srp256.PublicKeyHex); - WriteMultiPartHelper(result, IscCodes.CNCT_specific_data, specificData); - - var plugins = string.Join(",", new[] { _srp256.Name, _srp.Name }); - var pluginsBytes = Encoding.UTF8.GetBytes(plugins); - result.WriteByte(IscCodes.CNCT_plugin_list); - result.WriteByte((byte)pluginsBytes.Length); - result.Write(pluginsBytes, 0, pluginsBytes.Length); + { + var slen = Encoding.UTF8.GetByteCount(User); + byte[] rented = null; + Span bytes = slen > STACKALLOC_LIMIT + ? (rented = System.Buffers.ArrayPool.Shared.Rent(slen)).AsSpan(0, slen) + : stackalloc byte[slen]; + int real_len = Encoding.UTF8.GetBytes(User, bytes); + result.WriteByte(IscCodes.CNCT_login); + result.WriteByte((byte)real_len); + result.Write(bytes); + if (rented != null) + { + System.Buffers.ArrayPool.Shared.Return(rented, clearArray: true); + } + } + { + var slen = Encoding.UTF8.GetByteCount(_srp256.Name); + byte[] rented = null; + Span bytes = slen > STACKALLOC_LIMIT + ? (rented = System.Buffers.ArrayPool.Shared.Rent(slen)).AsSpan(0, slen) + : stackalloc byte[slen]; + int real_len = Encoding.UTF8.GetBytes(_srp256.Name, bytes); + result.WriteByte(IscCodes.CNCT_plugin_name); + result.WriteByte((byte)real_len); + result.Write(bytes[..real_len]); + if (rented != null) + { + System.Buffers.ArrayPool.Shared.Return(rented, clearArray: true); + } + } + { + var slen = Encoding.UTF8.GetByteCount(_srp256.PublicKeyHex); + byte[] rented = null; + Span specificData = slen > STACKALLOC_LIMIT + ? (rented = System.Buffers.ArrayPool.Shared.Rent(slen)).AsSpan(0, slen) + : stackalloc byte[slen]; + Encoding.UTF8.GetBytes(_srp256.PublicKeyHex.AsSpan(), specificData); + WriteMultiPartHelper(result, IscCodes.CNCT_specific_data, specificData); + if (rented != null) + { + System.Buffers.ArrayPool.Shared.Return(rented, clearArray: true); + } + } + { + var slen1 = Encoding.UTF8.GetByteCount(_srp256.Name); + byte[] rented1 = null; + Span bytes1 = slen1 > STACKALLOC_LIMIT + ? (rented1 = System.Buffers.ArrayPool.Shared.Rent(slen1)).AsSpan(0, slen1) + : stackalloc byte[slen1]; + Span bytes2 = stackalloc byte[1]; + var slen3 = Encoding.UTF8.GetByteCount(_srp.Name); + byte[] rented3 = null; + Span bytes3 = slen3 > STACKALLOC_LIMIT + ? (rented3 = System.Buffers.ArrayPool.Shared.Rent(slen3)).AsSpan(0, slen3) + : stackalloc byte[slen3]; + int l1 = Encoding.UTF8.GetBytes(_srp256.Name.AsSpan(), bytes1); + int l2 = Encoding.UTF8.GetBytes(",".AsSpan(), bytes2); + int l3 = Encoding.UTF8.GetBytes(_srp.Name.AsSpan(), bytes3); + result.WriteByte(IscCodes.CNCT_plugin_list); + result.WriteByte((byte)(l1+l2+l3)); + result.Write(bytes1); + result.Write(bytes2); + result.Write(bytes3); + if (rented1 != null) + { + System.Buffers.ArrayPool.Shared.Return(rented1, clearArray: true); + } + if (rented3 != null) + { + System.Buffers.ArrayPool.Shared.Return(rented3, clearArray: true); + } + } - result.WriteByte(IscCodes.CNCT_client_crypt); - result.WriteByte(4); - result.Write(TypeEncoder.EncodeInt32(WireCryptOptionValue(WireCrypt)), 0, 4); + { + result.WriteByte(IscCodes.CNCT_client_crypt); + result.WriteByte(4); + Span bytes = stackalloc byte[4]; + if (!BitConverter.TryWriteBytes(bytes, IPAddress.NetworkToHostOrder(WireCryptOptionValue(WireCrypt)))) + { + throw new InvalidOperationException("Failed to write wire crypt option bytes."); + } + result.Write(bytes); + } } else { - var pluginNameBytes = Encoding.UTF8.GetBytes(_sspi.Name); + var slen = Encoding.UTF8.GetByteCount(_sspi.Name); + byte[] rented = null; + Span pluginNameBytes = slen > STACKALLOC_LIMIT + ? (rented = System.Buffers.ArrayPool.Shared.Rent(slen)).AsSpan(0, slen) + : stackalloc byte[slen]; + int pluginNameLen = Encoding.UTF8.GetBytes(_sspi.Name.AsSpan(), pluginNameBytes); result.WriteByte(IscCodes.CNCT_plugin_name); - result.WriteByte((byte)pluginNameBytes.Length); - result.Write(pluginNameBytes, 0, pluginNameBytes.Length); + result.WriteByte((byte)pluginNameLen); + result.Write(pluginNameBytes[..pluginNameLen]); + var specificData = _sspi.InitializeClientSecurity(); WriteMultiPartHelper(result, IscCodes.CNCT_specific_data, specificData); result.WriteByte(IscCodes.CNCT_plugin_list); - result.WriteByte((byte)pluginNameBytes.Length); - result.Write(pluginNameBytes, 0, pluginNameBytes.Length); + result.WriteByte((byte)pluginNameLen); + result.Write(pluginNameBytes[..pluginNameLen]); result.WriteByte(IscCodes.CNCT_client_crypt); result.WriteByte(4); - result.Write(TypeEncoder.EncodeInt32(IscCodes.WIRE_CRYPT_DISABLED), 0, 4); + Span wireCryptBytes = stackalloc byte[4]; + if (!BitConverter.TryWriteBytes(wireCryptBytes, IPAddress.NetworkToHostOrder(IscCodes.WIRE_CRYPT_DISABLED))) + { + throw new InvalidOperationException("Failed to write wire crypt disabled bytes."); + } + result.Write(wireCryptBytes); + if (rented != null) + { + System.Buffers.ArrayPool.Shared.Return(rented, clearArray: true); + } } return result.ToArray(); @@ -309,7 +411,21 @@ void ReleaseAuth() _sspi = null; } - static void WriteMultiPartHelper(Stream stream, byte code, byte[] data) + static void WriteMultiPartHelper(MemoryStream stream, byte code, byte[] data) + { + const int MaxLength = 255 - 1; + var part = 0; + for (var i = 0; i < data.Length; i += MaxLength) { + stream.WriteByte(code); + var length = Math.Min(data.Length - i, MaxLength); + stream.WriteByte((byte)(length + 1)); + stream.WriteByte((byte)part); + stream.Write(data, i, length); + part++; + } + } + + static void WriteMultiPartHelper(MemoryStream stream, byte code, ReadOnlySpan data) { const int MaxLength = 255 - 1; var part = 0; @@ -319,7 +435,7 @@ static void WriteMultiPartHelper(Stream stream, byte code, byte[] data) var length = Math.Min(data.Length - i, MaxLength); stream.WriteByte((byte)(length + 1)); stream.WriteByte((byte)part); - stream.Write(data, i, length); + stream.Write(data[i..(i+length)]); part++; } } 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 19b0cb79b..2f1ef41ff 100644 --- a/src/FirebirdSql.Data.FirebirdClient/Client/Managed/FirebirdNetworkHandlingWrapper.cs +++ b/src/FirebirdSql.Data.FirebirdClient/Client/Managed/FirebirdNetworkHandlingWrapper.cs @@ -87,6 +87,33 @@ public int Read(byte[] buffer, int offset, int count) var dataLength = ReadFromInputBuffer(buffer, offset, count); return dataLength; } + + public int Read(Span buffer, int offset, int count) + { + if (_inputBuffer.Count < count) { + var readBuffer = _readBuffer; + int read; + try { + read = _dataProvider.Read(readBuffer, 0, readBuffer.Length); + } + catch (IOException) { + IOFailed = true; + throw; + } + if (read != 0) { + if (_decryptor != null) { + _decryptor.ProcessBytes(readBuffer, 0, read, readBuffer, 0); + } + if (_decompressor != null) { + read = HandleDecompression(readBuffer, read); + readBuffer = _compressionBuffer; + } + WriteToInputBuffer(readBuffer, read); + } + } + var dataLength = ReadFromInputBuffer(buffer, offset, count); + return dataLength; + } public async ValueTask ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken = default) { if (_inputBuffer.Count < count) @@ -120,6 +147,24 @@ public async ValueTask ReadAsync(byte[] buffer, int offset, int count, Canc return dataLength; } + public async ValueTask ReadAsync(Memory buffer, int offset, int count, CancellationToken cancellationToken = default) + { + var rented = new byte[count]; + try + { + var read = await ReadAsync(rented, 0, count, cancellationToken).ConfigureAwait(false); + rented.AsSpan(0, read).CopyTo(buffer.Span.Slice(offset, read)); + return read; + } + finally { } + } + + public void Write(ReadOnlySpan buffer) + { + foreach (var b in buffer) + _outputBuffer.Enqueue(b); + } + public void Write(byte[] buffer, int offset, int count) { for (var i = offset; i < count; i++) @@ -132,6 +177,14 @@ public ValueTask WriteAsync(byte[] buffer, int offset, int count, CancellationTo return ValueTask.CompletedTask; } + public ValueTask WriteAsync(ReadOnlyMemory buffer, int offset, int count, CancellationToken cancellationToken = default) + { + var span = buffer.Span.Slice(offset, count); + foreach (var b in span) + _outputBuffer.Enqueue(b); + return ValueTask2.CompletedTask; + } + public void Flush() { var buffer = _outputBuffer.ToArray(); @@ -206,6 +259,15 @@ int ReadFromInputBuffer(byte[] buffer, int offset, int count) return read; } + int ReadFromInputBuffer(Span buffer, int offset, int count) + { + var read = Math.Min(count, _inputBuffer.Count); + for (var i = 0; i < read; i++) { + buffer[offset+i] = _inputBuffer.Dequeue(); + } + return read; + } + void WriteToInputBuffer(byte[] data, int count) { for (var i = 0; i < count; i++) 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/Srp/SrpClientBase.cs b/src/FirebirdSql.Data.FirebirdClient/Client/Managed/Srp/SrpClientBase.cs index b84ab5a17..78d4d2b21 100644 --- a/src/FirebirdSql.Data.FirebirdClient/Client/Managed/Srp/SrpClientBase.cs +++ b/src/FirebirdSql.Data.FirebirdClient/Client/Managed/Srp/SrpClientBase.cs @@ -16,6 +16,7 @@ //$Authors = Hajime Nakagami (nakagami@gmail.com), Jiri Cincura (jiri@cincura.net) using System; +using System.Buffers; using System.Globalization; using System.Linq; using System.Numerics; @@ -36,6 +37,7 @@ abstract class SrpClientBase private const int SRP_KEY_SIZE = 128; private const int SRP_SALT_SIZE = 32; + private const int STACKALLOC_LIMIT = 512; private static readonly BigInteger N = BigInteger.Parse("00E67D2E994B2F900C3F41F08F5BB2627ED0D49EE1FE767A52EFCD565CD6E768812C3E1E9CE8F0A8BEA6CB13CD29DDEBF7A96D4A93B55D488DF099A15C89DCB0640738EB2CBDD9A8F7BAB561AB1B0DC1C6CDABF303264A08D1BCA932D1F1EE428B619D970F342ABA9A65793B8B2F041AE5364350C16F735F56ECBCA87BD57B29E7", NumberStyles.HexNumber); private static readonly BigInteger g = new BigInteger(2); private static readonly BigInteger k = BigInteger.Parse("1277432915985975349439481660349303019122249719989"); @@ -95,7 +97,7 @@ public byte[] ClientProof(string user, string password, byte[] authData) return (B, b); } - public byte[] GetServerSessionKey(string user, string password, byte[] salt, BigInteger A, BigInteger B, BigInteger b) + public static byte[] GetServerSessionKey(string user, string password, byte[] salt, BigInteger A, BigInteger B, BigInteger b) { var u = GetScramble(A, B); var v = BigInteger.ModPow(g, GetUserHash(user, password, salt), N); @@ -105,12 +107,12 @@ public byte[] GetServerSessionKey(string user, string password, byte[] salt, Big return ComputeSHA1Hash(BigIntegerToByteArray(sessionSecret)); } - public byte[] GetSalt() + public static byte[] GetSalt() { return GetRandomBytes(SRP_SALT_SIZE); } - private BigInteger GetSecret() + private static BigInteger GetSecret() { return new BigInteger(GetRandomBytes(SRP_KEY_SIZE / 8).Concat(new byte[] { 0 }).ToArray()); } @@ -146,12 +148,41 @@ private static BigInteger GetUserHash(string user, string password, byte[] salt) private static BigInteger BigIntegerFromByteArray(byte[] b) { - return new BigInteger(b.AsEnumerable().Reverse().Concat(new byte[] { 0 }).ToArray()); + Span bytes = [0, ..b]; + bytes.Reverse(); + return new BigInteger(bytes); } private static byte[] BigIntegerToByteArray(BigInteger n) { - return n.ToByteArray().AsEnumerable().Reverse().SkipWhile((e, i) => i == 0 && e == 0).ToArray(); + var byteCount = n.GetByteCount(); + byte[] rented = null; + Span bytes = byteCount > STACKALLOC_LIMIT + ? (rented = ArrayPool.Shared.Rent(byteCount)).AsSpan(0, byteCount) + : stackalloc byte[byteCount]; + if (!n.TryWriteBytes(bytes, out _)) + { + if (rented != null) + { + ArrayPool.Shared.Return(rented, clearArray: true); + } + throw new InvalidOperationException("Failed to write BigInteger bytes."); + } + bytes.Reverse(); + byte[] result; + if (bytes[0] == 0) + { + result = bytes[1..].ToArray(); + } + else + { + result = bytes.ToArray(); + } + if (rented != null) + { + ArrayPool.Shared.Return(rented, clearArray: true); + } + return result; } private static byte[] ComputeSHA1Hash(params byte[][] ba) diff --git a/src/FirebirdSql.Data.FirebirdClient/Client/Managed/Version10/GdsArray.cs b/src/FirebirdSql.Data.FirebirdClient/Client/Managed/Version10/GdsArray.cs index c5ebe7d7f..e22b9c43d 100644 --- a/src/FirebirdSql.Data.FirebirdClient/Client/Managed/Version10/GdsArray.cs +++ b/src/FirebirdSql.Data.FirebirdClient/Client/Managed/Version10/GdsArray.cs @@ -16,6 +16,7 @@ //$Authors = Carlos Guzman Alvarez, Jiri Cincura (jiri@cincura.net) using System; +using System.Buffers; using System.Globalization; using System.IO; using System.Reflection; @@ -28,6 +29,11 @@ namespace FirebirdSql.Data.Client.Managed.Version10; internal sealed class GdsArray : ArrayBase { const long ArrayHandle = 0; + const int StackallocThreshold = 512; + + private static readonly byte[] zeroIntBuf = TypeEncoder.EncodeInt32(0); + private static readonly byte[] bufOpGetSlice = TypeEncoder.EncodeInt32(IscCodes.op_get_slice); + private static readonly byte[] bufOpPutSlice = TypeEncoder.EncodeInt32(IscCodes.op_put_slice); #region Fields @@ -87,13 +93,13 @@ public override byte[] GetSlice(int sliceLength) { var sdl = GenerateSDL(Descriptor); - _database.Xdr.Write(IscCodes.op_get_slice); + _database.Xdr.WriteBytes(bufOpGetSlice); _database.Xdr.Write(_transaction.Handle); _database.Xdr.Write(_handle); _database.Xdr.Write(sliceLength); _database.Xdr.WriteBuffer(sdl); _database.Xdr.Write(string.Empty); - _database.Xdr.Write(0); + _database.Xdr.WriteBytes(zeroIntBuf); _database.Xdr.Flush(); return ReceiveSliceResponse(Descriptor); @@ -109,13 +115,13 @@ public override async ValueTask GetSliceAsync(int sliceLength, Cancellat { var sdl = GenerateSDL(Descriptor); - await _database.Xdr.WriteAsync(IscCodes.op_get_slice, cancellationToken).ConfigureAwait(false); + await _database.Xdr.WriteBytesAsync(bufOpGetSlice, 4, cancellationToken).ConfigureAwait(false); await _database.Xdr.WriteAsync(_transaction.Handle, cancellationToken).ConfigureAwait(false); await _database.Xdr.WriteAsync(_handle, cancellationToken).ConfigureAwait(false); await _database.Xdr.WriteAsync(sliceLength, cancellationToken).ConfigureAwait(false); await _database.Xdr.WriteBufferAsync(sdl, cancellationToken).ConfigureAwait(false); await _database.Xdr.WriteAsync(string.Empty, cancellationToken).ConfigureAwait(false); - await _database.Xdr.WriteAsync(0, cancellationToken).ConfigureAwait(false); + await _database.Xdr.WriteBytesAsync(zeroIntBuf, 4, cancellationToken).ConfigureAwait(false); await _database.Xdr.FlushAsync(cancellationToken).ConfigureAwait(false); return await ReceiveSliceResponseAsync(Descriptor, cancellationToken).ConfigureAwait(false); @@ -133,7 +139,7 @@ public override void PutSlice(Array sourceArray, int sliceLength) var sdl = GenerateSDL(Descriptor); var slice = EncodeSliceArray(sourceArray); - _database.Xdr.Write(IscCodes.op_put_slice); + _database.Xdr.WriteBytes(bufOpPutSlice); _database.Xdr.Write(_transaction.Handle); _database.Xdr.Write(ArrayHandle); _database.Xdr.Write(sliceLength); @@ -159,7 +165,7 @@ public override async ValueTask PutSliceAsync(Array sourceArray, int sliceLength var sdl = GenerateSDL(Descriptor); var slice = await EncodeSliceArrayAsync(sourceArray, cancellationToken).ConfigureAwait(false); - await _database.Xdr.WriteAsync(IscCodes.op_put_slice, cancellationToken).ConfigureAwait(false); + await _database.Xdr.WriteBytesAsync(bufOpPutSlice, 4, cancellationToken).ConfigureAwait(false); await _database.Xdr.WriteAsync(_transaction.Handle, cancellationToken).ConfigureAwait(false); await _database.Xdr.WriteAsync(ArrayHandle, cancellationToken).ConfigureAwait(false); await _database.Xdr.WriteAsync(sliceLength, cancellationToken).ConfigureAwait(false); @@ -185,14 +191,10 @@ public override async ValueTask PutSliceAsync(Array sourceArray, int sliceLength protected override Array DecodeSlice(byte[] slice) { - var dbType = DbDataType.Array; - Array sliceData = null; - Array tempData = null; - var systemType = GetSystemType(); + var systemType = GetSystemType(); var lengths = new int[Descriptor.Dimensions]; var lowerBounds = new int[Descriptor.Dimensions]; - var type = 0; - var index = 0; + var index = 0; for (var i = 0; i < Descriptor.Dimensions; i++) { @@ -205,13 +207,13 @@ protected override Array DecodeSlice(byte[] slice) } } - sliceData = Array.CreateInstance(systemType, lengths, lowerBounds); - tempData = Array.CreateInstance(systemType, sliceData.Length); + Array sliceData = Array.CreateInstance(systemType, lengths, lowerBounds); + Array tempData = Array.CreateInstance(systemType, sliceData.Length); - type = TypeHelper.GetSqlTypeFromBlrType(Descriptor.DataType); - dbType = TypeHelper.GetDbDataTypeFromBlrType(Descriptor.DataType, 0, Descriptor.Scale); + int type = TypeHelper.GetSqlTypeFromBlrType(Descriptor.DataType); + DbDataType dbType = TypeHelper.GetDbDataTypeFromBlrType(Descriptor.DataType, 0, Descriptor.Scale); - using (var ms = new MemoryStream(slice)) + using (var ms = new MemoryStream(slice)) { var xdr = new XdrReaderWriter(new DataProviderStreamWrapper(ms), _database.Charset); while (ms.Position < ms.Length) @@ -282,14 +284,10 @@ protected override Array DecodeSlice(byte[] slice) } protected override async ValueTask DecodeSliceAsync(byte[] slice, CancellationToken cancellationToken = default) { - var dbType = DbDataType.Array; - Array sliceData = null; - Array tempData = null; - var systemType = GetSystemType(); + var systemType = GetSystemType(); var lengths = new int[Descriptor.Dimensions]; var lowerBounds = new int[Descriptor.Dimensions]; - var type = 0; - var index = 0; + var index = 0; for (var i = 0; i < Descriptor.Dimensions; i++) { @@ -302,13 +300,13 @@ protected override async ValueTask DecodeSliceAsync(byte[] slice, Cancell } } - sliceData = Array.CreateInstance(systemType, lengths, lowerBounds); - tempData = Array.CreateInstance(systemType, sliceData.Length); + Array sliceData = Array.CreateInstance(systemType, lengths, lowerBounds); + Array tempData = Array.CreateInstance(systemType, sliceData.Length); - type = TypeHelper.GetSqlTypeFromBlrType(Descriptor.DataType); - dbType = TypeHelper.GetDbDataTypeFromBlrType(Descriptor.DataType, 0, Descriptor.Scale); + int type = TypeHelper.GetSqlTypeFromBlrType(Descriptor.DataType); + DbDataType dbType = TypeHelper.GetDbDataTypeFromBlrType(Descriptor.DataType, 0, Descriptor.Scale); - using (var ms = new MemoryStream(slice)) + using (var ms = new MemoryStream(slice)) { var xdr = new XdrReaderWriter(new DataProviderStreamWrapper(ms), _database.Charset); while (ms.Position < ms.Length) @@ -423,8 +421,7 @@ private byte[] ReceiveSliceResponse(ArrayDesc desc) var xdr = new XdrReaderWriter(new DataProviderStreamWrapper(ms)); for (var i = 0; i < elements; i++) { - var buffer = _database.Xdr.ReadOpaque(_database.Xdr.ReadInt32()); - xdr.WriteBuffer(buffer, buffer.Length); + SliceInner(xdr); } xdr.Flush(); return ms.ToArray(); @@ -446,6 +443,22 @@ private byte[] ReceiveSliceResponse(ArrayDesc desc) throw IscException.ForIOException(ex); } } + + private void SliceInner(XdrReaderWriter xdr) + { + int len = _database.Xdr.ReadInt32(); + byte[] rented = null; + Span buffer = len > StackallocThreshold + ? (rented = ArrayPool.Shared.Rent(len)).AsSpan(0, len) + : stackalloc byte[len]; + _database.Xdr.ReadOpaque(buffer, len); + xdr.WriteBuffer(buffer); + if (rented != null) + { + ArrayPool.Shared.Return(rented); + } + } + private async ValueTask ReceiveSliceResponseAsync(ArrayDesc desc, CancellationToken cancellationToken = default) { try @@ -485,10 +498,16 @@ private async ValueTask ReceiveSliceResponseAsync(ArrayDesc desc, Cancel using (var ms = new MemoryStream()) { var xdr = new XdrReaderWriter(new DataProviderStreamWrapper(ms)); - for (var i = 0; i < elements; i++) - { - var buffer = await _database.Xdr.ReadOpaqueAsync(await _database.Xdr.ReadInt32Async(cancellationToken).ConfigureAwait(false), cancellationToken).ConfigureAwait(false); - await xdr.WriteBufferAsync(buffer, buffer.Length, cancellationToken).ConfigureAwait(false); + for(var i = 0; i < elements; i++) { + var elen = await _database.Xdr.ReadInt32Async(cancellationToken).ConfigureAwait(false); + var rented = System.Buffers.ArrayPool.Shared.Rent(elen); + try { + await _database.Xdr.ReadOpaqueAsync(rented.AsMemory(0, elen), elen, cancellationToken).ConfigureAwait(false); + await xdr.WriteBufferAsync(rented.AsMemory(0, elen), cancellationToken).ConfigureAwait(false); + } + finally { + System.Buffers.ArrayPool.Shared.Return(rented); + } } await xdr.FlushAsync(cancellationToken).ConfigureAwait(false); return ms.ToArray(); @@ -513,19 +532,16 @@ private async ValueTask ReceiveSliceResponseAsync(ArrayDesc desc, Cancel private byte[] EncodeSliceArray(Array sourceArray) { - var dbType = DbDataType.Array; - var charset = _database.Charset; + var charset = _database.Charset; var subType = (Descriptor.Scale < 0) ? 2 : 0; - var type = 0; - - using (var ms = new MemoryStream()) + using (var ms = new MemoryStream()) { var xdr = new XdrReaderWriter(new DataProviderStreamWrapper(ms), _database.Charset); - type = TypeHelper.GetSqlTypeFromBlrType(Descriptor.DataType); - dbType = TypeHelper.GetDbDataTypeFromBlrType(Descriptor.DataType, subType, Descriptor.Scale); + int type = TypeHelper.GetSqlTypeFromBlrType(Descriptor.DataType); + DbDataType dbType = TypeHelper.GetDbDataTypeFromBlrType(Descriptor.DataType, subType, Descriptor.Scale); - foreach (var source in sourceArray) + foreach (var source in sourceArray) { switch (dbType) { @@ -586,19 +602,16 @@ private byte[] EncodeSliceArray(Array sourceArray) } private async ValueTask EncodeSliceArrayAsync(Array sourceArray, CancellationToken cancellationToken = default) { - var dbType = DbDataType.Array; - var charset = _database.Charset; + var charset = _database.Charset; var subType = (Descriptor.Scale < 0) ? 2 : 0; - var type = 0; - - using (var ms = new MemoryStream()) + using (var ms = new MemoryStream()) { var xdr = new XdrReaderWriter(new DataProviderStreamWrapper(ms), _database.Charset); - type = TypeHelper.GetSqlTypeFromBlrType(Descriptor.DataType); - dbType = TypeHelper.GetDbDataTypeFromBlrType(Descriptor.DataType, subType, Descriptor.Scale); + int type = TypeHelper.GetSqlTypeFromBlrType(Descriptor.DataType); + DbDataType dbType = TypeHelper.GetDbDataTypeFromBlrType(Descriptor.DataType, subType, Descriptor.Scale); - foreach (var source in sourceArray) + foreach (var source in sourceArray) { switch (dbType) { @@ -658,7 +671,7 @@ private async ValueTask EncodeSliceArrayAsync(Array sourceArray, Cancell } } - private byte[] GenerateSDL(ArrayDesc desc) + private static byte[] GenerateSDL(ArrayDesc desc) { int n; int from; @@ -744,7 +757,7 @@ private byte[] GenerateSDL(ArrayDesc desc) return ((MemoryStream)sdl.BaseStream).ToArray(); } - private void Stuff(BinaryWriter sdl, short count, params object[] args) + private static void Stuff(BinaryWriter sdl, short count, params object[] args) { for (var i = 0; i < count; i++) { @@ -752,27 +765,27 @@ private void Stuff(BinaryWriter sdl, short count, params object[] args) } } - private void Stuff(BinaryWriter sdl, byte[] args) + private static void Stuff(BinaryWriter sdl, byte[] args) { sdl.Write(args); } - private void StuffSdl(BinaryWriter sdl, byte sdl_byte) + private static void StuffSdl(BinaryWriter sdl, byte sdl_byte) { Stuff(sdl, 1, sdl_byte); } - private void StuffWord(BinaryWriter sdl, short word) + private static void StuffWord(BinaryWriter sdl, short word) { Stuff(sdl, BitConverter.GetBytes(word)); } - private void StuffLong(BinaryWriter sdl, int word) + private static void StuffLong(BinaryWriter sdl, int word) { Stuff(sdl, BitConverter.GetBytes(word)); } - private void StuffLiteral(BinaryWriter sdl, int literal) + private static void StuffLiteral(BinaryWriter sdl, int literal) { if (literal >= -128 && literal <= 127) { @@ -793,7 +806,7 @@ private void StuffLiteral(BinaryWriter sdl, int literal) StuffLong(sdl, literal); } - private void StuffString(BinaryWriter sdl, int constant, string value) + private static void StuffString(BinaryWriter sdl, int constant, string value) { StuffSdl(sdl, (byte)constant); StuffSdl(sdl, (byte)value.Length); diff --git a/src/FirebirdSql.Data.FirebirdClient/Client/Managed/Version10/GdsBlob.cs b/src/FirebirdSql.Data.FirebirdClient/Client/Managed/Version10/GdsBlob.cs index 7d6785144..dfc5d66c9 100644 --- a/src/FirebirdSql.Data.FirebirdClient/Client/Managed/Version10/GdsBlob.cs +++ b/src/FirebirdSql.Data.FirebirdClient/Client/Managed/Version10/GdsBlob.cs @@ -28,6 +28,12 @@ internal sealed class GdsBlob : BlobBase const int DataSegment = 0; const int SeekMode = 0; + private static readonly byte[] zeroIntBuf = TypeEncoder.EncodeInt32(0); + private static readonly byte[] bufOpInfoBlob = TypeEncoder.EncodeInt32(IscCodes.op_info_blob); + private static readonly byte[] bufOpGetSegment = TypeEncoder.EncodeInt32(IscCodes.op_get_segment); + private static readonly byte[] bufOpBatchSegments = TypeEncoder.EncodeInt32(IscCodes.op_batch_segments); + private static readonly byte[] bufOpSeekBlob = TypeEncoder.EncodeInt32(IscCodes.op_seek_blob); + #region Fields private readonly GdsDatabase _database; @@ -125,12 +131,11 @@ public override int GetLength() Open(); var bufferLength = 20; - var buffer = new byte[bufferLength]; - _database.Xdr.Write(IscCodes.op_info_blob); + _database.Xdr.WriteBytes(bufOpInfoBlob); _database.Xdr.Write(_blobHandle); - _database.Xdr.Write(0); - _database.Xdr.WriteBuffer(new byte[] { IscCodes.isc_info_blob_total_length }, 1); + _database.Xdr.WriteBytes(zeroIntBuf); + _database.Xdr.WriteBuffer([IscCodes.isc_info_blob_total_length], 1); _database.Xdr.Write(bufferLength); _database.Xdr.Flush(); @@ -144,7 +149,7 @@ public override int GetLength() responseLength = response.Data.Length; } - Buffer.BlockCopy(response.Data, 0, buffer, 0, responseLength); + var buffer = response.Data.AsSpan()[..responseLength]; var length = IscHelper.VaxInteger(buffer, 1, 2); var size = IscHelper.VaxInteger(buffer, 3, (int)length); @@ -165,12 +170,11 @@ public override async ValueTask GetLengthAsync(CancellationToken cancellati await OpenAsync(cancellationToken).ConfigureAwait(false); var bufferLength = 20; - var buffer = new byte[bufferLength]; - await _database.Xdr.WriteAsync(IscCodes.op_info_blob, cancellationToken).ConfigureAwait(false); + await _database.Xdr.WriteBytesAsync(bufOpInfoBlob, 4, cancellationToken).ConfigureAwait(false); await _database.Xdr.WriteAsync(_blobHandle, cancellationToken).ConfigureAwait(false); - await _database.Xdr.WriteAsync(0, cancellationToken).ConfigureAwait(false); - await _database.Xdr.WriteBufferAsync(new byte[] { IscCodes.isc_info_blob_total_length }, 1, cancellationToken).ConfigureAwait(false); + await _database.Xdr.WriteBytesAsync(zeroIntBuf, 4, cancellationToken).ConfigureAwait(false); + await _database.Xdr.WriteBufferAsync([IscCodes.isc_info_blob_total_length], 1, cancellationToken).ConfigureAwait(false); await _database.Xdr.WriteAsync(bufferLength, cancellationToken).ConfigureAwait(false); await _database.Xdr.FlushAsync(cancellationToken).ConfigureAwait(false); @@ -184,7 +188,7 @@ public override async ValueTask GetLengthAsync(CancellationToken cancellati responseLength = response.Data.Length; } - Buffer.BlockCopy(response.Data, 0, buffer, 0, responseLength); + var buffer = response.Data.AsSpan()[..responseLength]; var length = IscHelper.VaxInteger(buffer, 1, 2); var size = IscHelper.VaxInteger(buffer, 3, (int)length); @@ -203,10 +207,10 @@ public override void GetSegment(Stream stream) try { - _database.Xdr.Write(IscCodes.op_get_segment); + _database.Xdr.WriteBytes(bufOpGetSegment); _database.Xdr.Write(_blobHandle); _database.Xdr.Write(requested < short.MaxValue - 12 ? requested : short.MaxValue - 12); - _database.Xdr.Write(DataSegment); + _database.Xdr.WriteBytes(zeroIntBuf); _database.Xdr.Flush(); var response = (GenericResponse)_database.ReadResponse(); @@ -221,7 +225,7 @@ public override void GetSegment(Stream stream) RblAddValue(IscCodes.RBL_eof_pending); } - var buffer = response.Data; + var buffer = response.Data.AsSpan(); if (buffer.Length == 0) { @@ -233,11 +237,11 @@ public override void GetSegment(Stream stream) var srcpos = 0; while (srcpos < buffer.Length) - { + { len = (int)IscHelper.VaxInteger(buffer, srcpos, 2); srcpos += 2; - stream.Write(buffer, srcpos, len); + stream.Write(buffer.Slice(srcpos, len)); srcpos += len; } } @@ -252,10 +256,10 @@ public override async ValueTask GetSegmentAsync(Stream stream, CancellationToken try { - await _database.Xdr.WriteAsync(IscCodes.op_get_segment, cancellationToken).ConfigureAwait(false); + await _database.Xdr.WriteBytesAsync(bufOpGetSegment, 4, cancellationToken).ConfigureAwait(false); await _database.Xdr.WriteAsync(_blobHandle, cancellationToken).ConfigureAwait(false); await _database.Xdr.WriteAsync(requested < short.MaxValue - 12 ? requested : short.MaxValue - 12, cancellationToken).ConfigureAwait(false); - await _database.Xdr.WriteAsync(DataSegment, cancellationToken).ConfigureAwait(false); + await _database.Xdr.WriteBytesAsync(zeroIntBuf, 4, cancellationToken).ConfigureAwait(false); await _database.Xdr.FlushAsync(cancellationToken).ConfigureAwait(false); var response = (GenericResponse)await _database.ReadResponseAsync(cancellationToken).ConfigureAwait(false); @@ -270,7 +274,7 @@ public override async ValueTask GetSegmentAsync(Stream stream, CancellationToken RblAddValue(IscCodes.RBL_eof_pending); } - var buffer = response.Data; + var buffer = response.Data.AsSpan(); if (buffer.Length == 0) { @@ -282,11 +286,11 @@ public override async ValueTask GetSegmentAsync(Stream stream, CancellationToken var srcpos = 0; while (srcpos < buffer.Length) - { + { len = (int)IscHelper.VaxInteger(buffer, srcpos, 2); srcpos += 2; - stream.Write(buffer, srcpos, len); + stream.Write(buffer.Slice(srcpos, len)); srcpos += len; } } @@ -320,7 +324,7 @@ public override byte[] GetSegment() RblAddValue(IscCodes.RBL_eof_pending); } - var buffer = response.Data; + var buffer = response.Data.AsSpan(); if (buffer.Length == 0) { @@ -333,11 +337,11 @@ public override byte[] GetSegment() var tmp = new byte[requested * 2]; while (posInInput < buffer.Length) - { + { var len = (int)IscHelper.VaxInteger(buffer, posInInput, 2); posInInput += 2; - Array.Copy(buffer, posInInput, tmp, posInOutput, len); + buffer.Slice(posInInput, len).CopyTo(tmp.AsSpan(posInOutput, len)); posInOutput += len; posInInput += len; } @@ -358,7 +362,7 @@ public override async ValueTask GetSegmentAsync(CancellationToken cancel try { - await _database.Xdr.WriteAsync(IscCodes.op_get_segment, cancellationToken).ConfigureAwait(false); + await _database.Xdr.WriteBytesAsync(bufOpGetSegment, 4, cancellationToken).ConfigureAwait(false); await _database.Xdr.WriteAsync(_blobHandle, cancellationToken).ConfigureAwait(false); await _database.Xdr.WriteAsync(requested < short.MaxValue - 12 ? requested : short.MaxValue - 12, cancellationToken).ConfigureAwait(false); await _database.Xdr.WriteAsync(DataSegment, cancellationToken).ConfigureAwait(false); @@ -376,7 +380,7 @@ public override async ValueTask GetSegmentAsync(CancellationToken cancel RblAddValue(IscCodes.RBL_eof_pending); } - var buffer = response.Data; + var buffer = response.Data.AsSpan(); if (buffer.Length == 0) { @@ -389,11 +393,11 @@ public override async ValueTask GetSegmentAsync(CancellationToken cancel var tmp = new byte[requested * 2]; while (posInInput < buffer.Length) - { + { var len = (int)IscHelper.VaxInteger(buffer, posInInput, 2); posInInput += 2; - Array.Copy(buffer, posInInput, tmp, posInOutput, len); + buffer.Slice(posInInput, len).CopyTo(tmp.AsSpan(posInOutput, len)); posInOutput += len; posInInput += len; } @@ -413,7 +417,7 @@ public override void PutSegment(byte[] buffer) { try { - _database.Xdr.Write(IscCodes.op_batch_segments); + _database.Xdr.WriteBytes(bufOpBatchSegments); _database.Xdr.Write(_blobHandle); _database.Xdr.WriteBlobBuffer(buffer); _database.Xdr.Flush(); @@ -429,7 +433,7 @@ public override async ValueTask PutSegmentAsync(byte[] buffer, CancellationToken { try { - await _database.Xdr.WriteAsync(IscCodes.op_batch_segments, cancellationToken).ConfigureAwait(false); + await _database.Xdr.WriteBytesAsync(bufOpBatchSegments, 4, cancellationToken).ConfigureAwait(false); await _database.Xdr.WriteAsync(_blobHandle, cancellationToken).ConfigureAwait(false); await _database.Xdr.WriteBlobBufferAsync(buffer, cancellationToken).ConfigureAwait(false); await _database.Xdr.FlushAsync(cancellationToken).ConfigureAwait(false); @@ -446,7 +450,7 @@ public override void Seek(int offset, int seekMode) { try { - _database.Xdr.Write(IscCodes.op_seek_blob); + _database.Xdr.WriteBytes(bufOpSeekBlob); _database.Xdr.Write(_blobHandle); _database.Xdr.Write(seekMode); _database.Xdr.Write(offset); @@ -465,7 +469,7 @@ public override async ValueTask SeekAsync(int offset, int seekMode, Cancellation { try { - await _database.Xdr.WriteAsync(IscCodes.op_seek_blob, cancellationToken).ConfigureAwait(false); + await _database.Xdr.WriteBytesAsync(bufOpSeekBlob, 4, cancellationToken).ConfigureAwait(false); await _database.Xdr.WriteAsync(_blobHandle, cancellationToken).ConfigureAwait(false); await _database.Xdr.WriteAsync(seekMode, cancellationToken).ConfigureAwait(false); await _database.Xdr.WriteAsync(offset, cancellationToken).ConfigureAwait(false); diff --git a/src/FirebirdSql.Data.FirebirdClient/Client/Managed/Version10/GdsDatabase.cs b/src/FirebirdSql.Data.FirebirdClient/Client/Managed/Version10/GdsDatabase.cs index 37f134aa1..fa1aefcb6 100644 --- a/src/FirebirdSql.Data.FirebirdClient/Client/Managed/Version10/GdsDatabase.cs +++ b/src/FirebirdSql.Data.FirebirdClient/Client/Managed/Version10/GdsDatabase.cs @@ -16,6 +16,7 @@ //$Authors = Carlos Guzman Alvarez, Jiri Cincura (jiri@cincura.net) using System; +using System.Buffers; using System.Collections.Generic; using System.IO; using System.Net; @@ -31,8 +32,9 @@ internal class GdsDatabase : DatabaseBase protected const int PartnerIdentification = 0; protected const int AddressOfAstRoutine = 0; protected const int ArgumentToAstRoutine = 0; - protected internal const int DatabaseObjectId = 0; +protected internal const int DatabaseObjectId = 0; protected internal const int Incarnation = 0; + const int StackallocThreshold = 512; #region Fields @@ -71,9 +73,9 @@ public AuthBlock AuthBlock get { return _connection.AuthBlock; } } - #endregion + #endregion - #region Constructors + #region Constructors public GdsDatabase(GdsConnection connection) : base(connection.Charset, connection.PacketSize, connection.Dialect) @@ -82,11 +84,11 @@ public GdsDatabase(GdsConnection connection) _handle = -1; } - #endregion + #endregion - #region Attach/Detach Methods + #region Attach/Detach Methods - public override void Attach(DatabaseParameterBufferBase dpb, string database, byte[] cryptKey) + public override void Attach(DatabaseParameterBufferBase dpb, string database, byte[] cryptKey) { try { @@ -419,31 +421,38 @@ public virtual (int auxHandle, string ipAddress, int portNumber, int timeout) Co var auxHandle = Xdr.ReadInt32(); - var garbage1 = new byte[8]; + Span garbage1 = stackalloc byte[8]; Xdr.ReadBytes(garbage1, 8); var respLen = Xdr.ReadInt32(); respLen += respLen % 4; - var sin_family = new byte[2]; + Span sin_family = stackalloc byte[2]; Xdr.ReadBytes(sin_family, 2); respLen -= 2; - var sin_port = new byte[2]; + Span sin_port = stackalloc byte[2]; Xdr.ReadBytes(sin_port, 2); - var portNumber = (ushort)IPAddress.NetworkToHostOrder(BitConverter.ToInt16(sin_port, 0)); + var portNumber = (ushort)IPAddress.NetworkToHostOrder(BitConverter.ToInt16(sin_port)); respLen -= 2; // * The address returned by the server may be incorrect if it is behind a NAT box // * so we must use the address that was used to connect the main socket, not the // * address reported by the server. - var sin_addr = new byte[4]; + Span sin_addr = stackalloc byte[4]; Xdr.ReadBytes(sin_addr, 4); var ipAddress = _connection.IPAddress.ToString(); respLen -= 4; - var garbage2 = new byte[respLen]; + byte[] rented = null; + Span garbage2 = respLen > StackallocThreshold + ? (rented = ArrayPool.Shared.Rent(respLen)).AsSpan(0, respLen) + : stackalloc byte[respLen]; Xdr.ReadBytes(garbage2, respLen); + if (rented != null) + { + ArrayPool.Shared.Return(rented); + } Xdr.ReadStatusVector(); @@ -904,7 +913,7 @@ private void DatabaseInfo(byte[] items, byte[] buffer, int bufferLength) responseLength = response.Data.Length; } - Buffer.BlockCopy(response.Data, 0, buffer, 0, responseLength); + response.Data.AsSpan().Slice(0, responseLength).CopyTo(buffer.AsSpan(0, responseLength)); } catch (IOException ex) { @@ -932,7 +941,7 @@ private async ValueTask DatabaseInfoAsync(byte[] items, byte[] buffer, int buffe responseLength = response.Data.Length; } - Buffer.BlockCopy(response.Data, 0, buffer, 0, responseLength); + response.Data.AsSpan().Slice(0, responseLength).CopyTo(buffer.AsSpan(0, responseLength)); } catch (IOException ex) { diff --git a/src/FirebirdSql.Data.FirebirdClient/Client/Managed/Version10/GdsServiceManager.cs b/src/FirebirdSql.Data.FirebirdClient/Client/Managed/Version10/GdsServiceManager.cs index 20fb3b9cb..878ae2fa2 100644 --- a/src/FirebirdSql.Data.FirebirdClient/Client/Managed/Version10/GdsServiceManager.cs +++ b/src/FirebirdSql.Data.FirebirdClient/Client/Managed/Version10/GdsServiceManager.cs @@ -25,6 +25,9 @@ namespace FirebirdSql.Data.Client.Managed.Version10; internal class GdsServiceManager : ServiceManagerBase { + private static readonly byte[] zeroIntBuf = TypeEncoder.EncodeInt32(0); + private static readonly byte[] bufOpServiceStart = TypeEncoder.EncodeInt32(IscCodes.op_service_start); + #region Fields private GdsConnection _connection; @@ -185,9 +188,9 @@ public override void Start(ServiceParameterBufferBase spb) { try { - _database.Xdr.Write(IscCodes.op_service_start); + _database.Xdr.WriteBytes(bufOpServiceStart); _database.Xdr.Write(Handle); - _database.Xdr.Write(0); + _database.Xdr.WriteBytes(zeroIntBuf); _database.Xdr.WriteBuffer(spb.ToArray(), spb.Length); _database.Xdr.Flush(); @@ -209,9 +212,9 @@ public override async ValueTask StartAsync(ServiceParameterBufferBase spb, Cance { try { - await _database.Xdr.WriteAsync(IscCodes.op_service_start, cancellationToken).ConfigureAwait(false); + await _database.Xdr.WriteBytesAsync(bufOpServiceStart, 4, cancellationToken).ConfigureAwait(false); await _database.Xdr.WriteAsync(Handle, cancellationToken).ConfigureAwait(false); - await _database.Xdr.WriteAsync(0, cancellationToken).ConfigureAwait(false); + await _database.Xdr.WriteBytesAsync(zeroIntBuf, 4, cancellationToken).ConfigureAwait(false); await _database.Xdr.WriteBufferAsync(spb.ToArray(), spb.Length, cancellationToken).ConfigureAwait(false); await _database.Xdr.FlushAsync(cancellationToken).ConfigureAwait(false); @@ -252,7 +255,7 @@ public override void Query(ServiceParameterBufferBase spb, int requestLength, by responseLength = response.Data.Length; } - Buffer.BlockCopy(response.Data, 0, buffer, 0, responseLength); + response.Data.AsSpan().Slice(0, responseLength).CopyTo(buffer.AsSpan(0, responseLength)); } catch (IOException ex) { @@ -281,7 +284,7 @@ public override async ValueTask QueryAsync(ServiceParameterBufferBase spb, int r responseLength = response.Data.Length; } - Buffer.BlockCopy(response.Data, 0, buffer, 0, responseLength); + response.Data.AsSpan().Slice(0, responseLength).CopyTo(buffer.AsSpan(0, responseLength)); } catch (IOException ex) { diff --git a/src/FirebirdSql.Data.FirebirdClient/Client/Managed/Version10/GdsStatement.cs b/src/FirebirdSql.Data.FirebirdClient/Client/Managed/Version10/GdsStatement.cs index 54b5698cc..200ad6f70 100644 --- a/src/FirebirdSql.Data.FirebirdClient/Client/Managed/Version10/GdsStatement.cs +++ b/src/FirebirdSql.Data.FirebirdClient/Client/Managed/Version10/GdsStatement.cs @@ -398,10 +398,10 @@ public override DbValue[] Fetch() { try { - _database.Xdr.Write(IscCodes.op_fetch); + _database.Xdr.WriteBytes(bufOpFetch); _database.Xdr.Write(_handle); _database.Xdr.WriteBuffer(_fields.ToBlr().Data); - _database.Xdr.Write(0); // p_sqldata_message_number + _database.Xdr.WriteBytes(zeroIntBuf); // p_sqldata_message_number _database.Xdr.Write(_fetchSize); // p_sqldata_messages _database.Xdr.Flush(); @@ -479,10 +479,10 @@ public override async ValueTask FetchAsync(CancellationToken cancella { try { - await _database.Xdr.WriteAsync(IscCodes.op_fetch, cancellationToken).ConfigureAwait(false); + await _database.Xdr.WriteBytesAsync(bufOpFetch, 4, cancellationToken).ConfigureAwait(false); await _database.Xdr.WriteAsync(_handle, cancellationToken).ConfigureAwait(false); await _database.Xdr.WriteBufferAsync(_fields.ToBlr().Data, cancellationToken).ConfigureAwait(false); - await _database.Xdr.WriteAsync(0, cancellationToken).ConfigureAwait(false); // p_sqldata_message_number + await _database.Xdr.WriteBytesAsync(zeroIntBuf, 4, cancellationToken).ConfigureAwait(false); // p_sqldata_message_number await _database.Xdr.WriteAsync(_fetchSize, cancellationToken).ConfigureAwait(false); // p_sqldata_messages await _database.Xdr.FlushAsync(cancellationToken).ConfigureAwait(false); @@ -546,37 +546,51 @@ public override async ValueTask FetchAsync(CancellationToken cancella #region op_prepare methods protected void SendPrepareToBuffer(string commandText) { - _database.Xdr.Write(IscCodes.op_prepare_statement); + _database.Xdr.WriteBytes(bufOpPrepare); _database.Xdr.Write(_transaction.Handle); _database.Xdr.Write(_handle); _database.Xdr.Write((int)_database.Dialect); _database.Xdr.Write(commandText); _database.Xdr.WriteBuffer(DescribeInfoAndBindInfoItems, DescribeInfoAndBindInfoItems.Length); - _database.Xdr.Write(IscCodes.PREPARE_INFO_BUFFER_SIZE); + _database.Xdr.WriteBytes(bufPrepareInfoSize); } protected async ValueTask SendPrepareToBufferAsync(string commandText, CancellationToken cancellationToken = default) { - await _database.Xdr.WriteAsync(IscCodes.op_prepare_statement, cancellationToken).ConfigureAwait(false); + await _database.Xdr.WriteBytesAsync(bufOpPrepare, 4, cancellationToken).ConfigureAwait(false); await _database.Xdr.WriteAsync(_transaction.Handle, cancellationToken).ConfigureAwait(false); await _database.Xdr.WriteAsync(_handle, cancellationToken).ConfigureAwait(false); await _database.Xdr.WriteAsync((int)_database.Dialect, cancellationToken).ConfigureAwait(false); await _database.Xdr.WriteAsync(commandText, cancellationToken).ConfigureAwait(false); await _database.Xdr.WriteBufferAsync(DescribeInfoAndBindInfoItems, DescribeInfoAndBindInfoItems.Length, cancellationToken).ConfigureAwait(false); - await _database.Xdr.WriteAsync(IscCodes.PREPARE_INFO_BUFFER_SIZE, cancellationToken).ConfigureAwait(false); + await _database.Xdr.WriteBytesAsync(bufPrepareInfoSize, 4, cancellationToken).ConfigureAwait(false); } protected void ProcessPrepareResponse(GenericResponse response) { - var descriptors = ParseSqlInfo(response.Data, DescribeInfoAndBindInfoItems, new Descriptor[] { null, null }); + var info = response.Data.AsSpan(); + var descriptors = ParseSqlInfoSpan(info, DescribeInfoAndBindInfoItems, new Descriptor[] { null, null }); _fields = descriptors[0]; _parameters = descriptors[1]; } + protected async ValueTask ProcessPrepareResponseAsync(GenericResponse response, CancellationToken cancellationToken = default) { - var descriptors = await ParseSqlInfoAsync(response.Data, DescribeInfoAndBindInfoItems, new Descriptor[] { null, null }, cancellationToken).ConfigureAwait(false); + var info = response.Data; + var descriptors = await ParseSqlInfoSpanAsync(info, DescribeInfoAndBindInfoItems.AsMemory(), new Descriptor[] { null, null }, cancellationToken).ConfigureAwait(false); _fields = descriptors[0]; _parameters = descriptors[1]; } + + // Span-based parsing to avoid intermediate arrays when possible + private Descriptor[] ParseSqlInfoSpan(ReadOnlySpan info, ReadOnlySpan items, Descriptor[] rowDescs) + { + return ParseTruncSqlInfoSpan(info, items, rowDescs); + } + + private ValueTask ParseSqlInfoSpanAsync(ReadOnlyMemory info, ReadOnlyMemory items, Descriptor[] rowDescs, CancellationToken cancellationToken) + { + return ParseTruncSqlInfoSpanAsync(info, items, rowDescs, cancellationToken); + } #endregion #region op_info_sql methods @@ -618,30 +632,31 @@ protected async ValueTask DoInfoSqlPacketAsync(byte[] items, int bufferLength, C protected void SendInfoSqlToBuffer(byte[] items, int bufferLength) { - _database.Xdr.Write(IscCodes.op_info_sql); + _database.Xdr.WriteBytes(bufOpInfoSql); _database.Xdr.Write(_handle); - _database.Xdr.Write(0); + _database.Xdr.WriteBytes(zeroIntBuf); _database.Xdr.WriteBuffer(items, items.Length); _database.Xdr.Write(bufferLength); } protected async ValueTask SendInfoSqlToBufferAsync(byte[] items, int bufferLength, CancellationToken cancellationToken = default) { - await _database.Xdr.WriteAsync(IscCodes.op_info_sql, cancellationToken).ConfigureAwait(false); + await _database.Xdr.WriteBytesAsync(bufOpInfoSql, 4, cancellationToken).ConfigureAwait(false); await _database.Xdr.WriteAsync(_handle, cancellationToken).ConfigureAwait(false); - await _database.Xdr.WriteAsync(0, cancellationToken).ConfigureAwait(false); + await _database.Xdr.WriteBytesAsync(zeroIntBuf, 4, cancellationToken).ConfigureAwait(false); await _database.Xdr.WriteBufferAsync(items, items.Length, cancellationToken).ConfigureAwait(false); await _database.Xdr.WriteAsync(bufferLength, cancellationToken).ConfigureAwait(false); } - protected byte[] ProcessInfoSqlResponse(GenericResponse response) + protected static byte[] ProcessInfoSqlResponse(GenericResponse response) { - Debug.Assert(response.Data != null && response.Data.Length > 0); + Debug.Assert(response.Data.Length > 0); - return response.Data; + return response.Data.ToArray(); } - protected ValueTask ProcessInfoSqlResponseAsync(GenericResponse response, CancellationToken cancellationToken = default) + + protected static ValueTask ProcessInfoSqlResponseAsync(GenericResponse response, CancellationToken cancellationToken = default) { - Debug.Assert(response.Data != null && response.Data.Length > 0); + Debug.Assert(response.Data.Length > 0); return ValueTask.FromResult(response.Data); } @@ -682,7 +697,7 @@ protected void DoFreePacket(int option) { try { - _database.Xdr.Write(IscCodes.op_free_statement); + _database.Xdr.WriteBytes(bufOpFreeStatement); _database.Xdr.Write(_handle); _database.Xdr.Write(option); _database.Xdr.Flush(); @@ -705,7 +720,7 @@ protected async ValueTask DoFreePacketAsync(int option, CancellationToken cancel { try { - await _database.Xdr.WriteAsync(IscCodes.op_free_statement, cancellationToken).ConfigureAwait(false); + await _database.Xdr.WriteBytesAsync(bufOpFreeStatement, 4, cancellationToken).ConfigureAwait(false); await _database.Xdr.WriteAsync(_handle, cancellationToken).ConfigureAwait(false); await _database.Xdr.WriteAsync(option, cancellationToken).ConfigureAwait(false); await _database.Xdr.FlushAsync(cancellationToken).ConfigureAwait(false); @@ -725,9 +740,9 @@ protected async ValueTask DoFreePacketAsync(int option, CancellationToken cancel } } - protected void ProcessFreeResponse(IResponse response) + protected static void ProcessFreeResponse(IResponse response) { } - protected ValueTask ProcessFreeResponseAsync(IResponse response, CancellationToken cancellationToken = default) + protected static ValueTask ProcessFreeResponseAsync(IResponse response, CancellationToken cancellationToken = default) { return ValueTask.CompletedTask; } @@ -736,12 +751,12 @@ protected ValueTask ProcessFreeResponseAsync(IResponse response, CancellationTok #region op_allocate_statement methods protected void SendAllocateToBuffer() { - _database.Xdr.Write(IscCodes.op_allocate_statement); + _database.Xdr.WriteBytes(bufOpAllocStatement); _database.Xdr.Write(_database.Handle); } protected async ValueTask SendAllocateToBufferAsync(CancellationToken cancellationToken = default) { - await _database.Xdr.WriteAsync(IscCodes.op_allocate_statement, cancellationToken).ConfigureAwait(false); + await _database.Xdr.WriteBytesAsync(bufOpAllocStatement, 4, cancellationToken).ConfigureAwait(false); await _database.Xdr.WriteAsync(_database.Handle, cancellationToken).ConfigureAwait(false); } @@ -763,18 +778,34 @@ protected ValueTask ProcessAllocateResponseAsync(GenericResponse response, Cance #endregion #region op_execute/op_execute2 methods + + private static readonly byte[] zeroIntBuf = TypeEncoder.EncodeInt32(0); + private static readonly byte[] oneIntBuf = TypeEncoder.EncodeInt32(1); + private static readonly byte[] bufOpEx1 = TypeEncoder.EncodeInt32(IscCodes.op_execute); + private static readonly byte[] bufOpEx2 = TypeEncoder.EncodeInt32(IscCodes.op_execute2); + private static readonly byte[] bufOpFetch = TypeEncoder.EncodeInt32(IscCodes.op_fetch); + private static readonly byte[] bufOpPrepare = TypeEncoder.EncodeInt32(IscCodes.op_prepare_statement); + private static readonly byte[] bufOpInfoSql = TypeEncoder.EncodeInt32(IscCodes.op_info_sql); + private static readonly byte[] bufOpFreeStatement = TypeEncoder.EncodeInt32(IscCodes.op_free_statement); + private static readonly byte[] bufOpAllocStatement = TypeEncoder.EncodeInt32(IscCodes.op_allocate_statement); + private static readonly byte[] bufPrepareInfoSize = TypeEncoder.EncodeInt32(IscCodes.PREPARE_INFO_BUFFER_SIZE); + protected virtual void SendExecuteToBuffer(int timeout, IDescriptorFiller descriptorFiller) { + ReadOnlySpan boe1 = bufOpEx1; + ReadOnlySpan boe2 = bufOpEx2; + ReadOnlySpan bzero = zeroIntBuf; + ReadOnlySpan bone = oneIntBuf; // this may throw error, so it needs to be before any writing var parametersData = GetParameterData(descriptorFiller, 0); if (StatementType == DbStatementType.StoredProcedure) { - _database.Xdr.Write(IscCodes.op_execute2); + _database.Xdr.WriteBytes(boe2); } else { - _database.Xdr.Write(IscCodes.op_execute); + _database.Xdr.WriteBytes(boe1); } _database.Xdr.Write(_handle); @@ -783,21 +814,21 @@ protected virtual void SendExecuteToBuffer(int timeout, IDescriptorFiller descri if (_parameters != null) { _database.Xdr.WriteBuffer(_parameters.ToBlr().Data); - _database.Xdr.Write(0); // Message number - _database.Xdr.Write(1); // Number of messages + _database.Xdr.WriteBytes(bzero); // Message number + _database.Xdr.WriteBytes(bone); // Number of messages _database.Xdr.WriteBytes(parametersData, parametersData.Length); } else { _database.Xdr.WriteBuffer(null); - _database.Xdr.Write(0); - _database.Xdr.Write(0); + _database.Xdr.WriteBytes(bzero); + _database.Xdr.WriteBytes(bzero); } if (StatementType == DbStatementType.StoredProcedure) { _database.Xdr.WriteBuffer(_fields?.ToBlr().Data); - _database.Xdr.Write(0); // Output message number + _database.Xdr.WriteBytes(bzero); } } protected virtual async ValueTask SendExecuteToBufferAsync(int timeout, IDescriptorFiller descriptorFiller, CancellationToken cancellationToken = default) @@ -807,11 +838,11 @@ protected virtual async ValueTask SendExecuteToBufferAsync(int timeout, IDescrip if (StatementType == DbStatementType.StoredProcedure) { - await _database.Xdr.WriteAsync(IscCodes.op_execute2, cancellationToken).ConfigureAwait(false); + await _database.Xdr.WriteBytesAsync(bufOpEx2, 4, cancellationToken).ConfigureAwait(false); } else { - await _database.Xdr.WriteAsync(IscCodes.op_execute, cancellationToken).ConfigureAwait(false); + await _database.Xdr.WriteBytesAsync(bufOpEx1, 4, cancellationToken).ConfigureAwait(false); } await _database.Xdr.WriteAsync(_handle, cancellationToken).ConfigureAwait(false); @@ -820,27 +851,27 @@ protected virtual async ValueTask SendExecuteToBufferAsync(int timeout, IDescrip if (_parameters != null) { await _database.Xdr.WriteBufferAsync(_parameters.ToBlr().Data, cancellationToken).ConfigureAwait(false); - await _database.Xdr.WriteAsync(0, cancellationToken).ConfigureAwait(false); // Message number - await _database.Xdr.WriteAsync(1, cancellationToken).ConfigureAwait(false); // Number of messages + await _database.Xdr.WriteBytesAsync(zeroIntBuf, 4, cancellationToken).ConfigureAwait(false); // Message number + await _database.Xdr.WriteBytesAsync(oneIntBuf, 4, cancellationToken).ConfigureAwait(false); // Number of messages await _database.Xdr.WriteBytesAsync(parametersData, parametersData.Length, cancellationToken).ConfigureAwait(false); } else { await _database.Xdr.WriteBufferAsync(null, cancellationToken).ConfigureAwait(false); - await _database.Xdr.WriteAsync(0, cancellationToken).ConfigureAwait(false); - await _database.Xdr.WriteAsync(0, cancellationToken).ConfigureAwait(false); + await _database.Xdr.WriteBytesAsync(zeroIntBuf, 4, cancellationToken).ConfigureAwait(false); + await _database.Xdr.WriteBytesAsync(zeroIntBuf, 4, cancellationToken).ConfigureAwait(false); } if (StatementType == DbStatementType.StoredProcedure) { await _database.Xdr.WriteBufferAsync(_fields?.ToBlr().Data, cancellationToken).ConfigureAwait(false); - await _database.Xdr.WriteAsync(0, cancellationToken).ConfigureAwait(false); // Output message number + await _database.Xdr.WriteBytesAsync(zeroIntBuf, 4, cancellationToken).ConfigureAwait(false); // Output message number } } - protected void ProcessExecuteResponse(GenericResponse response) + protected static void ProcessExecuteResponse(GenericResponse response) { } - protected ValueTask ProcessExecuteResponseAsync(GenericResponse response, CancellationToken cancellationToken = default) + protected static ValueTask ProcessExecuteResponseAsync(GenericResponse response, CancellationToken cancellationToken = default) { return ValueTask.CompletedTask; } @@ -1033,7 +1064,7 @@ protected Descriptor[] ParseTruncSqlInfo(byte[] info, byte[] items, Descriptor[] } return rowDescs; } - protected async ValueTask ParseTruncSqlInfoAsync(byte[] info, byte[] items, Descriptor[] rowDescs, CancellationToken cancellationToken = default) + private Descriptor[] ParseTruncSqlInfoSpan(ReadOnlySpan info, ReadOnlySpan items, Descriptor[] rowDescs) { var currentPosition = 0; var currentDescriptorIndex = -1; @@ -1068,11 +1099,12 @@ protected async ValueTask ParseTruncSqlInfoAsync(byte[] info, byte newItems.Add(items[i]); } - info = await GetSqlInfoAsync(newItems.ToArray(), info.Length, cancellationToken).ConfigureAwait(false); + var refreshed = GetSqlInfo(newItems.ToArray(), info.Length); + info = refreshed; currentPosition = 0; currentDescriptorIndex = -1; - goto Break; + goto BreakSpan; case IscCodes.isc_info_sql_select: case IscCodes.isc_info_sql_bind: @@ -1091,7 +1123,7 @@ protected async ValueTask ParseTruncSqlInfoAsync(byte[] info, byte if (n == 0) { currentPosition += len; - goto Break; + goto BreakSpan; } } currentPosition += len; @@ -1135,37 +1167,172 @@ protected async ValueTask ParseTruncSqlInfoAsync(byte[] info, byte case IscCodes.isc_info_sql_field: len = (int)IscHelper.VaxInteger(info, currentPosition, 2); currentPosition += 2; - rowDescs[currentDescriptorIndex][currentItemIndex - 1].Name = _database.Charset.GetString(info, currentPosition, len); + rowDescs[currentDescriptorIndex][currentItemIndex - 1].Name = _database.Charset.GetString(info.Slice(currentPosition, len)); currentPosition += len; break; case IscCodes.isc_info_sql_relation: len = (int)IscHelper.VaxInteger(info, currentPosition, 2); currentPosition += 2; - rowDescs[currentDescriptorIndex][currentItemIndex - 1].Relation = _database.Charset.GetString(info, currentPosition, len); + rowDescs[currentDescriptorIndex][currentItemIndex - 1].Relation = _database.Charset.GetString(info.Slice(currentPosition, len)); currentPosition += len; break; case IscCodes.isc_info_sql_owner: len = (int)IscHelper.VaxInteger(info, currentPosition, 2); currentPosition += 2; - rowDescs[currentDescriptorIndex][currentItemIndex - 1].Owner = _database.Charset.GetString(info, currentPosition, len); + rowDescs[currentDescriptorIndex][currentItemIndex - 1].Owner = _database.Charset.GetString(info.Slice(currentPosition, len)); currentPosition += len; break; case IscCodes.isc_info_sql_alias: len = (int)IscHelper.VaxInteger(info, currentPosition, 2); currentPosition += 2; - rowDescs[currentDescriptorIndex][currentItemIndex - 1].Alias = _database.Charset.GetString(info, currentPosition, len); + rowDescs[currentDescriptorIndex][currentItemIndex - 1].Alias = _database.Charset.GetString(info.Slice(currentPosition, len)); currentPosition += len; break; default: throw IscException.ForErrorCode(IscCodes.isc_dsql_sqlda_err); + } + } + // just to get out of the loop + BreakSpan: + { } + } + return rowDescs; + } + + private ValueTask ParseTruncSqlInfoAsync(byte[] info, ReadOnlyMemory items, Descriptor[] rowDescs, CancellationToken cancellationToken) => + ParseTruncSqlInfoSpanAsync(info.AsMemory(), items, rowDescs, cancellationToken); + + private async ValueTask ParseTruncSqlInfoSpanAsync(ReadOnlyMemory info, ReadOnlyMemory items, Descriptor[] rowDescs, CancellationToken cancellationToken) + { + var currentPosition = 0; + var currentDescriptorIndex = -1; + var currentItemIndex = 0; + while(info.Span[currentPosition] != IscCodes.isc_info_end) { + byte item; + while((item = info.Span[currentPosition++]) != IscCodes.isc_info_sql_describe_end) { + switch(item) { + case IscCodes.isc_info_truncated: + currentItemIndex--; + + var newItems = new List(items.Length); + var part = 0; + var chock = 0; + for(var i = 0; i < items.Length; i++) { + if(items.Span[i] == IscCodes.isc_info_sql_describe_end) { + newItems.Insert(chock, IscCodes.isc_info_sql_sqlda_start); + newItems.Insert(chock + 1, 2); + + var processedItems = (rowDescs[part] != null ? rowDescs[part].Count : (short)0); + newItems.Insert(chock + 2, (byte)((part == currentDescriptorIndex ? currentItemIndex : processedItems) & 255)); + newItems.Insert(chock + 3, (byte)((part == currentDescriptorIndex ? currentItemIndex : processedItems) >> 8)); + + part++; + chock = i + 4 + 1; + } + newItems.Add(items.Span[i]); + } + + var refreshed = await GetSqlInfoAsync(newItems.ToArray(), info.Length, cancellationToken).ConfigureAwait(false); + info = refreshed; + + currentPosition = 0; + currentDescriptorIndex = -1; + goto BreakAsync; + + case IscCodes.isc_info_sql_select: + case IscCodes.isc_info_sql_bind: + currentDescriptorIndex++; + + if(info.Span[currentPosition] == IscCodes.isc_info_truncated) + break; + + currentPosition++; + var len = (int)IscHelper.VaxInteger(info.Span, currentPosition, 2); + currentPosition += 2; + if(rowDescs[currentDescriptorIndex] == null) { + var n = IscHelper.VaxInteger(info.Span, currentPosition, len); + rowDescs[currentDescriptorIndex] = new Descriptor((short)n); + if(n == 0) { + currentPosition += len; + goto BreakAsync; + } + } + currentPosition += len; + break; + + case IscCodes.isc_info_sql_sqlda_seq: + len = (int)IscHelper.VaxInteger(info.Span, currentPosition, 2); + currentPosition += 2; + currentItemIndex = (int)IscHelper.VaxInteger(info.Span, currentPosition, len); + currentPosition += len; + break; + + case IscCodes.isc_info_sql_type: + len = (int)IscHelper.VaxInteger(info.Span, currentPosition, 2); + currentPosition += 2; + rowDescs[currentDescriptorIndex][currentItemIndex - 1].DataType = (short)IscHelper.VaxInteger(info.Span, currentPosition, len); + currentPosition += len; + break; + + case IscCodes.isc_info_sql_sub_type: + len = (int)IscHelper.VaxInteger(info.Span, currentPosition, 2); + currentPosition += 2; + rowDescs[currentDescriptorIndex][currentItemIndex - 1].SubType = (short)IscHelper.VaxInteger(info.Span, currentPosition, len); + currentPosition += len; + break; + + case IscCodes.isc_info_sql_scale: + len = (int)IscHelper.VaxInteger(info.Span, currentPosition, 2); + currentPosition += 2; + rowDescs[currentDescriptorIndex][currentItemIndex - 1].NumericScale = (short)IscHelper.VaxInteger(info.Span, currentPosition, len); + currentPosition += len; + break; + + case IscCodes.isc_info_sql_length: + len = (int)IscHelper.VaxInteger(info.Span, currentPosition, 2); + currentPosition += 2; + rowDescs[currentDescriptorIndex][currentItemIndex - 1].Length = (short)IscHelper.VaxInteger(info.Span, currentPosition, len); + currentPosition += len; + break; + + case IscCodes.isc_info_sql_field: + len = (int)IscHelper.VaxInteger(info.Span, currentPosition, 2); + currentPosition += 2; + rowDescs[currentDescriptorIndex][currentItemIndex - 1].Name = _database.Charset.GetString(info.Span.Slice(currentPosition, len)); + currentPosition += len; + break; + + case IscCodes.isc_info_sql_relation: + len = (int)IscHelper.VaxInteger(info.Span, currentPosition, 2); + currentPosition += 2; + rowDescs[currentDescriptorIndex][currentItemIndex - 1].Relation = _database.Charset.GetString(info.Span.Slice(currentPosition, len)); + currentPosition += len; + break; + + case IscCodes.isc_info_sql_owner: + len = (int)IscHelper.VaxInteger(info.Span, currentPosition, 2); + currentPosition += 2; + rowDescs[currentDescriptorIndex][currentItemIndex - 1].Owner = _database.Charset.GetString(info.Span.Slice(currentPosition, len)); + currentPosition += len; + break; + + case IscCodes.isc_info_sql_alias: + len = (int)IscHelper.VaxInteger(info.Span, currentPosition, 2); + currentPosition += 2; + rowDescs[currentDescriptorIndex][currentItemIndex - 1].Alias = _database.Charset.GetString(info.Span.Slice(currentPosition, len)); + currentPosition += len; + break; + + default: + throw IscException.ForErrorCode(IscCodes.isc_dsql_sqlda_err); } } // just to get out of the loop - Break: + BreakAsync: { } } return rowDescs; @@ -1222,7 +1389,7 @@ protected virtual async ValueTask WriteParametersAsync(CancellationToken } } - protected void WriteRawParameter(IXdrWriter xdr, DbField field) + protected static void WriteRawParameter(IXdrWriter xdr, DbField field) { if (field.DbDataType != DbDataType.Null) { @@ -1235,23 +1402,38 @@ protected void WriteRawParameter(IXdrWriter xdr, DbField field) { xdr.WriteOpaque(field.DbValue.GetBinary(), field.Length); } - else if (field.Charset.IsNoneCharset) + else { - var bvalue = field.Charset.GetBytes(field.DbValue.GetString()); - if (bvalue.Length > field.Length) + var svalue = field.DbValue.GetString(); + if ((field.Length % field.Charset.BytesPerCharacter) == 0 && svalue.EnumerateRunes().Count() > field.CharCount) { throw IscException.ForErrorCodes(new[] { IscCodes.isc_arith_except, IscCodes.isc_string_truncation }); } - xdr.WriteOpaque(bvalue, field.Length); + var encoding = field.Charset.Encoding; + var byteCount = encoding.GetByteCount(svalue); + if (byteCount > field.Length) + { + throw IscException.ForErrorCodes([IscCodes.isc_arith_except, IscCodes.isc_string_truncation]); } + Span stack = byteCount <= 512 ? stackalloc byte[byteCount] : Span.Empty; + if (!stack.IsEmpty) + { + encoding.GetBytes(svalue.AsSpan(), stack); + xdr.WriteOpaque(stack, field.Length); + } else { - var svalue = field.DbValue.GetString(); - if ((field.Length % field.Charset.BytesPerCharacter) == 0 && svalue.EnumerateRunesToChars().Count() > field.CharCount) + var rented = System.Buffers.ArrayPool.Shared.Rent(byteCount); + try { - throw IscException.ForErrorCodes(new[] { IscCodes.isc_arith_except, IscCodes.isc_string_truncation }); + var written = encoding.GetBytes(svalue, 0, svalue.Length, rented, 0); + xdr.WriteOpaque(rented.AsSpan(0, written), field.Length); + } + finally + { + System.Buffers.ArrayPool.Shared.Return(rented); + } } - xdr.WriteOpaque(field.Charset.GetBytes(svalue), field.Length); } break; @@ -1260,23 +1442,34 @@ protected void WriteRawParameter(IXdrWriter xdr, DbField field) { xdr.WriteBuffer(field.DbValue.GetBinary()); } - else if (field.Charset.IsNoneCharset) + else { - var bvalue = field.Charset.GetBytes(field.DbValue.GetString()); - if (bvalue.Length > field.Length) + var svalue = field.DbValue.GetString(); + if ((field.Length % field.Charset.BytesPerCharacter) == 0 && svalue.EnumerateRunes().Count() > field.CharCount) { - throw IscException.ForErrorCodes(new[] { IscCodes.isc_arith_except, IscCodes.isc_string_truncation }); + throw IscException.ForErrorCodes([IscCodes.isc_arith_except, IscCodes.isc_string_truncation]); } - xdr.WriteBuffer(bvalue); + var encoding = field.Charset.Encoding; + var byteCount = encoding.GetByteCount(svalue); + Span stack = byteCount <= 512 ? stackalloc byte[byteCount] : Span.Empty; + if (!stack.IsEmpty) + { + encoding.GetBytes(svalue.AsSpan(), stack); + xdr.WriteBuffer(stack); } else { - var svalue = field.DbValue.GetString(); - if ((field.Length % field.Charset.BytesPerCharacter) == 0 && svalue.EnumerateRunesToChars().Count() > field.CharCount) + var rented = System.Buffers.ArrayPool.Shared.Rent(byteCount); + try + { + var written = encoding.GetBytes(svalue, 0, svalue.Length, rented, 0); + xdr.WriteBuffer(rented.AsSpan(0, written)); + } + finally { - throw IscException.ForErrorCodes(new[] { IscCodes.isc_arith_except, IscCodes.isc_string_truncation }); + System.Buffers.ArrayPool.Shared.Return(rented); + } } - xdr.WriteBuffer(field.Charset.GetBytes(svalue)); } break; @@ -1370,7 +1563,7 @@ protected void WriteRawParameter(IXdrWriter xdr, DbField field) } } } - protected async ValueTask WriteRawParameterAsync(IXdrWriter xdr, DbField field, CancellationToken cancellationToken = default) + protected static async ValueTask WriteRawParameterAsync(IXdrWriter xdr, DbField field, CancellationToken cancellationToken = default) { if (field.DbDataType != DbDataType.Null) { @@ -1383,23 +1576,31 @@ protected async ValueTask WriteRawParameterAsync(IXdrWriter xdr, DbField field, { await xdr.WriteOpaqueAsync(await field.DbValue.GetBinaryAsync(cancellationToken).ConfigureAwait(false), field.Length, cancellationToken).ConfigureAwait(false); } - else if (field.Charset.IsNoneCharset) + else { - var bvalue = field.Charset.GetBytes(await field.DbValue.GetStringAsync(cancellationToken).ConfigureAwait(false)); - if (bvalue.Length > field.Length) + var svalue = await field.DbValue.GetStringAsync(cancellationToken).ConfigureAwait(false); + if ((field.Length % field.Charset.BytesPerCharacter) == 0 && svalue.EnumerateRunes().Count() > field.CharCount) { - throw IscException.ForErrorCodes(new[] { IscCodes.isc_arith_except, IscCodes.isc_string_truncation }); + throw IscException.ForErrorCodes([IscCodes.isc_arith_except, IscCodes.isc_string_truncation]); } - await xdr.WriteOpaqueAsync(bvalue, field.Length, cancellationToken).ConfigureAwait(false); + var encoding = field.Charset.Encoding; + var byteCount = encoding.GetByteCount(svalue); + if (byteCount > field.Length) + { + throw IscException.ForErrorCodes([IscCodes.isc_arith_except, IscCodes.isc_string_truncation]); } - else + { + var rented = System.Buffers.ArrayPool.Shared.Rent(byteCount); + try { - var svalue = await field.DbValue.GetStringAsync(cancellationToken).ConfigureAwait(false); - if ((field.Length % field.Charset.BytesPerCharacter) == 0 && svalue.EnumerateRunesToChars().Count() > field.CharCount) + var written = encoding.GetBytes(svalue, 0, svalue.Length, rented, 0); + await xdr.WriteOpaqueAsync(rented.AsMemory(0, written), written, cancellationToken).ConfigureAwait(false); + } + finally { - throw IscException.ForErrorCodes(new[] { IscCodes.isc_arith_except, IscCodes.isc_string_truncation }); + System.Buffers.ArrayPool.Shared.Return(rented); + } } - await xdr.WriteOpaqueAsync(field.Charset.GetBytes(svalue), field.Length, cancellationToken).ConfigureAwait(false); } break; @@ -1408,23 +1609,27 @@ protected async ValueTask WriteRawParameterAsync(IXdrWriter xdr, DbField field, { await xdr.WriteBufferAsync(await field.DbValue.GetBinaryAsync(cancellationToken).ConfigureAwait(false), cancellationToken).ConfigureAwait(false); } - else if (field.Charset.IsNoneCharset) + else { - var bvalue = field.Charset.GetBytes(await field.DbValue.GetStringAsync(cancellationToken).ConfigureAwait(false)); - if (bvalue.Length > field.Length) + var svalue = await field.DbValue.GetStringAsync(cancellationToken).ConfigureAwait(false); + if ((field.Length % field.Charset.BytesPerCharacter) == 0 && svalue.EnumerateRunes().Count() > field.CharCount) { - throw IscException.ForErrorCodes(new[] { IscCodes.isc_arith_except, IscCodes.isc_string_truncation }); + throw IscException.ForErrorCodes([IscCodes.isc_arith_except, IscCodes.isc_string_truncation]); } - await xdr.WriteBufferAsync(bvalue, cancellationToken).ConfigureAwait(false); - } - else + var encoding = field.Charset.Encoding; + var byteCount = encoding.GetByteCount(svalue); + { + var rented = System.Buffers.ArrayPool.Shared.Rent(byteCount); + try { - var svalue = await field.DbValue.GetStringAsync(cancellationToken).ConfigureAwait(false); - if ((field.Length % field.Charset.BytesPerCharacter) == 0 && svalue.EnumerateRunesToChars().Count() > field.CharCount) + var written = encoding.GetBytes(svalue, 0, svalue.Length, rented, 0); + await xdr.WriteBufferAsync(rented.AsMemory(0, written), cancellationToken).ConfigureAwait(false); + } + finally { - throw IscException.ForErrorCodes(new[] { IscCodes.isc_arith_except, IscCodes.isc_string_truncation }); + System.Buffers.ArrayPool.Shared.Return(rented); + } } - await xdr.WriteBufferAsync(field.Charset.GetBytes(svalue), cancellationToken).ConfigureAwait(false); } break; diff --git a/src/FirebirdSql.Data.FirebirdClient/Client/Managed/Version10/GdsTransaction.cs b/src/FirebirdSql.Data.FirebirdClient/Client/Managed/Version10/GdsTransaction.cs index 418ccd44e..4a0fd085d 100644 --- a/src/FirebirdSql.Data.FirebirdClient/Client/Managed/Version10/GdsTransaction.cs +++ b/src/FirebirdSql.Data.FirebirdClient/Client/Managed/Version10/GdsTransaction.cs @@ -450,7 +450,7 @@ private void DatabaseInfo(byte[] items, byte[] buffer, int bufferLength) responseLength = response.Data.Length; } - Buffer.BlockCopy(response.Data, 0, buffer, 0, responseLength); + response.Data.AsSpan().Slice(0, responseLength).CopyTo(buffer.AsSpan(0, responseLength)); } catch (IOException ex) { @@ -478,7 +478,7 @@ private async ValueTask DatabaseInfoAsync(byte[] items, byte[] buffer, int buffe responseLength = response.Data.Length; } - Buffer.BlockCopy(response.Data, 0, buffer, 0, responseLength); + response.Data.AsSpan().Slice(0, responseLength).CopyTo(buffer.AsSpan(0, responseLength)); } catch (IOException ex) { diff --git a/src/FirebirdSql.Data.FirebirdClient/Client/Managed/Version11/GdsDatabase.cs b/src/FirebirdSql.Data.FirebirdClient/Client/Managed/Version11/GdsDatabase.cs index 7c9030be2..f1d575e55 100644 --- a/src/FirebirdSql.Data.FirebirdClient/Client/Managed/Version11/GdsDatabase.cs +++ b/src/FirebirdSql.Data.FirebirdClient/Client/Managed/Version11/GdsDatabase.cs @@ -126,10 +126,10 @@ protected IResponse ProcessTrustedAuthResponse(SspiHelper sspiHelper, IResponse } return response; } - protected async ValueTask ProcessTrustedAuthResponseAsync(SspiHelper sspiHelper, IResponse response, CancellationToken cancellationToken = default) - { - while (response is AuthResponse authResponse) + protected async ValueTask ProcessTrustedAuthResponseAsync(SspiHelper sspiHelper, IResponse response, CancellationToken cancellationToken = default) { + while (response is AuthResponse authResponse) + { var authData = sspiHelper.GetClientSecurity(authResponse.Data); await Xdr.WriteAsync(IscCodes.op_trusted_auth, cancellationToken).ConfigureAwait(false); await Xdr.WriteBufferAsync(authData, cancellationToken).ConfigureAwait(false); diff --git a/src/FirebirdSql.Data.FirebirdClient/Client/Managed/Version13/GdsStatement.cs b/src/FirebirdSql.Data.FirebirdClient/Client/Managed/Version13/GdsStatement.cs index 0ea953783..04a21da46 100644 --- a/src/FirebirdSql.Data.FirebirdClient/Client/Managed/Version13/GdsStatement.cs +++ b/src/FirebirdSql.Data.FirebirdClient/Client/Managed/Version13/GdsStatement.cs @@ -18,6 +18,7 @@ using System; using System.Collections; using System.IO; +using System.Buffers; using System.Threading; using System.Threading.Tasks; using FirebirdSql.Data.Common; @@ -26,6 +27,8 @@ namespace FirebirdSql.Data.Client.Managed.Version13; internal class GdsStatement : Version12.GdsStatement { + const int STACKALLOC_LIMIT = 1024; + #region Constructors public GdsStatement(GdsDatabase database) @@ -51,20 +54,25 @@ protected override byte[] WriteParameters() { var xdr = new XdrReaderWriter(new DataProviderStreamWrapper(ms), _database.Charset); - var bits = new BitArray(_parameters.Count); - for (var i = 0; i < _parameters.Count; i++) + var count = _parameters.Count; + var bytesLen = (int)Math.Ceiling(count / 8d); + byte[] rented = null; + Span buffer = bytesLen > STACKALLOC_LIMIT + ? (rented = ArrayPool.Shared.Rent(bytesLen)).AsSpan(0, bytesLen) + : stackalloc byte[bytesLen]; + buffer.Clear(); + for (var i = 0; i < count; i++) { - var field = _parameters[i]; - bits.Set(i, field.DbValue.IsDBNull()); + if (_parameters[i].DbValue.IsDBNull()) + { + buffer[i / 8] |= (byte)(1 << (i % 8)); + } } - var buffer = new byte[(int)Math.Ceiling(_parameters.Count / 8d)]; - for (var i = 0; i < buffer.Length * 8; i++) + xdr.WriteOpaque(buffer); + if (rented != null) { - var index = i / 8; - // LSB - buffer[index] = (byte)((buffer[index] >> 1) | (bits.Length > i && bits[i] ? 1 << 7 : 0)); + ArrayPool.Shared.Return(rented); } - xdr.WriteOpaque(buffer); for (var i = 0; i < _parameters.Count; i++) { @@ -96,20 +104,25 @@ protected override async ValueTask WriteParametersAsync(CancellationToke { var xdr = new XdrReaderWriter(new DataProviderStreamWrapper(ms), _database.Charset); - var bits = new BitArray(_parameters.Count); - for (var i = 0; i < _parameters.Count; i++) - { - var field = _parameters[i]; - bits.Set(i, field.DbValue.IsDBNull()); - } - var buffer = new byte[(int)Math.Ceiling(_parameters.Count / 8d)]; - for (var i = 0; i < buffer.Length * 8; i++) - { - var index = i / 8; - // LSB - buffer[index] = (byte)((buffer[index] >> 1) | (bits.Length > i && bits[i] ? 1 << 7 : 0)); - } - await xdr.WriteOpaqueAsync(buffer, cancellationToken).ConfigureAwait(false); + var count = _parameters.Count; + var len = (int)Math.Ceiling(count / 8d); + var buffer = ArrayPool.Shared.Rent(len); + Array.Clear(buffer, 0, len); + for (var i = 0; i < count; i++) + { + if (_parameters[i].DbValue.IsDBNull()) + { + buffer[i / 8] |= (byte)(1 << (i % 8)); + } + } + try + { + await xdr.WriteOpaqueAsync(buffer, len, cancellationToken).ConfigureAwait(false); + } + finally + { + ArrayPool.Shared.Return(buffer); + } for (var i = 0; i < _parameters.Count; i++) { @@ -133,25 +146,34 @@ protected override async ValueTask WriteParametersAsync(CancellationToke protected override DbValue[] ReadRow() { - var row = new DbValue[_fields.Count]; + var row = _fields.Count > 0 ? new DbValue[_fields.Count] : Array.Empty(); try { if (_fields.Count > 0) { - var nullBytes = _database.Xdr.ReadOpaque((int)Math.Ceiling(_fields.Count / 8d)); - var nullBits = new BitArray(nullBytes); - for (var i = 0; i < _fields.Count; i++) - { - if (nullBits.Get(i)) - { - row[i] = new DbValue(this, _fields[i], null); - } - else - { - var value = ReadRawValue(_database.Xdr, _fields[i]); - row[i] = new DbValue(this, _fields[i], value); - } - } + var len = (int)Math.Ceiling(_fields.Count / 8d); + var rented = ArrayPool.Shared.Rent(len); + try + { + _database.Xdr.ReadOpaque(rented.AsSpan(0, len), len); + for (var i = 0; i < _fields.Count; i++) + { + var isNull = (rented[i / 8] & (1 << (i % 8))) != 0; + if (isNull) + { + row[i] = new DbValue(this, _fields[i], null); + } + else + { + var value = ReadRawValue(_database.Xdr, _fields[i]); + row[i] = new DbValue(this, _fields[i], value); + } + } + } + finally + { + ArrayPool.Shared.Return(rented); + } } } catch (IOException ex) @@ -162,25 +184,34 @@ protected override DbValue[] ReadRow() } protected override async ValueTask ReadRowAsync(CancellationToken cancellationToken = default) { - var row = new DbValue[_fields.Count]; + var row = _fields.Count > 0 ? new DbValue[_fields.Count] : Array.Empty(); try { if (_fields.Count > 0) { - var nullBytes = await _database.Xdr.ReadOpaqueAsync((int)Math.Ceiling(_fields.Count / 8d), cancellationToken).ConfigureAwait(false); - var nullBits = new BitArray(nullBytes); - for (var i = 0; i < _fields.Count; i++) - { - if (nullBits.Get(i)) - { - row[i] = new DbValue(this, _fields[i], null); - } - else - { - var value = await ReadRawValueAsync(_database.Xdr, _fields[i], cancellationToken).ConfigureAwait(false); - row[i] = new DbValue(this, _fields[i], value); - } - } + var len = (int)Math.Ceiling(_fields.Count / 8d); + var rented = ArrayPool.Shared.Rent(len); + try + { + await _database.Xdr.ReadOpaqueAsync(rented.AsMemory(0, len), len, cancellationToken).ConfigureAwait(false); + for (var i = 0; i < _fields.Count; i++) + { + var isNull = (rented[i / 8] & (1 << (i % 8))) != 0; + if (isNull) + { + row[i] = new DbValue(this, _fields[i], null); + } + else + { + var value = await ReadRawValueAsync(_database.Xdr, _fields[i], cancellationToken).ConfigureAwait(false); + row[i] = new DbValue(this, _fields[i], value); + } + } + } + finally + { + ArrayPool.Shared.Return(rented); + } } } catch (IOException ex) diff --git a/src/FirebirdSql.Data.FirebirdClient/Client/Managed/XdrReaderWriter.cs b/src/FirebirdSql.Data.FirebirdClient/Client/Managed/XdrReaderWriter.cs index eeb4c322c..e57096274 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,6 +34,7 @@ sealed class XdrReaderWriter : IXdrReader, IXdrWriter readonly Charset _charset; byte[] _smallBuffer; + const int StackallocThreshold = 1024; public XdrReaderWriter(IDataProvider dataProvider, Charset charset) { @@ -42,7 +44,7 @@ public XdrReaderWriter(IDataProvider dataProvider, Charset charset) _smallBuffer = new byte[8]; } - public XdrReaderWriter(IDataProvider dataProvider) + public XdrReaderWriter(IDataProvider dataProvider) : this(dataProvider, Charset.DefaultCharset) { } @@ -69,6 +71,23 @@ 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 +110,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] : _emptyBuf; 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] : _emptyBuf; 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 +214,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() @@ -192,7 +293,18 @@ public Guid ReadGuid(int sqlType) } else { - return TypeDecoder.DecodeGuid(ReadOpaque(16)); + Span buf = stackalloc byte[16]; + ReadOpaque(buf, 16); + var rented = ArrayPool.Shared.Rent(16); + try + { + buf.CopyTo(rented); + return TypeDecoder.DecodeGuid(rented); + } + finally + { + ArrayPool.Shared.Return(rented); + } } } public async ValueTask ReadGuidAsync(int sqlType, CancellationToken cancellationToken = default) @@ -201,28 +313,57 @@ public async ValueTask ReadGuidAsync(int sqlType, CancellationToken cancel { return TypeDecoder.DecodeGuid(await ReadBufferAsync(cancellationToken).ConfigureAwait(false)); } - else + else + { + var rented = ArrayPool.Shared.Rent(16); + try + { + await ReadBytesAsync(rented, 16, cancellationToken).ConfigureAwait(false); + return TypeDecoder.DecodeGuid(rented); + } + finally + { + ArrayPool.Shared.Return(rented); + } + } + } + + public float Int2Single(int sqlType) + { + Span bytes = stackalloc byte[4]; + if (!BitConverter.TryWriteBytes(bytes, sqlType)) { - return TypeDecoder.DecodeGuid(await ReadOpaqueAsync(16, cancellationToken).ConfigureAwait(false)); + throw new InvalidOperationException("Failed to write Single bytes."); } + return BitConverter.ToSingle(bytes); } public float ReadSingle() { - return BitConverter.ToSingle(BitConverter.GetBytes(ReadInt32()), 0); + return Int2Single(ReadInt32()); } public async ValueTask ReadSingleAsync(CancellationToken cancellationToken = default) { - return BitConverter.ToSingle(BitConverter.GetBytes(await ReadInt32Async(cancellationToken).ConfigureAwait(false)), 0); + return Int2Single(await ReadInt32Async(cancellationToken).ConfigureAwait(false)); + } + + public double Long2Double(long sqlType) + { + Span bytes = stackalloc byte[8]; + if (!BitConverter.TryWriteBytes(bytes, sqlType)) + { + throw new InvalidOperationException("Failed to write Double bytes."); + } + return BitConverter.ToDouble(bytes); } public double ReadDouble() { - return BitConverter.ToDouble(BitConverter.GetBytes(ReadInt64()), 0); + return Long2Double(ReadInt64()); } public async ValueTask ReadDoubleAsync(CancellationToken cancellationToken = default) { - return BitConverter.ToDouble(BitConverter.GetBytes(await ReadInt64Async(cancellationToken).ConfigureAwait(false)), 0); + return Long2Double(await ReadInt64Async(cancellationToken).ConfigureAwait(false)); } public DateTime ReadDateTime() @@ -299,11 +440,15 @@ public async ValueTask ReadDecimalAsync(int type, int scale, Cancellati public bool ReadBoolean() { - return TypeDecoder.DecodeBoolean(ReadOpaque(1)); + Span bytes = stackalloc byte[1]; + ReadOpaque(bytes, 1); + return TypeDecoder.DecodeBoolean(bytes); } public async ValueTask ReadBooleanAsync(CancellationToken cancellationToken = default) { - return TypeDecoder.DecodeBoolean(await ReadOpaqueAsync(1, cancellationToken).ConfigureAwait(false)); + await ReadBytesAsync(_smallBuffer, 1, cancellationToken).ConfigureAwait(false); + await ReadPadAsync((4 - 1) & 3, cancellationToken).ConfigureAwait(false); + return TypeDecoder.DecodeBoolean(_smallBuffer); } public FbZonedDateTime ReadZonedDateTime(bool isExtended) @@ -330,29 +475,67 @@ public async ValueTask ReadZonedTimeAsync(bool isExtended, Cancella public FbDecFloat ReadDec16() { - return TypeDecoder.DecodeDec16(ReadOpaque(8)); + ReadBytes(_smallBuffer, 8); + return TypeDecoder.DecodeDec16(_smallBuffer); } public async ValueTask ReadDec16Async(CancellationToken cancellationToken = default) { - return TypeDecoder.DecodeDec16(await ReadOpaqueAsync(8, cancellationToken).ConfigureAwait(false)); + await ReadBytesAsync(_smallBuffer, 8, cancellationToken).ConfigureAwait(false); + return TypeDecoder.DecodeDec16(_smallBuffer); } public FbDecFloat ReadDec34() { - return TypeDecoder.DecodeDec34(ReadOpaque(16)); + var rented = ArrayPool.Shared.Rent(16); + try + { + ReadBytes(rented, 16); + return TypeDecoder.DecodeDec34(rented); + } + finally + { + ArrayPool.Shared.Return(rented); + } } public async ValueTask ReadDec34Async(CancellationToken cancellationToken = default) { - return TypeDecoder.DecodeDec34(await ReadOpaqueAsync(16, cancellationToken).ConfigureAwait(false)); + var rented = ArrayPool.Shared.Rent(16); + try + { + await ReadBytesAsync(rented, 16, cancellationToken).ConfigureAwait(false); + return TypeDecoder.DecodeDec34(rented); + } + finally + { + ArrayPool.Shared.Return(rented); + } } public BigInteger ReadInt128() { - return TypeDecoder.DecodeInt128(ReadOpaque(16)); + var rented = ArrayPool.Shared.Rent(16); + try + { + ReadBytes(rented, 16); + return TypeDecoder.DecodeInt128(rented); + } + finally + { + ArrayPool.Shared.Return(rented); + } } public async ValueTask ReadInt128Async(CancellationToken cancellationToken = default) { - return TypeDecoder.DecodeInt128(await ReadOpaqueAsync(16, cancellationToken).ConfigureAwait(false)); + var rented = ArrayPool.Shared.Rent(16); + try + { + await ReadBytesAsync(rented, 16, cancellationToken).ConfigureAwait(false); + return TypeDecoder.DecodeInt128(rented); + } + finally + { + ArrayPool.Shared.Return(rented); + } } public IscException ReadStatusVector() @@ -480,6 +663,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 +681,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 +760,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 +780,16 @@ 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 +807,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 +834,198 @@ 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) + { + int length; + Span typeByte = stackalloc byte[1]; + if (buffer == null) { + Write(1); + typeByte[0] = (byte)type; + _dataProvider.Write(typeByte); + length = 1; + } + else { + length = buffer.Length + 1; + Write(length); + typeByte[0] = (byte)type; + _dataProvider.Write(typeByte); + _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 +1039,76 @@ 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)); + Span buffer = stackalloc byte[4]; + if (!BitConverter.TryWriteBytes(buffer, value)) + { + throw new InvalidOperationException("Failed to write Single bytes."); + } + Write(BitConverter.ToInt32(buffer)); } public ValueTask WriteAsync(float value, CancellationToken cancellationToken = default) { - var buffer = BitConverter.GetBytes(value); - return WriteAsync(BitConverter.ToInt32(buffer, 0), cancellationToken); + var rented = ArrayPool.Shared.Rent(4); + if (!BitConverter.TryWriteBytes(rented, value)) + { + ArrayPool.Shared.Return(rented); + throw new InvalidOperationException("Failed to write Single bytes."); + } + var intVal = BitConverter.ToInt32(rented, 0); + ArrayPool.Shared.Return(rented); + return WriteAsync(intVal, cancellationToken); } public void Write(double value) { - var buffer = BitConverter.GetBytes(value); - Write(BitConverter.ToInt64(buffer, 0)); + Span buffer = stackalloc byte[8]; + if (!BitConverter.TryWriteBytes(buffer, value)) + { + throw new InvalidOperationException("Failed to write Double bytes."); + } + Write(BitConverter.ToInt64(buffer)); } public ValueTask WriteAsync(double value, CancellationToken cancellationToken = default) { - var buffer = BitConverter.GetBytes(value); - return WriteAsync(BitConverter.ToInt64(buffer, 0), cancellationToken); + var rented = ArrayPool.Shared.Rent(8); + if (!BitConverter.TryWriteBytes(rented, value)) + { + ArrayPool.Shared.Return(rented); + throw new InvalidOperationException("Failed to write Double bytes."); + } + var longVal = BitConverter.ToInt64(rented, 0); + ArrayPool.Shared.Return(rented); + return WriteAsync(longVal, cancellationToken); } public void Write(decimal value, int type, int scale) @@ -715,11 +1161,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 +1186,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 +1199,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 +1235,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..82667f108 100644 --- a/src/FirebirdSql.Data.FirebirdClient/Common/TypeDecoder.cs +++ b/src/FirebirdSql.Data.FirebirdClient/Common/TypeDecoder.cs @@ -98,13 +98,17 @@ 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 int DecodeInt32(byte[] value) diff --git a/src/FirebirdSql.Data.FirebirdClient/Common/TypeEncoder.cs b/src/FirebirdSql.Data.FirebirdClient/Common/TypeEncoder.cs index eda0a8058..b20e57677 100644 --- a/src/FirebirdSql.Data.FirebirdClient/Common/TypeEncoder.cs +++ b/src/FirebirdSql.Data.FirebirdClient/Common/TypeEncoder.cs @@ -97,6 +97,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,7 +114,38 @@ 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) @@ -117,11 +153,27 @@ public static byte[] EncodeInt32(int value) return BitConverter.GetBytes(IPAddress.NetworkToHostOrder(value)); } + public static void EncodeInt32(int value, Span destination) + { + if (!BitConverter.TryWriteBytes(destination, IPAddress.NetworkToHostOrder(value))) + { + throw new InvalidOperationException("Failed to write Int32 bytes."); + } + } + public static byte[] EncodeInt64(long value) { return BitConverter.GetBytes(IPAddress.NetworkToHostOrder(value)); } + public static void EncodeInt64(long value, Span destination) + { + if (!BitConverter.TryWriteBytes(destination, IPAddress.NetworkToHostOrder(value))) + { + throw new InvalidOperationException("Failed to write Int64 bytes."); + } + } + public static byte[] EncodeDec16(FbDecFloat value) { var result = DecimalCodec.DecFloat16.EncodeDecimal(value); From ddaf012ebd973efeaf352079871024ccf8f635e3 Mon Sep 17 00:00:00 2001 From: pl752 Date: Thu, 11 Dec 2025 11:06:17 +0500 Subject: [PATCH 02/11] Reworked the rune enumerator to not spam byte[1...4] alloc ations and optimized rune operations --- .../Client/Managed/Version10/GdsStatement.cs | 46 +++++++++---------- .../Common/DbField.cs | 12 ++--- .../Common/DbValue.cs | 8 ++-- .../Common/Extensions.cs | 34 ++++++++++++++ 4 files changed, 66 insertions(+), 34 deletions(-) diff --git a/src/FirebirdSql.Data.FirebirdClient/Client/Managed/Version10/GdsStatement.cs b/src/FirebirdSql.Data.FirebirdClient/Client/Managed/Version10/GdsStatement.cs index 200ad6f70..b5c0f3377 100644 --- a/src/FirebirdSql.Data.FirebirdClient/Client/Managed/Version10/GdsStatement.cs +++ b/src/FirebirdSql.Data.FirebirdClient/Client/Managed/Version10/GdsStatement.cs @@ -1405,7 +1405,7 @@ protected static void WriteRawParameter(IXdrWriter xdr, DbField field) else { var svalue = field.DbValue.GetString(); - if ((field.Length % field.Charset.BytesPerCharacter) == 0 && svalue.EnumerateRunes().Count() > field.CharCount) + if ((field.Length % field.Charset.BytesPerCharacter) == 0 && svalue.CountRunes() > field.CharCount) { throw IscException.ForErrorCodes(new[] { IscCodes.isc_arith_except, IscCodes.isc_string_truncation }); } @@ -1445,7 +1445,7 @@ protected static void WriteRawParameter(IXdrWriter xdr, DbField field) else { var svalue = field.DbValue.GetString(); - if ((field.Length % field.Charset.BytesPerCharacter) == 0 && svalue.EnumerateRunes().Count() > field.CharCount) + if ((field.Length % field.Charset.BytesPerCharacter) == 0 && svalue.CountRunes() > field.CharCount) { throw IscException.ForErrorCodes([IscCodes.isc_arith_except, IscCodes.isc_string_truncation]); } @@ -1579,7 +1579,7 @@ protected static async ValueTask WriteRawParameterAsync(IXdrWriter xdr, DbField else { var svalue = await field.DbValue.GetStringAsync(cancellationToken).ConfigureAwait(false); - if ((field.Length % field.Charset.BytesPerCharacter) == 0 && svalue.EnumerateRunes().Count() > field.CharCount) + if ((field.Length % field.Charset.BytesPerCharacter) == 0 && svalue.CountRunes() > field.CharCount) { throw IscException.ForErrorCodes([IscCodes.isc_arith_except, IscCodes.isc_string_truncation]); } @@ -1612,7 +1612,7 @@ protected static async ValueTask WriteRawParameterAsync(IXdrWriter xdr, DbField else { var svalue = await field.DbValue.GetStringAsync(cancellationToken).ConfigureAwait(false); - if ((field.Length % field.Charset.BytesPerCharacter) == 0 && svalue.EnumerateRunes().Count() > field.CharCount) + if ((field.Length % field.Charset.BytesPerCharacter) == 0 && svalue.CountRunes() > field.CharCount) { throw IscException.ForErrorCodes([IscCodes.isc_arith_except, IscCodes.isc_string_truncation]); } @@ -1738,16 +1738,7 @@ protected object ReadRawValue(IXdrReader xdr, DbField field) else { var s = xdr.ReadString(innerCharset, field.Length); - var runes = s.EnumerateRunesToChars().ToList(); - if ((field.Length % field.Charset.BytesPerCharacter) == 0 && - runes.Count > field.CharCount) - { - return new string([.. runes.Take(field.CharCount).SelectMany(x => x)]); - } - else - { - return s; - } + return TruncateStringByRuneCount(s, field); } case DbDataType.VarChar: @@ -1836,16 +1827,7 @@ protected async ValueTask ReadRawValueAsync(IXdrReader xdr, DbField fiel else { var s = await xdr.ReadStringAsync(innerCharset, field.Length, cancellationToken).ConfigureAwait(false); - var runes = s.EnumerateRunesToChars().ToList(); - if ((field.Length % field.Charset.BytesPerCharacter) == 0 && - runes.Count > field.CharCount) - { - return new string([.. runes.Take(field.CharCount).SelectMany(x => x)]); - } - else - { - return s; - } + return TruncateStringByRuneCount(s, field); } case DbDataType.VarChar: @@ -2002,6 +1984,22 @@ protected virtual async ValueTask ReadRowAsync(CancellationToken canc return row; } + private static string TruncateStringByRuneCount(string s, DbField field) + { + if ((field.Length % field.Charset.BytesPerCharacter) != 0) + { + return s; + } + + var runeCount = s.CountRunes(); + if (runeCount <= field.CharCount) + { + return s; + } + + return new string(s.TruncateStringToRuneCount(field.CharCount)); + } + #endregion #region Protected Internal Methods diff --git a/src/FirebirdSql.Data.FirebirdClient/Common/DbField.cs b/src/FirebirdSql.Data.FirebirdClient/Common/DbField.cs index 3be89be74..efc23524a 100644 --- a/src/FirebirdSql.Data.FirebirdClient/Common/DbField.cs +++ b/src/FirebirdSql.Data.FirebirdClient/Common/DbField.cs @@ -325,12 +325,12 @@ public void SetValue(byte[] buffer) else { var s = Charset.GetString(buffer, 0, buffer.Length); - - var runes = s.EnumerateRunesToChars().ToList(); - if ((Length % Charset.BytesPerCharacter) == 0 && - runes.Count > CharCount) - { - s = new string([.. runes.Take(CharCount).SelectMany(x => x)]); + if((Length % Charset.BytesPerCharacter) == 0) + { + var runes = s.CountRunes(); + if(runes > CharCount) { + s = new string(s.TruncateStringToRuneCount(CharCount)); + } } DbValue.SetValue(s); diff --git a/src/FirebirdSql.Data.FirebirdClient/Common/DbValue.cs b/src/FirebirdSql.Data.FirebirdClient/Common/DbValue.cs index 44b79a0f1..f4fe8abee 100644 --- a/src/FirebirdSql.Data.FirebirdClient/Common/DbValue.cs +++ b/src/FirebirdSql.Data.FirebirdClient/Common/DbValue.cs @@ -424,7 +424,7 @@ public byte[] GetBytes() else { var svalue = GetString(); - if ((Field.Length % Field.Charset.BytesPerCharacter) == 0 && svalue.EnumerateRunesToChars().Count() > Field.CharCount) + if ((Field.Length % Field.Charset.BytesPerCharacter) == 0 && svalue.CountRunes() > Field.CharCount) { throw IscException.ForErrorCodes(new[] { IscCodes.isc_arith_except, IscCodes.isc_string_truncation }); } @@ -460,7 +460,7 @@ public byte[] GetBytes() else { var svalue = GetString(); - if ((Field.Length % Field.Charset.BytesPerCharacter) == 0 && svalue.EnumerateRunesToChars().Count() > Field.CharCount) + if ((Field.Length % Field.Charset.BytesPerCharacter) == 0 && svalue.CountRunes() > Field.CharCount) { throw IscException.ForErrorCodes(new[] { IscCodes.isc_arith_except, IscCodes.isc_string_truncation }); } @@ -639,7 +639,7 @@ public async ValueTask GetBytesAsync(CancellationToken cancellationToken else { var svalue = await GetStringAsync(cancellationToken).ConfigureAwait(false); - if ((Field.Length % Field.Charset.BytesPerCharacter) == 0 && svalue.EnumerateRunesToChars().Count() > Field.CharCount) + if ((Field.Length % Field.Charset.BytesPerCharacter) == 0 && svalue.CountRunes() > Field.CharCount) { throw IscException.ForErrorCodes(new[] { IscCodes.isc_arith_except, IscCodes.isc_string_truncation }); } @@ -675,7 +675,7 @@ public async ValueTask GetBytesAsync(CancellationToken cancellationToken else { var svalue = await GetStringAsync(cancellationToken).ConfigureAwait(false); - if ((Field.Length % Field.Charset.BytesPerCharacter) == 0 && svalue.EnumerateRunesToChars().Count() > Field.CharCount) + if ((Field.Length % Field.Charset.BytesPerCharacter) == 0 && svalue.CountRunes() > Field.CharCount) { throw IscException.ForErrorCodes(new[] { IscCodes.isc_arith_except, IscCodes.isc_string_truncation }); } diff --git a/src/FirebirdSql.Data.FirebirdClient/Common/Extensions.cs b/src/FirebirdSql.Data.FirebirdClient/Common/Extensions.cs index 6143cc839..6c878e669 100644 --- a/src/FirebirdSql.Data.FirebirdClient/Common/Extensions.cs +++ b/src/FirebirdSql.Data.FirebirdClient/Common/Extensions.cs @@ -102,4 +102,38 @@ public static Encoding GetANSIEncoding() } } } + + public static int CountRunes(this ReadOnlySpan text) { + var count = 0; + var i = 0; + while(i < text.Length) { + if(char.IsHighSurrogate(text[i]) && i + 1 < text.Length && char.IsLowSurrogate(text[i + 1])) { + i += 2; + } + else { + i++; + } + count++; + } + return count; + } + + public static ReadOnlySpan TruncateStringToRuneCount(this ReadOnlySpan text, int maxRuneCount) { + var count = 0; + var i = 0; + while(i < text.Length && count < maxRuneCount) { + var nextI = i; + if(char.IsHighSurrogate(text[i]) && i + 1 < text.Length && char.IsLowSurrogate(text[i + 1])) { + nextI += 2; + } + else { + nextI++; + } + count++; + if(count <= maxRuneCount) { + i = nextI; + } + } + return text[..i]; + } } From 3a1e3ceee3c62af113d65bbe32b5e596d3162118 Mon Sep 17 00:00:00 2001 From: pl752 Date: Thu, 11 Dec 2025 11:11:52 +0500 Subject: [PATCH 03/11] Removed necessity to allocate 0 size array --- .../Client/Managed/Version10/GdsStatement.cs | 40 +++++++++---------- .../Client/Managed/XdrReaderWriter.cs | 6 +-- .../Client/Native/FesBlob.cs | 12 +++--- .../Client/Native/FesStatement.cs | 8 ++-- .../Common/Descriptor.cs | 2 +- 5 files changed, 34 insertions(+), 34 deletions(-) diff --git a/src/FirebirdSql.Data.FirebirdClient/Client/Managed/Version10/GdsStatement.cs b/src/FirebirdSql.Data.FirebirdClient/Client/Managed/Version10/GdsStatement.cs index b5c0f3377..4501479c9 100644 --- a/src/FirebirdSql.Data.FirebirdClient/Client/Managed/Version10/GdsStatement.cs +++ b/src/FirebirdSql.Data.FirebirdClient/Client/Managed/Version10/GdsStatement.cs @@ -315,7 +315,7 @@ public override void Execute(int timeout, IDescriptorFiller descriptorFiller) } var executeResponse = (GenericResponse)_database.ReadResponse(); - ProcessExecuteResponse(executeResponse); + ProcessExecuteResponse(executeResponse); if (DoRecordsAffected) { @@ -669,7 +669,7 @@ protected override void Free(int option) return; DoFreePacket(option); - ProcessFreeResponse(_database.ReadResponse()); + ProcessFreeResponse(_database.ReadResponse()); } protected override async ValueTask FreeAsync(int option, CancellationToken cancellationToken = default) { @@ -1351,7 +1351,7 @@ protected virtual byte[] WriteParameters() var field = _parameters[i]; try { - WriteRawParameter(xdr, field); + WriteRawParameter(xdr, field); xdr.Write(field.NullFlag); } catch (IOException ex) @@ -1414,25 +1414,25 @@ protected static void WriteRawParameter(IXdrWriter xdr, DbField field) if (byteCount > field.Length) { throw IscException.ForErrorCodes([IscCodes.isc_arith_except, IscCodes.isc_string_truncation]); - } + } Span stack = byteCount <= 512 ? stackalloc byte[byteCount] : Span.Empty; if (!stack.IsEmpty) { encoding.GetBytes(svalue.AsSpan(), stack); xdr.WriteOpaque(stack, field.Length); } - else - { + else + { var rented = System.Buffers.ArrayPool.Shared.Rent(byteCount); try - { + { var written = encoding.GetBytes(svalue, 0, svalue.Length, rented, 0); xdr.WriteOpaque(rented.AsSpan(0, written), field.Length); - } + } finally { System.Buffers.ArrayPool.Shared.Return(rented); - } + } } } break; @@ -1456,9 +1456,9 @@ protected static void WriteRawParameter(IXdrWriter xdr, DbField field) { encoding.GetBytes(svalue.AsSpan(), stack); xdr.WriteBuffer(stack); - } - else - { + } + else + { var rented = System.Buffers.ArrayPool.Shared.Rent(byteCount); try { @@ -1466,7 +1466,7 @@ protected static void WriteRawParameter(IXdrWriter xdr, DbField field) xdr.WriteBuffer(rented.AsSpan(0, written)); } finally - { + { System.Buffers.ArrayPool.Shared.Return(rented); } } @@ -1588,16 +1588,16 @@ protected static async ValueTask WriteRawParameterAsync(IXdrWriter xdr, DbField if (byteCount > field.Length) { throw IscException.ForErrorCodes([IscCodes.isc_arith_except, IscCodes.isc_string_truncation]); - } + } { var rented = System.Buffers.ArrayPool.Shared.Rent(byteCount); try - { + { var written = encoding.GetBytes(svalue, 0, svalue.Length, rented, 0); await xdr.WriteOpaqueAsync(rented.AsMemory(0, written), written, cancellationToken).ConfigureAwait(false); } finally - { + { System.Buffers.ArrayPool.Shared.Return(rented); } } @@ -1621,12 +1621,12 @@ protected static async ValueTask WriteRawParameterAsync(IXdrWriter xdr, DbField { var rented = System.Buffers.ArrayPool.Shared.Rent(byteCount); try - { + { var written = encoding.GetBytes(svalue, 0, svalue.Length, rented, 0); await xdr.WriteBufferAsync(rented.AsMemory(0, written), cancellationToken).ConfigureAwait(false); } finally - { + { System.Buffers.ArrayPool.Shared.Return(rented); } } @@ -1927,7 +1927,7 @@ protected void ClearAll() protected virtual DbValue[] ReadRow() { - var row = new DbValue[_fields.Count]; + var row = _fields.Count > 0 ? new DbValue[_fields.Count] : Array.Empty(); try { for (var i = 0; i < _fields.Count; i++) @@ -1956,7 +1956,7 @@ protected virtual DbValue[] ReadRow() } protected virtual async ValueTask ReadRowAsync(CancellationToken cancellationToken = default) { - var row = new DbValue[_fields.Count]; + var row = _fields.Count > 0 ? new DbValue[_fields.Count] : Array.Empty(); try { for (var i = 0; i < _fields.Count; i++) diff --git a/src/FirebirdSql.Data.FirebirdClient/Client/Managed/XdrReaderWriter.cs b/src/FirebirdSql.Data.FirebirdClient/Client/Managed/XdrReaderWriter.cs index e57096274..08289dd83 100644 --- a/src/FirebirdSql.Data.FirebirdClient/Client/Managed/XdrReaderWriter.cs +++ b/src/FirebirdSql.Data.FirebirdClient/Client/Managed/XdrReaderWriter.cs @@ -34,7 +34,7 @@ sealed class XdrReaderWriter : IXdrReader, IXdrWriter readonly Charset _charset; byte[] _smallBuffer; - const int StackallocThreshold = 1024; + const int StackallocThreshold = 1024; public XdrReaderWriter(IDataProvider dataProvider, Charset charset) { @@ -134,7 +134,7 @@ public async ValueTask ReadBytesAsync(Memory buffer, int count, Cancellati public byte[] ReadOpaque(int length) { - var buffer = length > 0 ? new byte[length] : _emptyBuf; + var buffer = length > 0 ? new byte[length] : Array.Empty(); ReadBytes(buffer, length); ReadPad((4 - length) & 3); return buffer; @@ -148,7 +148,7 @@ public void ReadOpaque(Span dst, int length) public async ValueTask ReadOpaqueAsync(int length, CancellationToken cancellationToken = default) { - var buffer = length > 0 ? new byte[length] : _emptyBuf; + 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; diff --git a/src/FirebirdSql.Data.FirebirdClient/Client/Native/FesBlob.cs b/src/FirebirdSql.Data.FirebirdClient/Client/Native/FesBlob.cs index 99022672f..17f7d5744 100644 --- a/src/FirebirdSql.Data.FirebirdClient/Client/Native/FesBlob.cs +++ b/src/FirebirdSql.Data.FirebirdClient/Client/Native/FesBlob.cs @@ -28,8 +28,8 @@ internal sealed class FesBlob : BlobBase { #region Fields - private FesDatabase _database; - private IntPtr[] _statusVector; + private readonly FesDatabase _database; + private readonly IntPtr[] _statusVector; private BlobHandle _blobHandle; #endregion @@ -84,7 +84,7 @@ public override void Create() ref _blobHandle, ref _blobId, 0, - new byte[0]); + Array.Empty()); _database.ProcessStatusVector(_statusVector); @@ -106,7 +106,7 @@ public override ValueTask CreateAsync(CancellationToken cancellationToken = defa ref _blobHandle, ref _blobId, 0, - new byte[0]); + Array.Empty()); _database.ProcessStatusVector(_statusVector); @@ -131,7 +131,7 @@ public override void Open() ref _blobHandle, ref _blobId, 0, - new byte[0]); + Array.Empty()); _database.ProcessStatusVector(_statusVector); @@ -151,7 +151,7 @@ public override ValueTask OpenAsync(CancellationToken cancellationToken = defaul ref _blobHandle, ref _blobId, 0, - new byte[0]); + Array.Empty()); _database.ProcessStatusVector(_statusVector); diff --git a/src/FirebirdSql.Data.FirebirdClient/Client/Native/FesStatement.cs b/src/FirebirdSql.Data.FirebirdClient/Client/Native/FesStatement.cs index 67a86c7cd..13788986d 100644 --- a/src/FirebirdSql.Data.FirebirdClient/Client/Native/FesStatement.cs +++ b/src/FirebirdSql.Data.FirebirdClient/Client/Native/FesStatement.cs @@ -408,7 +408,7 @@ public override void Execute(int timeout, IDescriptorFiller descriptorFiller) { var descriptor = XsqldaMarshaler.MarshalNativeToManaged(_database.Charset, outSqlda, true); - var values = new DbValue[descriptor.Count]; + var values = descriptor.Count > 0 ? new DbValue[descriptor.Count] : Array.Empty(); for (var i = 0; i < values.Length; i++) { @@ -480,7 +480,7 @@ public override async ValueTask ExecuteAsync(int timeout, IDescriptorFiller desc { var descriptor = XsqldaMarshaler.MarshalNativeToManaged(_database.Charset, outSqlda, true); - var values = new DbValue[descriptor.Count]; + var values = descriptor.Count > 0 ? new DbValue[descriptor.Count] : Array.Empty(); for (var i = 0; i < values.Length; i++) { @@ -569,7 +569,7 @@ public override DbValue[] Fetch() _database.ProcessStatusVector(_statusVector); - var row = new DbValue[_fields.ActualCount]; + var row = _fields.ActualCount > 0 ? new DbValue[_fields.ActualCount] : Array.Empty(); for (var i = 0; i < row.Length; i++) { var d = _fields[i]; @@ -639,7 +639,7 @@ public override async ValueTask FetchAsync(CancellationToken cancella _database.ProcessStatusVector(_statusVector); - var row = new DbValue[_fields.ActualCount]; + var row = _fields.ActualCount > 0 ? new DbValue[_fields.ActualCount] : Array.Empty(); for (var i = 0; i < row.Length; i++) { var d = _fields[i]; diff --git a/src/FirebirdSql.Data.FirebirdClient/Common/Descriptor.cs b/src/FirebirdSql.Data.FirebirdClient/Common/Descriptor.cs index beab8c576..ef47885d3 100644 --- a/src/FirebirdSql.Data.FirebirdClient/Common/Descriptor.cs +++ b/src/FirebirdSql.Data.FirebirdClient/Common/Descriptor.cs @@ -78,7 +78,7 @@ public Descriptor(short n) _version = IscCodes.SQLDA_VERSION1; _count = n; _actualCount = n; - _fields = new DbField[n]; + _fields = n > 0 ? new DbField[n] : Array.Empty(); for (var i = 0; i < n; i++) { From c058fc583148e42eee6b3f4edd4a88a8950f4d04 Mon Sep 17 00:00:00 2001 From: pl752 Date: Thu, 11 Dec 2025 11:14:03 +0500 Subject: [PATCH 04/11] Elliminated some linq queries and allocations --- .../Client/Managed/GdsConnection.cs | 4 ++-- .../Client/Managed/Version13/GdsDatabase.cs | 8 ++++---- .../Managed/Version13/GdsServiceManager.cs | 4 ++-- .../Client/Managed/Version16/GdsBatch.cs | 16 ++++++++++++---- 4 files changed, 20 insertions(+), 12 deletions(-) diff --git a/src/FirebirdSql.Data.FirebirdClient/Client/Managed/GdsConnection.cs b/src/FirebirdSql.Data.FirebirdClient/Client/Managed/GdsConnection.cs index cc39ba6b2..0f36505aa 100644 --- a/src/FirebirdSql.Data.FirebirdClient/Client/Managed/GdsConnection.cs +++ b/src/FirebirdSql.Data.FirebirdClient/Client/Managed/GdsConnection.cs @@ -216,7 +216,7 @@ public void Identify(string database) break; } - if (AuthBlock.ServerKeys.Any()) + if (AuthBlock.ServerKeys.Length > 0) { AuthBlock.SendWireCryptToBuffer(); Xdr.Flush(); @@ -330,7 +330,7 @@ await Xdr.ReadBooleanAsync(cancellationToken).ConfigureAwait(false), break; } - if (AuthBlock.ServerKeys.Any()) + if (AuthBlock.ServerKeys.Length > 0) { await AuthBlock.SendWireCryptToBufferAsync(cancellationToken).ConfigureAwait(false); await Xdr.FlushAsync(cancellationToken).ConfigureAwait(false); diff --git a/src/FirebirdSql.Data.FirebirdClient/Client/Managed/Version13/GdsDatabase.cs b/src/FirebirdSql.Data.FirebirdClient/Client/Managed/Version13/GdsDatabase.cs index d51bb9117..4b750e272 100644 --- a/src/FirebirdSql.Data.FirebirdClient/Client/Managed/Version13/GdsDatabase.cs +++ b/src/FirebirdSql.Data.FirebirdClient/Client/Managed/Version13/GdsDatabase.cs @@ -51,7 +51,7 @@ public override void Attach(DatabaseParameterBufferBase dpb, string database, by var genericResponse = (GenericResponse)response; ProcessAttachResponse(genericResponse); - if (genericResponse.Data.Any()) + if (genericResponse.Data.Length > 0) { AuthBlock.SendWireCryptToBuffer(); Xdr.Flush(); @@ -100,7 +100,7 @@ public override async ValueTask AttachAsync(DatabaseParameterBufferBase dpb, str var genericResponse = (GenericResponse)response; await ProcessAttachResponseAsync(genericResponse, cancellationToken).ConfigureAwait(false); - if (genericResponse.Data.Any()) + if (genericResponse.Data.Length > 0) { await AuthBlock.SendWireCryptToBufferAsync(cancellationToken).ConfigureAwait(false); await Xdr.FlushAsync(cancellationToken).ConfigureAwait(false); @@ -183,7 +183,7 @@ public override void CreateDatabase(DatabaseParameterBufferBase dpb, string data var genericResponse = (GenericResponse)response; ProcessCreateResponse(genericResponse); - if (genericResponse.Data.Any()) + if (genericResponse.Data.Length > 0) { AuthBlock.SendWireCryptToBuffer(); Xdr.Flush(); @@ -223,7 +223,7 @@ public override async ValueTask CreateDatabaseAsync(DatabaseParameterBufferBase var genericResponse = (GenericResponse)response; await ProcessCreateResponseAsync(genericResponse, cancellationToken).ConfigureAwait(false); - if (genericResponse.Data.Any()) + if (genericResponse.Data.Length > 0) { await AuthBlock.SendWireCryptToBufferAsync(cancellationToken).ConfigureAwait(false); await Xdr.FlushAsync(cancellationToken).ConfigureAwait(false); diff --git a/src/FirebirdSql.Data.FirebirdClient/Client/Managed/Version13/GdsServiceManager.cs b/src/FirebirdSql.Data.FirebirdClient/Client/Managed/Version13/GdsServiceManager.cs index 8e5218d70..d7b7c53a8 100644 --- a/src/FirebirdSql.Data.FirebirdClient/Client/Managed/Version13/GdsServiceManager.cs +++ b/src/FirebirdSql.Data.FirebirdClient/Client/Managed/Version13/GdsServiceManager.cs @@ -52,7 +52,7 @@ public override void Attach(ServiceParameterBufferBase spb, string dataSource, i var genericResponse = (GenericResponse)response; base.ProcessAttachResponse(genericResponse); - if (genericResponse.Data.Any()) + if (genericResponse.Data.Length > 0) { Database.AuthBlock.SendWireCryptToBuffer(); Database.Xdr.Flush(); @@ -98,7 +98,7 @@ public override async ValueTask AttachAsync(ServiceParameterBufferBase spb, stri var genericResponse = (GenericResponse)response; await base.ProcessAttachResponseAsync(genericResponse, cancellationToken).ConfigureAwait(false); - if (genericResponse.Data.Any()) + if (genericResponse.Data.Length > 0) { await Database.AuthBlock.SendWireCryptToBufferAsync(cancellationToken).ConfigureAwait(false); await Database.Xdr.FlushAsync(cancellationToken).ConfigureAwait(false); diff --git a/src/FirebirdSql.Data.FirebirdClient/Client/Managed/Version16/GdsBatch.cs b/src/FirebirdSql.Data.FirebirdClient/Client/Managed/Version16/GdsBatch.cs index def16aeee..f46d19b64 100644 --- a/src/FirebirdSql.Data.FirebirdClient/Client/Managed/Version16/GdsBatch.cs +++ b/src/FirebirdSql.Data.FirebirdClient/Client/Managed/Version16/GdsBatch.cs @@ -145,13 +145,21 @@ public override async ValueTask ExecuteAsync(int count, IDe public override int ComputeBatchSize(int count, IDescriptorFiller descriptorFiller) { - var parametersData = GetParametersData(count, descriptorFiller); - return parametersData.Sum(x => x.Length); + var total = 0; + for(var i = 0; i < count; i++) { + var item = _statement.GetParameterData(descriptorFiller, i); + total += item.Length; + } + return total; } public override async ValueTask ComputeBatchSizeAsync(int count, IDescriptorFiller descriptorFiller, CancellationToken cancellationToken = default) { - var parametersData = await GetParametersDataAsync(count, descriptorFiller, cancellationToken).ConfigureAwait(false); - return parametersData.Sum(x => x.Length); + var total = 0; + for(var i = 0; i < count; i++) { + var item = await _statement.GetParameterDataAsync(descriptorFiller, i, cancellationToken).ConfigureAwait(false); + total += item.Length; + } + return total; } public override void Release() From 8a921c258c9ff46bc1350e78815da68b4bbf4913 Mon Sep 17 00:00:00 2001 From: pl752 Date: Thu, 11 Dec 2025 12:21:32 +0500 Subject: [PATCH 05/11] Adjusted code style --- .../Client/Managed/AuthBlock.cs | 2 +- .../Managed/FirebirdNetworkHandlingWrapper.cs | 20 ++-- .../Client/Managed/Version10/GdsArray.cs | 24 ++--- .../Client/Managed/Version10/GdsBlob.cs | 8 +- .../Client/Managed/Version10/GdsDatabase.cs | 12 +-- .../Client/Managed/Version10/GdsStatement.cs | 18 ++-- .../Client/Managed/Version13/GdsStatement.cs | 92 +++++++++---------- .../Client/Managed/Version16/GdsBatch.cs | 6 +- .../Client/Managed/XdrReaderWriter.cs | 23 +++-- .../Common/DbField.cs | 3 +- .../Common/Extensions.cs | 27 ++++-- .../Common/IscHelper.cs | 3 +- .../Common/TypeEncoder.cs | 4 +- 13 files changed, 134 insertions(+), 108 deletions(-) diff --git a/src/FirebirdSql.Data.FirebirdClient/Client/Managed/AuthBlock.cs b/src/FirebirdSql.Data.FirebirdClient/Client/Managed/AuthBlock.cs index 55ebdf4b9..48442389e 100644 --- a/src/FirebirdSql.Data.FirebirdClient/Client/Managed/AuthBlock.cs +++ b/src/FirebirdSql.Data.FirebirdClient/Client/Managed/AuthBlock.cs @@ -66,7 +66,7 @@ public AuthBlock(GdsConnection connection, string user, string password, WireCry WireCrypt = wireCrypt; } - public byte[] UserIdentificationData() + public byte[] UserIdentificationData() { using (var result = new MemoryStream(256)) { diff --git a/src/FirebirdSql.Data.FirebirdClient/Client/Managed/FirebirdNetworkHandlingWrapper.cs b/src/FirebirdSql.Data.FirebirdClient/Client/Managed/FirebirdNetworkHandlingWrapper.cs index 2f1ef41ff..0dc364624 100644 --- a/src/FirebirdSql.Data.FirebirdClient/Client/Managed/FirebirdNetworkHandlingWrapper.cs +++ b/src/FirebirdSql.Data.FirebirdClient/Client/Managed/FirebirdNetworkHandlingWrapper.cs @@ -90,21 +90,27 @@ public int Read(byte[] buffer, int offset, int count) public int Read(Span buffer, int offset, int count) { - if (_inputBuffer.Count < count) { + if (_inputBuffer.Count < count) + { var readBuffer = _readBuffer; int read; - try { + try + { read = _dataProvider.Read(readBuffer, 0, readBuffer.Length); } - catch (IOException) { + catch (IOException) + { IOFailed = true; throw; } - if (read != 0) { - if (_decryptor != null) { + if (read != 0) + { + if (_decryptor != null) + { _decryptor.ProcessBytes(readBuffer, 0, read, readBuffer, 0); } - if (_decompressor != null) { + if (_decompressor != null) + { read = HandleDecompression(readBuffer, read); readBuffer = _compressionBuffer; } @@ -182,7 +188,7 @@ public ValueTask WriteAsync(ReadOnlyMemory buffer, int offset, int count, var span = buffer.Span.Slice(offset, count); foreach (var b in span) _outputBuffer.Enqueue(b); - return ValueTask2.CompletedTask; + return ValueTask.CompletedTask; } public void Flush() diff --git a/src/FirebirdSql.Data.FirebirdClient/Client/Managed/Version10/GdsArray.cs b/src/FirebirdSql.Data.FirebirdClient/Client/Managed/Version10/GdsArray.cs index e22b9c43d..0cf96c0b8 100644 --- a/src/FirebirdSql.Data.FirebirdClient/Client/Managed/Version10/GdsArray.cs +++ b/src/FirebirdSql.Data.FirebirdClient/Client/Managed/Version10/GdsArray.cs @@ -191,10 +191,10 @@ public override async ValueTask PutSliceAsync(Array sourceArray, int sliceLength protected override Array DecodeSlice(byte[] slice) { - var systemType = GetSystemType(); + var systemType = GetSystemType(); var lengths = new int[Descriptor.Dimensions]; var lowerBounds = new int[Descriptor.Dimensions]; - var index = 0; + var index = 0; for (var i = 0; i < Descriptor.Dimensions; i++) { @@ -213,7 +213,7 @@ protected override Array DecodeSlice(byte[] slice) int type = TypeHelper.GetSqlTypeFromBlrType(Descriptor.DataType); DbDataType dbType = TypeHelper.GetDbDataTypeFromBlrType(Descriptor.DataType, 0, Descriptor.Scale); - using (var ms = new MemoryStream(slice)) + using (var ms = new MemoryStream(slice)) { var xdr = new XdrReaderWriter(new DataProviderStreamWrapper(ms), _database.Charset); while (ms.Position < ms.Length) @@ -284,10 +284,10 @@ protected override Array DecodeSlice(byte[] slice) } protected override async ValueTask DecodeSliceAsync(byte[] slice, CancellationToken cancellationToken = default) { - var systemType = GetSystemType(); + var systemType = GetSystemType(); var lengths = new int[Descriptor.Dimensions]; var lowerBounds = new int[Descriptor.Dimensions]; - var index = 0; + var index = 0; for (var i = 0; i < Descriptor.Dimensions; i++) { @@ -306,7 +306,7 @@ protected override async ValueTask DecodeSliceAsync(byte[] slice, Cancell int type = TypeHelper.GetSqlTypeFromBlrType(Descriptor.DataType); DbDataType dbType = TypeHelper.GetDbDataTypeFromBlrType(Descriptor.DataType, 0, Descriptor.Scale); - using (var ms = new MemoryStream(slice)) + using (var ms = new MemoryStream(slice)) { var xdr = new XdrReaderWriter(new DataProviderStreamWrapper(ms), _database.Charset); while (ms.Position < ms.Length) @@ -532,16 +532,16 @@ private async ValueTask ReceiveSliceResponseAsync(ArrayDesc desc, Cancel private byte[] EncodeSliceArray(Array sourceArray) { - var charset = _database.Charset; + var charset = _database.Charset; var subType = (Descriptor.Scale < 0) ? 2 : 0; - using (var ms = new MemoryStream()) + using (var ms = new MemoryStream()) { var xdr = new XdrReaderWriter(new DataProviderStreamWrapper(ms), _database.Charset); int type = TypeHelper.GetSqlTypeFromBlrType(Descriptor.DataType); DbDataType dbType = TypeHelper.GetDbDataTypeFromBlrType(Descriptor.DataType, subType, Descriptor.Scale); - foreach (var source in sourceArray) + foreach (var source in sourceArray) { switch (dbType) { @@ -602,16 +602,16 @@ private byte[] EncodeSliceArray(Array sourceArray) } private async ValueTask EncodeSliceArrayAsync(Array sourceArray, CancellationToken cancellationToken = default) { - var charset = _database.Charset; + var charset = _database.Charset; var subType = (Descriptor.Scale < 0) ? 2 : 0; - using (var ms = new MemoryStream()) + using (var ms = new MemoryStream()) { var xdr = new XdrReaderWriter(new DataProviderStreamWrapper(ms), _database.Charset); int type = TypeHelper.GetSqlTypeFromBlrType(Descriptor.DataType); DbDataType dbType = TypeHelper.GetDbDataTypeFromBlrType(Descriptor.DataType, subType, Descriptor.Scale); - foreach (var source in sourceArray) + foreach (var source in sourceArray) { switch (dbType) { diff --git a/src/FirebirdSql.Data.FirebirdClient/Client/Managed/Version10/GdsBlob.cs b/src/FirebirdSql.Data.FirebirdClient/Client/Managed/Version10/GdsBlob.cs index dfc5d66c9..485666846 100644 --- a/src/FirebirdSql.Data.FirebirdClient/Client/Managed/Version10/GdsBlob.cs +++ b/src/FirebirdSql.Data.FirebirdClient/Client/Managed/Version10/GdsBlob.cs @@ -237,7 +237,7 @@ public override void GetSegment(Stream stream) var srcpos = 0; while (srcpos < buffer.Length) - { + { len = (int)IscHelper.VaxInteger(buffer, srcpos, 2); srcpos += 2; @@ -286,7 +286,7 @@ public override async ValueTask GetSegmentAsync(Stream stream, CancellationToken var srcpos = 0; while (srcpos < buffer.Length) - { + { len = (int)IscHelper.VaxInteger(buffer, srcpos, 2); srcpos += 2; @@ -337,7 +337,7 @@ public override byte[] GetSegment() var tmp = new byte[requested * 2]; while (posInInput < buffer.Length) - { + { var len = (int)IscHelper.VaxInteger(buffer, posInInput, 2); posInInput += 2; @@ -393,7 +393,7 @@ public override async ValueTask GetSegmentAsync(CancellationToken cancel var tmp = new byte[requested * 2]; while (posInInput < buffer.Length) - { + { var len = (int)IscHelper.VaxInteger(buffer, posInInput, 2); posInInput += 2; diff --git a/src/FirebirdSql.Data.FirebirdClient/Client/Managed/Version10/GdsDatabase.cs b/src/FirebirdSql.Data.FirebirdClient/Client/Managed/Version10/GdsDatabase.cs index fa1aefcb6..b69a03210 100644 --- a/src/FirebirdSql.Data.FirebirdClient/Client/Managed/Version10/GdsDatabase.cs +++ b/src/FirebirdSql.Data.FirebirdClient/Client/Managed/Version10/GdsDatabase.cs @@ -32,7 +32,7 @@ internal class GdsDatabase : DatabaseBase protected const int PartnerIdentification = 0; protected const int AddressOfAstRoutine = 0; protected const int ArgumentToAstRoutine = 0; -protected internal const int DatabaseObjectId = 0; + protected internal const int DatabaseObjectId = 0; protected internal const int Incarnation = 0; const int StackallocThreshold = 512; @@ -73,9 +73,9 @@ public AuthBlock AuthBlock get { return _connection.AuthBlock; } } - #endregion + #endregion - #region Constructors + #region Constructors public GdsDatabase(GdsConnection connection) : base(connection.Charset, connection.PacketSize, connection.Dialect) @@ -84,11 +84,11 @@ public GdsDatabase(GdsConnection connection) _handle = -1; } - #endregion + #endregion - #region Attach/Detach Methods + #region Attach/Detach Methods - public override void Attach(DatabaseParameterBufferBase dpb, string database, byte[] cryptKey) + public override void Attach(DatabaseParameterBufferBase dpb, string database, byte[] cryptKey) { try { diff --git a/src/FirebirdSql.Data.FirebirdClient/Client/Managed/Version10/GdsStatement.cs b/src/FirebirdSql.Data.FirebirdClient/Client/Managed/Version10/GdsStatement.cs index 4501479c9..9341b0d49 100644 --- a/src/FirebirdSql.Data.FirebirdClient/Client/Managed/Version10/GdsStatement.cs +++ b/src/FirebirdSql.Data.FirebirdClient/Client/Managed/Version10/GdsStatement.cs @@ -315,7 +315,7 @@ public override void Execute(int timeout, IDescriptorFiller descriptorFiller) } var executeResponse = (GenericResponse)_database.ReadResponse(); - ProcessExecuteResponse(executeResponse); + ProcessExecuteResponse(executeResponse); if (DoRecordsAffected) { @@ -669,7 +669,7 @@ protected override void Free(int option) return; DoFreePacket(option); - ProcessFreeResponse(_database.ReadResponse()); + ProcessFreeResponse(_database.ReadResponse()); } protected override async ValueTask FreeAsync(int option, CancellationToken cancellationToken = default) { @@ -1194,14 +1194,14 @@ private Descriptor[] ParseTruncSqlInfoSpan(ReadOnlySpan info, ReadOnlySpan default: throw IscException.ForErrorCode(IscCodes.isc_dsql_sqlda_err); - } - } - // just to get out of the loop - BreakSpan: - { } } - return rowDescs; + } + // just to get out of the loop + BreakSpan: + { } } + return rowDescs; + } private ValueTask ParseTruncSqlInfoAsync(byte[] info, ReadOnlyMemory items, Descriptor[] rowDescs, CancellationToken cancellationToken) => ParseTruncSqlInfoSpanAsync(info.AsMemory(), items, rowDescs, cancellationToken); @@ -1351,7 +1351,7 @@ protected virtual byte[] WriteParameters() var field = _parameters[i]; try { - WriteRawParameter(xdr, field); + WriteRawParameter(xdr, field); xdr.Write(field.NullFlag); } catch (IOException ex) diff --git a/src/FirebirdSql.Data.FirebirdClient/Client/Managed/Version13/GdsStatement.cs b/src/FirebirdSql.Data.FirebirdClient/Client/Managed/Version13/GdsStatement.cs index 04a21da46..8660afb30 100644 --- a/src/FirebirdSql.Data.FirebirdClient/Client/Managed/Version13/GdsStatement.cs +++ b/src/FirebirdSql.Data.FirebirdClient/Client/Managed/Version13/GdsStatement.cs @@ -151,29 +151,29 @@ protected override DbValue[] ReadRow() { if (_fields.Count > 0) { - var len = (int)Math.Ceiling(_fields.Count / 8d); - var rented = ArrayPool.Shared.Rent(len); - try - { - _database.Xdr.ReadOpaque(rented.AsSpan(0, len), len); - for (var i = 0; i < _fields.Count; i++) - { - var isNull = (rented[i / 8] & (1 << (i % 8))) != 0; - if (isNull) - { - row[i] = new DbValue(this, _fields[i], null); - } - else - { - var value = ReadRawValue(_database.Xdr, _fields[i]); - row[i] = new DbValue(this, _fields[i], value); - } - } - } - finally - { - ArrayPool.Shared.Return(rented); - } + var len = (int)Math.Ceiling(_fields.Count / 8d); + var rented = ArrayPool.Shared.Rent(len); + try + { + _database.Xdr.ReadOpaque(rented.AsSpan(0, len), len); + for (var i = 0; i < _fields.Count; i++) + { + var isNull = (rented[i / 8] & (1 << (i % 8))) != 0; + if (isNull) + { + row[i] = new DbValue(this, _fields[i], null); + } + else + { + var value = ReadRawValue(_database.Xdr, _fields[i]); + row[i] = new DbValue(this, _fields[i], value); + } + } + } + finally + { + ArrayPool.Shared.Return(rented); + } } } catch (IOException ex) @@ -189,29 +189,29 @@ protected override async ValueTask ReadRowAsync(CancellationToken can { if (_fields.Count > 0) { - var len = (int)Math.Ceiling(_fields.Count / 8d); - var rented = ArrayPool.Shared.Rent(len); - try - { - await _database.Xdr.ReadOpaqueAsync(rented.AsMemory(0, len), len, cancellationToken).ConfigureAwait(false); - for (var i = 0; i < _fields.Count; i++) - { - var isNull = (rented[i / 8] & (1 << (i % 8))) != 0; - if (isNull) - { - row[i] = new DbValue(this, _fields[i], null); - } - else - { - var value = await ReadRawValueAsync(_database.Xdr, _fields[i], cancellationToken).ConfigureAwait(false); - row[i] = new DbValue(this, _fields[i], value); - } - } - } - finally - { - ArrayPool.Shared.Return(rented); - } + var len = (int)Math.Ceiling(_fields.Count / 8d); + var rented = ArrayPool.Shared.Rent(len); + try + { + await _database.Xdr.ReadOpaqueAsync(rented.AsMemory(0, len), len, cancellationToken).ConfigureAwait(false); + for (var i = 0; i < _fields.Count; i++) + { + var isNull = (rented[i / 8] & (1 << (i % 8))) != 0; + if (isNull) + { + row[i] = new DbValue(this, _fields[i], null); + } + else + { + var value = await ReadRawValueAsync(_database.Xdr, _fields[i], cancellationToken).ConfigureAwait(false); + row[i] = new DbValue(this, _fields[i], value); + } + } + } + finally + { + ArrayPool.Shared.Return(rented); + } } } catch (IOException ex) diff --git a/src/FirebirdSql.Data.FirebirdClient/Client/Managed/Version16/GdsBatch.cs b/src/FirebirdSql.Data.FirebirdClient/Client/Managed/Version16/GdsBatch.cs index f46d19b64..7949094d3 100644 --- a/src/FirebirdSql.Data.FirebirdClient/Client/Managed/Version16/GdsBatch.cs +++ b/src/FirebirdSql.Data.FirebirdClient/Client/Managed/Version16/GdsBatch.cs @@ -146,7 +146,8 @@ public override async ValueTask ExecuteAsync(int count, IDe public override int ComputeBatchSize(int count, IDescriptorFiller descriptorFiller) { var total = 0; - for(var i = 0; i < count; i++) { + for(var i = 0; i < count; i++) + { var item = _statement.GetParameterData(descriptorFiller, i); total += item.Length; } @@ -155,7 +156,8 @@ public override int ComputeBatchSize(int count, IDescriptorFiller descriptorFill public override async ValueTask ComputeBatchSizeAsync(int count, IDescriptorFiller descriptorFiller, CancellationToken cancellationToken = default) { var total = 0; - for(var i = 0; i < count; i++) { + for(var i = 0; i < count; i++) + { var item = await _statement.GetParameterDataAsync(descriptorFiller, i, cancellationToken).ConfigureAwait(false); total += item.Length; } diff --git a/src/FirebirdSql.Data.FirebirdClient/Client/Managed/XdrReaderWriter.cs b/src/FirebirdSql.Data.FirebirdClient/Client/Managed/XdrReaderWriter.cs index 08289dd83..a1f4d6c07 100644 --- a/src/FirebirdSql.Data.FirebirdClient/Client/Managed/XdrReaderWriter.cs +++ b/src/FirebirdSql.Data.FirebirdClient/Client/Managed/XdrReaderWriter.cs @@ -44,7 +44,7 @@ public XdrReaderWriter(IDataProvider dataProvider, Charset charset) _smallBuffer = new byte[8]; } - public XdrReaderWriter(IDataProvider dataProvider) + public XdrReaderWriter(IDataProvider dataProvider) : this(dataProvider, Charset.DefaultCharset) { } @@ -74,14 +74,18 @@ public byte[] ReadBytes(byte[] buffer, int count) public void ReadBytes(Span dst, int count) { - if (count > 0) { + if (count > 0) + { var toRead = count; var currentlyRead = -1; - while (toRead > 0 && currentlyRead != 0) { + while (toRead > 0 && currentlyRead != 0) + { toRead -= (currentlyRead = _dataProvider.Read(dst, count - toRead, toRead)); } - if (currentlyRead == 0) { - if (_dataProvider is ITracksIOFailure tracksIOFailure) { + if (currentlyRead == 0) + { + if (_dataProvider is ITracksIOFailure tracksIOFailure) + { tracksIOFailure.IOFailed = true; } throw new IOException($"Missing {toRead} bytes to fill total {count}."); @@ -785,7 +789,8 @@ public void WriteBuffer(ReadOnlySpan buffer) { var length = buffer.Length; Write(length); - if (length > 0) { + if (length > 0) + { _dataProvider.Write(buffer); WritePad((4 - length) & 3); } @@ -890,13 +895,15 @@ public void WriteTyped(int type, ReadOnlySpan buffer) { int length; Span typeByte = stackalloc byte[1]; - if (buffer == null) { + if (buffer == null) + { Write(1); typeByte[0] = (byte)type; _dataProvider.Write(typeByte); length = 1; } - else { + else + { length = buffer.Length + 1; Write(length); typeByte[0] = (byte)type; diff --git a/src/FirebirdSql.Data.FirebirdClient/Common/DbField.cs b/src/FirebirdSql.Data.FirebirdClient/Common/DbField.cs index efc23524a..b5d27b5d8 100644 --- a/src/FirebirdSql.Data.FirebirdClient/Common/DbField.cs +++ b/src/FirebirdSql.Data.FirebirdClient/Common/DbField.cs @@ -328,7 +328,8 @@ public void SetValue(byte[] buffer) if((Length % Charset.BytesPerCharacter) == 0) { var runes = s.CountRunes(); - if(runes > CharCount) { + if(runes > CharCount) + { s = new string(s.TruncateStringToRuneCount(CharCount)); } } diff --git a/src/FirebirdSql.Data.FirebirdClient/Common/Extensions.cs b/src/FirebirdSql.Data.FirebirdClient/Common/Extensions.cs index 6c878e669..57ab69018 100644 --- a/src/FirebirdSql.Data.FirebirdClient/Common/Extensions.cs +++ b/src/FirebirdSql.Data.FirebirdClient/Common/Extensions.cs @@ -103,14 +103,18 @@ public static Encoding GetANSIEncoding() } } - public static int CountRunes(this ReadOnlySpan text) { + public static int CountRunes(this ReadOnlySpan text) + { var count = 0; var i = 0; - while(i < text.Length) { - if(char.IsHighSurrogate(text[i]) && i + 1 < text.Length && char.IsLowSurrogate(text[i + 1])) { + while(i < text.Length) + { + if(char.IsHighSurrogate(text[i]) && i + 1 < text.Length && char.IsLowSurrogate(text[i + 1])) + { i += 2; } - else { + else + { i++; } count++; @@ -118,19 +122,24 @@ public static int CountRunes(this ReadOnlySpan text) { return count; } - public static ReadOnlySpan TruncateStringToRuneCount(this ReadOnlySpan text, int maxRuneCount) { + public static ReadOnlySpan TruncateStringToRuneCount(this ReadOnlySpan text, int maxRuneCount) + { var count = 0; var i = 0; - while(i < text.Length && count < maxRuneCount) { + while(i < text.Length && count < maxRuneCount) + { var nextI = i; - if(char.IsHighSurrogate(text[i]) && i + 1 < text.Length && char.IsLowSurrogate(text[i + 1])) { + if(char.IsHighSurrogate(text[i]) && i + 1 < text.Length && char.IsLowSurrogate(text[i + 1])) + { nextI += 2; } - else { + else + { nextI++; } count++; - if(count <= maxRuneCount) { + if(count <= maxRuneCount) + { i = nextI; } } diff --git a/src/FirebirdSql.Data.FirebirdClient/Common/IscHelper.cs b/src/FirebirdSql.Data.FirebirdClient/Common/IscHelper.cs index b25a86b12..1ebfa2aa0 100644 --- a/src/FirebirdSql.Data.FirebirdClient/Common/IscHelper.cs +++ b/src/FirebirdSql.Data.FirebirdClient/Common/IscHelper.cs @@ -238,7 +238,8 @@ public static long VaxInteger(ReadOnlySpan buffer, int index, int length) var value = 0L; var shift = 0; var i = index; - while(--length >= 0) { + while(--length >= 0) + { value += (buffer[i++] & 0xffL) << shift; shift += 8; } diff --git a/src/FirebirdSql.Data.FirebirdClient/Common/TypeEncoder.cs b/src/FirebirdSql.Data.FirebirdClient/Common/TypeEncoder.cs index b20e57677..07ea637f9 100644 --- a/src/FirebirdSql.Data.FirebirdClient/Common/TypeEncoder.cs +++ b/src/FirebirdSql.Data.FirebirdClient/Common/TypeEncoder.cs @@ -114,8 +114,8 @@ 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) { From 1c46f86beb4d2afe6f287957d9c3847dfa4d3750 Mon Sep 17 00:00:00 2001 From: pl752 Date: Fri, 12 Dec 2025 13:57:07 +0500 Subject: [PATCH 06/11] Fixed boolean buffer overwrite mishap --- .../Client/Managed/XdrReaderWriter.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/FirebirdSql.Data.FirebirdClient/Client/Managed/XdrReaderWriter.cs b/src/FirebirdSql.Data.FirebirdClient/Client/Managed/XdrReaderWriter.cs index a1f4d6c07..3d43e79de 100644 --- a/src/FirebirdSql.Data.FirebirdClient/Client/Managed/XdrReaderWriter.cs +++ b/src/FirebirdSql.Data.FirebirdClient/Client/Managed/XdrReaderWriter.cs @@ -34,6 +34,7 @@ sealed class XdrReaderWriter : IXdrReader, IXdrWriter readonly Charset _charset; byte[] _smallBuffer; + byte[] _boolbuffer; const int StackallocThreshold = 1024; public XdrReaderWriter(IDataProvider dataProvider, Charset charset) @@ -42,6 +43,7 @@ public XdrReaderWriter(IDataProvider dataProvider, Charset charset) _charset = charset; _smallBuffer = new byte[8]; + _boolbuffer = new byte[1]; } public XdrReaderWriter(IDataProvider dataProvider) @@ -450,9 +452,8 @@ public bool ReadBoolean() } public async ValueTask ReadBooleanAsync(CancellationToken cancellationToken = default) { - await ReadBytesAsync(_smallBuffer, 1, cancellationToken).ConfigureAwait(false); - await ReadPadAsync((4 - 1) & 3, cancellationToken).ConfigureAwait(false); - return TypeDecoder.DecodeBoolean(_smallBuffer); + await ReadOpaqueAsync(_boolbuffer, 1, cancellationToken).ConfigureAwait(false); + return TypeDecoder.DecodeBoolean(_boolbuffer); } public FbZonedDateTime ReadZonedDateTime(bool isExtended) From c5fadc8461d44fd45a93f7a12162df60c366e455 Mon Sep 17 00:00:00 2001 From: pl752 Date: Fri, 12 Dec 2025 14:11:29 +0500 Subject: [PATCH 07/11] Fixed static/nonstatic call mismatch (Some internal methods were turned static, breaking tests) --- .../Srp256ClientTests.cs | 4 ++-- src/FirebirdSql.Data.FirebirdClient.Tests/SrpClientTests.cs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/FirebirdSql.Data.FirebirdClient.Tests/Srp256ClientTests.cs b/src/FirebirdSql.Data.FirebirdClient.Tests/Srp256ClientTests.cs index e3b466aa6..9aca5eead 100644 --- a/src/FirebirdSql.Data.FirebirdClient.Tests/Srp256ClientTests.cs +++ b/src/FirebirdSql.Data.FirebirdClient.Tests/Srp256ClientTests.cs @@ -31,9 +31,9 @@ public void KeyMatchTest() var user = "SYSDBA"; var password = "masterkey"; var client = new Srp256Client(); - var salt = client.GetSalt(); + var salt = Srp256Client.GetSalt(); var serverKeyPair = client.ServerSeed(user, password, salt); - var serverSessionKey = client.GetServerSessionKey(user, password, salt, client.PublicKey, serverKeyPair.Item1, serverKeyPair.Item2); + var serverSessionKey = Srp256Client.GetServerSessionKey(user, password, salt, client.PublicKey, serverKeyPair.Item1, serverKeyPair.Item2); client.ClientProof(user, password, salt, serverKeyPair.Item1); Assert.AreEqual(serverSessionKey.ToString(), client.SessionKey.ToString()); } diff --git a/src/FirebirdSql.Data.FirebirdClient.Tests/SrpClientTests.cs b/src/FirebirdSql.Data.FirebirdClient.Tests/SrpClientTests.cs index 0c82c6d44..3d3d9ba6c 100644 --- a/src/FirebirdSql.Data.FirebirdClient.Tests/SrpClientTests.cs +++ b/src/FirebirdSql.Data.FirebirdClient.Tests/SrpClientTests.cs @@ -31,9 +31,9 @@ public void KeyMatchTest() var user = "SYSDBA"; var password = "masterkey"; var client = new SrpClient(); - var salt = client.GetSalt(); + var salt = SrpClient.GetSalt(); var serverKeyPair = client.ServerSeed(user, password, salt); - var serverSessionKey = client.GetServerSessionKey(user, password, salt, client.PublicKey, serverKeyPair.Item1, serverKeyPair.Item2); + var serverSessionKey = SrpClient.GetServerSessionKey(user, password, salt, client.PublicKey, serverKeyPair.Item1, serverKeyPair.Item2); client.ClientProof(user, password, salt, serverKeyPair.Item1); Assert.AreEqual(serverSessionKey.ToString(), client.SessionKey.ToString()); } From d1c61a7503a33d6104e8e83caf9f7f6d430b3c36 Mon Sep 17 00:00:00 2001 From: pl752 Date: Fri, 12 Dec 2025 23:19:42 +0500 Subject: [PATCH 08/11] Consolidated bool and pad reading into single read --- .../Client/Managed/XdrReaderWriter.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/FirebirdSql.Data.FirebirdClient/Client/Managed/XdrReaderWriter.cs b/src/FirebirdSql.Data.FirebirdClient/Client/Managed/XdrReaderWriter.cs index 3d43e79de..f9b78b15c 100644 --- a/src/FirebirdSql.Data.FirebirdClient/Client/Managed/XdrReaderWriter.cs +++ b/src/FirebirdSql.Data.FirebirdClient/Client/Managed/XdrReaderWriter.cs @@ -446,13 +446,13 @@ public async ValueTask ReadDecimalAsync(int type, int scale, Cancellati public bool ReadBoolean() { - Span bytes = stackalloc byte[1]; - ReadOpaque(bytes, 1); + Span bytes = stackalloc byte[4]; + ReadBytes(bytes, 4); return TypeDecoder.DecodeBoolean(bytes); } public async ValueTask ReadBooleanAsync(CancellationToken cancellationToken = default) { - await ReadOpaqueAsync(_boolbuffer, 1, cancellationToken).ConfigureAwait(false); + await ReadBytesAsync(_boolbuffer, 4, cancellationToken).ConfigureAwait(false); return TypeDecoder.DecodeBoolean(_boolbuffer); } From f87f96506ab1fddcc8da46bb360c4cfe56107c86 Mon Sep 17 00:00:00 2001 From: pl752 Date: Fri, 12 Dec 2025 23:20:29 +0500 Subject: [PATCH 09/11] Fixed wrong commit push --- .../Client/Managed/XdrReaderWriter.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/FirebirdSql.Data.FirebirdClient/Client/Managed/XdrReaderWriter.cs b/src/FirebirdSql.Data.FirebirdClient/Client/Managed/XdrReaderWriter.cs index f9b78b15c..b8196eb00 100644 --- a/src/FirebirdSql.Data.FirebirdClient/Client/Managed/XdrReaderWriter.cs +++ b/src/FirebirdSql.Data.FirebirdClient/Client/Managed/XdrReaderWriter.cs @@ -34,7 +34,6 @@ sealed class XdrReaderWriter : IXdrReader, IXdrWriter readonly Charset _charset; byte[] _smallBuffer; - byte[] _boolbuffer; const int StackallocThreshold = 1024; public XdrReaderWriter(IDataProvider dataProvider, Charset charset) @@ -43,7 +42,6 @@ public XdrReaderWriter(IDataProvider dataProvider, Charset charset) _charset = charset; _smallBuffer = new byte[8]; - _boolbuffer = new byte[1]; } public XdrReaderWriter(IDataProvider dataProvider) @@ -452,8 +450,8 @@ public bool ReadBoolean() } public async ValueTask ReadBooleanAsync(CancellationToken cancellationToken = default) { - await ReadBytesAsync(_boolbuffer, 4, cancellationToken).ConfigureAwait(false); - return TypeDecoder.DecodeBoolean(_boolbuffer); + await ReadBytesAsync(_smallBuffer, 4, cancellationToken).ConfigureAwait(false); + return TypeDecoder.DecodeBoolean(_smallBuffer); } public FbZonedDateTime ReadZonedDateTime(bool isExtended) From 8835558f502ddd65955fdb78bcc47120726413ed Mon Sep 17 00:00:00 2001 From: pl752 Date: Sat, 13 Dec 2025 11:49:04 +0500 Subject: [PATCH 10/11] Refactored and optimized overcomplicated auth-block --- .../Client/Managed/AuthBlock.cs | 174 ++++-------------- 1 file changed, 40 insertions(+), 134 deletions(-) diff --git a/src/FirebirdSql.Data.FirebirdClient/Client/Managed/AuthBlock.cs b/src/FirebirdSql.Data.FirebirdClient/Client/Managed/AuthBlock.cs index 48442389e..246b5d9cf 100644 --- a/src/FirebirdSql.Data.FirebirdClient/Client/Managed/AuthBlock.cs +++ b/src/FirebirdSql.Data.FirebirdClient/Client/Managed/AuthBlock.cs @@ -52,7 +52,7 @@ sealed class AuthBlock public bool WireCryptInitialized { get; private set; } - private const int STACKALLOC_LIMIT = 512; + private const byte SEPARATOR_BYTE = (byte)','; public AuthBlock(GdsConnection connection, string user, string password, WireCryptOption wireCrypt) { @@ -70,166 +70,72 @@ public byte[] UserIdentificationData() { using (var result = new MemoryStream(256)) { - { - var userString = Environment.GetEnvironmentVariable("USERNAME") ?? Environment.GetEnvironmentVariable("USER") ?? string.Empty; - var slen = Encoding.UTF8.GetByteCount(userString); - byte[] rented = null; - Span user = slen > STACKALLOC_LIMIT - ? (rented = System.Buffers.ArrayPool.Shared.Rent(slen)).AsSpan(0, slen) - : stackalloc byte[slen]; - int real_len = Encoding.UTF8.GetBytes(userString, user); - result.WriteByte(IscCodes.CNCT_user); - result.WriteByte((byte)real_len); - result.Write(user); - if (rented != null) - { - System.Buffers.ArrayPool.Shared.Return(rented, clearArray: true); - } - } - - { - var hostName = Dns.GetHostName(); - var slen = Encoding.UTF8.GetByteCount(hostName); - byte[] rented = null; - Span host = slen > STACKALLOC_LIMIT - ? (rented = System.Buffers.ArrayPool.Shared.Rent(slen)).AsSpan(0, slen) - : stackalloc byte[slen]; - int real_len = Encoding.UTF8.GetBytes(hostName, host); - result.WriteByte(IscCodes.CNCT_host); - result.WriteByte((byte)real_len); - result.Write(host); - if (rented != null) - { - System.Buffers.ArrayPool.Shared.Return(rented, clearArray: true); - } - } + Span scratchpad = stackalloc byte[258]; + var userString = Environment.GetEnvironmentVariable("USERNAME") ?? Environment.GetEnvironmentVariable("USER") ?? string.Empty; + WriteUserIdentificationParams(result, scratchpad, IscCodes.CNCT_user, userString); + var hostName = Dns.GetHostName(); + WriteUserIdentificationParams(result, scratchpad, IscCodes.CNCT_host, hostName); result.WriteByte(IscCodes.CNCT_user_verification); result.WriteByte(0); if (!string.IsNullOrEmpty(User)) { - { - var slen = Encoding.UTF8.GetByteCount(User); - byte[] rented = null; - Span bytes = slen > STACKALLOC_LIMIT - ? (rented = System.Buffers.ArrayPool.Shared.Rent(slen)).AsSpan(0, slen) - : stackalloc byte[slen]; - int real_len = Encoding.UTF8.GetBytes(User, bytes); - result.WriteByte(IscCodes.CNCT_login); - result.WriteByte((byte)real_len); - result.Write(bytes); - if (rented != null) - { - System.Buffers.ArrayPool.Shared.Return(rented, clearArray: true); - } - } - { - var slen = Encoding.UTF8.GetByteCount(_srp256.Name); - byte[] rented = null; - Span bytes = slen > STACKALLOC_LIMIT - ? (rented = System.Buffers.ArrayPool.Shared.Rent(slen)).AsSpan(0, slen) - : stackalloc byte[slen]; - int real_len = Encoding.UTF8.GetBytes(_srp256.Name, bytes); - result.WriteByte(IscCodes.CNCT_plugin_name); - result.WriteByte((byte)real_len); - result.Write(bytes[..real_len]); - if (rented != null) - { - System.Buffers.ArrayPool.Shared.Return(rented, clearArray: true); - } - } - { - var slen = Encoding.UTF8.GetByteCount(_srp256.PublicKeyHex); - byte[] rented = null; - Span specificData = slen > STACKALLOC_LIMIT - ? (rented = System.Buffers.ArrayPool.Shared.Rent(slen)).AsSpan(0, slen) - : stackalloc byte[slen]; - Encoding.UTF8.GetBytes(_srp256.PublicKeyHex.AsSpan(), specificData); - WriteMultiPartHelper(result, IscCodes.CNCT_specific_data, specificData); - if (rented != null) - { - System.Buffers.ArrayPool.Shared.Return(rented, clearArray: true); - } - } - { - var slen1 = Encoding.UTF8.GetByteCount(_srp256.Name); - byte[] rented1 = null; - Span bytes1 = slen1 > STACKALLOC_LIMIT - ? (rented1 = System.Buffers.ArrayPool.Shared.Rent(slen1)).AsSpan(0, slen1) - : stackalloc byte[slen1]; - Span bytes2 = stackalloc byte[1]; - var slen3 = Encoding.UTF8.GetByteCount(_srp.Name); - byte[] rented3 = null; - Span bytes3 = slen3 > STACKALLOC_LIMIT - ? (rented3 = System.Buffers.ArrayPool.Shared.Rent(slen3)).AsSpan(0, slen3) - : stackalloc byte[slen3]; - int l1 = Encoding.UTF8.GetBytes(_srp256.Name.AsSpan(), bytes1); - int l2 = Encoding.UTF8.GetBytes(",".AsSpan(), bytes2); - int l3 = Encoding.UTF8.GetBytes(_srp.Name.AsSpan(), bytes3); - result.WriteByte(IscCodes.CNCT_plugin_list); - result.WriteByte((byte)(l1+l2+l3)); - result.Write(bytes1); - result.Write(bytes2); - result.Write(bytes3); - if (rented1 != null) - { - System.Buffers.ArrayPool.Shared.Return(rented1, clearArray: true); - } - if (rented3 != null) - { - System.Buffers.ArrayPool.Shared.Return(rented3, clearArray: true); - } - } + WriteUserIdentificationParams(result, scratchpad, IscCodes.CNCT_login, User); + WriteUserIdentificationParams(result, scratchpad, IscCodes.CNCT_plugin_name, _srp256.Name); + + var len = Encoding.UTF8.GetBytes(_srp256.PublicKeyHex, scratchpad); + WriteMultiPartHelper(result, IscCodes.CNCT_specific_data, scratchpad[..len]); + WriteUserIdentificationParams(result, scratchpad, IscCodes.CNCT_plugin_list, _srp256.Name, _srp.Name); + + result.WriteByte(IscCodes.CNCT_client_crypt); + result.WriteByte(4); + if (!BitConverter.TryWriteBytes(scratchpad, IPAddress.NetworkToHostOrder(WireCryptOptionValue(WireCrypt)))) { - result.WriteByte(IscCodes.CNCT_client_crypt); - result.WriteByte(4); - Span bytes = stackalloc byte[4]; - if (!BitConverter.TryWriteBytes(bytes, IPAddress.NetworkToHostOrder(WireCryptOptionValue(WireCrypt)))) - { - throw new InvalidOperationException("Failed to write wire crypt option bytes."); - } - result.Write(bytes); + throw new InvalidOperationException("Failed to write wire crypt option bytes."); } + result.Write(scratchpad[..4]); } else { - var slen = Encoding.UTF8.GetByteCount(_sspi.Name); - byte[] rented = null; - Span pluginNameBytes = slen > STACKALLOC_LIMIT - ? (rented = System.Buffers.ArrayPool.Shared.Rent(slen)).AsSpan(0, slen) - : stackalloc byte[slen]; - int pluginNameLen = Encoding.UTF8.GetBytes(_sspi.Name.AsSpan(), pluginNameBytes); - result.WriteByte(IscCodes.CNCT_plugin_name); - result.WriteByte((byte)pluginNameLen); - result.Write(pluginNameBytes[..pluginNameLen]); + WriteUserIdentificationParams(result, scratchpad, IscCodes.CNCT_plugin_name, _sspi.Name); var specificData = _sspi.InitializeClientSecurity(); WriteMultiPartHelper(result, IscCodes.CNCT_specific_data, specificData); - result.WriteByte(IscCodes.CNCT_plugin_list); - result.WriteByte((byte)pluginNameLen); - result.Write(pluginNameBytes[..pluginNameLen]); + WriteUserIdentificationParams(result, scratchpad, IscCodes.CNCT_plugin_list, _sspi.Name); result.WriteByte(IscCodes.CNCT_client_crypt); result.WriteByte(4); - Span wireCryptBytes = stackalloc byte[4]; - if (!BitConverter.TryWriteBytes(wireCryptBytes, IPAddress.NetworkToHostOrder(IscCodes.WIRE_CRYPT_DISABLED))) + if (!BitConverter.TryWriteBytes(scratchpad, IPAddress.NetworkToHostOrder(IscCodes.WIRE_CRYPT_DISABLED))) { throw new InvalidOperationException("Failed to write wire crypt disabled bytes."); } - result.Write(wireCryptBytes); - if (rented != null) - { - System.Buffers.ArrayPool.Shared.Return(rented, clearArray: true); - } + result.Write(scratchpad[..4]); } - + scratchpad.Clear(); return result.ToArray(); } } + static void WriteUserIdentificationParams(MemoryStream result, Span scratchpad, byte type, params ReadOnlySpan strings) + { + scratchpad[0] = type; + int len = 2; + if(strings.Length > 0) + { + len += Encoding.UTF8.GetBytes(strings[0], scratchpad[len..]); + for(int i = 1; i < strings.Length; i++) + { + scratchpad[len++] = SEPARATOR_BYTE; + len += Encoding.UTF8.GetBytes(strings[i], scratchpad[len..]); + } + } + scratchpad[1] = (byte)(len - 2); + result.Write(scratchpad[..len]); + } + public void SendContAuthToBuffer() { Connection.Xdr.Write(IscCodes.op_cont_auth); From c97b4ebb6aa4ae9bbf3e22bcbdb46fe7b0bb9dab Mon Sep 17 00:00:00 2001 From: pl752 Date: Sat, 13 Dec 2025 13:31:31 +0500 Subject: [PATCH 11/11] Forgot to optimize guid with span --- .../Client/Managed/XdrReaderWriter.cs | 11 +---------- .../Common/TypeDecoder.cs | 8 ++++++++ 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/src/FirebirdSql.Data.FirebirdClient/Client/Managed/XdrReaderWriter.cs b/src/FirebirdSql.Data.FirebirdClient/Client/Managed/XdrReaderWriter.cs index b8196eb00..2a9fac18e 100644 --- a/src/FirebirdSql.Data.FirebirdClient/Client/Managed/XdrReaderWriter.cs +++ b/src/FirebirdSql.Data.FirebirdClient/Client/Managed/XdrReaderWriter.cs @@ -299,16 +299,7 @@ public Guid ReadGuid(int sqlType) { Span buf = stackalloc byte[16]; ReadOpaque(buf, 16); - var rented = ArrayPool.Shared.Rent(16); - try - { - buf.CopyTo(rented); - return TypeDecoder.DecodeGuid(rented); - } - finally - { - ArrayPool.Shared.Return(rented); - } + return TypeDecoder.DecodeGuidSpan(buf); } } public async ValueTask ReadGuidAsync(int sqlType, CancellationToken cancellationToken = default) diff --git a/src/FirebirdSql.Data.FirebirdClient/Common/TypeDecoder.cs b/src/FirebirdSql.Data.FirebirdClient/Common/TypeDecoder.cs index 82667f108..c1b1f08c0 100644 --- a/src/FirebirdSql.Data.FirebirdClient/Common/TypeDecoder.cs +++ b/src/FirebirdSql.Data.FirebirdClient/Common/TypeDecoder.cs @@ -111,6 +111,14 @@ public static Guid DecodeGuid(byte[] value) 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));