Skip to content

Commit

Permalink
Update DcsSocket to dynamically determine tx_port if none is provided
Browse files Browse the repository at this point in the history
  • Loading branch information
charlestytler committed Apr 15, 2020
1 parent b180676 commit ee188b3
Show file tree
Hide file tree
Showing 6 changed files with 48 additions and 27 deletions.
2 changes: 1 addition & 1 deletion Sources/DcsInterface/DcsInterface.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
#include "StringUtilities.h"

DcsInterface::DcsInterface(const DcsConnectionSettings &settings)
: dcs_socket_(settings.rx_port, settings.tx_port, settings.ip_address), connection_settings_(settings) {
: dcs_socket_(settings.ip_address, settings.rx_port, settings.tx_port), connection_settings_(settings) {
// Send a reset to request a resend of data in case DCS mission is already running.
send_dcs_reset_command();
}
Expand Down
29 changes: 17 additions & 12 deletions Sources/DcsInterface/DcsSocket.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
// Set default timeout for socket.
DWORD socket_timeout_ms = 100;

DcsSocket::DcsSocket(const std::string &rx_port, const std::string &tx_port, const std::string &ip_address) {
DcsSocket::DcsSocket(const std::string &ip_address, const std::string &rx_port, const std::string &tx_port) {
// Detect any missing input settings.
if (rx_port.empty() || tx_port.empty() || ip_address.empty()) {
const std::string error_msg =
Expand Down Expand Up @@ -60,37 +60,42 @@ DcsSocket::DcsSocket(const std::string &rx_port, const std::string &tx_port, con
throw std::runtime_error(error_msg);
}

// Define send destination port.
getaddrinfo(ip_address.c_str(), tx_port.c_str(), &hints, &dest_port_);
if (tx_port != "dynamic") {
// Define send destination port.
addrinfo *send_to_port;
getaddrinfo(ip_address.c_str(), tx_port.c_str(), &hints, &send_to_port);
dest_addr_ = *send_to_port->ai_addr;
dest_addr_len_ = static_cast<int>(send_to_port->ai_addrlen);
freeaddrinfo(send_to_port);
}
}

DcsSocket::~DcsSocket() {
// Delete opened socket.
freeaddrinfo(dest_port_);
closesocket(socket_id_);
WSACleanup();
}

std::stringstream DcsSocket::DcsReceive() {
// Sender address - dummy variable as it is unused outside recvfrom.
sockaddr_in sender_addr;
sockaddr sender_addr;
int sender_addr_size = sizeof(sender_addr);

// Receive next UDP message.
constexpr int MAX_UDP_MSG_SIZE = 1024; // Maximum UDP buffer size to read.
char msg[MAX_UDP_MSG_SIZE] = {};
(void)recvfrom(socket_id_, msg, MAX_UDP_MSG_SIZE, 0, (SOCKADDR *)&sender_addr, &sender_addr_size);
(void)recvfrom(socket_id_, msg, MAX_UDP_MSG_SIZE, 0, &sender_addr, &sender_addr_size);

if (dest_addr_len_ == 0) {
dest_addr_ = sender_addr;
dest_addr_len_ = sender_addr_size;
}

std::stringstream ss;
ss << msg;
return ss;
}

void DcsSocket::DcsSend(const std::string &message) {
(void)sendto(socket_id_,
message.c_str(),
static_cast<int>(message.length()),
0,
dest_port_->ai_addr,
static_cast<int>(dest_port_->ai_addrlen));
(void)sendto(socket_id_, message.c_str(), static_cast<int>(message.length()), 0, &dest_addr_, dest_addr_len_);
}
15 changes: 8 additions & 7 deletions Sources/DcsInterface/DcsSocket.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,16 @@

class DcsSocket {
public:
// Binds a UDP socket to the rx port and also intializes the destination address using the tx port.
// Binds a UDP socket to the rx port and also initializes the destination address using the tx port.
/**
* @brief Construct a new Dcs Socket object bound to the rx port and initializes the destination address using the
* tx port.
* tx port if provided, or determines tx_port address dynamically if not.
*
* @param rx_port UDP receive port.
* @param tx_port UDP transmit port.
* @param tx_ip_address UDP transmit IP address.
* @param rx_port UDP receive port.
* @param tx_port UDP transmit port, defaults to dynamic (use recvfrom address) if not provided.
*/
DcsSocket(const std::string &rx_port, const std::string &tx_port, const std::string &ip_address);
DcsSocket(const std::string &ip_address, const std::string &rx_port, const std::string &tx_port = "dynamic");

/**
* @brief Destroy the Dcs Socket object
Expand Down Expand Up @@ -45,6 +45,7 @@ class DcsSocket {
void DcsSend(const std::string &message);

private:
SOCKET socket_id_; // Socket which is binded to the rx port.
addrinfo *dest_port_; // UDP address info for port which will be transmitted to.
SOCKET socket_id_; // Socket which is binded to the rx port.
sockaddr dest_addr_; // UDP address info for port which will be transmitted to.
int dest_addr_len_ = 0; // Size of dest address.
};
4 changes: 2 additions & 2 deletions Sources/Test/DcsInterfaceTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ TEST(DcsInterfaceTest, invalid_connection_port_settings) {

TEST(DcsInterfaceTest, dcs_reset_command_on_construction) {
DcsConnectionSettings connection_settings = {"1908", "1909", "127.0.0.1"};
DcsSocket mock_dcs(connection_settings.tx_port, connection_settings.rx_port, connection_settings.ip_address);
DcsSocket mock_dcs(connection_settings.ip_address, connection_settings.tx_port, connection_settings.rx_port);
DcsInterface dcs_interface(connection_settings);

// Test that the reset message "R" is received by DCS on creation of DcsInterface.
Expand All @@ -25,7 +25,7 @@ class DcsInterfaceTestFixture : public ::testing::Test {
public:
DcsInterfaceTestFixture()
: // Mock DCS socket uses the reverse rx and tx ports of dcs_interface so it can communicate with it.
mock_dcs(connection_settings.tx_port, connection_settings.rx_port, connection_settings.ip_address),
mock_dcs(connection_settings.ip_address, connection_settings.tx_port, connection_settings.rx_port),
dcs_interface(connection_settings) {

// Consume intial reset command sent to to mock_dcs.
Expand Down
23 changes: 19 additions & 4 deletions Sources/Test/DcsSocketTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,17 @@ namespace test {
TEST(DcsSocketTest, empty_connection_settings) { EXPECT_THROW(DcsSocket dcs_socket("", "", ""), std::runtime_error); }

TEST(DcsSocketTest, invalid_connection_port_settings) {
EXPECT_THROW(DcsSocket dcs_socket("19ab", "abc", "127.0.0.1"), std::runtime_error);
EXPECT_THROW(DcsSocket dcs_socket("127.0.0.1", "19ab", "abc"), std::runtime_error);
}

TEST(DcsSocketTest, invalid_connection_ip_addr_settings) {
EXPECT_THROW(DcsSocket dcs_socket("1908", "1909", "127001"), std::runtime_error);
EXPECT_THROW(DcsSocket dcs_socket("127001", "1908", "1909"), std::runtime_error);
}

class DcsSocketTestFixture : public ::testing::Test {
public:
DcsSocketTestFixture()
: sender_socket("1788", common_port, ip_address), receiver_socket(common_port, "1790", ip_address) {}
: sender_socket(ip_address, "1788", common_port), receiver_socket(ip_address, common_port, "1790") {}

DcsSocket sender_socket;
DcsSocket receiver_socket;
Expand All @@ -36,12 +36,27 @@ TEST_F(DcsSocketTestFixture, send_and_receive) {

TEST_F(DcsSocketTestFixture, unavailable_port_bind) {
// Expect exception thrown if try to bind a new socket to same rx_port.
EXPECT_THROW(DcsSocket duplicate_socket(common_port, "1801", ip_address), std::runtime_error);
EXPECT_THROW(DcsSocket duplicate_socket(ip_address, common_port, "1801"), std::runtime_error);
}

TEST_F(DcsSocketTestFixture, receive_timeout) {
std::stringstream ss_received = receiver_socket.DcsReceive();
// Expect timeout after 100 msec and return of empty string.
EXPECT_EQ(ss_received.str(), "");
}

TEST_F(DcsSocketTestFixture, dynamic_tx_port_discovery) {
const std::string new_common_port = "1791";
DcsSocket server_socket(ip_address, "1792", new_common_port);
DcsSocket client_socket(ip_address, new_common_port);
const std::string test_msg_a = "test_a";
server_socket.DcsSend(test_msg_a);
std::stringstream client_received_ss = client_socket.DcsReceive();
EXPECT_EQ(test_msg_a, client_received_ss.str());
// Expect client socket to have dynamically set tx_port to sender_socket's bound port.
const std::string test_msg_b = "test_b";
client_socket.DcsSend(test_msg_b);
std::stringstream server_received_ss = server_socket.DcsReceive();
EXPECT_EQ(test_msg_b, server_received_ss.str());
}
} // namespace test
2 changes: 1 addition & 1 deletion Sources/Test/StreamdeckContextTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ class StreamdeckContextTestFixture : public ::testing::Test {
public:
StreamdeckContextTestFixture()
: // Mock DCS socket uses the reverse rx and tx ports of dcs_interface so it can communicate with it.
mock_dcs(connection_settings.tx_port, connection_settings.rx_port, connection_settings.ip_address),
mock_dcs(connection_settings.ip_address, connection_settings.tx_port, connection_settings.rx_port),
dcs_interface(connection_settings), fixture_context(fixture_context_id) {

// Consume intial reset command sent to to mock_dcs.
Expand Down

0 comments on commit ee188b3

Please sign in to comment.