blob: 592dba331fc9e68dde68dd8ea86396304d013625 [file]
/*
* 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.
*/
#include <cstring>
#include <cstddef>
#include <sstream>
#include <ignite/common/fixed_size_array.h>
#include "impl/message.h"
#include "impl/ssl/ssl_api.h"
#include "impl/ssl/secure_socket_client.h"
#include "impl/net/tcp_socket_client.h"
#include "impl/net/remote_type_updater.h"
#include "impl/data_channel.h"
namespace ignite
{
namespace impl
{
namespace thin
{
/** Version 1.2.0. */
const ProtocolVersion DataChannel::VERSION_1_2_0(1, 2, 0);
/** Current version. */
const ProtocolVersion DataChannel::VERSION_CURRENT(VERSION_1_2_0);
DataChannel::VersionSet::value_type supportedArray[] = {
DataChannel::VERSION_1_2_0,
};
const DataChannel::VersionSet DataChannel::supportedVersions(supportedArray,
supportedArray + (sizeof(supportedArray) / sizeof(supportedArray[0])));
DataChannel::DataChannel(const ignite::thin::IgniteClientConfiguration& cfg,
binary::BinaryTypeManager& typeMgr) :
ioMutex(),
address(),
config(cfg),
typeMgr(typeMgr),
currentVersion(VERSION_CURRENT),
reqIdCounter(0),
socket()
{
// No-op.
}
DataChannel::~DataChannel()
{
// No-op.
}
bool DataChannel::Connect(const std::string& host, uint16_t port, int32_t timeout)
{
using ignite::thin::SslMode;
if (socket.get() != 0)
throw IgniteError(IgniteError::IGNITE_ERR_ILLEGAL_STATE, "Already connected.");
if (config.GetEndPoints().empty())
throw IgniteError(IgniteError::IGNITE_ERR_ILLEGAL_ARGUMENT, "No valid address to connect.");
SslMode::Type sslMode = config.GetSslMode();
if (sslMode != SslMode::DISABLE)
{
ssl::EnsureSslLoaded();
socket.reset(new ssl::SecureSocketClient(config.GetSslCertFile(),
config.GetSslKeyFile(), config.GetSslCaFile()));
}
else
socket.reset(new net::TcpSocketClient());
address.host = host;
address.port = port;
return TryRestoreConnection(timeout);
}
void DataChannel::Close()
{
if (socket.get() != 0)
{
socket->Close();
socket.reset();
}
}
void DataChannel::InternalSyncMessage(interop::InteropUnpooledMemory& mem, int32_t timeout)
{
common::concurrent::CsLockGuard lock(ioMutex);
bool success = Send(mem.Data(), mem.Length(), timeout);
if (!success)
throw IgniteError(IgniteError::IGNITE_ERR_GENERIC,
"Can not send message to remote host: timeout");
success = Receive(mem, timeout);
if (!success)
throw IgniteError(IgniteError::IGNITE_ERR_GENERIC,
"Can not send message to remote host: timeout");
}
bool DataChannel::Send(const int8_t* data, size_t len, int32_t timeout)
{
if (socket.get() == 0)
throw IgniteError(IgniteError::IGNITE_ERR_ILLEGAL_STATE, "Connection is not established");
OperationResult::T res = SendAll(data, len, timeout);
if (res == OperationResult::TIMEOUT)
return false;
if (res == OperationResult::FAIL)
throw IgniteError(IgniteError::IGNITE_ERR_GENERIC,
"Can not send message due to connection failure");
return true;
}
DataChannel::OperationResult::T DataChannel::SendAll(const int8_t* data, size_t len, int32_t timeout)
{
int sent = 0;
while (sent != static_cast<int64_t>(len))
{
int res = socket->Send(data + sent, len - sent, timeout);
if (res < 0 || res == SocketClient::WaitResult::TIMEOUT)
{
Close();
return res < 0 ? OperationResult::FAIL : OperationResult::TIMEOUT;
}
sent += res;
}
assert(static_cast<size_t>(sent) == len);
return OperationResult::SUCCESS;
}
bool DataChannel::Receive(interop::InteropMemory& msg, int32_t timeout)
{
assert(msg.Capacity() > 4);
if (socket.get() == 0)
throw IgniteError(IgniteError::IGNITE_ERR_ILLEGAL_STATE, "DataChannel is not established");
// Message size
msg.Length(4);
OperationResult::T res = ReceiveAll(msg.Data(), static_cast<size_t>(msg.Length()), timeout);
if (res == OperationResult::TIMEOUT)
return false;
if (res == OperationResult::FAIL)
throw IgniteError(IgniteError::IGNITE_ERR_GENERIC, "Can not receive message header");
interop::InteropInputStream inStream(&msg);
int32_t msgLen = inStream.ReadInt32();
if (msgLen < 0)
{
Close();
throw IgniteError(IgniteError::IGNITE_ERR_GENERIC, "Protocol error: Message length is negative");
}
if (msgLen == 0)
return true;
if (msg.Capacity() < msgLen + 4)
msg.Reallocate(msgLen + 4);
msg.Length(4 + msgLen);
res = ReceiveAll(msg.Data() + 4, msgLen, timeout);
if (res == OperationResult::TIMEOUT)
return false;
if (res == OperationResult::FAIL)
throw IgniteError(IgniteError::IGNITE_ERR_GENERIC,
"Connection failure: Can not receive message body");
return true;
}
DataChannel::OperationResult::T DataChannel::ReceiveAll(void* dst, size_t len, int32_t timeout)
{
size_t remain = len;
int8_t* buffer = reinterpret_cast<int8_t*>(dst);
while (remain)
{
size_t received = len - remain;
int res = socket->Receive(buffer + received, remain, timeout);
if (res < 0 || res == SocketClient::WaitResult::TIMEOUT)
{
Close();
return res < 0 ? OperationResult::FAIL : OperationResult::TIMEOUT;
}
remain -= static_cast<size_t>(res);
}
return OperationResult::SUCCESS;
}
bool DataChannel::MakeRequestHandshake(const ProtocolVersion& propVer,
ProtocolVersion& resVer, int32_t timeout)
{
currentVersion = propVer;
resVer = ProtocolVersion();
bool accepted = false;
try
{
// Workaround for some Linux systems that report connection on non-blocking
// sockets as successful but fail to establish real connection.
accepted = Handshake(propVer, resVer, timeout);
}
catch (const IgniteError&)
{
return false;
}
if (!accepted)
return false;
resVer = propVer;
return true;
}
bool DataChannel::Handshake(const ProtocolVersion& propVer, ProtocolVersion& resVer, int32_t timeout)
{
// Allocating 4KB just in case.
enum { BUFFER_SIZE = 1024 * 4 };
common::concurrent::CsLockGuard lock(ioMutex);
interop::InteropUnpooledMemory mem(BUFFER_SIZE);
interop::InteropOutputStream outStream(&mem);
binary::BinaryWriterImpl writer(&outStream, 0);
int32_t lenPos = outStream.Reserve(4);
writer.WriteInt8(RequestType::HANDSHAKE);
writer.WriteInt16(propVer.GetMajor());
writer.WriteInt16(propVer.GetMinor());
writer.WriteInt16(propVer.GetMaintenance());
writer.WriteInt8(ClientType::THIN_CLIENT);
writer.WriteString(config.GetUser());
writer.WriteString(config.GetPassword());
outStream.WriteInt32(lenPos, outStream.Position() - 4);
bool success = Send(mem.Data(), outStream.Position(), timeout);
if (!success)
return false;
success = Receive(mem, timeout);
if (!success)
return false;
interop::InteropInputStream inStream(&mem);
inStream.Position(4);
binary::BinaryReaderImpl reader(&inStream);
bool accepted = reader.ReadBool();
if (!accepted)
{
int16_t major = reader.ReadInt16();
int16_t minor = reader.ReadInt16();
int16_t maintenance = reader.ReadInt16();
resVer = ProtocolVersion(major, minor, maintenance);
std::string error;
reader.ReadString(error);
reader.ReadInt32();
return false;
}
return true;
}
bool DataChannel::EnsureConnected(int32_t timeout)
{
if (socket.get() != 0)
return false;
return TryRestoreConnection(timeout);
}
bool DataChannel::NegotiateProtocolVersion(int32_t timeout)
{
ProtocolVersion resVer;
ProtocolVersion propVer = VERSION_CURRENT;
bool success = MakeRequestHandshake(propVer, resVer, timeout);
while (!success)
{
if (resVer == propVer || !IsVersionSupported(resVer))
return false;
propVer = resVer;
success = MakeRequestHandshake(propVer, resVer, timeout);
}
return true;
}
bool DataChannel::TryRestoreConnection(int32_t timeout)
{
bool connected = false;
connected = socket->Connect(address.host.c_str(), address.port, timeout);
if (!connected)
{
Close();
return false;
}
connected = NegotiateProtocolVersion(timeout);
if (!connected)
{
Close();
return false;
}
return true;
}
bool DataChannel::IsVersionSupported(const ProtocolVersion& ver)
{
return supportedVersions.find(ver) != supportedVersions.end();
}
}
}
}