| /* |
| * Copyright (c) 2020, The OpenThread Authors. |
| * All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions are met: |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * 3. Neither the name of the copyright holder nor the |
| * names of its contributors may be used to endorse or promote products |
| * derived from this software without specific prior written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" |
| * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
| * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE |
| * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
| * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
| * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
| * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
| * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
| * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
| * POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| #include "rest/connection.hpp" |
| |
| #include <cerrno> |
| |
| #include <assert.h> |
| |
| #include <sys/socket.h> |
| #include <sys/time.h> |
| |
| using std::chrono::duration_cast; |
| using std::chrono::microseconds; |
| using std::chrono::seconds; |
| using std::chrono::steady_clock; |
| |
| namespace otbr { |
| namespace rest { |
| |
| // The timeout (in microseconds) since a connection is in wait callback state |
| static const uint32_t kCallbackTimeout = 10000000; |
| |
| // The time interval (in microseconds) for checking again if there is a connection need callback. |
| static const uint32_t kCallbackCheckInterval = 500000; |
| |
| // The timeout (in microseconds) since a connection is in wait write state |
| static const uint32_t kWriteTimeout = 10000000; |
| |
| // The timeout (in microseconds) since a connection is in wait read state |
| static const uint32_t kReadTimeout = 1000000; |
| |
| Connection::Connection(steady_clock::time_point aStartTime, Resource *aResource, int aFd) |
| : mTimeStamp(aStartTime) |
| , mFd(aFd) |
| , mState(ConnectionState::kInit) |
| , mParser(&mRequest) |
| , mResource(aResource) |
| { |
| } |
| |
| Connection::~Connection(void) |
| { |
| Disconnect(); |
| } |
| |
| void Connection::Init(void) |
| { |
| mParser.Init(); |
| } |
| |
| void Connection::UpdateReadFdSet(fd_set &aReadFdSet, int &aMaxFd) const |
| { |
| if (mState == ConnectionState::kReadWait || mState == ConnectionState::kInit) |
| { |
| FD_SET(mFd, &aReadFdSet); |
| aMaxFd = aMaxFd < mFd ? mFd : aMaxFd; |
| } |
| } |
| |
| void Connection::UpdateWriteFdSet(fd_set &aWriteFdSet, int &aMaxFd) const |
| { |
| if (mState == ConnectionState::kWriteWait) |
| { |
| FD_SET(mFd, &aWriteFdSet); |
| aMaxFd = aMaxFd < mFd ? mFd : aMaxFd; |
| } |
| } |
| |
| void Connection::UpdateTimeout(timeval &aTimeout) const |
| { |
| struct timeval timeout; |
| uint32_t timeoutLen = kReadTimeout; |
| auto duration = duration_cast<microseconds>(steady_clock::now() - mTimeStamp).count(); |
| |
| switch (mState) |
| { |
| case ConnectionState::kReadWait: |
| timeoutLen = kReadTimeout; |
| break; |
| case ConnectionState::kCallbackWait: |
| timeoutLen = kCallbackCheckInterval; |
| break; |
| case ConnectionState::kWriteWait: |
| timeoutLen = kWriteTimeout; |
| break; |
| case ConnectionState::kComplete: |
| timeoutLen = 0; |
| break; |
| default: |
| break; |
| } |
| |
| if (duration <= timeoutLen) |
| { |
| timeout.tv_sec = 0; |
| timeout.tv_usec = timeoutLen - duration; |
| } |
| else |
| { |
| timeout.tv_sec = 0; |
| timeout.tv_usec = 0; |
| } |
| |
| if (timercmp(&timeout, &aTimeout, <)) |
| { |
| aTimeout = timeout; |
| } |
| } |
| |
| void Connection::Update(MainloopContext &aMainloop) |
| { |
| UpdateTimeout(aMainloop.mTimeout); |
| UpdateReadFdSet(aMainloop.mReadFdSet, aMainloop.mMaxFd); |
| UpdateWriteFdSet(aMainloop.mWriteFdSet, aMainloop.mMaxFd); |
| } |
| |
| void Connection::Disconnect(void) |
| { |
| mState = ConnectionState::kComplete; |
| |
| if (mFd != -1) |
| { |
| close(mFd); |
| mFd = -1; |
| } |
| } |
| |
| void Connection::Process(const MainloopContext &aMainloop) |
| { |
| otbrError error = OTBR_ERROR_NONE; |
| |
| switch (mState) |
| { |
| // Initial state, directly read for the first time. |
| case ConnectionState::kInit: |
| case ConnectionState::kReadWait: |
| ProcessWaitRead(aMainloop.mReadFdSet); |
| break; |
| case ConnectionState::kCallbackWait: |
| // Wait for Callback process. |
| ProcessWaitCallback(); |
| break; |
| case ConnectionState::kWriteWait: |
| ProcessWaitWrite(aMainloop.mWriteFdSet); |
| break; |
| default: |
| assert(false); |
| } |
| |
| if (error != OTBR_ERROR_NONE) |
| { |
| Disconnect(); |
| } |
| } |
| |
| void Connection::ProcessWaitRead(const fd_set &aReadFdSet) |
| { |
| otbrError error = OTBR_ERROR_NONE; |
| int32_t received = 0, err; |
| char buf[2048]; |
| auto duration = duration_cast<microseconds>(steady_clock::now() - mTimeStamp).count(); |
| |
| // Reach a read timeout, will send response about this timeout later. |
| VerifyOrExit(duration <= kReadTimeout, error = OTBR_ERROR_REST); |
| |
| // It will succeed either fd is set or it is in kInit state. |
| VerifyOrExit(FD_ISSET(mFd, &aReadFdSet) || mState == ConnectionState::kInit); |
| |
| do |
| { |
| mState = ConnectionState::kReadWait; |
| received = read(mFd, buf, sizeof(buf)); |
| err = errno; |
| if (received > 0) |
| { |
| mParser.Process(buf, received); |
| } |
| } while ((received > 0 && !mRequest.IsComplete()) || err == EINTR); |
| |
| if (mRequest.IsComplete()) |
| { |
| Handle(); |
| } |
| |
| // Check first failure situation: received == 0 (indicate another side at least has closes its write side ) |
| // and at the same time, the request has not been parsed completely. |
| VerifyOrExit(received != 0 || mRequest.IsComplete(), error = OTBR_ERROR_REST); |
| |
| // Check second failure situation : received = -1 error(indicates that our system call read raise an error ) |
| // then try to send back a response that there is an internal error. |
| VerifyOrExit(received > 0 || (received == -1 && (err == EAGAIN || err == EWOULDBLOCK)), error = OTBR_ERROR_REST); |
| |
| exit: |
| if (error != OTBR_ERROR_NONE) |
| { |
| if (received < 0) |
| { |
| mResource->ErrorHandler(mResponse, HttpStatusCode::kStatusInternalServerError); |
| Write(); |
| } |
| else |
| { |
| mResource->ErrorHandler(mResponse, HttpStatusCode::kStatusRequestTimeout); |
| Write(); |
| } |
| } |
| } |
| |
| void Connection::Handle(void) |
| { |
| otbrError error = OTBR_ERROR_NONE; |
| |
| // Try to close server read side here, because we have started to handle the request and no longler read from |
| // socket. |
| VerifyOrExit((shutdown(mFd, SHUT_RD) == 0), error = OTBR_ERROR_REST); |
| |
| mResource->Handle(mRequest, mResponse); |
| |
| if (mResponse.NeedCallback()) |
| { |
| mState = ConnectionState::kCallbackWait; |
| mTimeStamp = steady_clock::now(); |
| } |
| else |
| { |
| // Normal Write back process. |
| Write(); |
| } |
| |
| exit: |
| |
| if (error != OTBR_ERROR_NONE) |
| { |
| mResource->ErrorHandler(mResponse, HttpStatusCode::kStatusInternalServerError); |
| Write(); |
| } |
| } |
| |
| void Connection::ProcessWaitCallback(void) |
| { |
| auto duration = duration_cast<microseconds>(steady_clock::now() - mTimeStamp).count(); |
| |
| mResource->HandleCallback(mRequest, mResponse); |
| |
| if (mResponse.IsComplete()) |
| { |
| Write(); |
| } |
| else |
| { |
| if (duration >= kCallbackTimeout) |
| { |
| mResource->ErrorHandler(mResponse, HttpStatusCode::kStatusInternalServerError); |
| Write(); |
| } |
| } |
| } |
| |
| void Connection::ProcessWaitWrite(const fd_set &aWriteFdSet) |
| { |
| auto duration = duration_cast<microseconds>(steady_clock::now() - mTimeStamp).count(); |
| |
| if (duration <= kWriteTimeout) |
| { |
| if (FD_ISSET(mFd, &aWriteFdSet)) |
| { |
| Write(); |
| } |
| } |
| else |
| { |
| Disconnect(); |
| } |
| } |
| |
| void Connection::Write(void) |
| { |
| otbrError error = OTBR_ERROR_NONE; |
| std::string errorCode; |
| int32_t sendLength; |
| int32_t err; |
| |
| if (mState != ConnectionState::kWriteWait) |
| { |
| // Change its state when try write for the first time. |
| mState = ConnectionState::kWriteWait; |
| mTimeStamp = steady_clock::now(); |
| mWriteContent = mResponse.Serialize(); |
| } |
| |
| // Check we do have something to write. |
| VerifyOrExit(mWriteContent.size() > 0, error = OTBR_ERROR_REST); |
| |
| sendLength = write(mFd, mWriteContent.c_str(), mWriteContent.size()); |
| err = errno; |
| |
| // Write successfully |
| if (sendLength == static_cast<int32_t>(mWriteContent.size())) |
| { |
| // Normal Exit |
| Disconnect(); |
| } |
| else if (sendLength > 0) |
| { |
| // Partly write |
| mWriteContent = mWriteContent.substr(sendLength); |
| } |
| else |
| { |
| if (errno == EINTR) |
| { |
| // Try again |
| Write(); |
| } |
| else |
| { |
| // There is an error when we write, if this, we directly disconnect this connection. |
| VerifyOrExit(err == EAGAIN || err == EWOULDBLOCK, error = OTBR_ERROR_REST); |
| } |
| } |
| |
| exit: |
| if (error != OTBR_ERROR_NONE) |
| { |
| Disconnect(); |
| } |
| } |
| |
| bool Connection::IsComplete() const |
| { |
| return mState == ConnectionState::kComplete; |
| } |
| |
| } // namespace rest |
| } // namespace otbr |