Skip to content

Commit 8c1f6b2

Browse files
authored
Merge branch 'devel' into ccsds-protocols
2 parents 333dec8 + 2b5d067 commit 8c1f6b2

14 files changed

Lines changed: 474 additions & 388 deletions

File tree

.github/actions/spelling/expect.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -401,6 +401,10 @@ NACI
401401
nasafprime
402402
nbits
403403
ncsl
404+
ndiffs
405+
neascout
406+
netinet
407+
newloc
404408
newroot
405409
newtio
406410
nmsgs

Drv/Ip/UdpSocket.cpp

Lines changed: 27 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ struct SocketState {
5050
}
5151
};
5252

53-
UdpSocket::UdpSocket() : IpSocket(), m_state(new(std::nothrow) SocketState), m_recv_port(0) {
53+
UdpSocket::UdpSocket() : IpSocket(), m_state(new(std::nothrow) SocketState), m_recv_port(0), m_recv_configured(false) {
5454
FW_ASSERT(m_state != nullptr);
5555
}
5656

@@ -67,7 +67,6 @@ SocketIpStatus UdpSocket::configure(const char* const hostname, const U16 port,
6767

6868
SocketIpStatus UdpSocket::configureSend(const char* const hostname, const U16 port, const U32 timeout_seconds, const U32 timeout_microseconds) {
6969
//Timeout is for the send, so configure send will work with the base class
70-
FW_ASSERT(port != 0, static_cast<FwAssertArgType>(port)); // Send cannot be on port 0
7170
FW_ASSERT(hostname != nullptr);
7271
return this->IpSocket::configure(hostname, port, timeout_seconds, timeout_microseconds);
7372
}
@@ -77,6 +76,7 @@ SocketIpStatus UdpSocket::configureRecv(const char* hostname, const U16 port) {
7776
FW_ASSERT(hostname != nullptr);
7877
this->m_recv_port = port;
7978
(void) Fw::StringUtils::string_copy(this->m_recv_hostname, hostname, static_cast<FwSizeType>(SOCKET_MAX_HOSTNAME_SIZE));
79+
this->m_recv_configured = true;
8080
return SOCK_SUCCESS;
8181
}
8282

@@ -112,6 +112,9 @@ SocketIpStatus UdpSocket::bind(const PlatformIntType fd) {
112112
return SOCK_FAILED_TO_READ_BACK_PORT;
113113
}
114114

115+
// Update m_recv_port with the actual port assigned (for ephemeral port support)
116+
this->m_recv_port = ntohs(address.sin_port);
117+
115118
FW_ASSERT(sizeof(this->m_state->m_addr_recv) == sizeof(address), static_cast<FwAssertArgType>(sizeof(this->m_state->m_addr_recv)), static_cast<FwAssertArgType>(sizeof(address)));
116119
memcpy(&this->m_state->m_addr_recv, &address, sizeof(this->m_state->m_addr_recv));
117120

@@ -124,6 +127,7 @@ SocketIpStatus UdpSocket::openProtocol(SocketDescriptor& socketDescriptor) {
124127
struct sockaddr_in address;
125128

126129
U16 port = this->m_port;
130+
U16 recv_port = this->m_recv_port;
127131

128132
// Acquire a socket, or return error
129133
if ((socketFd = ::socket(AF_INET, SOCK_DGRAM, 0)) == -1) {
@@ -157,11 +161,10 @@ SocketIpStatus UdpSocket::openProtocol(SocketDescriptor& socketDescriptor) {
157161
memcpy(&this->m_state->m_addr_send, &address, sizeof(this->m_state->m_addr_send));
158162
}
159163

160-
// Receive port set up only done when configure receive was called
161-
U16 recv_port = this->m_recv_port;
162-
if (recv_port != 0) {
164+
// Only bind if configureRecv was called (including ephemeral)
165+
if (this->m_recv_configured) {
163166
status = this->bind(socketFd);
164-
// When we are setting up for receiving as well, then we must bind to a port
167+
165168
if (status != SOCK_SUCCESS) {
166169
(void) ::close(socketFd); // Closing FD as a retry will reopen send side
167170
return status;
@@ -170,22 +173,13 @@ SocketIpStatus UdpSocket::openProtocol(SocketDescriptor& socketDescriptor) {
170173

171174
// Log message for UDP
172175
if ((port == 0) && (recv_port > 0)) {
173-
Fw::Logger::log("Setup to only receive udp at %s:%hu\n", m_recv_hostname,
174-
recv_port);
175-
} else if ((port > 0) && (recv_port == 0)) {
176-
Fw::Logger::log("Setup to only send udp at %s:%hu\n", m_hostname,
177-
port);
178-
} else if ((port > 0) && (recv_port > 0)) {
179-
Fw::Logger::log("Setup to receive udp at %s:%hu and send to %s:%hu\n",
180-
m_recv_hostname,
181-
recv_port,
182-
m_hostname,
183-
port);
184-
}
185-
// Neither configuration method was called
186-
else {
187-
FW_ASSERT(port > 0 || recv_port > 0, static_cast<FwAssertArgType>(port), static_cast<FwAssertArgType>(recv_port));
176+
Fw::Logger::log("Setup to only receive udp at %s:%hu\n", m_recv_hostname, recv_port);
177+
} else if ((port > 0) && (recv_port == 0)) {
178+
Fw::Logger::log("Setup to only send udp at %s:%hu\n", m_hostname, port);
179+
} else if ((port > 0) && (recv_port > 0)) {
180+
Fw::Logger::log("Setup to receive udp at %s:%hu and send to %s:%hu\n", m_recv_hostname, recv_port, m_hostname, port);
188181
}
182+
189183
FW_ASSERT(status == SOCK_SUCCESS, static_cast<FwAssertArgType>(status));
190184
socketDescriptor.fd = socketFd;
191185
return status;
@@ -199,7 +193,18 @@ I32 UdpSocket::sendProtocol(const SocketDescriptor& socketDescriptor, const U8*
199193

200194
I32 UdpSocket::recvProtocol(const SocketDescriptor& socketDescriptor, U8* const data, const U32 size) {
201195
FW_ASSERT(this->m_state->m_addr_recv.sin_family != 0); // Make sure the address was previously setup
202-
return static_cast<I32>(::recvfrom(socketDescriptor.fd, data, size, SOCKET_IP_RECV_FLAGS, nullptr, nullptr));
196+
197+
struct sockaddr_in sender_addr;
198+
socklen_t sender_addr_len = sizeof(sender_addr);
199+
I32 received = static_cast<I32>(::recvfrom(socketDescriptor.fd, data, size, SOCKET_IP_RECV_FLAGS,
200+
reinterpret_cast<struct sockaddr*>(&sender_addr), &sender_addr_len));
201+
// If we have not configured a send port, set it to the source of the last received packet
202+
if (received > 0 && this->m_state->m_addr_send.sin_port == 0) {
203+
this->m_state->m_addr_send = sender_addr;
204+
this->m_port = ntohs(sender_addr.sin_port);
205+
Fw::Logger::log("Configured send port to %hu as specified by the last received packet.\n", this->m_port);
206+
}
207+
return received;
203208
}
204209

205210
} // namespace Drv

Drv/Ip/UdpSocket.hpp

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -51,13 +51,14 @@ class UdpSocket : public IpSocket {
5151
* Configures the UDP handler to use the given hostname and port for outgoing transmissions. Incoming hostname
5252
* and port are configured using the `configureRecv` function call for UDP as it requires separate host/port pairs
5353
* for outgoing and incoming transmissions. Hostname DNS translation is left up to the caller and thus hostname must
54-
* be an IP address in dot-notation of the form "x.x.x.x". Port cannot be set to 0 as dynamic port assignment is not
55-
* supported on remote ports. It is possible to configure the UDP port as a single-direction send port only.
54+
* be an IP address in dot-notation of the form "x.x.x.x". If port is set to 0, the socket will be configured for
55+
* ephemeral send (dynamic reply-to) and will use the sender's address from the first received datagram for replies.
56+
* It is possible to configure the UDP port as a single-direction send port only.
5657
*
5758
* Note: delegates to `IpSocket::configure`
5859
*
5960
* \param hostname: socket uses for outgoing transmissions. Must be of form x.x.x.x
60-
* \param port: port socket uses for outgoing transmissions. Must NOT be 0.
61+
* \param port: port socket uses for outgoing transmissions. Can be 0 for ephemeral reply-to mode.
6162
* \param send_timeout_seconds: send timeout seconds portion
6263
* \param send_timeout_microseconds: send timeout microseconds portion. Must be less than 1000000
6364
* \return status of configure
@@ -75,7 +76,7 @@ class UdpSocket : public IpSocket {
7576
* single-direction receive port only.
7677
*
7778
* \param hostname: socket uses for incoming transmissions. Must be of form x.x.x.x
78-
* \param port: port socket uses for incoming transmissions.
79+
* \param port: port socket uses for incoming transmissions. Can be 0 for ephemeral port assignment.
7980
* \return status of configure
8081
*/
8182
SocketIpStatus configureRecv(const char* hostname, const U16 port);
@@ -122,8 +123,9 @@ class UdpSocket : public IpSocket {
122123
I32 recvProtocol(const SocketDescriptor& socketDescriptor, U8* const data, const U32 size) override;
123124
private:
124125
SocketState* m_state; //!< State storage
125-
U16 m_recv_port; //!< IP address port used
126-
char m_recv_hostname[SOCKET_MAX_HOSTNAME_SIZE]; //!< Hostname to supply
126+
U16 m_recv_port; //!< Port to receive on
127+
CHAR m_recv_hostname[SOCKET_MAX_HOSTNAME_SIZE]; //!< Hostname to receive on
128+
bool m_recv_configured; //!< True if configureRecv was called
127129
};
128130
} // namespace Drv
129131

Drv/Ip/docs/sdd.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,17 @@ socketBoth.configureSend(127.0.0.1, 60211, 0, 100);
152152
socketBoth.configureRecv(127.0.0.1, 60212);
153153
...
154154
```
155+
### Support for Ephemeral Ports
156+
157+
Drv::UdpSocket supports ephemeral ports through passing a 0 as the port argument for either `Drv::UdpSocket::configureSend`
158+
or `Drv::UdpSocket::configureRecv`.
159+
160+
For `Drv::UdpSocket::configureSend` this means that you would like to set up the UdpSocket to be able to respond to the source
161+
port that is indicated in the UDP datagrams you receive. Note that this configuration will set up the send port to the port
162+
specified only in the first message received.
163+
164+
For `Drv::UdpSocket::configureRecv` this means that you would like to be assigned an ephemeral port. This would generally be used
165+
for setting up a sender that would like to receive responses to messages on an ephemeral port.
155166

156167
## Drv::SocketComponentHelper Virtual Baseclass
157168

Drv/Ip/test/ut/TestUdp.cpp

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,11 @@
22
// Created by mstarch on 12/7/20.
33
//
44
#include <gtest/gtest.h>
5+
#include <cstring>
6+
#include <string>
7+
#include <sys/socket.h>
8+
#include <netinet/in.h>
9+
#include <arpa/inet.h>
510
#include <Drv/Ip/UdpSocket.hpp>
611
#include <Drv/Ip/IpSocket.hpp>
712
#include <Os/Console.hpp>
@@ -99,6 +104,48 @@ TEST(SingleSide, TestSingleSideMultipleSendUdp) {
99104
test_with_loop(100, SEND);
100105
}
101106

107+
TEST(Ephemeral, TestEphemeralPorts) {
108+
Drv::UdpSocket receiver;
109+
Drv::SocketDescriptor recv_fd;
110+
const U16 recv_port = 50001;
111+
// Configure receiver as receiver-only with no send port.
112+
receiver.configureRecv("127.0.0.1", recv_port);
113+
receiver.configureSend("127.0.0.1", 0, 0, 100);
114+
ASSERT_EQ(receiver.open(recv_fd), Drv::SOCK_SUCCESS);
115+
116+
Drv::UdpSocket sender;
117+
Drv::SocketDescriptor send_fd;
118+
// Configure sender for both send and receive (duplex) with ephemeral receive port
119+
sender.configureSend("127.0.0.1", recv_port, 0, 100);
120+
sender.configureRecv("127.0.0.1", 0);
121+
ASSERT_EQ(sender.open(send_fd), Drv::SOCK_SUCCESS);
122+
123+
// Send a test message
124+
const char* msg = "hello from ephemeral sender";
125+
U32 msg_len = static_cast<U32>(strlen(msg) + 1);
126+
ASSERT_EQ(sender.send(send_fd, reinterpret_cast<const U8*>(msg), msg_len), Drv::SOCK_SUCCESS);
127+
128+
// Receive the message and capture sender's port
129+
char recv_buf[64] = {0};
130+
U32 recv_buf_len = sizeof(recv_buf);
131+
ASSERT_EQ(receiver.recv(recv_fd, reinterpret_cast<U8*>(recv_buf), recv_buf_len), Drv::SOCK_SUCCESS);
132+
ASSERT_STREQ(msg, recv_buf);
133+
134+
// Receiver sends a response back to sender
135+
const char* reply = "reply from receiver";
136+
U32 reply_len = static_cast<U32>(strlen(reply) + 1);
137+
ASSERT_EQ(receiver.send(recv_fd, reinterpret_cast<const U8*>(reply), reply_len), Drv::SOCK_SUCCESS);
138+
139+
// Sender receives the response
140+
char reply_buf[64] = {0};
141+
U32 reply_buf_len = sizeof(reply_buf);
142+
ASSERT_EQ(sender.recv(send_fd, reinterpret_cast<U8*>(reply_buf), reply_buf_len), Drv::SOCK_SUCCESS);
143+
ASSERT_STREQ(reply, reply_buf);
144+
145+
sender.close(send_fd);
146+
receiver.close(recv_fd);
147+
}
148+
102149
int main(int argc, char** argv) {
103150
::testing::InitGoogleTest(&argc, argv);
104151
return RUN_ALL_TESTS();

Drv/Udp/docs/sdd.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,15 @@ wait for the thread to exit using `join`.
2727
Since UDP support single or bidirectional communication, configuring each direction is done separately using the two
2828
methods `configureSend` and `configureRecv`. The user must call at least one of the configure methods and may call both.
2929

30+
### Ephemeral Port Support
31+
32+
The Drv::UdpComponentImpl supports ephemeral ports for receiving data. This is done by setting the port number to 0
33+
when calling `configureRecv`. The port number will be returned when the socket is opened.
34+
35+
When configured as a receiver-only the Drv::UdpComponentImpl can also be set up to send a response back to the sender and use the
36+
response port that the sender has indicated in the UDP datagram. This is done by setting the port number to 0 when calling
37+
`configureSend`.
38+
3039
```c++
3140
Drv::UdpComponentImpl comm = Drv::UdpComponentImpl("UDP Client");
3241

Os/File.hpp

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,11 @@
88
#include <Fw/FPrimeBasicTypes.hpp>
99
#include <Os/Os.hpp>
1010

11+
// Forward declaration for UTs
12+
namespace Os {namespace Test {namespace FileTest {struct Tester;}}}
13+
1114
namespace Os {
15+
1216
//! \brief base implementation of FileHandle
1317
//!
1418
struct FileHandle {};
@@ -213,6 +217,9 @@ class FileInterface {
213217
};
214218

215219
class File final : public FileInterface {
220+
221+
friend struct Os::Test::FileTest::Tester;
222+
216223
public:
217224
//! \brief constructor
218225
//!
@@ -504,7 +511,8 @@ class File final : public FileInterface {
504511
Status finalizeCrc(U32& crc);
505512

506513
private:
507-
PRIVATE : static const U32 INITIAL_CRC = 0xFFFFFFFF; //!< Initial value for CRC calculation
514+
static const U32 INITIAL_CRC = 0xFFFFFFFF; //!< Initial value for CRC calculation
515+
508516
Mode m_mode = Mode::OPEN_NO_MODE; //!< Stores mode for error checking
509517
const CHAR* m_path = nullptr; //!< Path last opened
510518

Os/Posix/test/ut/PosixFileTests.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
#include <csignal>
1414
namespace Os {
1515
namespace Test {
16-
namespace File {
16+
namespace FileTest {
1717

1818
std::vector<std::shared_ptr<const std::string> > FILES;
1919

@@ -129,8 +129,8 @@ class PosixTester : public Tester {
129129

130130
};
131131

132-
std::unique_ptr<Os::Test::File::Tester> get_tester_implementation() {
133-
return std::unique_ptr<Os::Test::File::Tester>(new Os::Test::File::PosixTester());
132+
std::unique_ptr<Os::Test::FileTest::Tester> get_tester_implementation() {
133+
return std::unique_ptr<Os::Test::FileTest::Tester>(new Os::Test::FileTest::PosixTester());
134134
}
135135

136136
} // namespace File

Os/Stub/test/ut/StubFileTests.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111

1212
namespace Os {
1313
namespace Test {
14-
namespace File {
14+
namespace FileTest {
1515

1616
//! Set up for the test ensures that the test can run at all
1717
//!
@@ -59,8 +59,8 @@ class StubsTester : public Tester {
5959

6060
};
6161

62-
std::unique_ptr<Os::Test::File::Tester> get_tester_implementation() {
63-
return std::unique_ptr<Os::Test::File::Tester>(new Os::Test::File::StubsTester());
62+
std::unique_ptr<Os::Test::FileTest::Tester> get_tester_implementation() {
63+
return std::unique_ptr<Os::Test::FileTest::Tester>(new Os::Test::FileTest::StubsTester());
6464
}
6565

6666

0 commit comments

Comments
 (0)