This is an automated email from the ASF dual-hosted git repository.
curth pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/arrow-adbc.git
The following commit(s) were added to refs/heads/main by this push:
new eb6c088a1 feat(csharp/src/Drivers/Apache): Add support for Sasl
transport in Hive and Impala ADBC Driver (#2822)
eb6c088a1 is described below
commit eb6c088a11bc946408d44796c5492f897337bbf0
Author: amangoyal <[email protected]>
AuthorDate: Wed May 14 20:00:25 2025 +0530
feat(csharp/src/Drivers/Apache): Add support for Sasl transport in Hive and
Impala ADBC Driver (#2822)
1. Added support of SASL transport in thrift folder.
2. Implemented PLAIN mechanism in SASL transport
3. Added HiveServer2StandardConnection to support binary transport mode.
4. Used SASL transport in Hiveserver2 standard connection with basic
auth.
---------
Co-authored-by: Aman Goyal <[email protected]>
Co-authored-by: Sudhir Emmadi <[email protected]>
---
.../src/Apache.Arrow.Adbc/Apache.Arrow.Adbc.csproj | 2 +-
.../Apache/Hive2/HiveServer2ConnectionFactory.cs | 7 +-
.../Apache/Hive2/HiveServer2ExtendedConnection.cs | 177 ++++++++++++++++++++
.../Apache/Hive2/HiveServer2HttpConnection.cs | 147 +----------------
.../Drivers/Apache/Hive2/HiveServer2Parameters.cs | 1 +
.../Apache/Hive2/HiveServer2StandardConnection.cs | 176 ++++++++++++++++++++
.../Apache/Hive2/HiveServer2TransportType.cs | 6 +-
.../Apache/Impala/ImpalaStandardConnection.cs | 26 ++-
.../Drivers/Apache/Thrift/Sasl/ISaslMechanism.cs | 43 +++++
.../Apache/Thrift/Sasl/NegotiationStatus.cs | 65 ++++++++
.../Apache/Thrift/Sasl/PlainSaslMechanism.cs | 66 ++++++++
.../Drivers/Apache/Thrift/Sasl/TSaslTransport.cs | 181 +++++++++++++++++++++
12 files changed, 752 insertions(+), 145 deletions(-)
diff --git a/csharp/src/Apache.Arrow.Adbc/Apache.Arrow.Adbc.csproj
b/csharp/src/Apache.Arrow.Adbc/Apache.Arrow.Adbc.csproj
index 598655d59..12110bafa 100644
--- a/csharp/src/Apache.Arrow.Adbc/Apache.Arrow.Adbc.csproj
+++ b/csharp/src/Apache.Arrow.Adbc/Apache.Arrow.Adbc.csproj
@@ -1,4 +1,4 @@
-<Project Sdk="Microsoft.NET.Sdk">
+<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netstandard2.0;net6.0</TargetFrameworks>
diff --git a/csharp/src/Drivers/Apache/Hive2/HiveServer2ConnectionFactory.cs
b/csharp/src/Drivers/Apache/Hive2/HiveServer2ConnectionFactory.cs
index 1666ff264..47d3e32c4 100644
--- a/csharp/src/Drivers/Apache/Hive2/HiveServer2ConnectionFactory.cs
+++ b/csharp/src/Drivers/Apache/Hive2/HiveServer2ConnectionFactory.cs
@@ -33,7 +33,12 @@ namespace Apache.Arrow.Adbc.Drivers.Apache.Hive2
{
throw new ArgumentOutOfRangeException(nameof(properties),
$"Unsupported or unknown value '{type}' given for property
'{HiveServer2Parameters.TransportType}'. Supported types:
{HiveServer2TransportTypeParser.SupportedList}");
}
- return new HiveServer2HttpConnection(properties);
+ return typeValue switch
+ {
+ HiveServer2TransportType.Http => new
HiveServer2HttpConnection(properties),
+ HiveServer2TransportType.Standard => new
HiveServer2StandardConnection(properties),
+ _ => throw new ArgumentOutOfRangeException(nameof(properties),
$"Unsupported or unknown value '{type}' given for property
'{HiveServer2Parameters.TransportType}'. Supported types:
{HiveServer2TransportTypeParser.SupportedList}"),
+ };
}
}
}
diff --git a/csharp/src/Drivers/Apache/Hive2/HiveServer2ExtendedConnection.cs
b/csharp/src/Drivers/Apache/Hive2/HiveServer2ExtendedConnection.cs
new file mode 100644
index 000000000..49fba7084
--- /dev/null
+++ b/csharp/src/Drivers/Apache/Hive2/HiveServer2ExtendedConnection.cs
@@ -0,0 +1,177 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one or more
+* contributor license agreements. See the NOTICE file distributed with
+* this work for additional information regarding copyright ownership.
+* The ASF licenses this file to You under the Apache License, Version 2.0
+* (the "License"); you may not use this file except in compliance with
+* the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+using Apache.Arrow.Ipc;
+using Apache.Hive.Service.Rpc.Thrift;
+using System;
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Apache.Arrow.Adbc.Drivers.Apache.Hive2
+{
+ internal abstract class HiveServer2ExtendedConnection :
HiveServer2Connection
+ {
+ private const string ProductVersionDefault = "1.0.0";
+ private const string DriverName = "ADBC Hive Driver";
+ private const string ArrowVersion = "1.0.0";
+ private const string BasicAuthenticationScheme = "Basic";
+ private readonly Lazy<string> _productVersion;
+ internal static readonly string s_userAgent = $"{DriverName.Replace("
", "")}/{ProductVersionDefault}";
+
+ protected override string GetProductVersionDefault() =>
ProductVersionDefault;
+
+ protected override string ProductVersion => _productVersion.Value;
+
+ public HiveServer2ExtendedConnection(IReadOnlyDictionary<string,
string> properties) : base(properties)
+ {
+ ValidateProperties();
+ _productVersion = new Lazy<string>(() => GetProductVersion(),
LazyThreadSafetyMode.PublicationOnly);
+ }
+
+ private void ValidateProperties()
+ {
+ ValidateAuthentication();
+ ValidateConnection();
+ ValidateOptions();
+ }
+
+ protected abstract void ValidateAuthentication();
+
+ protected abstract void ValidateConnection();
+
+ protected abstract void ValidateOptions();
+
+ public override AdbcStatement CreateStatement()
+ {
+ return new HiveServer2Statement(this);
+ }
+
+ internal override IArrowArrayStream NewReader<T>(T statement, Schema
schema, TGetResultSetMetadataResp? metadataResp = null) => new
HiveServer2Reader(
+ statement,
+ schema,
+ dataTypeConversion: statement.Connection.DataTypeConversion,
+ enableBatchSizeStopCondition: false);
+
+ internal override void SetPrecisionScaleAndTypeName(
+ short colType,
+ string typeName,
+ TableInfo? tableInfo,
+ int columnSize,
+ int decimalDigits)
+ {
+ // Keep the original type name
+ tableInfo?.TypeName.Add(typeName);
+ switch (colType)
+ {
+ case (short)ColumnTypeId.DECIMAL:
+ case (short)ColumnTypeId.NUMERIC:
+ {
+ // Precision/scale is provide in the API call.
+ SqlDecimalParserResult result =
SqlTypeNameParser<SqlDecimalParserResult>.Parse(typeName, colType);
+ tableInfo?.Precision.Add(columnSize);
+ tableInfo?.Scale.Add((short)decimalDigits);
+ tableInfo?.BaseTypeName.Add(result.BaseTypeName);
+ break;
+ }
+
+ case (short)ColumnTypeId.CHAR:
+ case (short)ColumnTypeId.NCHAR:
+ case (short)ColumnTypeId.VARCHAR:
+ case (short)ColumnTypeId.LONGVARCHAR:
+ case (short)ColumnTypeId.LONGNVARCHAR:
+ case (short)ColumnTypeId.NVARCHAR:
+ {
+ // Precision is provide in the API call.
+ SqlCharVarcharParserResult result =
SqlTypeNameParser<SqlCharVarcharParserResult>.Parse(typeName, colType);
+ tableInfo?.Precision.Add(columnSize);
+ tableInfo?.Scale.Add(null);
+ tableInfo?.BaseTypeName.Add(result.BaseTypeName);
+ break;
+ }
+
+ default:
+ {
+ SqlTypeNameParserResult result =
SqlTypeNameParser<SqlTypeNameParserResult>.Parse(typeName, colType);
+ tableInfo?.Precision.Add(null);
+ tableInfo?.Scale.Add(null);
+ tableInfo?.BaseTypeName.Add(result.BaseTypeName);
+ break;
+ }
+ }
+ }
+
+ protected override ColumnsMetadataColumnNames
GetColumnsMetadataColumnNames()
+ {
+ return new ColumnsMetadataColumnNames()
+ {
+ TableCatalog = TableCat,
+ TableSchema = TableSchem,
+ TableName = TableName,
+ ColumnName = ColumnName,
+ DataType = DataType,
+ TypeName = TypeName,
+ Nullable = Nullable,
+ ColumnDef = ColumnDef,
+ OrdinalPosition = OrdinalPosition,
+ IsNullable = IsNullable,
+ IsAutoIncrement = IsAutoIncrement,
+ ColumnSize = ColumnSize,
+ DecimalDigits = DecimalDigits,
+ };
+ }
+
+ protected override Task<TGetResultSetMetadataResp>
GetResultSetMetadataAsync(TGetSchemasResp response, CancellationToken
cancellationToken = default) =>
+ GetResultSetMetadataAsync(response.OperationHandle, Client,
cancellationToken);
+ protected override Task<TGetResultSetMetadataResp>
GetResultSetMetadataAsync(TGetCatalogsResp response, CancellationToken
cancellationToken = default) =>
+ GetResultSetMetadataAsync(response.OperationHandle, Client,
cancellationToken);
+ protected override Task<TGetResultSetMetadataResp>
GetResultSetMetadataAsync(TGetColumnsResp response, CancellationToken
cancellationToken = default) =>
+ GetResultSetMetadataAsync(response.OperationHandle, Client,
cancellationToken);
+ protected override Task<TGetResultSetMetadataResp>
GetResultSetMetadataAsync(TGetTablesResp response, CancellationToken
cancellationToken = default) =>
+ GetResultSetMetadataAsync(response.OperationHandle, Client,
cancellationToken);
+ protected internal override Task<TGetResultSetMetadataResp>
GetResultSetMetadataAsync(TGetPrimaryKeysResp response, CancellationToken
cancellationToken = default) =>
+ GetResultSetMetadataAsync(response.OperationHandle, Client,
cancellationToken);
+ protected override Task<TRowSet> GetRowSetAsync(TGetTableTypesResp
response, CancellationToken cancellationToken = default) =>
+ FetchResultsAsync(response.OperationHandle, cancellationToken:
cancellationToken);
+ protected override Task<TRowSet> GetRowSetAsync(TGetColumnsResp
response, CancellationToken cancellationToken = default) =>
+ FetchResultsAsync(response.OperationHandle, cancellationToken:
cancellationToken);
+ protected override Task<TRowSet> GetRowSetAsync(TGetTablesResp
response, CancellationToken cancellationToken = default) =>
+ FetchResultsAsync(response.OperationHandle, cancellationToken:
cancellationToken);
+ protected override Task<TRowSet> GetRowSetAsync(TGetCatalogsResp
response, CancellationToken cancellationToken = default) =>
+ FetchResultsAsync(response.OperationHandle, cancellationToken:
cancellationToken);
+ protected override Task<TRowSet> GetRowSetAsync(TGetSchemasResp
response, CancellationToken cancellationToken = default) =>
+ FetchResultsAsync(response.OperationHandle, cancellationToken:
cancellationToken);
+ protected internal override Task<TRowSet>
GetRowSetAsync(TGetPrimaryKeysResp response, CancellationToken
cancellationToken = default) =>
+ FetchResultsAsync(response.OperationHandle, cancellationToken:
cancellationToken);
+
+ protected internal override int PositionRequiredOffset => 0;
+
+ protected override string InfoDriverName => DriverName;
+
+ protected override string InfoDriverArrowVersion => ArrowVersion;
+
+ protected override bool IsColumnSizeValidForDecimal => false;
+
+ protected override bool GetObjectsPatternsRequireLowerCase => false;
+
+ internal override SchemaParser SchemaParser => new
HiveServer2SchemaParser();
+
+ protected abstract HiveServer2TransportType Type { get; }
+
+ protected override int ColumnMapIndexOffset => 1;
+ }
+}
diff --git a/csharp/src/Drivers/Apache/Hive2/HiveServer2HttpConnection.cs
b/csharp/src/Drivers/Apache/Hive2/HiveServer2HttpConnection.cs
index 0bffe8714..0bba4735c 100644
--- a/csharp/src/Drivers/Apache/Hive2/HiveServer2HttpConnection.cs
+++ b/csharp/src/Drivers/Apache/Hive2/HiveServer2HttpConnection.cs
@@ -15,6 +15,7 @@
* limitations under the License.
*/
+using Apache.Hive.Service.Rpc.Thrift;
using System;
using System.Collections.Generic;
using System.Globalization;
@@ -24,8 +25,6 @@ using System.Net.Http.Headers;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
-using Apache.Arrow.Ipc;
-using Apache.Hive.Service.Rpc.Thrift;
using Thrift;
using Thrift.Protocol;
using Thrift.Transport;
@@ -33,33 +32,15 @@ using Thrift.Transport.Client;
namespace Apache.Arrow.Adbc.Drivers.Apache.Hive2
{
- internal class HiveServer2HttpConnection : HiveServer2Connection
+ internal class HiveServer2HttpConnection : HiveServer2ExtendedConnection
{
- private const string ProductVersionDefault = "1.0.0";
- private const string DriverName = "ADBC Hive Driver";
- private const string ArrowVersion = "1.0.0";
private const string BasicAuthenticationScheme = "Basic";
- private readonly Lazy<string> _productVersion;
- private static readonly string s_userAgent = $"{DriverName.Replace("
", "")}/{ProductVersionDefault}";
-
- protected override string GetProductVersionDefault() =>
ProductVersionDefault;
-
- protected override string ProductVersion => _productVersion.Value;
public HiveServer2HttpConnection(IReadOnlyDictionary<string, string>
properties) : base(properties)
{
- ValidateProperties();
- _productVersion = new Lazy<string>(() => GetProductVersion(),
LazyThreadSafetyMode.PublicationOnly);
}
- private void ValidateProperties()
- {
- ValidateAuthentication();
- ValidateConnection();
- ValidateOptions();
- }
-
- private void ValidateAuthentication()
+ protected override void ValidateAuthentication()
{
// Validate authentication parameters
Properties.TryGetValue(AdbcOptions.Username, out string? username);
@@ -96,7 +77,7 @@ namespace Apache.Arrow.Adbc.Drivers.Apache.Hive2
}
}
- private void ValidateConnection()
+ protected override void ValidateConnection()
{
// HostName or Uri is required parameter
Properties.TryGetValue(AdbcOptions.Uri, out string? uri);
@@ -125,7 +106,7 @@ namespace Apache.Arrow.Adbc.Drivers.Apache.Hive2
};
}
- private void ValidateOptions()
+ protected override void ValidateOptions()
{
Properties.TryGetValue(HiveServer2Parameters.DataTypeConv, out
string? dataTypeConv);
DataTypeConversion = DataTypeConversionParser.Parse(dataTypeConv);
@@ -139,17 +120,6 @@ namespace Apache.Arrow.Adbc.Drivers.Apache.Hive2
TlsOptions = HiveServer2TlsImpl.GetHttpTlsOptions(Properties);
}
- public override AdbcStatement CreateStatement()
- {
- return new HiveServer2Statement(this);
- }
-
- internal override IArrowArrayStream NewReader<T>(T statement, Schema
schema, TGetResultSetMetadataResp? metadataResp = null) => new
HiveServer2Reader(
- statement,
- schema,
- dataTypeConversion: statement.Connection.DataTypeConversion,
- enableBatchSizeStopCondition: false);
-
protected override TTransport CreateTransport()
{
// Assumption: parameters have already been validated.
@@ -225,111 +195,6 @@ namespace Apache.Arrow.Adbc.Drivers.Apache.Hive2
return req;
}
- internal override void SetPrecisionScaleAndTypeName(
- short colType,
- string typeName,
- TableInfo? tableInfo,
- int columnSize,
- int decimalDigits)
- {
- // Keep the original type name
- tableInfo?.TypeName.Add(typeName);
- switch (colType)
- {
- case (short)ColumnTypeId.DECIMAL:
- case (short)ColumnTypeId.NUMERIC:
- {
- // Precision/scale is provide in the API call.
- SqlDecimalParserResult result =
SqlTypeNameParser<SqlDecimalParserResult>.Parse(typeName, colType);
- tableInfo?.Precision.Add(columnSize);
- tableInfo?.Scale.Add((short)decimalDigits);
- tableInfo?.BaseTypeName.Add(result.BaseTypeName);
- break;
- }
-
- case (short)ColumnTypeId.CHAR:
- case (short)ColumnTypeId.NCHAR:
- case (short)ColumnTypeId.VARCHAR:
- case (short)ColumnTypeId.LONGVARCHAR:
- case (short)ColumnTypeId.LONGNVARCHAR:
- case (short)ColumnTypeId.NVARCHAR:
- {
- // Precision is provide in the API call.
- SqlCharVarcharParserResult result =
SqlTypeNameParser<SqlCharVarcharParserResult>.Parse(typeName, colType);
- tableInfo?.Precision.Add(columnSize);
- tableInfo?.Scale.Add(null);
- tableInfo?.BaseTypeName.Add(result.BaseTypeName);
- break;
- }
-
- default:
- {
- SqlTypeNameParserResult result =
SqlTypeNameParser<SqlTypeNameParserResult>.Parse(typeName, colType);
- tableInfo?.Precision.Add(null);
- tableInfo?.Scale.Add(null);
- tableInfo?.BaseTypeName.Add(result.BaseTypeName);
- break;
- }
- }
- }
-
- protected override ColumnsMetadataColumnNames
GetColumnsMetadataColumnNames()
- {
- return new ColumnsMetadataColumnNames()
- {
- TableCatalog = TableCat,
- TableSchema = TableSchem,
- TableName = TableName,
- ColumnName = ColumnName,
- DataType = DataType,
- TypeName = TypeName,
- Nullable = Nullable,
- ColumnDef = ColumnDef,
- OrdinalPosition = OrdinalPosition,
- IsNullable = IsNullable,
- IsAutoIncrement = IsAutoIncrement,
- ColumnSize = ColumnSize,
- DecimalDigits = DecimalDigits,
- };
- }
-
- protected override Task<TGetResultSetMetadataResp>
GetResultSetMetadataAsync(TGetSchemasResp response, CancellationToken
cancellationToken = default) =>
- GetResultSetMetadataAsync(response.OperationHandle, Client,
cancellationToken);
- protected override Task<TGetResultSetMetadataResp>
GetResultSetMetadataAsync(TGetCatalogsResp response, CancellationToken
cancellationToken = default) =>
- GetResultSetMetadataAsync(response.OperationHandle, Client,
cancellationToken);
- protected override Task<TGetResultSetMetadataResp>
GetResultSetMetadataAsync(TGetColumnsResp response, CancellationToken
cancellationToken = default) =>
- GetResultSetMetadataAsync(response.OperationHandle, Client,
cancellationToken);
- protected override Task<TGetResultSetMetadataResp>
GetResultSetMetadataAsync(TGetTablesResp response, CancellationToken
cancellationToken = default) =>
- GetResultSetMetadataAsync(response.OperationHandle, Client,
cancellationToken);
- protected internal override Task<TGetResultSetMetadataResp>
GetResultSetMetadataAsync(TGetPrimaryKeysResp response, CancellationToken
cancellationToken = default) =>
- GetResultSetMetadataAsync(response.OperationHandle, Client,
cancellationToken);
- protected override Task<TRowSet> GetRowSetAsync(TGetTableTypesResp
response, CancellationToken cancellationToken = default) =>
- FetchResultsAsync(response.OperationHandle, cancellationToken:
cancellationToken);
- protected override Task<TRowSet> GetRowSetAsync(TGetColumnsResp
response, CancellationToken cancellationToken = default) =>
- FetchResultsAsync(response.OperationHandle, cancellationToken:
cancellationToken);
- protected override Task<TRowSet> GetRowSetAsync(TGetTablesResp
response, CancellationToken cancellationToken = default) =>
- FetchResultsAsync(response.OperationHandle, cancellationToken:
cancellationToken);
- protected override Task<TRowSet> GetRowSetAsync(TGetCatalogsResp
response, CancellationToken cancellationToken = default) =>
- FetchResultsAsync(response.OperationHandle, cancellationToken:
cancellationToken);
- protected override Task<TRowSet> GetRowSetAsync(TGetSchemasResp
response, CancellationToken cancellationToken = default) =>
- FetchResultsAsync(response.OperationHandle, cancellationToken:
cancellationToken);
- protected internal override Task<TRowSet>
GetRowSetAsync(TGetPrimaryKeysResp response, CancellationToken
cancellationToken = default) =>
- FetchResultsAsync(response.OperationHandle, cancellationToken:
cancellationToken);
-
- protected internal override int PositionRequiredOffset => 0;
-
- protected override string InfoDriverName => DriverName;
-
- protected override string InfoDriverArrowVersion => ArrowVersion;
-
- protected override bool IsColumnSizeValidForDecimal => false;
-
- protected override bool GetObjectsPatternsRequireLowerCase => false;
-
- internal override SchemaParser SchemaParser => new
HiveServer2SchemaParser();
-
- internal HiveServer2TransportType Type =>
HiveServer2TransportType.Http;
-
- protected override int ColumnMapIndexOffset => 1;
+ protected override HiveServer2TransportType Type =>
HiveServer2TransportType.Http;
}
}
diff --git a/csharp/src/Drivers/Apache/Hive2/HiveServer2Parameters.cs
b/csharp/src/Drivers/Apache/Hive2/HiveServer2Parameters.cs
index 5d44948bf..d40ee2cc9 100644
--- a/csharp/src/Drivers/Apache/Hive2/HiveServer2Parameters.cs
+++ b/csharp/src/Drivers/Apache/Hive2/HiveServer2Parameters.cs
@@ -38,6 +38,7 @@ namespace Apache.Arrow.Adbc.Drivers.Apache.Hive2
public static class HiveServer2TransportTypeConstants
{
public const string Http = "http";
+ public const string Standard = "standard";
}
public static class DataTypeConversionOptions
diff --git a/csharp/src/Drivers/Apache/Hive2/HiveServer2StandardConnection.cs
b/csharp/src/Drivers/Apache/Hive2/HiveServer2StandardConnection.cs
new file mode 100644
index 000000000..29c9ea795
--- /dev/null
+++ b/csharp/src/Drivers/Apache/Hive2/HiveServer2StandardConnection.cs
@@ -0,0 +1,176 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one or more
+* contributor license agreements. See the NOTICE file distributed with
+* this work for additional information regarding copyright ownership.
+* The ASF licenses this file to You under the Apache License, Version 2.0
+* (the "License"); you may not use this file except in compliance with
+* the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+using Apache.Hive.Service.Rpc.Thrift;
+using System;
+using System.Collections.Generic;
+using System.Net;
+using System.Security.Cryptography.X509Certificates;
+using System.Threading;
+using System.Threading.Tasks;
+using Thrift.Protocol;
+using Thrift.Transport;
+using Thrift.Transport.Client;
+
+namespace Apache.Arrow.Adbc.Drivers.Apache.Hive2
+{
+ internal class HiveServer2StandardConnection :
HiveServer2ExtendedConnection
+ {
+ public HiveServer2StandardConnection(IReadOnlyDictionary<string,
string> properties) : base(properties)
+ {
+ }
+
+ protected override void ValidateAuthentication()
+ {
+ Properties.TryGetValue(AdbcOptions.Username, out string? username);
+ Properties.TryGetValue(AdbcOptions.Password, out string? password);
+ Properties.TryGetValue(HiveServer2Parameters.AuthType, out string?
authType);
+ if (!HiveServer2AuthTypeParser.TryParse(authType, out
HiveServer2AuthType authTypeValue))
+ {
+ throw new
ArgumentOutOfRangeException(HiveServer2Parameters.AuthType, authType,
$"Unsupported {HiveServer2Parameters.AuthType} value.");
+ }
+ switch (authTypeValue)
+ {
+ case HiveServer2AuthType.None:
+ break;
+ case HiveServer2AuthType.Basic:
+ if (string.IsNullOrWhiteSpace(username) ||
string.IsNullOrWhiteSpace(password))
+ throw new ArgumentException(
+ $"Parameter '{HiveServer2Parameters.AuthType}' is
set to '{HiveServer2AuthTypeConstants.Basic}' but parameters
'{AdbcOptions.Username}' or '{AdbcOptions.Password}' are not set. Please
provide a values for these parameters.",
+ nameof(Properties));
+ break;
+ case HiveServer2AuthType.Empty:
+ if (string.IsNullOrWhiteSpace(username) ||
string.IsNullOrWhiteSpace(password))
+ throw new ArgumentException(
+ $"Parameters must include valid authentiation
settings. Please provide '{AdbcOptions.Username}' and
'{AdbcOptions.Password}'.",
+ nameof(Properties));
+ break;
+ default:
+ throw new
ArgumentOutOfRangeException(HiveServer2Parameters.AuthType, authType,
$"Unsupported {HiveServer2Parameters.AuthType} value.");
+ }
+ }
+
+ protected override void ValidateConnection()
+ {
+ // HostName is required parameter
+ Properties.TryGetValue(HiveServer2Parameters.HostName, out string?
hostName);
+ if (Uri.CheckHostName(hostName) == UriHostNameType.Unknown)
+ {
+ throw new ArgumentException(
+ $"Required parameter '{HiveServer2Parameters.HostName}' is
missing or invalid. Please provide a valid hostname for the data source.",
+ nameof(Properties));
+ }
+
+ // Validate port range
+ Properties.TryGetValue(HiveServer2Parameters.Port, out string?
port);
+ if (int.TryParse(port, out int portNumber) && (portNumber <=
IPEndPoint.MinPort || portNumber > IPEndPoint.MaxPort))
+ throw new ArgumentOutOfRangeException(
+ nameof(Properties),
+ port,
+ $"Parameter '{HiveServer2Parameters.Port}' value is not in
the valid range of {IPEndPoint.MinPort + 1} .. {IPEndPoint.MaxPort}.");
+ }
+
+ protected override void ValidateOptions()
+ {
+ Properties.TryGetValue(HiveServer2Parameters.DataTypeConv, out
string? dataTypeConv);
+ DataTypeConversion = DataTypeConversionParser.Parse(dataTypeConv);
+ TlsOptions = HiveServer2TlsImpl.GetStandardTlsOptions(Properties);
+ }
+
+ protected override TTransport CreateTransport()
+ {
+ // Required properties (validated previously)
+ Properties.TryGetValue(HiveServer2Parameters.HostName, out string?
hostName);
+ Properties.TryGetValue(HiveServer2Parameters.Port, out string?
port);
+ Properties.TryGetValue(HiveServer2Parameters.AuthType, out string?
authType);
+
+ if (!HiveServer2AuthTypeParser.TryParse(authType, out
HiveServer2AuthType authTypeValue))
+ {
+ throw new
ArgumentOutOfRangeException(HiveServer2Parameters.AuthType, authType,
$"Unsupported {HiveServer2Parameters.AuthType} value.");
+ }
+
+ // Delay the open connection until later.
+ bool connectClient = false;
+ int portValue = int.Parse(port!);
+
+ // TLS setup
+ TTransport baseTransport;
+ if (TlsOptions.IsTlsEnabled)
+ {
+ X509Certificate2? trustedCert =
!string.IsNullOrEmpty(TlsOptions.TrustedCertificatePath)
+ ? new X509Certificate2(TlsOptions.TrustedCertificatePath!)
+ : null;
+
+ var certValidator =
HiveServer2TlsImpl.GetCertificateValidator(TlsOptions);
+
+ if (IPAddress.TryParse(hostName!, out var ipAddress))
+ {
+ baseTransport = new TTlsSocketTransport(ipAddress,
portValue, config: new(), 0, trustedCert, certValidator);
+ }
+ else
+ {
+ baseTransport = new TTlsSocketTransport(hostName!,
portValue, config: new(), 0, trustedCert, certValidator);
+ }
+ }
+ else
+ {
+ baseTransport = new TSocketTransport(hostName!, portValue,
connectClient, config: new());
+ }
+
+ TBufferedTransport bufferedTransport = new
TBufferedTransport(baseTransport);
+ switch (authTypeValue)
+ {
+ case HiveServer2AuthType.None:
+ return bufferedTransport;
+
+ case HiveServer2AuthType.Basic:
+ Properties.TryGetValue(AdbcOptions.Username, out string?
username);
+ Properties.TryGetValue(AdbcOptions.Password, out string?
password);
+
+ if (string.IsNullOrWhiteSpace(username) ||
string.IsNullOrWhiteSpace(password))
+ {
+ throw new InvalidOperationException("Username and
password must be provided for this authentication type.");
+ }
+
+ PlainSaslMechanism saslMechanism = new(username, password);
+ TSaslTransport saslTransport = new(bufferedTransport,
saslMechanism, config: new());
+ return new TFramedTransport(saslTransport);
+
+ default:
+ throw new NotSupportedException($"Authentication type
'{authTypeValue}' is not supported.");
+ }
+ }
+
+ protected override async Task<TProtocol>
CreateProtocolAsync(TTransport transport, CancellationToken cancellationToken =
default)
+ {
+ if (!transport.IsOpen) await
transport.OpenAsync(cancellationToken);
+ return new TBinaryProtocol(transport, true, true);
+ }
+
+ protected override TOpenSessionReq CreateSessionRequest()
+ {
+ TOpenSessionReq request = new TOpenSessionReq
+ {
+ Client_protocol =
TProtocolVersion.HIVE_CLI_SERVICE_PROTOCOL_V11,
+ CanUseMultipleCatalogs = true,
+ };
+ return request;
+ }
+
+ protected override HiveServer2TransportType Type =>
HiveServer2TransportType.Standard;
+ }
+}
diff --git a/csharp/src/Drivers/Apache/Hive2/HiveServer2TransportType.cs
b/csharp/src/Drivers/Apache/Hive2/HiveServer2TransportType.cs
index b0c0ee83a..38deab210 100644
--- a/csharp/src/Drivers/Apache/Hive2/HiveServer2TransportType.cs
+++ b/csharp/src/Drivers/Apache/Hive2/HiveServer2TransportType.cs
@@ -20,12 +20,13 @@ namespace Apache.Arrow.Adbc.Drivers.Apache.Hive2
internal enum HiveServer2TransportType
{
Http,
+ Standard,
Empty = int.MaxValue,
}
internal static class HiveServer2TransportTypeParser
{
- internal const string SupportedList =
HiveServer2TransportTypeConstants.Http;
+ internal const string SupportedList =
HiveServer2TransportTypeConstants.Http + ", " +
HiveServer2TransportTypeConstants.Standard;
internal static bool TryParse(string? serverType, out
HiveServer2TransportType serverTypeValue)
{
@@ -38,6 +39,9 @@ namespace Apache.Arrow.Adbc.Drivers.Apache.Hive2
case HiveServer2TransportTypeConstants.Http:
serverTypeValue = HiveServer2TransportType.Http;
return true;
+ case HiveServer2TransportTypeConstants.Standard:
+ serverTypeValue = HiveServer2TransportType.Standard;
+ return true;
default:
serverTypeValue = default;
return false;
diff --git a/csharp/src/Drivers/Apache/Impala/ImpalaStandardConnection.cs
b/csharp/src/Drivers/Apache/Impala/ImpalaStandardConnection.cs
index c1cc1320e..a016a4dc0 100644
--- a/csharp/src/Drivers/Apache/Impala/ImpalaStandardConnection.cs
+++ b/csharp/src/Drivers/Apache/Impala/ImpalaStandardConnection.cs
@@ -104,6 +104,11 @@ namespace Apache.Arrow.Adbc.Drivers.Apache.Impala
// Assumption: hostName and port have already been validated.
Properties.TryGetValue(ImpalaParameters.HostName, out string?
hostName);
Properties.TryGetValue(ImpalaParameters.Port, out string? port);
+ Properties.TryGetValue(ImpalaParameters.AuthType, out string?
authType);
+ if (!ImpalaAuthTypeParser.TryParse(authType, out ImpalaAuthType
authTypeValue))
+ {
+ throw new
ArgumentOutOfRangeException(ImpalaParameters.AuthType, authType, $"Unsupported
{ImpalaParameters.AuthType} value.");
+ }
// Delay the open connection until later.
bool connectClient = false;
@@ -125,7 +130,26 @@ namespace Apache.Arrow.Adbc.Drivers.Apache.Impala
}
TBufferedTransport bufferedTransport = new(transport);
- return bufferedTransport;
+ switch (authTypeValue)
+ {
+ case ImpalaAuthType.None:
+ return bufferedTransport;
+
+ case ImpalaAuthType.Basic:
+ Properties.TryGetValue(AdbcOptions.Username, out string?
username);
+ Properties.TryGetValue(AdbcOptions.Password, out string?
password);
+ if (string.IsNullOrWhiteSpace(username) ||
string.IsNullOrWhiteSpace(password))
+ {
+ throw new InvalidOperationException("Username and
password must be provided for this authentication type.");
+ }
+
+ PlainSaslMechanism saslMechanism = new(username, password);
+ TSaslTransport saslTransport = new(bufferedTransport,
saslMechanism, config: new());
+ return new TFramedTransport(saslTransport);
+
+ default:
+ throw new NotSupportedException($"Authentication type
'{authTypeValue}' is not supported.");
+ }
}
protected override async Task<TProtocol>
CreateProtocolAsync(TTransport transport, CancellationToken cancellationToken =
default)
diff --git a/csharp/src/Drivers/Apache/Thrift/Sasl/ISaslMechanism.cs
b/csharp/src/Drivers/Apache/Thrift/Sasl/ISaslMechanism.cs
new file mode 100644
index 000000000..6c072d56c
--- /dev/null
+++ b/csharp/src/Drivers/Apache/Thrift/Sasl/ISaslMechanism.cs
@@ -0,0 +1,43 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one or more
+* contributor license agreements. See the NOTICE file distributed with
+* this work for additional information regarding copyright ownership.
+* The ASF licenses this file to You under the Apache License, Version 2.0
+* (the "License"); you may not use this file except in compliance with
+* the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+namespace Apache.Arrow.Adbc.Drivers.Apache
+{
+ /// <summary>
+ /// Defines the contract for implementing SASL authentication mechanisms
(e.g., PLAIN, GSSAPI).
+ /// This interface allows the client to authenticate over a Thrift
transport using the selected SASL mechanism.
+ /// </summary>
+ internal interface ISaslMechanism
+ {
+ /// <summary>
+ /// Gets the name of the SASL mechanism (e.g., "PLAIN", "GSSAPI").
+ /// </summary>
+ string Name { get; }
+
+ /// <summary>
+ /// Evaluates the challenge from the server and returns the
appropriate response payload.
+ /// </summary>
+ /// <param name="challenge">The server challenge; may be null or empty
for mechanisms like PLAIN.</param>
+ /// <returns>The response payload to send to the server.</returns>
+ byte[] EvaluateChallenge(byte[]? challenge);
+
+ /// <summary>
+ /// Gets a value indicating whether the SASL negotiation process has
completed.
+ /// </summary>
+ bool IsNegotiationCompleted { get; set; }
+ }
+}
diff --git a/csharp/src/Drivers/Apache/Thrift/Sasl/NegotiationStatus.cs
b/csharp/src/Drivers/Apache/Thrift/Sasl/NegotiationStatus.cs
new file mode 100644
index 000000000..0c5bdd52b
--- /dev/null
+++ b/csharp/src/Drivers/Apache/Thrift/Sasl/NegotiationStatus.cs
@@ -0,0 +1,65 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one or more
+* contributor license agreements. See the NOTICE file distributed with
+* this work for additional information regarding copyright ownership.
+* The ASF licenses this file to You under the Apache License, Version 2.0
+* (the "License"); you may not use this file except in compliance with
+* the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+namespace Apache.Arrow.Adbc.Drivers.Apache
+{
+ /// <summary>
+ /// Represents the status of a SASL negotiation between client and server.
+ /// </summary>
+ internal sealed class NegotiationStatus
+ {
+ public static readonly NegotiationStatus Start = new
NegotiationStatus(0x01, nameof(Start));
+ public static readonly NegotiationStatus Ok = new
NegotiationStatus(0x02, nameof(Ok));
+ public static readonly NegotiationStatus Bad = new
NegotiationStatus(0x03, nameof(Bad));
+ public static readonly NegotiationStatus Error = new
NegotiationStatus(0x04, nameof(Error));
+ public static readonly NegotiationStatus Complete = new
NegotiationStatus(0x05, nameof(Complete));
+
+ /// <summary>
+ /// Gets the byte value representing the negotiation status.
+ /// </summary>
+ public byte Value { get; }
+
+ /// <summary>
+ /// Gets the name of the status (e.g., "Start", "Ok").
+ /// </summary>
+ public string Name { get; }
+
+ private NegotiationStatus(byte value, string name)
+ {
+ Value = value;
+ Name = name;
+ }
+
+ /// <summary>
+ /// Gets a <see cref="NegotiationStatus"/> instance by its byte value.
+ /// </summary>
+ /// <param name="value">The byte value of the status.</param>
+ /// <returns>The corresponding <see cref="NegotiationStatus"/>, or
null if unknown.</returns>
+ public static NegotiationStatus? FromValue(byte value)
+ {
+ return value switch
+ {
+ 0x01 => Start,
+ 0x02 => Ok,
+ 0x03 => Bad,
+ 0x04 => Error,
+ 0x05 => Complete,
+ _ => null
+ };
+ }
+ }
+}
diff --git a/csharp/src/Drivers/Apache/Thrift/Sasl/PlainSaslMechanism.cs
b/csharp/src/Drivers/Apache/Thrift/Sasl/PlainSaslMechanism.cs
new file mode 100644
index 000000000..da096a0ff
--- /dev/null
+++ b/csharp/src/Drivers/Apache/Thrift/Sasl/PlainSaslMechanism.cs
@@ -0,0 +1,66 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one or more
+* contributor license agreements. See the NOTICE file distributed with
+* this work for additional information regarding copyright ownership.
+* The ASF licenses this file to You under the Apache License, Version 2.0
+* (the "License"); you may not use this file except in compliance with
+* the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+using System;
+using System.Text;
+
+namespace Apache.Arrow.Adbc.Drivers.Apache
+{
+ /// <summary>
+ /// Implements the SASL PLAIN mechanism for simple username/password
authentication.
+ /// </summary>
+ internal class PlainSaslMechanism : ISaslMechanism
+ {
+ private readonly string _username;
+ private readonly string _password;
+ private readonly string _authorizationId;
+ private bool _isNegotiationCompleted;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="PlainSaslMechanism"/>
class.
+ /// </summary>
+ /// <param name="username">The username for authentication.</param>
+ /// <param name="password">The password for authentication.</param>
+ public PlainSaslMechanism(string username, string password, string
authorizationId = "")
+ {
+ _username = username ?? throw new
ArgumentNullException(nameof(username));
+ _password = password ?? throw new
ArgumentNullException(nameof(password));
+ _authorizationId = authorizationId;
+ }
+
+ public string Name => "PLAIN";
+
+ public byte[] EvaluateChallenge(byte[]? challenge)
+ {
+ if (_isNegotiationCompleted)
+ {
+ // PLAIN is single-step, so return empty array if already done
+ return [];
+ }
+
+ string message = $"{_authorizationId}\0{_username}\0{_password}";
+ _isNegotiationCompleted = true;
+ return Encoding.UTF8.GetBytes(message);
+ }
+
+ public bool IsNegotiationCompleted
+ {
+ get => _isNegotiationCompleted;
+ set => _isNegotiationCompleted = value;
+ }
+ }
+}
diff --git a/csharp/src/Drivers/Apache/Thrift/Sasl/TSaslTransport.cs
b/csharp/src/Drivers/Apache/Thrift/Sasl/TSaslTransport.cs
new file mode 100644
index 000000000..87f0e0d4f
--- /dev/null
+++ b/csharp/src/Drivers/Apache/Thrift/Sasl/TSaslTransport.cs
@@ -0,0 +1,181 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one or more
+* contributor license agreements. See the NOTICE file distributed with
+* this work for additional information regarding copyright ownership.
+* The ASF licenses this file to You under the Apache License, Version 2.0
+* (the "License"); you may not use this file except in compliance with
+* the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+using System;
+using System.Security.Authentication;
+using System.Text;
+using System.Threading.Tasks;
+using System.Threading;
+using Thrift;
+using Thrift.Transport;
+
+namespace Apache.Arrow.Adbc.Drivers.Apache
+{
+ internal class TSaslTransport : TEndpointTransport
+ {
+ private readonly TTransport _innerTransport;
+ private readonly ISaslMechanism _saslMechanism;
+ private readonly TConfiguration _configuration;
+ private byte[] messageHeader = new byte[STATUS_BYTES +
PAYLOAD_LENGTH_BYTES];
+
+ protected const int MECHANISM_NAME_BYTES = 1;
+ protected const int STATUS_BYTES = 1;
+ protected const int PAYLOAD_LENGTH_BYTES = 4;
+
+ public TSaslTransport(TTransport innerTransport, ISaslMechanism
saslMechanism, TConfiguration config)
+ : base(config)
+ {
+ _innerTransport = innerTransport ?? throw new
ArgumentNullException(nameof(innerTransport));
+ _saslMechanism = saslMechanism ?? throw new
ArgumentNullException(nameof(saslMechanism));
+ _configuration = config ?? new TConfiguration();
+ }
+
+ public override bool IsOpen => _innerTransport.IsOpen;
+
+ public override async Task OpenAsync(CancellationToken
cancellationToken = default)
+ {
+ // Open the transport and negotiate SASL authentication
+ await
_innerTransport.OpenAsync(cancellationToken).ConfigureAwait(false);
+ await NegotiateAsync(cancellationToken).ConfigureAwait(false);
+ }
+
+ public override void Close()
+ {
+ _innerTransport.Close();
+ }
+
+ public override ValueTask<int> ReadAsync(byte[] buffer, int offset,
int length, CancellationToken cancellationToken = default)
+ {
+ return _innerTransport.ReadAsync(buffer, offset, length,
cancellationToken);
+ }
+
+ public override Task WriteAsync(byte[] buffer, int offset, int length,
CancellationToken cancellationToken = default)
+ {
+ return _innerTransport.WriteAsync(buffer, offset, length,
cancellationToken);
+ }
+
+ public override Task FlushAsync(CancellationToken cancellationToken =
default)
+ {
+ return _innerTransport.FlushAsync(cancellationToken);
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing)
+ {
+ _innerTransport.Dispose();
+ }
+ }
+
+ private async Task NegotiateAsync(CancellationToken cancellationToken)
+ {
+ // Send the SASL mechanism name
+ await SendMechanismAsync(_saslMechanism.Name,
cancellationToken).ConfigureAwait(false);
+
+ // Send the authentication message
+ var authMessage = _saslMechanism.EvaluateChallenge(null);
+ await SendSaslMessageAsync(NegotiationStatus.Ok, authMessage,
cancellationToken).ConfigureAwait(false);
+
+ // Receive server's response (authentication status)
+ var serverResponse = await
ReceiveSaslMessageAsync(cancellationToken).ConfigureAwait(false);
+
+ if (serverResponse.status == null || serverResponse.status !=
NegotiationStatus.Complete)
+ {
+ throw new AuthenticationException($"SASL {_saslMechanism.Name}
authentication failed.");
+ }
+
+ _saslMechanism.IsNegotiationCompleted = true;
+ }
+
+ private async Task SendMechanismAsync(string mechanismName,
CancellationToken cancellationToken)
+ {
+ // Send the mechanism name to the server
+ byte[] mechanismNameBytes = Encoding.UTF8.GetBytes(mechanismName);
+ await SendSaslMessageAsync(NegotiationStatus.Start,
mechanismNameBytes, cancellationToken);
+ }
+
+ protected async Task SendSaslMessageAsync(NegotiationStatus status,
byte[] payload, CancellationToken cancellationToken)
+ {
+ payload ??= [];
+ // Set status byte
+ messageHeader[0] = status.Value;
+ // Encode payload length
+ EncodeBigEndian(payload.Length, messageHeader, STATUS_BYTES);
+ await _innerTransport.WriteAsync(messageHeader, 0,
messageHeader.Length);
+ await _innerTransport.WriteAsync(payload, 0, payload.Length);
+ await _innerTransport.FlushAsync(cancellationToken);
+ }
+
+ protected async Task<SaslResponse>
ReceiveSaslMessageAsync(CancellationToken cancellationToken = default)
+ {
+ await _innerTransport.ReadAllAsync(messageHeader, 0,
messageHeader.Length, cancellationToken);
+
+ byte statusByte = messageHeader[0];
+
+ NegotiationStatus? status =
NegotiationStatus.FromValue(statusByte);
+ if (status == null)
+ {
+ throw new TTransportException("Received invalid SASL
negotiation status. The status byte was null.");
+ }
+
+ int payloadBytes = DecodeBigEndian(messageHeader, STATUS_BYTES);
+ if (payloadBytes < 0 || payloadBytes >
_configuration.MaxMessageSize)
+ {
+ throw new TTransportException($"Received payload size out of
range: {payloadBytes}. Expected between 0 and {new
TConfiguration().MaxMessageSize}.");
+ }
+
+ byte[] payload = new byte[payloadBytes];
+ await _innerTransport.ReadAllAsync(payload, 0, payload.Length,
cancellationToken);
+
+ if (status == NegotiationStatus.Bad || status ==
NegotiationStatus.Error)
+ {
+ string remoteMessage = Encoding.UTF8.GetString(payload);
+ throw new TTransportException($"Peer indicated failure:
{remoteMessage}");
+ }
+
+ return new SaslResponse(status, payload);
+ }
+
+ private int DecodeBigEndian(byte[] buf, int offset)
+ {
+ return ((buf[offset] & 0xff) << 24)
+ | ((buf[offset + 1] & 0xff) << 16)
+ | ((buf[offset + 2] & 0xff) << 8)
+ | (buf[offset + 3] & 0xff);
+ }
+
+ private void EncodeBigEndian(int value, byte[] buf, int offset)
+ {
+ buf[offset] = (byte)((value >> 24) & 0xff);
+ buf[offset + 1] = (byte)((value >> 16) & 0xff);
+ buf[offset + 2] = (byte)((value >> 8) & 0xff);
+ buf[offset + 3] = (byte)(value & 0xff);
+ }
+ }
+
+ internal class SaslResponse
+ {
+ public NegotiationStatus status;
+ public byte[] payload;
+
+ public SaslResponse(NegotiationStatus status, byte[] payload)
+ {
+ this.status = status;
+ this.payload = payload;
+ }
+ }
+}