public ClickHouseTcpClient( TcpClient client, ClickHouseBinaryProtocolReader reader, ClickHouseBinaryProtocolWriter writer, ClickHouseConnectionSettings settings, ClickHouseServerInfo serverInfo, IClickHouseTypeInfoProvider typeInfoProvider) { _reader = reader ?? throw new ArgumentNullException(nameof(reader)); _writer = writer ?? throw new ArgumentNullException(nameof(writer)); _client = client ?? throw new ArgumentNullException(nameof(client)); _settings = settings ?? throw new ArgumentNullException(nameof(settings)); ServerInfo = serverInfo; TypeInfoProvider = typeInfoProvider; }
private async ValueTask Open(bool async, CancellationToken cancellationToken) { if (!BitConverter.IsLittleEndian) { throw new NotSupportedException( "An architecture of the processor is not supported. Only little-endian architectures are supported." + Environment.NewLine + "Please, report an issue if you see this message (https://github.com/Octonica/ClickHouseClient/issues)."); } var connectionState = _connectionState; switch (connectionState.State) { case ConnectionState.Closed: break; case ConnectionState.Open: return; // Re-entrance is allowed case ConnectionState.Connecting: throw new ClickHouseException(ClickHouseErrorCodes.InvalidConnectionState, "The connection is already opening."); case ConnectionState.Broken: throw new ClickHouseException(ClickHouseErrorCodes.InvalidConnectionState, "The connection is broken."); default: throw new NotSupportedException($"Internal error. The state {_connectionState} is not supported."); } if (!TryChangeConnectionState(connectionState, ConnectionState.Connecting, out connectionState, out var onStateChanged)) { throw new ClickHouseException(ClickHouseErrorCodes.InvalidConnectionState, "The state of the connection was modified."); } var stateChangeEx = onStateChanged(this); var connectionSettings = connectionState.Settings; if (stateChangeEx != null || connectionSettings == null) { var initialEx = stateChangeEx ?? new ClickHouseException(ClickHouseErrorCodes.InvalidConnectionState, "The connection is not initialized."); if (!TryChangeConnectionState(connectionState, ConnectionState.Closed, out _, out onStateChanged)) { throw new AggregateException(initialEx, new ClickHouseException(ClickHouseErrorCodes.InvalidConnectionState, "The state of the connection was modified.")); } var stateChangeEx2 = onStateChanged(this); if (stateChangeEx2 != null) { throw new AggregateException(initialEx, stateChangeEx2); } if (stateChangeEx != null) { throw new ClickHouseException(ClickHouseErrorCodes.CallbackError, "External callback error. See the inner exception for details.", stateChangeEx); } throw initialEx; } const int defaultHttpPort = 8123; TcpClient?client = null; SslStream?sslStream = null; ClickHouseBinaryProtocolWriter?writer = null; ClickHouseBinaryProtocolReader?reader = null; try { try { client = new TcpClient { SendTimeout = connectionSettings.ReadWriteTimeout, ReceiveTimeout = connectionSettings.ReadWriteTimeout }; if (async) { await client.ConnectAsync(connectionSettings.Host, connectionSettings.Port); } else { client.Connect(connectionSettings.Host, connectionSettings.Port); } } catch { client?.Client?.Close(0); client?.Dispose(); client = null; throw; } if (connectionSettings.TlsMode == ClickHouseTlsMode.Require) { var certValidationCallback = RemoteCertificateValidationCallback; if (certValidationCallback == null && (connectionSettings.RootCertificate != null || !connectionSettings.ServerCertificateHash.IsEmpty)) { certValidationCallback = (_, cert, chain, errors) => ValidateServerCertificate(connectionSettings, cert, chain, errors); } sslStream = new SslStream(client.GetStream(), true, certValidationCallback); try { if (async) { await sslStream.AuthenticateAsClientAsync(new SslClientAuthenticationOptions { TargetHost = connectionSettings.Host }, cancellationToken); } else { sslStream.AuthenticateAsClient(connectionSettings.Host); } } catch (AuthenticationException authEx) { throw new ClickHouseException(ClickHouseErrorCodes.TlsError, $"TLS handshake error.", authEx); } } var stream = sslStream ?? (Stream)client.GetStream(); writer = new ClickHouseBinaryProtocolWriter(stream, Math.Max(connectionSettings.BufferSize, MinBufferSize)); var clientHello = new ClientHelloMessage.Builder { ClientName = connectionSettings.ClientName, ClientVersion = connectionSettings.ClientVersion, User = connectionSettings.User, Database = connectionSettings.Database, Password = connectionSettings.Password, ProtocolRevision = ClickHouseProtocolRevisions.CurrentRevision }.Build(); clientHello.Write(writer); await writer.Flush(async, cancellationToken); reader = new ClickHouseBinaryProtocolReader(stream, Math.Max(connectionSettings.BufferSize, MinBufferSize)); var message = await reader.ReadMessage(false, async, cancellationToken); switch (message.MessageCode) { case ServerMessageCode.Hello: var helloMessage = (ServerHelloMessage)message; bool hasExtraByte = reader.TryPeekByte(out var extraByte); if (!hasExtraByte && client.Available > 0) { hasExtraByte = true; extraByte = await reader.ReadByte(async, cancellationToken); } if (hasExtraByte) { throw new ClickHouseException(ClickHouseErrorCodes.ProtocolUnexpectedResponse, $"Expected the end of the data. Unexpected byte (0x{extraByte:X}) received from the server."); } var serverInfo = helloMessage.ServerInfo; var configuredTypeInfoProvider = (_typeInfoProvider ?? DefaultTypeInfoProvider.Instance).Configure(serverInfo); var tcpClient = new ClickHouseTcpClient(client, reader, writer, connectionSettings, serverInfo, configuredTypeInfoProvider, sslStream); if (!TryChangeConnectionState(connectionState, ConnectionState.Open, tcpClient, out _, out onStateChanged)) { throw new ClickHouseException(ClickHouseErrorCodes.InvalidConnectionState, "The state of the connection was modified."); } break; case ServerMessageCode.Error: throw ((ServerErrorMessage)message).Exception; default: if ((int)message.MessageCode == 'H') { // It looks like HTTP string httpDetectedMessage; if (connectionSettings.Port == defaultHttpPort) { // It's definitely HTTP httpDetectedMessage = $"Detected an attempt to connect by HTTP protocol with the default port {defaultHttpPort}. "; } else { httpDetectedMessage = $"Internal error. Unexpected message code (0x{message.MessageCode:X}) received from the server. " + "This error may by caused by an attempt to connect with HTTP protocol. "; } httpDetectedMessage += $"{ClickHouseConnectionStringBuilder.DefaultClientName} supports only ClickHouse native protocol. " + $"The default port for the native protocol is {ClickHouseConnectionStringBuilder.DefaultPort}."; throw new ClickHouseException(ClickHouseErrorCodes.ProtocolUnexpectedResponse, httpDetectedMessage); } if ((int)message.MessageCode == 0x15) { // 0x15 stands for TLS alert message var sslAlertMessage = $"Unexpected message code (0x{message.MessageCode:X}) received from the server. " + "This code may indicate that the server requires establishing a connection over TLS."; throw new ClickHouseException(ClickHouseErrorCodes.ProtocolUnexpectedResponse, sslAlertMessage); } throw new ClickHouseException(ClickHouseErrorCodes.ProtocolUnexpectedResponse, $"Internal error. Unexpected message code (0x{message.MessageCode:X}) received from the server."); } } catch (Exception ex) { reader?.Dispose(); writer?.Dispose(); sslStream?.Dispose(); client?.Client?.Close(0); client?.Dispose(); if (TryChangeConnectionState(connectionState, ConnectionState.Closed, out _, out onStateChanged)) { stateChangeEx = onStateChanged(this); } if (connectionSettings.Port == defaultHttpPort && ex is IOException) { var extraMessage = $"{ex.Message} This error may be caused by an attempt to connect to the default HTTP port ({defaultHttpPort}). " + $"{ClickHouseConnectionStringBuilder.DefaultClientName} supports only ClickHouse native protocol. " + $"The default port for the native protocol is {ClickHouseConnectionStringBuilder.DefaultPort}."; var extraEx = new IOException(extraMessage, ex); if (stateChangeEx != null) { throw new AggregateException(extraEx, stateChangeEx); } throw extraEx; } if (stateChangeEx != null) { throw new AggregateException(ex, stateChangeEx); } throw; } stateChangeEx = onStateChanged.Invoke(this); if (stateChangeEx != null) { throw new ClickHouseException(ClickHouseErrorCodes.CallbackError, "External callback error. See the inner exception for details.", stateChangeEx); } }