blob: 5580fe4eefdb996d430820edbf11a656a9876320 [file] [log] [blame]
/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
/**
* Copyright (c) 2014-2015, Regents of the University of California,
* Arizona Board of Regents,
* Colorado State University,
* University Pierre & Marie Curie, Sorbonne University,
* Washington University in St. Louis,
* Beijing Institute of Technology,
* The University of Memphis.
*
* This file is part of NFD (Named Data Networking Forwarding Daemon).
* See AUTHORS.md for complete list of NFD authors and contributors.
*
* NFD is free software: you can redistribute it and/or modify it under the terms
* of the GNU General Public License as published by the Free Software Foundation,
* either version 3 of the License, or (at your option) any later version.
*
* NFD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with
* NFD, e.g., in COPYING.md file. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef NFD_DAEMON_FACE_STREAM_FACE_HPP
#define NFD_DAEMON_FACE_STREAM_FACE_HPP
#include "face.hpp"
#include "local-face.hpp"
#include "core/global-io.hpp"
#include <queue>
namespace nfd {
// forward declaration
template<class T, class U, class V> struct StreamFaceSenderImpl;
template<class Protocol, class FaceBase = Face>
class StreamFace : public FaceBase
{
public:
typedef Protocol protocol;
StreamFace(const FaceUri& remoteUri, const FaceUri& localUri,
typename protocol::socket socket, bool isOnDemand);
// from FaceBase
void
sendInterest(const Interest& interest) DECL_OVERRIDE;
void
sendData(const Data& data) DECL_OVERRIDE;
void
close() DECL_OVERRIDE;
protected:
void
processErrorCode(const boost::system::error_code& error);
void
sendFromQueue();
void
handleSend(const boost::system::error_code& error,
size_t nBytesSent);
void
handleReceive(const boost::system::error_code& error,
size_t nBytesReceived);
void
shutdownSocket();
void
deferredClose(const shared_ptr<Face>& face);
protected:
typename protocol::socket m_socket;
NFD_LOG_INCLASS_DECLARE();
private:
uint8_t m_inputBuffer[ndn::MAX_NDN_PACKET_SIZE];
size_t m_inputBufferSize;
std::queue<Block> m_sendQueue;
friend struct StreamFaceSenderImpl<Protocol, FaceBase, Interest>;
friend struct StreamFaceSenderImpl<Protocol, FaceBase, Data>;
};
// All inherited classes must use
// NFD_LOG_INCLASS_TEMPLATE_SPECIALIZATION_DEFINE(StreamFace, <specialization-parameter>, "Name");
/** \brief Class allowing validation of the StreamFace use
*
* For example, partial specialization based on boost::asio::ip::tcp should check
* that local endpoint is loopback
*
* @throws Face::Error if validation failed
*/
template<class Protocol, class U>
struct StreamFaceValidator
{
static void
validateSocket(const typename Protocol::socket& socket)
{
}
};
template<class T, class FaceBase>
inline
StreamFace<T, FaceBase>::StreamFace(const FaceUri& remoteUri, const FaceUri& localUri,
typename StreamFace::protocol::socket socket, bool isOnDemand)
: FaceBase(remoteUri, localUri)
, m_socket(std::move(socket))
, m_inputBufferSize(0)
{
NFD_LOG_FACE_INFO("Creating face");
this->setPersistency(isOnDemand ? ndn::nfd::FACE_PERSISTENCY_ON_DEMAND : ndn::nfd::FACE_PERSISTENCY_PERSISTENT);
StreamFaceValidator<T, FaceBase>::validateSocket(m_socket);
m_socket.async_receive(boost::asio::buffer(m_inputBuffer, ndn::MAX_NDN_PACKET_SIZE),
bind(&StreamFace<T, FaceBase>::handleReceive, this,
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
}
template<class Protocol, class FaceBase, class Packet>
struct StreamFaceSenderImpl
{
static void
send(StreamFace<Protocol, FaceBase>& face, const Packet& packet)
{
bool wasQueueEmpty = face.m_sendQueue.empty();
face.m_sendQueue.push(packet.wireEncode());
if (wasQueueEmpty)
face.sendFromQueue();
}
};
// partial specialization (only classes can be partially specialized)
template<class Protocol, class Packet>
struct StreamFaceSenderImpl<Protocol, LocalFace, Packet>
{
static void
send(StreamFace<Protocol, LocalFace>& face, const Packet& packet)
{
bool wasQueueEmpty = face.m_sendQueue.empty();
if (!face.isEmptyFilteredLocalControlHeader(packet.getLocalControlHeader()))
{
face.m_sendQueue.push(face.filterAndEncodeLocalControlHeader(packet));
}
face.m_sendQueue.push(packet.wireEncode());
if (wasQueueEmpty)
face.sendFromQueue();
}
};
template<class T, class U>
inline void
StreamFace<T, U>::sendInterest(const Interest& interest)
{
NFD_LOG_FACE_TRACE(__func__);
this->emitSignal(onSendInterest, interest);
StreamFaceSenderImpl<T, U, Interest>::send(*this, interest);
}
template<class T, class U>
inline void
StreamFace<T, U>::sendData(const Data& data)
{
NFD_LOG_FACE_TRACE(__func__);
this->emitSignal(onSendData, data);
StreamFaceSenderImpl<T, U, Data>::send(*this, data);
}
template<class T, class U>
inline void
StreamFace<T, U>::close()
{
if (!m_socket.is_open())
return;
NFD_LOG_FACE_INFO("Closing face");
shutdownSocket();
this->fail("Face closed");
}
template<class T, class U>
inline void
StreamFace<T, U>::processErrorCode(const boost::system::error_code& error)
{
if (error == boost::asio::error::operation_aborted || // when cancel() is called
error == boost::asio::error::shut_down) // after shutdown() is called
return;
if (!m_socket.is_open())
{
this->fail("Connection closed");
return;
}
if (error != boost::asio::error::eof)
NFD_LOG_FACE_WARN("Send or receive operation failed: " << error.message());
shutdownSocket();
if (error == boost::asio::error::eof)
this->fail("Connection closed");
else
this->fail(error.message());
}
template<class T, class U>
inline void
StreamFace<T, U>::sendFromQueue()
{
boost::asio::async_write(m_socket, boost::asio::buffer(m_sendQueue.front()),
bind(&StreamFace<T, U>::handleSend, this,
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
}
template<class T, class U>
inline void
StreamFace<T, U>::handleSend(const boost::system::error_code& error,
size_t nBytesSent)
{
if (error)
return processErrorCode(error);
BOOST_ASSERT(!m_sendQueue.empty());
NFD_LOG_FACE_TRACE("Successfully sent: " << nBytesSent << " bytes");
this->getMutableCounters().getNOutBytes() += nBytesSent;
m_sendQueue.pop();
if (!m_sendQueue.empty())
sendFromQueue();
}
template<class T, class U>
inline void
StreamFace<T, U>::handleReceive(const boost::system::error_code& error,
size_t nBytesReceived)
{
if (error)
return processErrorCode(error);
NFD_LOG_FACE_TRACE("Received: " << nBytesReceived << " bytes");
this->getMutableCounters().getNInBytes() += nBytesReceived;
m_inputBufferSize += nBytesReceived;
size_t offset = 0;
bool isOk = true;
Block element;
while (m_inputBufferSize - offset > 0) {
std::tie(isOk, element) = Block::fromBuffer(m_inputBuffer + offset, m_inputBufferSize - offset);
if (!isOk)
break;
offset += element.size();
BOOST_ASSERT(offset <= m_inputBufferSize);
if (!this->decodeAndDispatchInput(element)) {
NFD_LOG_FACE_WARN("Received unrecognized TLV block of type " << element.type());
// ignore unknown packet and proceed
}
}
if (!isOk && m_inputBufferSize == ndn::MAX_NDN_PACKET_SIZE && offset == 0)
{
NFD_LOG_FACE_WARN("Failed to parse incoming packet or packet too large to process");
shutdownSocket();
this->fail("Failed to parse incoming packet or packet too large to process");
return;
}
if (offset > 0)
{
if (offset != m_inputBufferSize)
{
std::copy(m_inputBuffer + offset, m_inputBuffer + m_inputBufferSize,
m_inputBuffer);
m_inputBufferSize -= offset;
}
else
{
m_inputBufferSize = 0;
}
}
m_socket.async_receive(boost::asio::buffer(m_inputBuffer + m_inputBufferSize,
ndn::MAX_NDN_PACKET_SIZE - m_inputBufferSize),
bind(&StreamFace<T, U>::handleReceive, this,
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
}
template<class T, class U>
inline void
StreamFace<T, U>::shutdownSocket()
{
NFD_LOG_FACE_TRACE(__func__);
// Cancel all outstanding operations and shutdown the socket
// so that no further sends or receives are possible.
// Use the non-throwing variants and ignore errors, if any.
boost::system::error_code error;
m_socket.cancel(error);
m_socket.shutdown(protocol::socket::shutdown_both, error);
// ensure that the Face object is alive at least until all pending
// handlers are dispatched
getGlobalIoService().post(bind(&StreamFace<T, U>::deferredClose,
this, this->shared_from_this()));
// Some bug or feature of Boost.Asio (see http://redmine.named-data.net/issues/1856):
//
// When shutdownSocket is called from within a socket event (e.g., from handleReceive),
// m_socket.shutdown() does not trigger the cancellation of the handleSend callback.
// Instead, handleSend is invoked as nothing bad happened.
//
// In order to prevent the assertion in handleSend from failing, we clear the queue
// and close the socket in deferredClose, i.e., after all callbacks scheduled up to
// this point have been executed. If more send operations are scheduled after this
// point, they will fail because the socket has been shutdown, and their callbacks
// will be invoked with error code == asio::error::shut_down.
}
template<class T, class U>
inline void
StreamFace<T, U>::deferredClose(const shared_ptr<Face>& face)
{
NFD_LOG_FACE_TRACE(__func__);
// clear send queue
std::queue<Block> emptyQueue;
std::swap(emptyQueue, m_sendQueue);
// use the non-throwing variant and ignore errors, if any
boost::system::error_code error;
m_socket.close(error);
}
} // namespace nfd
#endif // NFD_DAEMON_FACE_STREAM_FACE_HPP