2
0
mirror of https://gitlab.isc.org/isc-projects/kea synced 2025-09-06 00:45:23 +00:00
Files
kea/src/lib/asiolink/tests/tcp_socket_unittest.cc

350 lines
13 KiB
C++
Raw Normal View History

// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
/// \brief Test of TCPSocket
///
/// Tests the fuctionality of a TCPSocket by working through an open-send-
/// receive-close sequence and checking that the asynchronous notifications
/// work.
#include <string>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <algorithm>
#include <cstdlib>
#include <cstddef>
#include <vector>
#include <gtest/gtest.h>
#include <boost/bind.hpp>
#include <boost/shared_ptr.hpp>
#include <asio.hpp>
#include <asiolink/io_service.h>
#include <asiolink/tcp_endpoint.h>
#include <asiolink/tcp_socket.h>
using namespace asio;
using namespace asio::ip;
using namespace asiolink;
using namespace std;
namespace {
const char SERVER_ADDRESS[] = "127.0.0.1";
const unsigned short SERVER_PORT = 5303;
// TODO: Shouldn't we send something that is real message?
const char OUTBOUND_DATA[] = "Data sent from client to server";
const char INBOUND_DATA[] = "Returned data from server to client";
}
///
/// An instance of this object is passed to the asynchronous I/O functions
/// and the operator() method is called when when an asynchronous I/O
/// completes. The arguments to the completion callback are stored for later
/// retrieval.
class TCPCallback {
public:
/// \brief Operations the server is doing
enum Operation {
ACCEPT = 0, ///< accept() was issued
OPEN = 1, /// Client connected to server
READ = 2, ///< Asynchronous read completed
WRITE = 3, ///< Asynchronous write completed
NONE = 4 ///< "Not set" state
};
/// \brief Minimim size of buffers
enum {
MIN_SIZE = 4096
};
struct PrivateData {
PrivateData() :
error_code_(), length_(0), name_(""), queued_(NONE), called_(NONE)
{}
asio::error_code error_code_; ///< Completion error code
size_t length_; ///< Number of bytes transferred
std::string name_; ///< Which of the objects this is
Operation queued_; ///< Queued operation
Operation called_; ///< Which callback called
};
/// \brief Constructor
///
/// Constructs the object. It also creates the data member pointed to by
/// a shared pointer. When used as a callback object, this is copied as it
/// is passed into the asynchronous function. This means that there are two
/// objects and inspecting the one we passed in does not tell us anything.
///
/// Therefore we use a boost::shared_ptr. When the object is copied, the
/// shared pointer is copied, which leaves both objects pointing to the same
/// data.
///
/// \param which Which of the two callback objects this is
TCPCallback(std::string which) : ptr_(new PrivateData())
{
setName(which);
}
/// \brief Destructor
///
/// No code needed, destroying the shared pointer destroys the private data.
virtual ~TCPCallback()
{}
/// \brief Client Callback Function
///
/// Called when an asynchronous connect is completed by the client, this
/// stores the origin of the operation in the client_called_ data member.
///
/// \param ec I/O completion error code passed to callback function.
/// \param length Number of bytes transferred
void operator()(asio::error_code ec = asio::error_code(),
size_t length = 0)
{
setCode(ec.value());
setCalled(getQueued());
setLength(length);
}
/// \brief Get I/O completion error code
int getCode() {
return (ptr_->error_code_.value());
}
/// \brief Set I/O completion code
///
/// \param code New value of completion code
void setCode(int code) {
ptr_->error_code_ = asio::error_code(code, asio::error_code().category());
}
/// \brief Get number of bytes transferred in I/O
size_t getLength() {
return (ptr_->length_);
}
/// \brief Set number of bytes transferred in I/O
///
/// \param length New value of length parameter
void setLength(size_t length) {
ptr_->length_ = length;
}
/// \brief Get flag to say what was queued
Operation getQueued() {
return (ptr_->queued_);
}
/// \brief Set flag to say what is being queued
///
/// \param called New value of queued parameter
void setQueued(Operation queued) {
ptr_->queued_ = queued;
}
/// \brief Get flag to say when callback was called
Operation getCalled() {
return (ptr_->called_);
}
/// \brief Set flag to say when callback was called
///
/// \param called New value of called parameter
void setCalled(Operation called) {
ptr_->called_ = called;
}
/// \brief Return instance of callback name
std::string getName() {
return (ptr_->name_);
}
/// \brief Set callback name
///
/// \param name New value of the callback name
void setName(const std::string& name) {
ptr_->name_ = name;
}
private:
boost::shared_ptr<PrivateData> ptr_; ///< Pointer to private data
};
// TODO: Need to add a test to check the cancel() method
// Tests the operation of a TCPSocket by opening it, sending an asynchronous
// message to a server, receiving an asynchronous message from the server and
// closing.
TEST(TCPSocket, SequenceTest) {
// Common objects.
IOService service; // Service object for async control
// Server
IOAddress server_address(SERVER_ADDRESS);
// Address of target server
TCPCallback server_cb("Server"); // Server callback
TCPEndpoint server_endpoint(server_address, SERVER_PORT);
// Endpoint describing server
TCPEndpoint server_remote_endpoint; // Address where server received message from
tcp::socket server_socket(service.get_io_service());
// Socket used for server
char server_data[TCPCallback::MIN_SIZE];
// Data received by server
ASSERT_GT(sizeof(server_data), sizeof(OUTBOUND_DATA));
// Make sure it's large enough
// The client - the TCPSocket being tested
TCPSocket<TCPCallback> client(service);// Socket under test
TCPCallback client_cb("Client"); // Async I/O callback function
TCPEndpoint client_remote_endpoint; // Where client receives message from
char client_data[TCPCallback::MIN_SIZE];
// Data received by client
ASSERT_GT(sizeof(client_data), sizeof(OUTBOUND_DATA));
// Make sure it's large enough
//size_t client_cumulative = 0; // Cumulative data received
// The server - with which the client communicates. For convenience, we
// use the same io_service, and use the endpoint object created for
// the client to send to as the endpoint object in the constructor.
std::cerr << "Setting up acceptor\n";
// Set up the server to accept incoming connections.
server_cb.setQueued(TCPCallback::ACCEPT);
server_cb.setCalled(TCPCallback::NONE);
server_cb.setCode(42); // Some error
tcp::acceptor acceptor(service.get_io_service(),
tcp::endpoint(tcp::v4(), SERVER_PORT));
acceptor.set_option(tcp::acceptor::reuse_address(true));
acceptor.async_accept(server_socket, server_cb);
std::cerr << "Setting up client\n";
// Open the client socket - the operation should be asynchronous
client_cb.setQueued(TCPCallback::OPEN);
client_cb.setCalled(TCPCallback::NONE);
client_cb.setCode(43); // Some error
EXPECT_FALSE(client.isOpenSynchronous());
client.open(&server_endpoint, client_cb);
// Run the open and the accept callback and check that they ran.
service.run_one();
service.run_one();
EXPECT_EQ(TCPCallback::ACCEPT, server_cb.getCalled());
EXPECT_EQ(0, server_cb.getCode());
EXPECT_EQ(TCPCallback::OPEN, client_cb.getCalled());
EXPECT_EQ(0, client_cb.getCode());
// Write something to the server using the client and read it on ther server.
server_cb.setCalled(TCPCallback::NONE);
server_cb.setQueued(TCPCallback::READ);
server_cb.setCode(142); // Arbitrary number
server_cb.setLength(0);
server_socket.async_receive(buffer(server_data, sizeof(server_data)), server_cb);
client_cb.setCalled(TCPCallback::NONE);
client_cb.setQueued(TCPCallback::WRITE);
client_cb.setCode(143); // Arbitrary number
client_cb.setLength(0);
client.asyncSend(OUTBOUND_DATA, sizeof(OUTBOUND_DATA), &server_endpoint, client_cb);
// Run the write and read callback and check they ran
service.run_one();
service.run_one();
// Check lengths. As the client wrote the buffer, currently two bytes
// will be prepended by the client containing the length.
EXPECT_EQ(TCPCallback::READ, server_cb.getCalled());
EXPECT_EQ(0, server_cb.getCode());
EXPECT_EQ(sizeof(OUTBOUND_DATA) + 2, server_cb.getLength());
EXPECT_EQ(TCPCallback::WRITE, client_cb.getCalled());
EXPECT_EQ(0, client_cb.getCode());
EXPECT_EQ(sizeof(OUTBOUND_DATA) + 2, client_cb.getLength());
// Check that the first two bytes of the buffer are in fact the remaining
// length of the buffer (code copied from isc::dns::buffer.h)
uint16_t count = ((unsigned int)(server_data[0])) << 8;
count |= ((unsigned int)(server_data[1]));
EXPECT_EQ(sizeof(OUTBOUND_DATA), count);
// ... and check data received by server is what we expect
EXPECT_TRUE(equal(&server_data[2], &server_data[server_cb.getLength() - 1],
OUTBOUND_DATA));
// TODO: Complete this server test
// TODO: Add in loop for server to read data - read 2 bytes, then as much as needed
/*
// Now return data from the server to the client. Issue the read on the
// client.
client_cb.setCalled(TCPCallback::NONE);
client_cb.setQueued(TCPCallback::READ);
client_cb.setCode(143); // Arbitrary number
client_cb.setLength(0);
client.asyncReceive(OUTBOUND_DATA, sizeof(OUTBOUND_DATA), &server_endpoint, client_cb);
client_cb.setLength(12345); // Arbitrary number
client_cb.setCalled(false);
client_cb.setCode(32); // Arbitrary number
client.asyncReceive(data, sizeof(data), client_cumulative,
&client_remote_endpoint, client_cb);
// Issue the write on the server side to the source of the data it received.
server_cb.setLength(22345); // Arbitrary number
server_cb.setCalled(false);
server_cb.setCode(232); // Arbitrary number
server.async_send_to(buffer(INBOUND_DATA, sizeof(INBOUND_DATA)),
server_remote_endpoint.getASIOEndpoint(), server_cb);
// Expect two callbacks to run
service.get_io_service().poll();
//service.run_one();
EXPECT_TRUE(client_cb.getCalled());
EXPECT_EQ(0, client_cb.getCode());
EXPECT_EQ(sizeof(INBOUND_DATA), client_cb.getLength());
EXPECT_TRUE(server_cb.getCalled());
EXPECT_EQ(0, server_cb.getCode());
EXPECT_EQ(sizeof(INBOUND_DATA), server_cb.getLength());
EXPECT_TRUE(equal(&data[0], &data[server_cb.getLength() - 1], INBOUND_DATA));
// Check that the address/port received by the client corresponds to the
// address and port the server is listening on.
EXPECT_TRUE(server_address == client_remote_endpoint.getAddress());
EXPECT_EQ(SERVER_PORT, client_remote_endpoint.getPort());
// Finally, check that the receive received a complete buffer's worth of data.
EXPECT_TRUE(client.receiveComplete(&data[0], client_cb.getLength(),
client_cumulative));
EXPECT_EQ(client_cb.getLength(), client_cumulative);
// Close client and server.
EXPECT_NO_THROW(client.close());
EXPECT_NO_THROW(server.close());
* */
}