You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
274 lines
8.0 KiB
274 lines
8.0 KiB
/*
|
|
* Copyright (C) 2017 The Android Open Source Project
|
|
*
|
|
* Licensed 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 "chre_host/socket_client.h"
|
|
|
|
#include <inttypes.h>
|
|
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
|
|
#include <chrono>
|
|
|
|
#include <cutils/sockets.h>
|
|
#include <sys/socket.h>
|
|
#include <utils/RefBase.h>
|
|
#include <utils/StrongPointer.h>
|
|
|
|
#include "chre_host/log.h"
|
|
|
|
namespace android {
|
|
namespace chre {
|
|
|
|
SocketClient::SocketClient() {
|
|
std::atomic_init(&mSockFd, INVALID_SOCKET);
|
|
}
|
|
|
|
SocketClient::~SocketClient() {
|
|
disconnect();
|
|
}
|
|
|
|
bool SocketClient::connect(const char *socketName,
|
|
const sp<ICallbacks> &callbacks) {
|
|
return doConnect(socketName, callbacks, false /* connectInBackground */);
|
|
}
|
|
|
|
bool SocketClient::connectInBackground(const char *socketName,
|
|
const sp<ICallbacks> &callbacks) {
|
|
return doConnect(socketName, callbacks, true /* connectInBackground */);
|
|
}
|
|
|
|
void SocketClient::disconnect() {
|
|
if (inReceiveThread()) {
|
|
LOGE("disconnect() can't be called from a receive thread callback");
|
|
} else if (receiveThreadRunning()) {
|
|
// Inform the RX thread that we're requesting a shutdown, breaking it out of
|
|
// the retry wait if it's currently blocked there
|
|
{
|
|
std::lock_guard<std::mutex> lock(mShutdownMutex);
|
|
mGracefulShutdown = true;
|
|
}
|
|
mShutdownCond.notify_all();
|
|
|
|
// Invalidate the socket (will kick the RX thread out of recv if it's
|
|
// currently blocked there)
|
|
if (mSockFd != INVALID_SOCKET && shutdown(mSockFd, SHUT_RDWR) != 0) {
|
|
LOG_ERROR("Couldn't shut down socket", errno);
|
|
}
|
|
|
|
if (mRxThread.joinable()) {
|
|
LOGD("Waiting for RX thread to exit");
|
|
mRxThread.join();
|
|
}
|
|
}
|
|
}
|
|
|
|
bool SocketClient::isConnected() const {
|
|
return (mSockFd != INVALID_SOCKET);
|
|
}
|
|
|
|
bool SocketClient::sendMessage(const void *data, size_t length) {
|
|
bool success = false;
|
|
|
|
if (mSockFd == INVALID_SOCKET) {
|
|
LOGW("Tried sending a message, but don't have a valid socket handle");
|
|
} else {
|
|
ssize_t bytesSent = send(mSockFd, data, length, 0);
|
|
if (bytesSent < 0) {
|
|
LOGE("Failed to send %zu bytes of data: %s", length, strerror(errno));
|
|
} else if (bytesSent == 0) {
|
|
LOGW("Failed to send data; remote side disconnected");
|
|
} else if (static_cast<size_t>(bytesSent) != length) {
|
|
LOGW("Truncated packet, tried sending %zu bytes, only %zd went through",
|
|
length, bytesSent);
|
|
} else {
|
|
success = true;
|
|
}
|
|
}
|
|
|
|
return success;
|
|
}
|
|
|
|
bool SocketClient::doConnect(const char *socketName,
|
|
const sp<ICallbacks> &callbacks,
|
|
bool connectInBackground) {
|
|
bool success = false;
|
|
if (inReceiveThread()) {
|
|
LOGE("Can't attempt to connect from a receive thread callback");
|
|
} else {
|
|
if (receiveThreadRunning()) {
|
|
LOGW("Re-connecting socket with implicit disconnect");
|
|
disconnect();
|
|
}
|
|
|
|
size_t socketNameLen =
|
|
strlcpy(mSocketName, socketName, sizeof(mSocketName));
|
|
if (socketNameLen >= sizeof(mSocketName)) {
|
|
LOGE("Socket name length parameter is too long (%zu, max %zu)",
|
|
socketNameLen, sizeof(mSocketName));
|
|
} else if (callbacks == nullptr) {
|
|
LOGE("Callbacks parameter must be provided");
|
|
} else if (connectInBackground || tryConnect()) {
|
|
mGracefulShutdown = false;
|
|
mCallbacks = callbacks;
|
|
mRxThread = std::thread([this]() { receiveThread(); });
|
|
success = true;
|
|
}
|
|
}
|
|
|
|
return success;
|
|
}
|
|
|
|
bool SocketClient::inReceiveThread() const {
|
|
return (std::this_thread::get_id() == mRxThread.get_id());
|
|
}
|
|
|
|
void SocketClient::receiveThread() {
|
|
constexpr size_t kReceiveBufferSize = 4096;
|
|
uint8_t buffer[kReceiveBufferSize];
|
|
|
|
LOGV("Receive thread started");
|
|
while (!mGracefulShutdown && (mSockFd != INVALID_SOCKET || reconnect())) {
|
|
while (!mGracefulShutdown) {
|
|
ssize_t bytesReceived = recv(mSockFd, buffer, sizeof(buffer), 0);
|
|
if (bytesReceived < 0) {
|
|
LOG_ERROR("Exiting RX thread", errno);
|
|
break;
|
|
} else if (bytesReceived == 0) {
|
|
if (!mGracefulShutdown) {
|
|
LOGI("Socket disconnected on remote end");
|
|
mCallbacks->onDisconnected();
|
|
}
|
|
break;
|
|
}
|
|
|
|
mCallbacks->onMessageReceived(buffer, bytesReceived);
|
|
}
|
|
|
|
if (close(mSockFd) != 0) {
|
|
LOG_ERROR("Couldn't close socket", errno);
|
|
}
|
|
mSockFd = INVALID_SOCKET;
|
|
}
|
|
|
|
if (!mGracefulShutdown) {
|
|
mCallbacks->onConnectionAborted();
|
|
}
|
|
|
|
mCallbacks.clear();
|
|
LOGV("Exiting receive thread");
|
|
}
|
|
|
|
bool SocketClient::receiveThreadRunning() const {
|
|
return mRxThread.joinable();
|
|
}
|
|
|
|
bool SocketClient::reconnect() {
|
|
constexpr auto kMinDelay = std::chrono::duration<int32_t, std::milli>(250);
|
|
constexpr auto kMaxDelay = std::chrono::minutes(5);
|
|
// Try reconnecting at initial delay this many times before backing off
|
|
constexpr unsigned int kExponentialBackoffDelay =
|
|
std::chrono::seconds(10) / kMinDelay;
|
|
// Give up after this many tries (~2.5 hours)
|
|
constexpr unsigned int kRetryLimit = kExponentialBackoffDelay + 40;
|
|
auto delay = kMinDelay;
|
|
unsigned int retryCount = 0;
|
|
|
|
while (retryCount++ < kRetryLimit) {
|
|
{
|
|
std::unique_lock<std::mutex> lock(mShutdownMutex);
|
|
mShutdownCond.wait_for(lock, delay,
|
|
[this]() { return mGracefulShutdown.load(); });
|
|
if (mGracefulShutdown) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
bool suppressErrorLogs = (delay == kMinDelay);
|
|
if (!tryConnect(suppressErrorLogs)) {
|
|
if (!suppressErrorLogs) {
|
|
LOGW("Failed to (re)connect, next try in %" PRId32 " ms",
|
|
delay.count());
|
|
}
|
|
if (retryCount > kExponentialBackoffDelay) {
|
|
delay *= 2;
|
|
}
|
|
if (delay > kMaxDelay) {
|
|
delay = kMaxDelay;
|
|
}
|
|
} else {
|
|
LOGD("Successfully (re)connected");
|
|
mCallbacks->onConnected();
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool SocketClient::tryConnect(bool suppressErrorLogs) {
|
|
bool success = false;
|
|
|
|
errno = 0;
|
|
int sockFd = socket(AF_LOCAL, SOCK_SEQPACKET, 0);
|
|
if (sockFd >= 0) {
|
|
// Set the send buffer size to 2MB to allow plenty of room for nanoapp
|
|
// loading
|
|
int sndbuf = 2 * 1024 * 1024;
|
|
// Normally, send() should effectively return immediately, but in the event
|
|
// that we get blocked due to flow control, don't stay blocked for more than
|
|
// 3 seconds
|
|
struct timeval timeout = {
|
|
.tv_sec = 3,
|
|
.tv_usec = 0,
|
|
};
|
|
int ret;
|
|
|
|
if ((ret = setsockopt(sockFd, SOL_SOCKET, SO_SNDBUF, &sndbuf,
|
|
sizeof(sndbuf))) != 0) {
|
|
if (!suppressErrorLogs) {
|
|
LOGE("Failed to set SO_SNDBUF to %d: %s", sndbuf, strerror(errno));
|
|
}
|
|
} else if ((ret = setsockopt(sockFd, SOL_SOCKET, SO_SNDTIMEO, &timeout,
|
|
sizeof(timeout))) != 0) {
|
|
if (!suppressErrorLogs) {
|
|
LOGE("Failed to set SO_SNDTIMEO: %s", strerror(errno));
|
|
}
|
|
} else {
|
|
mSockFd = socket_local_client_connect(sockFd, mSocketName,
|
|
ANDROID_SOCKET_NAMESPACE_RESERVED,
|
|
SOCK_SEQPACKET);
|
|
if (mSockFd != INVALID_SOCKET) {
|
|
success = true;
|
|
} else if (!suppressErrorLogs) {
|
|
LOGE("Couldn't connect client socket to '%s': %s", mSocketName,
|
|
strerror(errno));
|
|
}
|
|
}
|
|
|
|
if (!success) {
|
|
close(sockFd);
|
|
}
|
|
} else if (!suppressErrorLogs) {
|
|
LOGE("Couldn't create local socket: %s", strerror(errno));
|
|
}
|
|
|
|
return success;
|
|
}
|
|
|
|
} // namespace chre
|
|
} // namespace android
|