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()); } diff --git a/src/FirebirdSql.Data.FirebirdClient/Client/Managed/AuthBlock.cs b/src/FirebirdSql.Data.FirebirdClient/Client/Managed/AuthBlock.cs index 0a8a14513..48442389e 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(); @@ -68,60 +70,160 @@ 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..0dc364624 100644 --- a/src/FirebirdSql.Data.FirebirdClient/Client/Managed/FirebirdNetworkHandlingWrapper.cs +++ b/src/FirebirdSql.Data.FirebirdClient/Client/Managed/FirebirdNetworkHandlingWrapper.cs @@ -87,6 +87,39 @@ 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 +153,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 +183,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 ValueTask.CompletedTask; + } + public void Flush() { var buffer = _outputBuffer.ToArray(); @@ -206,6 +265,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/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/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..0cf96c0b8 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,13 +191,9 @@ 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 lengths = new int[Descriptor.Dimensions]; var lowerBounds = new int[Descriptor.Dimensions]; - var type = 0; var index = 0; for (var i = 0; i < Descriptor.Dimensions; i++) @@ -205,11 +207,11 @@ 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)) { @@ -282,13 +284,9 @@ 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 lengths = new int[Descriptor.Dimensions]; var lowerBounds = new int[Descriptor.Dimensions]; - var type = 0; var index = 0; for (var i = 0; i < Descriptor.Dimensions; i++) @@ -302,11 +300,11 @@ 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)) { @@ -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,17 +532,14 @@ private async ValueTask ReceiveSliceResponseAsync(ArrayDesc desc, Cancel private byte[] EncodeSliceArray(Array sourceArray) { - var dbType = DbDataType.Array; var charset = _database.Charset; var subType = (Descriptor.Scale < 0) ? 2 : 0; - var type = 0; - 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) { @@ -586,17 +602,14 @@ private byte[] EncodeSliceArray(Array sourceArray) } private async ValueTask EncodeSliceArrayAsync(Array sourceArray, CancellationToken cancellationToken = default) { - var dbType = DbDataType.Array; var charset = _database.Charset; var subType = (Descriptor.Scale < 0) ? 2 : 0; - var type = 0; - 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) { @@ -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..485666846 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) { @@ -237,7 +241,7 @@ public override void GetSegment(Stream stream) 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) { @@ -286,7 +290,7 @@ public override async ValueTask GetSegmentAsync(Stream stream, CancellationToken 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) { @@ -337,7 +341,7 @@ public override byte[] GetSegment() 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) { @@ -393,7 +397,7 @@ public override async ValueTask GetSegmentAsync(CancellationToken cancel 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..b69a03210 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; @@ -33,6 +34,7 @@ internal class GdsDatabase : DatabaseBase protected const int ArgumentToAstRoutine = 0; protected internal const int DatabaseObjectId = 0; protected internal const int Incarnation = 0; + const int StackallocThreshold = 512; #region Fields @@ -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..9341b0d49 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,28 +1167,28 @@ 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; @@ -1165,7 +1197,142 @@ protected async ValueTask ParseTruncSqlInfoAsync(byte[] info, byte } } // just to get out of the loop - Break: + 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 + 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) - { - var bvalue = field.Charset.GetBytes(field.DbValue.GetString()); - if (bvalue.Length > field.Length) - { - throw IscException.ForErrorCodes(new[] { IscCodes.isc_arith_except, IscCodes.isc_string_truncation }); - } - xdr.WriteOpaque(bvalue, field.Length); - } else { var svalue = field.DbValue.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 }); } - xdr.WriteOpaque(field.Charset.GetBytes(svalue), 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 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; @@ -1260,23 +1442,34 @@ protected void WriteRawParameter(IXdrWriter xdr, DbField field) { xdr.WriteBuffer(field.DbValue.GetBinary()); } - else if (field.Charset.IsNoneCharset) - { - var bvalue = field.Charset.GetBytes(field.DbValue.GetString()); - if (bvalue.Length > field.Length) - { - throw IscException.ForErrorCodes(new[] { IscCodes.isc_arith_except, IscCodes.isc_string_truncation }); - } - xdr.WriteBuffer(bvalue); - } else { var svalue = field.DbValue.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 }); + throw IscException.ForErrorCodes([IscCodes.isc_arith_except, IscCodes.isc_string_truncation]); + } + 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 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 + { + 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) - { - var bvalue = field.Charset.GetBytes(await field.DbValue.GetStringAsync(cancellationToken).ConfigureAwait(false)); - if (bvalue.Length > field.Length) - { - throw IscException.ForErrorCodes(new[] { IscCodes.isc_arith_except, IscCodes.isc_string_truncation }); - } - await xdr.WriteOpaqueAsync(bvalue, field.Length, cancellationToken).ConfigureAwait(false); - } else { var svalue = await field.DbValue.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 }); + throw IscException.ForErrorCodes([IscCodes.isc_arith_except, IscCodes.isc_string_truncation]); + } + 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]); + } + { + 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); + } } - 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) - { - var bvalue = field.Charset.GetBytes(await field.DbValue.GetStringAsync(cancellationToken).ConfigureAwait(false)); - if (bvalue.Length > field.Length) - { - throw IscException.ForErrorCodes(new[] { IscCodes.isc_arith_except, IscCodes.isc_string_truncation }); - } - await xdr.WriteBufferAsync(bvalue, cancellationToken).ConfigureAwait(false); - } else { var svalue = await field.DbValue.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 }); + throw IscException.ForErrorCodes([IscCodes.isc_arith_except, IscCodes.isc_string_truncation]); + } + var encoding = field.Charset.Encoding; + var byteCount = encoding.GetByteCount(svalue); + { + 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); + } } - await xdr.WriteBufferAsync(field.Charset.GetBytes(svalue), cancellationToken).ConfigureAwait(false); } break; @@ -1533,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: @@ -1631,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: @@ -1740,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++) @@ -1769,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++) @@ -1797,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/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/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/Version13/GdsStatement.cs b/src/FirebirdSql.Data.FirebirdClient/Client/Managed/Version13/GdsStatement.cs index 0ea953783..8660afb30 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++) + var len = (int)Math.Ceiling(_fields.Count / 8d); + var rented = ArrayPool.Shared.Rent(len); + try { - if (nullBits.Get(i)) + _database.Xdr.ReadOpaque(rented.AsSpan(0, len), len); + for (var i = 0; i < _fields.Count; 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 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++) + var len = (int)Math.Ceiling(_fields.Count / 8d); + var rented = ArrayPool.Shared.Rent(len); + try { - if (nullBits.Get(i)) - { - row[i] = new DbValue(this, _fields[i], null); - } - else + await _database.Xdr.ReadOpaqueAsync(rented.AsMemory(0, len), len, cancellationToken).ConfigureAwait(false); + for (var i = 0; i < _fields.Count; i++) { - var value = await ReadRawValueAsync(_database.Xdr, _fields[i], cancellationToken).ConfigureAwait(false); - row[i] = new DbValue(this, _fields[i], value); + 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 def16aeee..7949094d3 100644 --- a/src/FirebirdSql.Data.FirebirdClient/Client/Managed/Version16/GdsBatch.cs +++ b/src/FirebirdSql.Data.FirebirdClient/Client/Managed/Version16/GdsBatch.cs @@ -145,13 +145,23 @@ 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() diff --git a/src/FirebirdSql.Data.FirebirdClient/Client/Managed/XdrReaderWriter.cs b/src/FirebirdSql.Data.FirebirdClient/Client/Managed/XdrReaderWriter.cs index eeb4c322c..b8196eb00 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) { @@ -69,6 +71,27 @@ public byte[] ReadBytes(byte[] buffer, int count) } return buffer; } + + public void ReadBytes(Span dst, int count) + { + if (count > 0) + { + var toRead = count; + var currentlyRead = -1; + while (toRead > 0 && currentlyRead != 0) + { + toRead -= (currentlyRead = _dataProvider.Read(dst, count - toRead, toRead)); + } + if (currentlyRead == 0) + { + if (_dataProvider is ITracksIOFailure tracksIOFailure) + { + tracksIOFailure.IOFailed = true; + } + throw new IOException($"Missing {toRead} bytes to fill total {count}."); + } + } + } public async ValueTask ReadBytesAsync(byte[] buffer, int count, CancellationToken cancellationToken = default) { if (count > 0) @@ -91,30 +114,81 @@ public async ValueTask ReadBytesAsync(byte[] buffer, int count, Cancella return buffer; } + public async ValueTask ReadBytesAsync(Memory buffer, int count, CancellationToken cancellationToken = default) + { + if (count <= 0) + return; + var toRead = count; + var offset = 0; + while (toRead > 0) + { + var chunk = await _dataProvider.ReadAsync(buffer.Slice(offset, toRead), 0, toRead, cancellationToken).ConfigureAwait(false); + if (chunk == 0) + { + if (_dataProvider is ITracksIOFailure tracksIOFailure) + { + tracksIOFailure.IOFailed = true; + } + throw new IOException($"Missing {toRead} bytes to fill total {count}."); + } + offset += chunk; + toRead -= chunk; + } + } + public byte[] ReadOpaque(int length) { - var buffer = new byte[length]; + var buffer = length > 0 ? new byte[length] : Array.Empty(); ReadBytes(buffer, length); ReadPad((4 - length) & 3); return buffer; } + + public void ReadOpaque(Span dst, int length) + { + ReadBytes(dst, length); + ReadPad((4 - length) & 3); + } + public async ValueTask ReadOpaqueAsync(int length, CancellationToken cancellationToken = default) { - var buffer = new byte[length]; + var buffer = length > 0 ? new byte[length] : Array.Empty(); await ReadBytesAsync(buffer, length, cancellationToken).ConfigureAwait(false); await ReadPadAsync((4 - length) & 3, cancellationToken).ConfigureAwait(false); return buffer; } + public async ValueTask ReadOpaqueAsync(Memory buffer, int length, CancellationToken cancellationToken = default) + { + if (length <= 0) + return; + await ReadBytesAsync(buffer, length, cancellationToken).ConfigureAwait(false); + await ReadPadAsync((4 - length) & 3, cancellationToken).ConfigureAwait(false); + } + public byte[] ReadBuffer() { return ReadOpaque((ushort)ReadInt32()); } + + public void ReadBuffer(Span dst) + { + ReadOpaque(dst, (ushort)ReadInt32()); + } + public async ValueTask ReadBufferAsync(CancellationToken cancellationToken = default) { return await ReadOpaqueAsync((ushort)await ReadInt32Async(cancellationToken).ConfigureAwait(false), cancellationToken).ConfigureAwait(false); } + public async ValueTask ReadBufferAsync(Memory dst, CancellationToken cancellationToken = default) + { + var length = (ushort)await ReadInt32Async(cancellationToken).ConfigureAwait(false); + if (dst.Length < length) + throw new IOException($"Destination too small. Need {length}, have {dst.Length}."); + await ReadOpaqueAsync(dst, length, cancellationToken).ConfigureAwait(false); + } + public string ReadString() { return ReadString(_charset); @@ -144,13 +218,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 +297,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 +317,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 +444,14 @@ public async ValueTask ReadDecimalAsync(int type, int scale, Cancellati public bool ReadBoolean() { - return TypeDecoder.DecodeBoolean(ReadOpaque(1)); + Span bytes = stackalloc byte[4]; + ReadBytes(bytes, 4); + return TypeDecoder.DecodeBoolean(bytes); } public async ValueTask ReadBooleanAsync(CancellationToken cancellationToken = default) { - return TypeDecoder.DecodeBoolean(await ReadOpaqueAsync(1, cancellationToken).ConfigureAwait(false)); + await ReadBytesAsync(_smallBuffer, 4, cancellationToken).ConfigureAwait(false); + return TypeDecoder.DecodeBoolean(_smallBuffer); } public FbZonedDateTime ReadZonedDateTime(bool isExtended) @@ -330,29 +478,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 +666,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 +684,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 +763,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 +783,17 @@ public void WriteBuffer(byte[] buffer, int length) WritePad((4 - length) & 3); } } + + public void WriteBuffer(ReadOnlySpan buffer) + { + var length = buffer.Length; + Write(length); + if (length > 0) + { + _dataProvider.Write(buffer); + WritePad((4 - length) & 3); + } + } public async ValueTask WriteBufferAsync(byte[] buffer, int length, CancellationToken cancellationToken = default) { await WriteAsync(length, cancellationToken).ConfigureAwait(false); @@ -552,10 +811,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 +838,200 @@ 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 +1045,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 +1167,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 +1192,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 +1205,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 +1241,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/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/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/DbField.cs b/src/FirebirdSql.Data.FirebirdClient/Common/DbField.cs index 3be89be74..b5d27b5d8 100644 --- a/src/FirebirdSql.Data.FirebirdClient/Common/DbField.cs +++ b/src/FirebirdSql.Data.FirebirdClient/Common/DbField.cs @@ -325,12 +325,13 @@ 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/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++) { diff --git a/src/FirebirdSql.Data.FirebirdClient/Common/Extensions.cs b/src/FirebirdSql.Data.FirebirdClient/Common/Extensions.cs index 6143cc839..57ab69018 100644 --- a/src/FirebirdSql.Data.FirebirdClient/Common/Extensions.cs +++ b/src/FirebirdSql.Data.FirebirdClient/Common/Extensions.cs @@ -102,4 +102,47 @@ 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]; + } } diff --git a/src/FirebirdSql.Data.FirebirdClient/Common/IscHelper.cs b/src/FirebirdSql.Data.FirebirdClient/Common/IscHelper.cs index 578895f7a..1ebfa2aa0 100644 --- a/src/FirebirdSql.Data.FirebirdClient/Common/IscHelper.cs +++ b/src/FirebirdSql.Data.FirebirdClient/Common/IscHelper.cs @@ -232,4 +232,17 @@ 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..07ea637f9 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(); @@ -112,16 +117,63 @@ public static byte[] EncodeGuid(Guid value) }; } + public static void EncodeGuid(Guid value, Span destination) + { + Span data = stackalloc byte[16]; + if (!value.TryWriteBytes(data)) + { + throw new InvalidOperationException("Failed to write Guid bytes."); + } + + Span a = stackalloc byte[4]; + Span b = stackalloc byte[2]; + Span c = stackalloc byte[2]; + + if (!BitConverter.TryWriteBytes(a, IPAddress.NetworkToHostOrder(BitConverter.ToInt32(data[..4])))) + { + throw new InvalidOperationException("Failed to write Guid bytes."); + } + if (!BitConverter.TryWriteBytes(b, IPAddress.NetworkToHostOrder(BitConverter.ToInt16(data.Slice(4, 2))))) + { + throw new InvalidOperationException("Failed to write Guid bytes."); + } + if (!BitConverter.TryWriteBytes(c, IPAddress.NetworkToHostOrder(BitConverter.ToInt16(data.Slice(6, 2))))) + { + throw new InvalidOperationException("Failed to write Guid bytes."); + } + + a.CopyTo(destination[..4]); + b.CopyTo(destination.Slice(4, 2)); + c.CopyTo(destination.Slice(6, 2)); + data.Slice(8, 8).CopyTo(destination[8..]); + } + public static byte[] EncodeInt32(int value) { return BitConverter.GetBytes(IPAddress.NetworkToHostOrder(value)); } + 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);