blob: 1596b59ab01ef882943e8395852336b3df4ecf89 [file] [log] [blame]
James Kuszmaulcf324122023-01-14 14:07:17 -08001// Copyright (c) FIRST and other WPILib contributors.
2// Open Source Software; you can modify and/or share it under the terms of
3// the WPILib BSD license file in the root directory of this project.
4
5#include "wpinet/UDPClient.h"
6
7#ifdef _WIN32
8#include <WinSock2.h>
9#include <Ws2tcpip.h>
10#pragma comment(lib, "Ws2_32.lib")
11#else
12#include <arpa/inet.h>
13#include <fcntl.h>
14#include <netinet/in.h>
15#include <unistd.h>
16#endif
17
18#include <wpi/Logger.h>
19#include <wpi/SmallString.h>
20
21#include "wpinet/SocketError.h"
22
23using namespace wpi;
24
25UDPClient::UDPClient(Logger& logger) : UDPClient("", logger) {}
26
27UDPClient::UDPClient(std::string_view address, Logger& logger)
28 : m_lsd(0), m_port(0), m_address(address), m_logger(logger) {}
29
30UDPClient::UDPClient(UDPClient&& other)
31 : m_lsd(other.m_lsd),
32 m_port(other.m_port),
33 m_address(std::move(other.m_address)),
34 m_logger(other.m_logger) {
35 other.m_lsd = 0;
36 other.m_port = 0;
37}
38
39UDPClient::~UDPClient() {
40 if (m_lsd > 0) {
41 shutdown();
42 }
43}
44
45UDPClient& UDPClient::operator=(UDPClient&& other) {
46 if (this == &other) {
47 return *this;
48 }
49 shutdown();
50 m_logger = other.m_logger;
51 m_lsd = other.m_lsd;
52 m_address = std::move(other.m_address);
53 m_port = other.m_port;
54 other.m_lsd = 0;
55 other.m_port = 0;
56 return *this;
57}
58
59int UDPClient::start() {
60 return start(0);
61}
62
63int UDPClient::start(int port) {
64 if (m_lsd > 0) {
65 return 0;
66 }
67
68#ifdef _WIN32
69 WSAData wsaData;
70 WORD wVersionRequested = MAKEWORD(2, 2);
71 WSAStartup(wVersionRequested, &wsaData);
72#endif
73
74 m_lsd = socket(AF_INET, SOCK_DGRAM, 0);
75
76 if (m_lsd < 0) {
77 WPI_ERROR(m_logger, "could not create socket");
78 return -1;
79 }
80
81 struct sockaddr_in addr;
82 std::memset(&addr, 0, sizeof(addr));
83 addr.sin_family = AF_INET;
84 if (m_address.size() > 0) {
85#ifdef _WIN32
86 SmallString<128> addr_copy(m_address);
87 addr_copy.push_back('\0');
88 int res = InetPton(PF_INET, addr_copy.data(), &(addr.sin_addr));
89#else
90 int res = inet_pton(PF_INET, m_address.c_str(), &(addr.sin_addr));
91#endif
92 if (res != 1) {
93 WPI_ERROR(m_logger, "could not resolve {} address", m_address);
94 return -1;
95 }
96 } else {
97 addr.sin_addr.s_addr = INADDR_ANY;
98 }
99 addr.sin_port = htons(port);
100
101 if (port != 0) {
102#ifdef _WIN32
103 int optval = 1;
104 setsockopt(m_lsd, SOL_SOCKET, SO_EXCLUSIVEADDRUSE,
105 reinterpret_cast<char*>(&optval), sizeof optval);
106#else
107 int optval = 1;
108 setsockopt(m_lsd, SOL_SOCKET, SO_REUSEADDR,
109 reinterpret_cast<char*>(&optval), sizeof optval);
110#endif
111 }
112
Maxwell Henderson80bec322024-01-09 15:48:44 -0800113 // NOLINTNEXTLINE(modernize-avoid-bind)
James Kuszmaulcf324122023-01-14 14:07:17 -0800114 int result = bind(m_lsd, reinterpret_cast<sockaddr*>(&addr), sizeof(addr));
115 if (result != 0) {
116 WPI_ERROR(m_logger, "bind() failed: {}", SocketStrerror());
117 return result;
118 }
119 m_port = port;
120 return 0;
121}
122
123void UDPClient::shutdown() {
124 if (m_lsd > 0) {
125#ifdef _WIN32
126 ::shutdown(m_lsd, SD_BOTH);
127 closesocket(m_lsd);
128 WSACleanup();
129#else
130 ::shutdown(m_lsd, SHUT_RDWR);
131 close(m_lsd);
132#endif
133 m_lsd = 0;
134 m_port = 0;
135 }
136}
137
138int UDPClient::send(std::span<const uint8_t> data, std::string_view server,
139 int port) {
140 // server must be a resolvable IP address
141 struct sockaddr_in addr;
142 std::memset(&addr, 0, sizeof(addr));
143 addr.sin_family = AF_INET;
144 SmallString<128> remoteAddr{server};
145 if (remoteAddr.empty()) {
146 WPI_ERROR(m_logger, "server must be passed");
147 return -1;
148 }
149
150#ifdef _WIN32
151 int res = InetPton(AF_INET, remoteAddr.c_str(), &(addr.sin_addr));
152#else
153 int res = inet_pton(AF_INET, remoteAddr.c_str(), &(addr.sin_addr));
154#endif
155 if (res != 1) {
156 WPI_ERROR(m_logger, "could not resolve {} address", server);
157 return -1;
158 }
159 addr.sin_port = htons(port);
160
161 // sendto should not block
162 int result =
163 sendto(m_lsd, reinterpret_cast<const char*>(data.data()), data.size(), 0,
164 reinterpret_cast<sockaddr*>(&addr), sizeof(addr));
165 return result;
166}
167
168int UDPClient::send(std::string_view data, std::string_view server, int port) {
169 // server must be a resolvable IP address
170 struct sockaddr_in addr;
171 std::memset(&addr, 0, sizeof(addr));
172 addr.sin_family = AF_INET;
173 SmallString<128> remoteAddr{server};
174 if (remoteAddr.empty()) {
175 WPI_ERROR(m_logger, "server must be passed");
176 return -1;
177 }
178
179#ifdef _WIN32
180 int res = InetPton(AF_INET, remoteAddr.c_str(), &(addr.sin_addr));
181#else
182 int res = inet_pton(AF_INET, remoteAddr.c_str(), &(addr.sin_addr));
183#endif
184 if (res != 1) {
185 WPI_ERROR(m_logger, "could not resolve {} address", server);
186 return -1;
187 }
188 addr.sin_port = htons(port);
189
190 // sendto should not block
191 int result = sendto(m_lsd, data.data(), data.size(), 0,
192 reinterpret_cast<sockaddr*>(&addr), sizeof(addr));
193 return result;
194}
195
196int UDPClient::receive(uint8_t* data_received, int receive_len) {
197 if (m_port == 0) {
198 return -1; // return if not receiving
199 }
200 return recv(m_lsd, reinterpret_cast<char*>(data_received), receive_len, 0);
201}
202
203int UDPClient::receive(uint8_t* data_received, int receive_len,
204 SmallVectorImpl<char>* addr_received,
205 int* port_received) {
206 if (m_port == 0) {
207 return -1; // return if not receiving
208 }
209
210 struct sockaddr_in remote;
211 socklen_t remote_len = sizeof(remote);
212 std::memset(&remote, 0, sizeof(remote));
213
214 int result =
215 recvfrom(m_lsd, reinterpret_cast<char*>(data_received), receive_len, 0,
216 reinterpret_cast<sockaddr*>(&remote), &remote_len);
217
218 char ip[50];
219#ifdef _WIN32
220 InetNtop(PF_INET, &(remote.sin_addr.s_addr), ip, sizeof(ip) - 1);
221#else
222 inet_ntop(PF_INET, reinterpret_cast<in_addr*>(&(remote.sin_addr.s_addr)), ip,
223 sizeof(ip) - 1);
224#endif
225
226 ip[49] = '\0';
227 int addr_len = std::strlen(ip);
228 addr_received->clear();
229 addr_received->append(&ip[0], &ip[addr_len]);
230
231 *port_received = ntohs(remote.sin_port);
232
233 return result;
234}
235
236int UDPClient::set_timeout(double timeout) {
237 if (timeout < 0) {
238 return -1;
239 }
240 struct timeval tv;
241 tv.tv_sec = timeout; // truncating will give seconds
242 timeout -= tv.tv_sec; // remove seconds portion
243 tv.tv_usec = timeout * 1000000; // fractions of a second to us
244 int ret = setsockopt(m_lsd, SOL_SOCKET, SO_RCVTIMEO,
245 reinterpret_cast<char*>(&tv), sizeof(tv));
246 if (ret < 0) {
247 WPI_ERROR(m_logger, "set timeout failed");
248 }
249 return ret;
250}