blob: 5963d605aa4ebf4207f6fb1da71c62669699ec4b [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
113 int result = bind(m_lsd, reinterpret_cast<sockaddr*>(&addr), sizeof(addr));
114 if (result != 0) {
115 WPI_ERROR(m_logger, "bind() failed: {}", SocketStrerror());
116 return result;
117 }
118 m_port = port;
119 return 0;
120}
121
122void UDPClient::shutdown() {
123 if (m_lsd > 0) {
124#ifdef _WIN32
125 ::shutdown(m_lsd, SD_BOTH);
126 closesocket(m_lsd);
127 WSACleanup();
128#else
129 ::shutdown(m_lsd, SHUT_RDWR);
130 close(m_lsd);
131#endif
132 m_lsd = 0;
133 m_port = 0;
134 }
135}
136
137int UDPClient::send(std::span<const uint8_t> data, std::string_view server,
138 int port) {
139 // server must be a resolvable IP address
140 struct sockaddr_in addr;
141 std::memset(&addr, 0, sizeof(addr));
142 addr.sin_family = AF_INET;
143 SmallString<128> remoteAddr{server};
144 if (remoteAddr.empty()) {
145 WPI_ERROR(m_logger, "server must be passed");
146 return -1;
147 }
148
149#ifdef _WIN32
150 int res = InetPton(AF_INET, remoteAddr.c_str(), &(addr.sin_addr));
151#else
152 int res = inet_pton(AF_INET, remoteAddr.c_str(), &(addr.sin_addr));
153#endif
154 if (res != 1) {
155 WPI_ERROR(m_logger, "could not resolve {} address", server);
156 return -1;
157 }
158 addr.sin_port = htons(port);
159
160 // sendto should not block
161 int result =
162 sendto(m_lsd, reinterpret_cast<const char*>(data.data()), data.size(), 0,
163 reinterpret_cast<sockaddr*>(&addr), sizeof(addr));
164 return result;
165}
166
167int UDPClient::send(std::string_view data, std::string_view server, int port) {
168 // server must be a resolvable IP address
169 struct sockaddr_in addr;
170 std::memset(&addr, 0, sizeof(addr));
171 addr.sin_family = AF_INET;
172 SmallString<128> remoteAddr{server};
173 if (remoteAddr.empty()) {
174 WPI_ERROR(m_logger, "server must be passed");
175 return -1;
176 }
177
178#ifdef _WIN32
179 int res = InetPton(AF_INET, remoteAddr.c_str(), &(addr.sin_addr));
180#else
181 int res = inet_pton(AF_INET, remoteAddr.c_str(), &(addr.sin_addr));
182#endif
183 if (res != 1) {
184 WPI_ERROR(m_logger, "could not resolve {} address", server);
185 return -1;
186 }
187 addr.sin_port = htons(port);
188
189 // sendto should not block
190 int result = sendto(m_lsd, data.data(), data.size(), 0,
191 reinterpret_cast<sockaddr*>(&addr), sizeof(addr));
192 return result;
193}
194
195int UDPClient::receive(uint8_t* data_received, int receive_len) {
196 if (m_port == 0) {
197 return -1; // return if not receiving
198 }
199 return recv(m_lsd, reinterpret_cast<char*>(data_received), receive_len, 0);
200}
201
202int UDPClient::receive(uint8_t* data_received, int receive_len,
203 SmallVectorImpl<char>* addr_received,
204 int* port_received) {
205 if (m_port == 0) {
206 return -1; // return if not receiving
207 }
208
209 struct sockaddr_in remote;
210 socklen_t remote_len = sizeof(remote);
211 std::memset(&remote, 0, sizeof(remote));
212
213 int result =
214 recvfrom(m_lsd, reinterpret_cast<char*>(data_received), receive_len, 0,
215 reinterpret_cast<sockaddr*>(&remote), &remote_len);
216
217 char ip[50];
218#ifdef _WIN32
219 InetNtop(PF_INET, &(remote.sin_addr.s_addr), ip, sizeof(ip) - 1);
220#else
221 inet_ntop(PF_INET, reinterpret_cast<in_addr*>(&(remote.sin_addr.s_addr)), ip,
222 sizeof(ip) - 1);
223#endif
224
225 ip[49] = '\0';
226 int addr_len = std::strlen(ip);
227 addr_received->clear();
228 addr_received->append(&ip[0], &ip[addr_len]);
229
230 *port_received = ntohs(remote.sin_port);
231
232 return result;
233}
234
235int UDPClient::set_timeout(double timeout) {
236 if (timeout < 0) {
237 return -1;
238 }
239 struct timeval tv;
240 tv.tv_sec = timeout; // truncating will give seconds
241 timeout -= tv.tv_sec; // remove seconds portion
242 tv.tv_usec = timeout * 1000000; // fractions of a second to us
243 int ret = setsockopt(m_lsd, SOL_SOCKET, SO_RCVTIMEO,
244 reinterpret_cast<char*>(&tv), sizeof(tv));
245 if (ret < 0) {
246 WPI_ERROR(m_logger, "set timeout failed");
247 }
248 return ret;
249}