[wpinet] Move network portions of wpiutil into new wpinet library (#4077)

This commit is contained in:
Peter Johnson
2022-05-07 10:54:14 -07:00
committed by GitHub
parent b33715db15
commit d673ead481
327 changed files with 1783 additions and 1179 deletions

View File

@@ -1,107 +0,0 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#include "wpi/DsClient.h"
#include <fmt/format.h>
#include <wpi/StringExtras.h>
#include <wpi/json.h>
#include <wpi/uv/Tcp.h>
#include <wpi/uv/Timer.h>
#include "wpi/Logger.h"
using namespace wpi;
static constexpr uv::Timer::Time kReconnectTime{500};
DsClient::DsClient(wpi::uv::Loop& loop, wpi::Logger& logger,
const private_init&)
: m_logger{logger},
m_tcp{uv::Tcp::Create(loop)},
m_timer{uv::Timer::Create(loop)} {
m_tcp->end.connect([this] {
WPI_DEBUG4(m_logger, "{}", "DS connection closed");
clearIp();
// try to connect again
m_tcp->Reuse([this] { m_timer->Start(kReconnectTime); });
});
m_tcp->data.connect([this](wpi::uv::Buffer buf, size_t len) {
HandleIncoming({buf.base, len});
});
m_timer->timeout.connect([this] { Connect(); });
Connect();
}
DsClient::~DsClient() = default;
void DsClient::Close() {
m_tcp->Close();
m_timer->Close();
clearIp();
}
void DsClient::Connect() {
auto connreq = std::make_shared<uv::TcpConnectReq>();
connreq->connected.connect([this] {
m_json.clear();
m_tcp->StopRead();
m_tcp->StartRead();
});
connreq->error = [this](uv::Error err) {
WPI_DEBUG4(m_logger, "DS connect failure: {}", err.str());
// try to connect again
m_tcp->Reuse([this] { m_timer->Start(kReconnectTime); });
};
WPI_DEBUG4(m_logger, "{}", "Starting DS connection attempt");
m_tcp->Connect("127.0.0.1", 1742, connreq);
}
void DsClient::HandleIncoming(std::string_view in) {
// this is very bare-bones, as there are never nested {} in these messages
while (!in.empty()) {
// if json is empty, look for the first { (and discard)
if (m_json.empty()) {
auto start = in.find('{');
in = wpi::slice(in, start, std::string_view::npos);
}
// look for the terminating } (and save)
auto end = in.find('}');
if (end == std::string_view::npos) {
m_json.append(in);
return; // nothing left to read
}
// have complete json message
++end;
m_json.append(wpi::slice(in, 0, end));
in = wpi::slice(in, end, std::string_view::npos);
ParseJson();
m_json.clear();
}
}
void DsClient::ParseJson() {
WPI_DEBUG4(m_logger, "DsClient JSON: {}", m_json);
unsigned int ip = 0;
try {
ip = wpi::json::parse(m_json).at("robotIP").get<unsigned int>();
} catch (wpi::json::exception& e) {
WPI_INFO(m_logger, "DsClient JSON error: {}", e.what());
return;
}
if (ip == 0) {
clearIp();
} else {
// Convert number into dotted quad
auto newip = fmt::format("{}.{}.{}.{}", (ip >> 24) & 0xff,
(ip >> 16) & 0xff, (ip >> 8) & 0xff, ip & 0xff);
WPI_INFO(m_logger, "DS received server IP: {}", newip);
setIp(newip);
}
}

View File

@@ -1,90 +0,0 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#include "wpi/EventLoopRunner.h"
#include "wpi/SmallVector.h"
#include "wpi/condition_variable.h"
#include "wpi/mutex.h"
#include "wpi/uv/AsyncFunction.h"
#include "wpi/uv/Loop.h"
using namespace wpi;
class EventLoopRunner::Thread : public SafeThread {
public:
using UvExecFunc = uv::AsyncFunction<void(LoopFunc)>;
Thread() : m_loop(uv::Loop::Create()) {
// set up async handles
if (!m_loop) {
return;
}
// run function
m_doExec = UvExecFunc::Create(
m_loop, [loop = m_loop.get()](auto out, LoopFunc func) {
func(*loop);
out.set_value();
});
}
void Main() override {
if (m_loop) {
m_loop->Run();
}
}
// the loop
std::shared_ptr<uv::Loop> m_loop;
// run function
std::weak_ptr<UvExecFunc> m_doExec;
};
EventLoopRunner::EventLoopRunner() {
m_owner.Start();
}
EventLoopRunner::~EventLoopRunner() {
Stop();
}
void EventLoopRunner::Stop() {
ExecAsync([](uv::Loop& loop) {
// close all handles; this will (eventually) stop the loop
loop.Walk([](uv::Handle& h) {
h.SetLoopClosing(true);
h.Close();
});
});
m_owner.Join();
}
void EventLoopRunner::ExecAsync(LoopFunc func) {
if (auto thr = m_owner.GetThread()) {
if (auto doExec = thr->m_doExec.lock()) {
doExec->Call(std::move(func));
}
}
}
void EventLoopRunner::ExecSync(LoopFunc func) {
wpi::future<void> f;
if (auto thr = m_owner.GetThread()) {
if (auto doExec = thr->m_doExec.lock()) {
f = doExec->Call(std::move(func));
}
}
if (f.valid()) {
f.wait();
}
}
std::shared_ptr<uv::Loop> EventLoopRunner::GetLoop() {
if (auto thr = m_owner.GetThread()) {
return thr->m_loop;
}
return nullptr;
}

View File

@@ -1,193 +0,0 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#include "wpi/HttpParser.h"
using namespace wpi;
uint32_t HttpParser::GetParserVersion() {
return static_cast<uint32_t>(http_parser_version());
}
HttpParser::HttpParser(Type type) {
http_parser_init(&m_parser,
static_cast<http_parser_type>(static_cast<int>(type)));
m_parser.data = this;
http_parser_settings_init(&m_settings);
// Unlike the underlying http_parser library, we don't perform callbacks
// (other than body) with partial data; instead we buffer and call the user
// callback only when the data is complete.
// on_message_begin: initialize our state, call user callback
m_settings.on_message_begin = [](http_parser* p) -> int {
auto& self = *static_cast<HttpParser*>(p->data);
self.m_urlBuf.clear();
self.m_state = kStart;
self.messageBegin();
return self.m_aborted;
};
// on_url: collect into buffer
m_settings.on_url = [](http_parser* p, const char* at, size_t length) -> int {
auto& self = *static_cast<HttpParser*>(p->data);
// append to buffer
if ((self.m_urlBuf.size() + length) > self.m_maxLength) {
return 1;
}
self.m_urlBuf += std::string_view{at, length};
self.m_state = kUrl;
return 0;
};
// on_status: collect into buffer, call user URL callback
m_settings.on_status = [](http_parser* p, const char* at,
size_t length) -> int {
auto& self = *static_cast<HttpParser*>(p->data);
// use valueBuf for the status
if ((self.m_valueBuf.size() + length) > self.m_maxLength) {
return 1;
}
self.m_valueBuf += std::string_view{at, length};
self.m_state = kStatus;
return 0;
};
// on_header_field: collect into buffer, call user header/status callback
m_settings.on_header_field = [](http_parser* p, const char* at,
size_t length) -> int {
auto& self = *static_cast<HttpParser*>(p->data);
// once we're in header, we know the URL is complete
if (self.m_state == kUrl) {
self.url(self.m_urlBuf);
if (self.m_aborted) {
return 1;
}
}
// once we're in header, we know the status is complete
if (self.m_state == kStatus) {
self.status(self.m_valueBuf);
if (self.m_aborted) {
return 1;
}
}
// if we previously were in value state, that means we finished a header
if (self.m_state == kValue) {
self.header(self.m_fieldBuf, self.m_valueBuf);
if (self.m_aborted) {
return 1;
}
}
// clear field and value when we enter this state
if (self.m_state != kField) {
self.m_state = kField;
self.m_fieldBuf.clear();
self.m_valueBuf.clear();
}
// append data to field buffer
if ((self.m_fieldBuf.size() + length) > self.m_maxLength) {
return 1;
}
self.m_fieldBuf += std::string_view{at, length};
return 0;
};
// on_header_field: collect into buffer
m_settings.on_header_value = [](http_parser* p, const char* at,
size_t length) -> int {
auto& self = *static_cast<HttpParser*>(p->data);
// if we weren't previously in value state, clear the buffer
if (self.m_state != kValue) {
self.m_state = kValue;
self.m_valueBuf.clear();
}
// append data to value buffer
if ((self.m_valueBuf.size() + length) > self.m_maxLength) {
return 1;
}
self.m_valueBuf += std::string_view{at, length};
return 0;
};
// on_headers_complete: call user status/header/complete callback
m_settings.on_headers_complete = [](http_parser* p) -> int {
auto& self = *static_cast<HttpParser*>(p->data);
// if we previously were in url state, that means we finished the url
if (self.m_state == kUrl) {
self.url(self.m_urlBuf);
if (self.m_aborted) {
return 1;
}
}
// if we previously were in status state, that means we finished the status
if (self.m_state == kStatus) {
self.status(self.m_valueBuf);
if (self.m_aborted) {
return 1;
}
}
// if we previously were in value state, that means we finished a header
if (self.m_state == kValue) {
self.header(self.m_fieldBuf, self.m_valueBuf);
if (self.m_aborted) {
return 1;
}
}
self.headersComplete(self.ShouldKeepAlive());
return self.m_aborted;
};
// on_body: call user callback
m_settings.on_body = [](http_parser* p, const char* at,
size_t length) -> int {
auto& self = *static_cast<HttpParser*>(p->data);
self.body(std::string_view{at, length}, self.IsBodyFinal());
return self.m_aborted;
};
// on_message_complete: call user callback
m_settings.on_message_complete = [](http_parser* p) -> int {
auto& self = *static_cast<HttpParser*>(p->data);
self.messageComplete(self.ShouldKeepAlive());
return self.m_aborted;
};
// on_chunk_header: call user callback
m_settings.on_chunk_header = [](http_parser* p) -> int {
auto& self = *static_cast<HttpParser*>(p->data);
self.chunkHeader(p->content_length);
return self.m_aborted;
};
// on_chunk_complete: call user callback
m_settings.on_chunk_complete = [](http_parser* p) -> int {
auto& self = *static_cast<HttpParser*>(p->data);
self.chunkComplete();
return self.m_aborted;
};
}
void HttpParser::Reset(Type type) {
http_parser_init(&m_parser,
static_cast<http_parser_type>(static_cast<int>(type)));
m_parser.data = this;
m_maxLength = 1024;
m_state = kStart;
m_urlBuf.clear();
m_fieldBuf.clear();
m_valueBuf.clear();
m_aborted = false;
}

View File

@@ -1,178 +0,0 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#include "wpi/HttpServerConnection.h"
#include "fmt/format.h"
#include "wpi/SmallString.h"
#include "wpi/SmallVector.h"
#include "wpi/SpanExtras.h"
#include "wpi/StringExtras.h"
#include "wpi/fmt/raw_ostream.h"
#include "wpi/raw_uv_ostream.h"
using namespace wpi;
HttpServerConnection::HttpServerConnection(std::shared_ptr<uv::Stream> stream)
: m_stream(*stream) {
// process HTTP messages
m_messageCompleteConn =
m_request.messageComplete.connect_connection([this](bool keepAlive) {
m_keepAlive = keepAlive;
ProcessRequest();
});
// look for Accept-Encoding headers to determine if gzip is acceptable
m_request.messageBegin.connect([this] { m_acceptGzip = false; });
m_request.header.connect(
[this](std::string_view name, std::string_view value) {
if (wpi::equals_lower(name, "accept-encoding") &&
wpi::contains(value, "gzip")) {
m_acceptGzip = true;
}
});
// pass incoming data to HTTP parser
m_dataConn =
stream->data.connect_connection([this](uv::Buffer& buf, size_t size) {
m_request.Execute({buf.base, size});
if (m_request.HasError()) {
// could not parse; just close the connection
m_stream.Close();
}
});
// close when remote side closes
m_endConn =
stream->end.connect_connection([h = stream.get()] { h->Close(); });
// start reading
stream->StartRead();
}
void HttpServerConnection::BuildCommonHeaders(raw_ostream& os) {
os << "Server: WebServer/1.0\r\n"
"Cache-Control: no-store, no-cache, must-revalidate, pre-check=0, "
"post-check=0, max-age=0\r\n"
"Pragma: no-cache\r\n"
"Expires: Mon, 3 Jan 2000 12:34:56 GMT\r\n";
}
void HttpServerConnection::BuildHeader(raw_ostream& os, int code,
std::string_view codeText,
std::string_view contentType,
uint64_t contentLength,
std::string_view extra) {
fmt::print(os, "HTTP/{}.{} {} {}\r\n", m_request.GetMajor(),
m_request.GetMinor(), code, codeText);
if (contentLength == 0) {
m_keepAlive = false;
}
if (!m_keepAlive) {
os << "Connection: close\r\n";
}
BuildCommonHeaders(os);
os << "Content-Type: " << contentType << "\r\n";
if (contentLength != 0) {
fmt::print(os, "Content-Length: {}\r\n", contentLength);
}
os << "Access-Control-Allow-Origin: *\r\nAccess-Control-Allow-Methods: *\r\n";
if (!extra.empty()) {
os << extra;
}
os << "\r\n"; // header ends with a blank line
}
void HttpServerConnection::SendData(span<const uv::Buffer> bufs,
bool closeAfter) {
m_stream.Write(bufs, [closeAfter, stream = &m_stream](auto bufs, uv::Error) {
for (auto&& buf : bufs) {
buf.Deallocate();
}
if (closeAfter) {
stream->Close();
}
});
}
void HttpServerConnection::SendResponse(int code, std::string_view codeText,
std::string_view contentType,
std::string_view content,
std::string_view extraHeader) {
SmallVector<uv::Buffer, 4> toSend;
raw_uv_ostream os{toSend, 4096};
BuildHeader(os, code, codeText, contentType, content.size(), extraHeader);
os << content;
// close after write completes if we aren't keeping alive
SendData(os.bufs(), !m_keepAlive);
}
void HttpServerConnection::SendStaticResponse(
int code, std::string_view codeText, std::string_view contentType,
std::string_view content, bool gzipped, std::string_view extraHeader) {
// TODO: handle remote side not accepting gzip (very rare)
std::string_view contentEncodingHeader;
if (gzipped /* && m_acceptGzip*/) {
contentEncodingHeader = "Content-Encoding: gzip\r\n";
}
SmallVector<uv::Buffer, 4> bufs;
raw_uv_ostream os{bufs, 4096};
BuildHeader(os, code, codeText, contentType, content.size(),
fmt::format("{}{}", extraHeader, contentEncodingHeader));
// can send content without copying
bufs.emplace_back(content);
m_stream.Write(bufs, [closeAfter = !m_keepAlive, stream = &m_stream](
auto bufs, uv::Error) {
// don't deallocate the static content
for (auto&& buf : wpi::drop_back(bufs)) {
buf.Deallocate();
}
if (closeAfter) {
stream->Close();
}
});
}
void HttpServerConnection::SendError(int code, std::string_view message) {
std::string_view codeText, extra, baseMessage;
switch (code) {
case 401:
codeText = "Unauthorized";
extra = "WWW-Authenticate: Basic realm=\"CameraServer\"";
baseMessage = "401: Not Authenticated!";
break;
case 404:
codeText = "Not Found";
baseMessage = "404: Not Found!";
break;
case 500:
codeText = "Internal Server Error";
baseMessage = "500: Internal Server Error!";
break;
case 400:
codeText = "Bad Request";
baseMessage = "400: Not Found!";
break;
case 403:
codeText = "Forbidden";
baseMessage = "403: Forbidden!";
break;
case 503:
codeText = "Service Unavailable";
baseMessage = "503: Service Unavailable";
break;
default:
code = 501;
codeText = "Not Implemented";
baseMessage = "501: Not Implemented!";
break;
}
SmallString<256> content{baseMessage};
content += "\r\n";
content += message;
SendResponse(code, codeText, "text/plain", content.str(), extra);
}

View File

@@ -1,493 +0,0 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#include "wpi/HttpUtil.h"
#include <cctype>
#include "fmt/format.h"
#include "wpi/Base64.h"
#include "wpi/StringExtras.h"
#include "wpi/TCPConnector.h"
#include "wpi/raw_ostream.h"
namespace wpi {
std::string_view UnescapeURI(std::string_view str, SmallVectorImpl<char>& buf,
bool* error) {
buf.clear();
for (auto i = str.begin(), end = str.end(); i != end; ++i) {
// pass non-escaped characters to output
if (*i != '%') {
// decode + to space
if (*i == '+') {
buf.push_back(' ');
} else {
buf.push_back(*i);
}
continue;
}
// are there enough characters left?
if (i + 2 >= end) {
*error = true;
return {};
}
// replace %xx with the corresponding character
unsigned val1 = hexDigitValue(*++i);
if (val1 == -1U) {
*error = true;
return {};
}
unsigned val2 = hexDigitValue(*++i);
if (val2 == -1U) {
*error = true;
return {};
}
buf.push_back((val1 << 4) | val2);
}
*error = false;
return {buf.data(), buf.size()};
}
std::string_view EscapeURI(std::string_view str, SmallVectorImpl<char>& buf,
bool spacePlus) {
static const char* const hexLut = "0123456789ABCDEF";
buf.clear();
for (auto i = str.begin(), end = str.end(); i != end; ++i) {
// pass unreserved characters to output
if (std::isalnum(*i) || *i == '-' || *i == '_' || *i == '.' || *i == '~') {
buf.push_back(*i);
continue;
}
// encode space to +
if (spacePlus && *i == ' ') {
buf.push_back('+');
continue;
}
// convert others to %xx
buf.push_back('%');
buf.push_back(hexLut[((*i) >> 4) & 0x0f]);
buf.push_back(hexLut[(*i) & 0x0f]);
}
return {buf.data(), buf.size()};
}
HttpQueryMap::HttpQueryMap(std::string_view query) {
SmallVector<std::string_view, 16> queryElems;
split(query, queryElems, '&', 100, false);
for (auto elem : queryElems) {
auto [nameEsc, valueEsc] = split(elem, '=');
SmallString<64> nameBuf;
bool err = false;
auto name = wpi::UnescapeURI(nameEsc, nameBuf, &err);
// note: ignores duplicates
if (!err) {
m_elems.try_emplace(name, valueEsc);
}
}
}
std::optional<std::string_view> HttpQueryMap::Get(
std::string_view name, wpi::SmallVectorImpl<char>& buf) const {
auto it = m_elems.find(name);
if (it == m_elems.end()) {
return {};
}
bool err = false;
auto val = wpi::UnescapeURI(it->second, buf, &err);
if (err) {
return {};
}
return val;
}
HttpPath::HttpPath(std::string_view path) {
// special-case root path to be a single empty element
if (path == "/") {
m_pathEnds.emplace_back(0);
return;
}
wpi::SmallVector<std::string_view, 16> pathElems;
split(path, pathElems, '/', 100, false);
for (auto elem : pathElems) {
SmallString<64> buf;
bool err = false;
auto val = wpi::UnescapeURI(elem, buf, &err);
if (err) {
m_pathEnds.clear();
return;
}
m_pathBuf += val;
m_pathEnds.emplace_back(m_pathBuf.size());
}
}
bool HttpPath::startswith(size_t start,
span<const std::string_view> match) const {
if (m_pathEnds.size() < (start + match.size())) {
return false;
}
bool first = start == 0;
auto p = m_pathEnds.begin() + start;
for (auto m : match) {
auto val = slice(m_pathBuf, first ? 0 : *(p - 1), *p);
if (val != m) {
return false;
}
first = false;
++p;
}
return true;
}
std::string_view HttpPath::operator[](size_t n) const {
return slice(m_pathBuf, n == 0 ? 0 : m_pathEnds[n - 1], m_pathEnds[n]);
}
bool ParseHttpHeaders(raw_istream& is, SmallVectorImpl<char>* contentType,
SmallVectorImpl<char>* contentLength) {
if (contentType) {
contentType->clear();
}
if (contentLength) {
contentLength->clear();
}
bool inContentType = false;
bool inContentLength = false;
SmallString<64> lineBuf;
for (;;) {
std::string_view line = rtrim(is.getline(lineBuf, 1024));
if (is.has_error()) {
return false;
}
if (line.empty()) {
return true; // empty line signals end of headers
}
// header fields start at the beginning of the line
if (!std::isspace(line[0])) {
inContentType = false;
inContentLength = false;
std::string_view field;
std::tie(field, line) = split(line, ':');
field = rtrim(field);
if (equals_lower(field, "content-type")) {
inContentType = true;
} else if (equals_lower(field, "content-length")) {
inContentLength = true;
} else {
continue; // ignore other fields
}
}
// collapse whitespace
line = ltrim(line);
// save field data
if (inContentType && contentType) {
contentType->append(line.begin(), line.end());
} else if (inContentLength && contentLength) {
contentLength->append(line.begin(), line.end());
}
}
}
bool FindMultipartBoundary(raw_istream& is, std::string_view boundary,
std::string* saveBuf) {
SmallString<64> searchBuf;
searchBuf.resize(boundary.size() + 2);
size_t searchPos = 0;
// Per the spec, the --boundary should be preceded by \r\n, so do a first
// pass of 1-byte reads to throw those away (common case) and keep the
// last non-\r\n character in searchBuf.
if (!saveBuf) {
do {
is.read(searchBuf.data(), 1);
if (is.has_error()) {
return false;
}
} while (searchBuf[0] == '\r' || searchBuf[0] == '\n');
searchPos = 1;
}
// Look for --boundary. Read boundarysize+2 bytes at a time
// during the search to speed up the reads, then fast-scan for -,
// and only then match the entire boundary. This will be slow if
// there's a bunch of continuous -'s in the output, but that's unlikely.
for (;;) {
is.read(searchBuf.data() + searchPos, searchBuf.size() - searchPos);
if (is.has_error()) {
return false;
}
// Did we find the boundary?
if (searchBuf[0] == '-' && searchBuf[1] == '-' &&
wpi::substr(searchBuf, 2) == boundary) {
return true;
}
// Fast-scan for '-'
size_t pos = searchBuf.find('-', searchBuf[0] == '-' ? 1 : 0);
if (pos == std::string_view::npos) {
if (saveBuf) {
saveBuf->append(searchBuf.data(), searchBuf.size());
}
} else {
if (saveBuf) {
saveBuf->append(searchBuf.data(), pos);
}
// move '-' and following to start of buffer (next read will fill)
std::memmove(searchBuf.data(), searchBuf.data() + pos,
searchBuf.size() - pos);
searchPos = searchBuf.size() - pos;
}
}
}
HttpLocation::HttpLocation(std::string_view url_, bool* error,
std::string* errorMsg)
: url{url_} {
// Split apart into components
std::string_view query{url};
// scheme:
std::string_view scheme;
std::tie(scheme, query) = split(query, ':');
if (!equals_lower(scheme, "http")) {
*errorMsg = "only supports http URLs";
*error = true;
return;
}
// "//"
if (!starts_with(query, "//")) {
*errorMsg = "expected http://...";
*error = true;
return;
}
query.remove_prefix(2);
// user:password@host:port/
std::string_view authority;
std::tie(authority, query) = split(query, '/');
auto [userpass, hostport] = split(authority, '@');
// split leaves the RHS empty if the split char isn't present...
if (hostport.empty()) {
hostport = userpass;
userpass = {};
}
if (!userpass.empty()) {
auto [rawUser, rawPassword] = split(userpass, ':');
SmallString<64> userBuf, passBuf;
user = UnescapeURI(rawUser, userBuf, error);
if (*error) {
*errorMsg = fmt::format("could not unescape user \"{}\"", rawUser);
return;
}
password = UnescapeURI(rawPassword, passBuf, error);
if (*error) {
*errorMsg =
fmt::format("could not unescape password \"{}\"", rawPassword);
return;
}
}
std::string_view portStr;
std::tie(host, portStr) = rsplit(hostport, ':');
if (host.empty()) {
*errorMsg = "host is empty";
*error = true;
return;
}
if (portStr.empty()) {
port = 80;
} else if (auto p = parse_integer<int>(portStr, 10)) {
port = p.value();
} else {
*errorMsg = fmt::format("port \"{}\" is not an integer", portStr);
*error = true;
return;
}
// path?query#fragment
std::tie(query, fragment) = split(query, '#');
std::tie(path, query) = split(query, '?');
// Split query string into parameters
while (!query.empty()) {
// split out next param and value
std::string_view rawParam, rawValue;
std::tie(rawParam, query) = split(query, '&');
if (rawParam.empty()) {
continue; // ignore "&&"
}
std::tie(rawParam, rawValue) = split(rawParam, '=');
// unescape param
*error = false;
SmallString<64> paramBuf;
std::string_view param = UnescapeURI(rawParam, paramBuf, error);
if (*error) {
*errorMsg = fmt::format("could not unescape parameter \"{}\"", rawParam);
return;
}
// unescape value
SmallString<64> valueBuf;
std::string_view value = UnescapeURI(rawValue, valueBuf, error);
if (*error) {
*errorMsg = fmt::format("could not unescape value \"{}\"", rawValue);
return;
}
params.emplace_back(std::make_pair(param, value));
}
*error = false;
}
void HttpRequest::SetAuth(const HttpLocation& loc) {
if (!loc.user.empty()) {
SmallString<64> userpass;
userpass += loc.user;
userpass += ':';
userpass += loc.password;
Base64Encode(userpass.str(), &auth);
}
}
bool HttpConnection::Handshake(const HttpRequest& request,
std::string* warnMsg) {
// send GET request
os << "GET /" << request.path << " HTTP/1.1\r\n";
os << "Host: " << request.host << "\r\n";
if (!request.auth.empty()) {
os << "Authorization: Basic " << request.auth << "\r\n";
}
os << "\r\n";
os.flush();
// read first line of response
SmallString<64> lineBuf;
std::string_view line = rtrim(is.getline(lineBuf, 1024));
if (is.has_error()) {
*warnMsg = "disconnected before response";
return false;
}
// see if we got a HTTP 200 response
std::string_view httpver, code, codeText;
std::tie(httpver, line) = split(line, ' ');
std::tie(code, codeText) = split(line, ' ');
if (!starts_with(httpver, "HTTP")) {
*warnMsg = "did not receive HTTP response";
return false;
}
if (code != "200") {
*warnMsg = fmt::format("received {} {} response", code, codeText);
return false;
}
// Parse headers
if (!ParseHttpHeaders(is, &contentType, &contentLength)) {
*warnMsg = "disconnected during headers";
return false;
}
return true;
}
void HttpMultipartScanner::SetBoundary(std::string_view boundary) {
m_boundaryWith = "\n--";
m_boundaryWith += boundary;
m_boundaryWithout = "\n";
m_boundaryWithout += boundary;
m_dashes = kUnknown;
}
void HttpMultipartScanner::Reset(bool saveSkipped) {
m_saveSkipped = saveSkipped;
m_state = kBoundary;
m_posWith = 0;
m_posWithout = 0;
m_buf.resize(0);
}
std::string_view HttpMultipartScanner::Execute(std::string_view in) {
if (m_state == kDone) {
Reset(m_saveSkipped);
}
if (m_saveSkipped) {
m_buf += in;
}
size_t pos = 0;
if (m_state == kBoundary) {
for (char ch : in) {
++pos;
if (m_dashes != kWithout) {
if (ch == m_boundaryWith[m_posWith]) {
++m_posWith;
if (m_posWith == m_boundaryWith.size()) {
// Found the boundary; transition to padding
m_state = kPadding;
m_dashes = kWith; // no longer accept plain 'boundary'
break;
}
} else if (ch == m_boundaryWith[0]) {
m_posWith = 1;
} else {
m_posWith = 0;
}
}
if (m_dashes != kWith) {
if (ch == m_boundaryWithout[m_posWithout]) {
++m_posWithout;
if (m_posWithout == m_boundaryWithout.size()) {
// Found the boundary; transition to padding
m_state = kPadding;
m_dashes = kWithout; // no longer accept '--boundary'
break;
}
} else if (ch == m_boundaryWithout[0]) {
m_posWithout = 1;
} else {
m_posWithout = 0;
}
}
}
}
if (m_state == kPadding) {
for (char ch : drop_front(in, pos)) {
++pos;
if (ch == '\n') {
// Found the LF; return remaining input buffer (following it)
m_state = kDone;
if (m_saveSkipped) {
m_buf.resize(m_buf.size() - in.size() + pos);
}
return drop_front(in, pos);
}
}
}
// We consumed the entire input
return {};
}
} // namespace wpi

View File

@@ -1,69 +0,0 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#include "wpi/MimeTypes.h"
#include "wpi/StringExtras.h"
#include "wpi/StringMap.h"
namespace wpi {
// derived partially from
// https://github.com/DEGoodmanWilson/libmime/blob/stable/0.1.2/mime/mime.cpp
std::string_view MimeTypeFromPath(std::string_view path) {
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types
static StringMap<const char*> mimeTypes{
// text
{"css", "text/css"},
{"csv", "text/csv"},
{"htm", "text/html"},
{"html", "text/html"},
{"js", "text/javascript"},
{"json", "application/json"},
{"map", "application/json"},
{"md", "text/markdown"},
{"txt", "text/plain"},
{"xml", "text/xml"},
// images
{"apng", "image/apng"},
{"bmp", "image/bmp"},
{"gif", "image/gif"},
{"cur", "image/x-icon"},
{"ico", "image/x-icon"},
{"jpg", "image/jpeg"},
{"jpeg", "image/jpeg"},
{"png", "image/png"},
{"svg", "image/svg+xml"},
{"tif", "image/tiff"},
{"tiff", "image/tiff"},
{"webp", "image/webp"},
// fonts
{"otf", "font/otf"},
{"ttf", "font/ttf"},
{"woff", "font/woff"},
// misc
{"pdf", "application/pdf"},
{"zip", "application/zip"},
};
static const char* defaultType = "application/octet-stream";
auto pos = path.find_last_of("/");
if (pos != std::string_view::npos) {
path = wpi::substr(path, pos + 1);
}
auto dot_pos = path.find_last_of(".");
if (dot_pos > 0 && dot_pos != std::string_view::npos) {
auto type = mimeTypes.find(wpi::substr(path, dot_pos + 1));
if (type != mimeTypes.end()) {
return type->getValue();
}
}
return defaultType;
}
} // namespace wpi

View File

@@ -1,12 +0,0 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#include "MulticastHandleManager.h"
using namespace wpi;
MulticastHandleManager& wpi::GetMulticastManager() {
static MulticastHandleManager manager;
return manager;
}

View File

@@ -1,25 +0,0 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#pragma once
#include <memory>
#include "wpi/DenseMap.h"
#include "wpi/MulticastServiceAnnouncer.h"
#include "wpi/MulticastServiceResolver.h"
#include "wpi/UidVector.h"
namespace wpi {
struct MulticastHandleManager {
wpi::mutex mutex;
wpi::UidVector<int, 8> handleIds;
wpi::DenseMap<size_t, std::unique_ptr<wpi::MulticastServiceResolver>>
resolvers;
wpi::DenseMap<size_t, std::unique_ptr<wpi::MulticastServiceAnnouncer>>
announcers;
};
MulticastHandleManager& GetMulticastManager();
} // namespace wpi

View File

@@ -1,67 +0,0 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#include "wpi/MulticastServiceAnnouncer.h"
#include <wpi/SmallVector.h>
#include "MulticastHandleManager.h"
extern "C" {
WPI_MulticastServiceAnnouncerHandle WPI_CreateMulticastServiceAnnouncer(
const char* serviceName, const char* serviceType, int32_t port,
int32_t txtCount, const char** keys, const char** values)
{
auto& manager = wpi::GetMulticastManager();
std::scoped_lock lock{manager.mutex};
wpi::SmallVector<std::pair<std::string_view, std::string_view>, 8> txts;
for (int32_t i = 0; i < txtCount; i++) {
txts.emplace_back(
std::pair<std::string_view, std::string_view>{keys[i], values[i]});
}
auto announcer = std::make_unique<wpi::MulticastServiceAnnouncer>(
serviceName, serviceType, port, txts);
size_t index = manager.handleIds.emplace_back(3);
manager.announcers[index] = std::move(announcer);
return index;
}
void WPI_FreeMulticastServiceAnnouncer(
WPI_MulticastServiceAnnouncerHandle handle) {
auto& manager = wpi::GetMulticastManager();
std::scoped_lock lock{manager.mutex};
manager.announcers[handle] = nullptr;
manager.handleIds.erase(handle);
}
void WPI_StartMulticastServiceAnnouncer(
WPI_MulticastServiceAnnouncerHandle handle) {
auto& manager = wpi::GetMulticastManager();
std::scoped_lock lock{manager.mutex};
auto& announcer = manager.announcers[handle];
announcer->Start();
}
void WPI_StopMulticastServiceAnnouncer(
WPI_MulticastServiceAnnouncerHandle handle) {
auto& manager = wpi::GetMulticastManager();
std::scoped_lock lock{manager.mutex};
auto& announcer = manager.announcers[handle];
announcer->Stop();
}
int32_t WPI_GetMulticastServiceAnnouncerHasImplementation(
WPI_MulticastServiceAnnouncerHandle handle) {
auto& manager = wpi::GetMulticastManager();
std::scoped_lock lock{manager.mutex};
auto& announcer = manager.announcers[handle];
return announcer->HasImplementation();
}
} // extern "C"

View File

@@ -1,148 +0,0 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#include "wpi/MulticastServiceResolver.h"
#include "MulticastHandleManager.h"
#include "wpi/MemAlloc.h"
extern "C" {
WPI_MulticastServiceResolverHandle WPI_CreateMulticastServiceResolver(
const char* serviceType)
{
auto& manager = wpi::GetMulticastManager();
std::scoped_lock lock{manager.mutex};
auto resolver = std::make_unique<wpi::MulticastServiceResolver>(serviceType);
size_t index = manager.handleIds.emplace_back(2);
manager.resolvers[index] = std::move(resolver);
return index;
}
void WPI_FreeMulticastServiceResolver(
WPI_MulticastServiceResolverHandle handle) {
auto& manager = wpi::GetMulticastManager();
std::scoped_lock lock{manager.mutex};
manager.resolvers[handle] = nullptr;
manager.handleIds.erase(handle);
}
void WPI_StartMulticastServiceResolver(
WPI_MulticastServiceResolverHandle handle) {
auto& manager = wpi::GetMulticastManager();
std::scoped_lock lock{manager.mutex};
auto& resolver = manager.resolvers[handle];
resolver->Start();
}
void WPI_StopMulticastServiceResolver(
WPI_MulticastServiceResolverHandle handle) {
auto& manager = wpi::GetMulticastManager();
std::scoped_lock lock{manager.mutex};
auto& resolver = manager.resolvers[handle];
resolver->Stop();
}
int32_t WPI_GetMulticastServiceResolverHasImplementation(
WPI_MulticastServiceResolverHandle handle) {
auto& manager = wpi::GetMulticastManager();
std::scoped_lock lock{manager.mutex};
auto& resolver = manager.resolvers[handle];
return resolver->HasImplementation();
}
WPI_EventHandle WPI_GetMulticastServiceResolverEventHandle(
WPI_MulticastServiceResolverHandle handle) {
auto& manager = wpi::GetMulticastManager();
std::scoped_lock lock{manager.mutex};
auto& resolver = manager.resolvers[handle];
return resolver->GetEventHandle();
}
WPI_ServiceData* WPI_GetMulticastServiceResolverData(
WPI_MulticastServiceResolverHandle handle, int32_t* dataCount) {
std::vector<wpi::MulticastServiceResolver::ServiceData> allData;
{
auto& manager = wpi::GetMulticastManager();
std::scoped_lock lock{manager.mutex};
auto& resolver = manager.resolvers[handle];
allData = resolver->GetData();
}
if (allData.empty()) {
*dataCount = 0;
return nullptr;
}
size_t allocSize = sizeof(WPI_ServiceData) * allData.size();
for (auto&& data : allData) {
// Include space for hostName and serviceType (+ terminators)
allocSize += data.hostName.size() + data.serviceName.size() + 2;
size_t keysTotalLength = 0;
size_t valuesTotalLength = 0;
// Include space for all keys and values, and pointer array
for (auto&& t : data.txt) {
allocSize += sizeof(const char*);
keysTotalLength += (t.first.size() + 1);
allocSize += sizeof(const char*);
valuesTotalLength += (t.second.size() + 1);
}
allocSize += keysTotalLength;
allocSize += valuesTotalLength;
}
uint8_t* cDataRaw = reinterpret_cast<uint8_t*>(wpi::safe_malloc(allocSize));
if (!cDataRaw) {
return nullptr;
}
WPI_ServiceData* rootArray = reinterpret_cast<WPI_ServiceData*>(cDataRaw);
cDataRaw += (sizeof(WPI_ServiceData) + allData.size());
WPI_ServiceData* currentData = rootArray;
for (auto&& data : allData) {
currentData->ipv4Address = data.ipv4Address;
currentData->port = data.port;
currentData->txtCount = data.txt.size();
std::memcpy(cDataRaw, data.hostName.c_str(), data.hostName.size() + 1);
currentData->hostName = reinterpret_cast<const char*>(cDataRaw);
cDataRaw += data.hostName.size() + 1;
std::memcpy(cDataRaw, data.serviceName.c_str(),
data.serviceName.size() + 1);
currentData->serviceName = reinterpret_cast<const char*>(cDataRaw);
cDataRaw += data.serviceName.size() + 1;
char** valuesPtrArr = reinterpret_cast<char**>(cDataRaw);
cDataRaw += (sizeof(char**) * data.txt.size());
char** keysPtrArr = reinterpret_cast<char**>(cDataRaw);
cDataRaw += (sizeof(char**) * data.txt.size());
currentData->txtKeys = const_cast<const char**>(keysPtrArr);
currentData->txtValues = const_cast<const char**>(valuesPtrArr);
for (size_t i = 0; i < data.txt.size(); i++) {
keysPtrArr[i] = reinterpret_cast<char*>(cDataRaw);
std::memcpy(keysPtrArr[i], data.txt[i].first.c_str(),
data.txt[i].first.size() + 1);
cDataRaw += (data.txt[i].first.size() + 1);
valuesPtrArr[i] = reinterpret_cast<char*>(cDataRaw);
std::memcpy(valuesPtrArr[i], data.txt[i].second.c_str(),
data.txt[i].second.size() + 1);
cDataRaw += (data.txt[i].second.size() + 1);
}
currentData++;
}
return rootArray;
}
void WPI_FreeServiceData(WPI_ServiceData* serviceData, int32_t length) {
std::free(serviceData);
}
} // extern "C"

View File

@@ -1,177 +0,0 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#include "wpi/ParallelTcpConnector.h"
#include <fmt/format.h>
#include "wpi/Logger.h"
#include "wpi/uv/GetAddrInfo.h"
#include "wpi/uv/Loop.h"
#include "wpi/uv/Tcp.h"
#include "wpi/uv/Timer.h"
#include "wpi/uv/util.h"
using namespace wpi;
ParallelTcpConnector::ParallelTcpConnector(
wpi::uv::Loop& loop, wpi::uv::Timer::Time reconnectRate,
wpi::Logger& logger, std::function<void(wpi::uv::Tcp& tcp)> connected,
const private_init&)
: m_loop{loop},
m_logger{logger},
m_reconnectRate{reconnectRate},
m_connected{std::move(connected)},
m_reconnectTimer{uv::Timer::Create(loop)} {
m_reconnectTimer->timeout.connect([this] {
if (!IsConnected()) {
WPI_DEBUG1(m_logger, "{}", "timed out, reconnecting");
Connect();
}
});
}
ParallelTcpConnector::~ParallelTcpConnector() = default;
void ParallelTcpConnector::Close() {
CancelAll();
m_reconnectTimer->Close();
}
void ParallelTcpConnector::SetServers(
wpi::span<const std::pair<std::string, unsigned int>> servers) {
m_servers.assign(servers.begin(), servers.end());
if (!IsConnected()) {
Connect();
}
}
void ParallelTcpConnector::Disconnected() {
if (m_isConnected) {
m_isConnected = false;
Connect();
}
}
void ParallelTcpConnector::Succeeded(uv::Tcp& tcp) {
if (!m_isConnected) {
m_isConnected = true;
m_reconnectTimer->Stop();
CancelAll(&tcp);
}
}
void ParallelTcpConnector::Connect() {
if (IsConnected()) {
return;
}
CancelAll();
m_reconnectTimer->Start(m_reconnectRate);
WPI_DEBUG3(m_logger, "{}", "starting new connection attempts");
// kick off parallel lookups
for (auto&& server : m_servers) {
auto req = std::make_shared<uv::GetAddrInfoReq>();
m_resolvers.emplace_back(req);
req->resolved.connect(
[this, req = req.get()](const addrinfo& addrinfo) {
if (IsConnected()) {
return;
}
// kick off parallel connection attempts
for (auto ai = &addrinfo; ai; ai = ai->ai_next) {
auto tcp = uv::Tcp::Create(m_loop);
m_attempts.emplace_back(tcp);
auto connreq = std::make_shared<uv::TcpConnectReq>();
connreq->connected.connect(
[this, tcp = tcp.get()] {
if (m_logger.min_level() <= wpi::WPI_LOG_DEBUG4) {
std::string ip;
unsigned int port = 0;
uv::AddrToName(tcp->GetPeer(), &ip, &port);
WPI_DEBUG4(m_logger,
"successful connection ({}) to {} port {}",
static_cast<void*>(tcp), ip, port);
}
if (IsConnected()) {
tcp->Shutdown([tcp] { tcp->Close(); });
return;
}
if (m_connected) {
m_connected(*tcp);
}
},
shared_from_this());
connreq->error = [selfWeak = weak_from_this(),
tcp = tcp.get()](uv::Error err) {
if (auto self = selfWeak.lock()) {
WPI_DEBUG1(self->m_logger, "connect failure ({}): {}",
static_cast<void*>(tcp), err.str());
}
};
if (m_logger.min_level() <= wpi::WPI_LOG_DEBUG4) {
std::string ip;
unsigned int port = 0;
uv::AddrToName(*reinterpret_cast<sockaddr_storage*>(ai->ai_addr),
&ip, &port);
WPI_DEBUG4(
m_logger,
"Info({}) starting connection attempt ({}) to {} port {}",
static_cast<void*>(req), static_cast<void*>(tcp.get()), ip,
port);
}
tcp->Connect(*ai->ai_addr, connreq);
}
},
shared_from_this());
req->error = [req = req.get(), selfWeak = weak_from_this()](uv::Error err) {
if (auto self = selfWeak.lock()) {
WPI_DEBUG1(self->m_logger, "GetAddrInfo({}) failure: {}",
static_cast<void*>(req), err.str());
}
};
WPI_DEBUG4(m_logger, "starting GetAddrInfo({}) for {} port {}",
static_cast<void*>(req.get()), server.first, server.second);
addrinfo hints;
std::memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = IPPROTO_TCP;
hints.ai_flags = AI_NUMERICSERV | AI_ADDRCONFIG;
uv::GetAddrInfo(m_loop, req, server.first, fmt::format("{}", server.second),
&hints);
}
}
void ParallelTcpConnector::CancelAll(wpi::uv::Tcp* except) {
WPI_DEBUG4(m_logger, "{}", "canceling previous attempts");
for (auto&& resolverWeak : m_resolvers) {
if (auto resolver = resolverWeak.lock()) {
WPI_DEBUG4(m_logger, "canceling GetAddrInfo({})",
static_cast<void*>(resolver.get()));
resolver->Cancel();
}
}
m_resolvers.clear();
for (auto&& tcpWeak : m_attempts) {
if (auto tcp = tcpWeak.lock()) {
if (tcp.get() != except) {
WPI_DEBUG4(m_logger, "canceling connection attempt ({})",
static_cast<void*>(tcp.get()));
tcp->Close();
}
}
}
m_attempts.clear();
}

View File

@@ -1,157 +0,0 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#include "wpi/PortForwarder.h"
#include "fmt/format.h"
#include "wpi/DenseMap.h"
#include "wpi/EventLoopRunner.h"
#include "wpi/uv/GetAddrInfo.h"
#include "wpi/uv/Tcp.h"
#include "wpi/uv/Timer.h"
using namespace wpi;
struct PortForwarder::Impl {
public:
EventLoopRunner runner;
DenseMap<unsigned int, std::weak_ptr<uv::Tcp>> servers;
};
PortForwarder::PortForwarder() : m_impl{new Impl} {}
PortForwarder& PortForwarder::GetInstance() {
static PortForwarder instance;
return instance;
}
static void CopyStream(uv::Stream& in, std::weak_ptr<uv::Stream> outWeak) {
in.data.connect([&in, outWeak](uv::Buffer& buf, size_t len) {
uv::Buffer buf2 = buf.Dup();
buf2.len = len;
auto out = outWeak.lock();
if (!out) {
buf2.Deallocate();
in.Close();
return;
}
out->Write({buf2}, [](auto bufs, uv::Error) {
for (auto buf : bufs) {
buf.Deallocate();
}
});
});
}
void PortForwarder::Add(unsigned int port, std::string_view remoteHost,
unsigned int remotePort) {
m_impl->runner.ExecSync([&](uv::Loop& loop) {
auto server = uv::Tcp::Create(loop);
// bind to local port
server->Bind("", port);
// when we get a connection, accept it
server->connection.connect([serverPtr = server.get(),
host = std::string{remoteHost}, remotePort] {
auto& loop = serverPtr->GetLoopRef();
auto client = serverPtr->Accept();
if (!client) {
return;
}
// close on error
client->error.connect(
[clientPtr = client.get()](uv::Error err) { clientPtr->Close(); });
// connected flag
auto connected = std::make_shared<bool>(false);
client->SetData(connected);
auto remote = uv::Tcp::Create(loop);
remote->error.connect(
[remotePtr = remote.get(),
clientWeak = std::weak_ptr<uv::Tcp>(client)](uv::Error err) {
remotePtr->Close();
if (auto client = clientWeak.lock()) {
client->Close();
}
});
// resolve address
uv::GetAddrInfo(
loop,
[clientWeak = std::weak_ptr<uv::Tcp>(client),
remoteWeak = std::weak_ptr<uv::Tcp>(remote)](const addrinfo& addr) {
auto remote = remoteWeak.lock();
if (!remote) {
return;
}
// connect to remote address/port
remote->Connect(*addr.ai_addr, [remotePtr = remote.get(),
remoteWeak, clientWeak] {
auto client = clientWeak.lock();
if (!client) {
remotePtr->Close();
return;
}
*(client->GetData<bool>()) = true;
// close both when either side closes
client->end.connect([clientPtr = client.get(), remoteWeak] {
clientPtr->Close();
if (auto remote = remoteWeak.lock()) {
remote->Close();
}
});
remotePtr->end.connect([remotePtr, clientWeak] {
remotePtr->Close();
if (auto client = clientWeak.lock()) {
client->Close();
}
});
// copy bidirectionally
client->StartRead();
remotePtr->StartRead();
CopyStream(*client, remoteWeak);
CopyStream(*remotePtr, clientWeak);
});
},
host, fmt::to_string(remotePort));
// time out for connection
uv::Timer::SingleShot(loop, uv::Timer::Time{500},
[connectedWeak = std::weak_ptr<bool>(connected),
clientWeak = std::weak_ptr<uv::Tcp>(client),
remoteWeak = std::weak_ptr<uv::Tcp>(remote)] {
if (auto connected = connectedWeak.lock()) {
if (!*connected) {
if (auto client = clientWeak.lock()) {
client->Close();
}
if (auto remote = remoteWeak.lock()) {
remote->Close();
}
}
}
});
});
// start listening for incoming connections
server->Listen();
m_impl->servers[port] = server;
});
}
void PortForwarder::Remove(unsigned int port) {
m_impl->runner.ExecSync([&](uv::Loop& loop) {
if (auto server = m_impl->servers.lookup(port).lock()) {
server->Close();
m_impl->servers.erase(port);
}
});
}

View File

@@ -1,37 +0,0 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#include "wpi/SocketError.h"
#ifdef _WIN32
#include <winsock2.h>
#else
#include <cerrno>
#include <cstring>
#endif
namespace wpi {
int SocketErrno() {
#ifdef _WIN32
return WSAGetLastError();
#else
return errno;
#endif
}
std::string SocketStrerror(int code) {
#ifdef _WIN32
LPSTR errstr = nullptr;
FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, 0,
code, 0, (LPSTR)&errstr, 0, 0);
std::string rv(errstr);
LocalFree(errstr);
return rv;
#else
return std::strerror(code);
#endif
}
} // namespace wpi

View File

@@ -1,210 +0,0 @@
/*
TCPAcceptor.cpp
TCPAcceptor class definition. TCPAcceptor provides methods to passively
establish TCP/IP connections with clients.
------------------------------------------
Copyright (c) 2013 [Vic Hargrave - http://vichargrave.com]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#include "wpi/TCPAcceptor.h"
#include <cstdio>
#include <cstring>
#ifdef _WIN32
#define _WINSOCK_DEPRECATED_NO_WARNINGS
#include <WinSock2.h>
#include <Ws2tcpip.h>
#pragma comment(lib, "Ws2_32.lib")
#else
#include <arpa/inet.h>
#include <fcntl.h>
#include <netinet/in.h>
#include <unistd.h>
#endif
#include "wpi/Logger.h"
#include "wpi/SmallString.h"
#include "wpi/SocketError.h"
using namespace wpi;
TCPAcceptor::TCPAcceptor(int port, std::string_view address, Logger& logger)
: m_lsd(0),
m_port(port),
m_address(address),
m_listening(false),
m_logger(logger) {
m_shutdown = false;
#ifdef _WIN32
WSAData wsaData;
WORD wVersionRequested = MAKEWORD(2, 2);
(void)WSAStartup(wVersionRequested, &wsaData);
#endif
}
TCPAcceptor::~TCPAcceptor() {
if (m_lsd > 0) {
shutdown();
#ifdef _WIN32
closesocket(m_lsd);
#else
close(m_lsd);
#endif
}
#ifdef _WIN32
WSACleanup();
#endif
}
int TCPAcceptor::start() {
if (m_listening) {
return 0;
}
m_lsd = socket(PF_INET, SOCK_STREAM, 0);
if (m_lsd < 0) {
WPI_ERROR(m_logger, "{}", "could not create socket");
return -1;
}
struct sockaddr_in address;
std::memset(&address, 0, sizeof(address));
address.sin_family = PF_INET;
if (m_address.size() > 0) {
#ifdef _WIN32
SmallString<128> addr_copy(m_address);
addr_copy.push_back('\0');
int res = InetPton(PF_INET, addr_copy.data(), &(address.sin_addr));
#else
int res = inet_pton(PF_INET, m_address.c_str(), &(address.sin_addr));
#endif
if (res != 1) {
WPI_ERROR(m_logger, "could not resolve {} address", m_address);
return -1;
}
} else {
address.sin_addr.s_addr = INADDR_ANY;
}
address.sin_port = htons(m_port);
#ifdef _WIN32
int optval = 1;
setsockopt(m_lsd, SOL_SOCKET, SO_EXCLUSIVEADDRUSE,
reinterpret_cast<char*>(&optval), sizeof optval);
#else
int optval = 1;
setsockopt(m_lsd, SOL_SOCKET, SO_REUSEADDR, reinterpret_cast<char*>(&optval),
sizeof optval);
#endif
int result = bind(m_lsd, reinterpret_cast<struct sockaddr*>(&address),
sizeof(address));
if (result != 0) {
WPI_ERROR(m_logger, "bind() to port {} failed: {}", m_port,
SocketStrerror());
return result;
}
result = listen(m_lsd, 5);
if (result != 0) {
WPI_ERROR(m_logger, "listen() on port {} failed: {}", m_port,
SocketStrerror());
return result;
}
m_listening = true;
return result;
}
void TCPAcceptor::shutdown() {
m_shutdown = true;
#ifdef _WIN32
::shutdown(m_lsd, SD_BOTH);
// this is ugly, but the easiest way to do this
// force wakeup of accept() with a non-blocking connect to ourselves
struct sockaddr_in address;
std::memset(&address, 0, sizeof(address));
address.sin_family = PF_INET;
SmallString<128> addr_copy;
if (m_address.size() > 0)
addr_copy = m_address;
else
addr_copy = "127.0.0.1";
addr_copy.push_back('\0');
int size = sizeof(address);
if (WSAStringToAddress(addr_copy.data(), PF_INET, nullptr,
(struct sockaddr*)&address, &size) != 0)
return;
address.sin_port = htons(m_port);
int result = -1, sd = socket(AF_INET, SOCK_STREAM, 0);
if (sd < 0)
return;
// Set socket to non-blocking
u_long mode = 1;
ioctlsocket(sd, FIONBIO, &mode);
// Try to connect
::connect(sd, (struct sockaddr*)&address, sizeof(address));
// Close
::closesocket(sd);
#else
::shutdown(m_lsd, SHUT_RDWR);
int nullfd = ::open("/dev/null", O_RDONLY);
if (nullfd >= 0) {
::dup2(nullfd, m_lsd);
::close(nullfd);
}
#endif
}
std::unique_ptr<NetworkStream> TCPAcceptor::accept() {
if (!m_listening || m_shutdown) {
return nullptr;
}
struct sockaddr_in address;
#ifdef _WIN32
int len = sizeof(address);
#else
socklen_t len = sizeof(address);
#endif
std::memset(&address, 0, sizeof(address));
int sd = ::accept(m_lsd, reinterpret_cast<struct sockaddr*>(&address), &len);
if (sd < 0) {
if (!m_shutdown) {
WPI_ERROR(m_logger, "accept() on port {} failed: {}", m_port,
SocketStrerror());
}
return nullptr;
}
if (m_shutdown) {
#ifdef _WIN32
closesocket(sd);
#else
close(sd);
#endif
return nullptr;
}
return std::unique_ptr<NetworkStream>(new TCPStream(sd, &address));
}

View File

@@ -1,218 +0,0 @@
/*
TCPConnector.h
TCPConnector class definition. TCPConnector provides methods to actively
establish TCP/IP connections with a server.
------------------------------------------
Copyright (c) 2013 [Vic Hargrave - http://vichargrave.com]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License
*/
#include "wpi/TCPConnector.h"
#include <fcntl.h>
#include <cerrno>
#include <cstdio>
#include <cstring>
#ifdef _WIN32
#include <WS2tcpip.h>
#include <WinSock2.h>
#else
#include <arpa/inet.h>
#include <netdb.h>
#include <netinet/in.h>
#include <sys/select.h>
#include <unistd.h>
#endif
#include "wpi/Logger.h"
#include "wpi/SmallString.h"
#include "wpi/SocketError.h"
#include "wpi/TCPStream.h"
using namespace wpi;
static int ResolveHostName(const char* hostname, struct in_addr* addr) {
struct addrinfo hints;
struct addrinfo* res;
hints.ai_flags = 0;
hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = 0;
hints.ai_addrlen = 0;
hints.ai_addr = nullptr;
hints.ai_canonname = nullptr;
hints.ai_next = nullptr;
int result = getaddrinfo(hostname, nullptr, &hints, &res);
if (result == 0) {
std::memcpy(
addr, &(reinterpret_cast<struct sockaddr_in*>(res->ai_addr)->sin_addr),
sizeof(struct in_addr));
freeaddrinfo(res);
}
return result;
}
std::unique_ptr<NetworkStream> TCPConnector::connect(const char* server,
int port, Logger& logger,
int timeout) {
#ifdef _WIN32
struct WSAHelper {
WSAHelper() {
WSAData wsaData;
WORD wVersionRequested = MAKEWORD(2, 2);
WSAStartup(wVersionRequested, &wsaData);
}
~WSAHelper() { WSACleanup(); }
};
static WSAHelper helper;
#endif
struct sockaddr_in address;
std::memset(&address, 0, sizeof(address));
address.sin_family = AF_INET;
if (ResolveHostName(server, &(address.sin_addr)) != 0) {
#ifdef _WIN32
SmallString<128> addr_copy(server);
addr_copy.push_back('\0');
int res = InetPton(PF_INET, addr_copy.data(), &(address.sin_addr));
#else
int res = inet_pton(PF_INET, server, &(address.sin_addr));
#endif
if (res != 1) {
WPI_ERROR(logger, "could not resolve {} address", server);
return nullptr;
}
}
address.sin_port = htons(port);
if (timeout == 0) {
int sd = socket(AF_INET, SOCK_STREAM, 0);
if (sd < 0) {
WPI_ERROR(logger, "{}", "could not create socket");
return nullptr;
}
if (::connect(sd, reinterpret_cast<struct sockaddr*>(&address),
sizeof(address)) != 0) {
WPI_ERROR(logger, "connect() to {} port {} failed: {}", server, port,
SocketStrerror());
#ifdef _WIN32
closesocket(sd);
#else
::close(sd);
#endif
return nullptr;
}
return std::unique_ptr<NetworkStream>(new TCPStream(sd, &address));
}
fd_set sdset;
struct timeval tv;
socklen_t len;
int result = -1, valopt, sd = socket(AF_INET, SOCK_STREAM, 0);
if (sd < 0) {
WPI_ERROR(logger, "{}", "could not create socket");
return nullptr;
}
// Set socket to non-blocking
#ifdef _WIN32
u_long mode = 1;
if (ioctlsocket(sd, FIONBIO, &mode) == SOCKET_ERROR)
WPI_WARNING(logger, "could not set socket to non-blocking: {}",
SocketStrerror());
#else
int arg;
arg = fcntl(sd, F_GETFL, nullptr);
if (arg < 0) {
WPI_WARNING(logger, "could not set socket to non-blocking: {}",
SocketStrerror());
} else {
arg |= O_NONBLOCK;
if (fcntl(sd, F_SETFL, arg) < 0) {
WPI_WARNING(logger, "could not set socket to non-blocking: {}",
SocketStrerror());
}
}
#endif
// Connect with time limit
if ((result = ::connect(sd, reinterpret_cast<struct sockaddr*>(&address),
sizeof(address))) < 0) {
int my_errno = SocketErrno();
#ifdef _WIN32
if (my_errno == WSAEWOULDBLOCK || my_errno == WSAEINPROGRESS) {
#else
if (my_errno == EWOULDBLOCK || my_errno == EINPROGRESS) {
#endif
tv.tv_sec = timeout;
tv.tv_usec = 0;
FD_ZERO(&sdset);
FD_SET(sd, &sdset);
if (select(sd + 1, nullptr, &sdset, nullptr, &tv) > 0) {
len = sizeof(int);
getsockopt(sd, SOL_SOCKET, SO_ERROR, reinterpret_cast<char*>(&valopt),
&len);
if (valopt) {
WPI_ERROR(logger, "select() to {} port {} error {} - {}", server,
port, valopt, SocketStrerror(valopt));
} else {
// connection established
result = 0;
}
} else {
WPI_INFO(logger, "connect() to {} port {} timed out", server, port);
}
} else {
WPI_ERROR(logger, "connect() to {} port {} error {} - {}", server, port,
SocketErrno(), SocketStrerror());
}
}
// Return socket to blocking mode
#ifdef _WIN32
mode = 0;
if (ioctlsocket(sd, FIONBIO, &mode) == SOCKET_ERROR)
WPI_WARNING(logger, "could not set socket to blocking: {}",
SocketStrerror());
#else
arg = fcntl(sd, F_GETFL, nullptr);
if (arg < 0) {
WPI_WARNING(logger, "could not set socket to blocking: {}",
SocketStrerror());
} else {
arg &= (~O_NONBLOCK);
if (fcntl(sd, F_SETFL, arg) < 0) {
WPI_WARNING(logger, "could not set socket to blocking: {}",
SocketStrerror());
}
}
#endif
// Create stream object if connected, close if not.
if (result == -1) {
#ifdef _WIN32
closesocket(sd);
#else
::close(sd);
#endif
return nullptr;
}
return std::unique_ptr<NetworkStream>(new TCPStream(sd, &address));
}

View File

@@ -1,131 +0,0 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#include "wpi/TCPConnector.h" // NOLINT(build/include_order)
#include <atomic>
#include <chrono>
#include <thread>
#include <tuple>
#include "wpi/SmallSet.h"
#include "wpi/condition_variable.h"
#include "wpi/mutex.h"
using namespace wpi;
// MSVC < 1900 doesn't have support for thread_local
#if !defined(_MSC_VER) || _MSC_VER >= 1900
// clang check for availability of thread_local
#if !defined(__has_feature) || __has_feature(cxx_thread_local)
#define HAVE_THREAD_LOCAL
#endif
#endif
std::unique_ptr<NetworkStream> TCPConnector::connect_parallel(
span<const std::pair<const char*, int>> servers, Logger& logger,
int timeout) {
if (servers.empty()) {
return nullptr;
}
// structure to make sure we don't start duplicate workers
struct GlobalState {
wpi::mutex mtx;
#ifdef HAVE_THREAD_LOCAL
SmallSet<std::pair<std::string, int>, 16> active;
#else
SmallSet<std::tuple<std::thread::id, std::string, int>, 16> active;
#endif
};
#ifdef HAVE_THREAD_LOCAL
thread_local auto global = std::make_shared<GlobalState>();
#else
static auto global = std::make_shared<GlobalState>();
auto this_id = std::this_thread::get_id();
#endif
auto local = global; // copy to an automatic variable for lambda capture
// structure shared between threads and this function
struct Result {
wpi::mutex mtx;
wpi::condition_variable cv;
std::unique_ptr<NetworkStream> stream;
std::atomic<unsigned int> count{0};
std::atomic<bool> done{false};
};
auto result = std::make_shared<Result>();
// start worker threads; this is I/O bound so we don't limit to # of procs
Logger* plogger = &logger;
unsigned int num_workers = 0;
for (const auto& server : servers) {
std::pair<std::string, int> server_copy{std::string{server.first},
server.second};
#ifdef HAVE_THREAD_LOCAL
const auto& active_tracker = server_copy;
#else
std::tuple<std::thread::id, std::string, int> active_tracker{
this_id, server_copy.first, server_copy.second};
#endif
// don't start a new worker if we had a previously still-active connection
// attempt to the same server
{
std::scoped_lock lock(local->mtx);
if (local->active.count(active_tracker) > 0) {
continue; // already in set
}
}
++num_workers;
// start the worker
std::thread([=] {
if (!result->done) {
// add to global state
{
std::scoped_lock lock(local->mtx);
local->active.insert(active_tracker);
}
// try to connect
auto stream = connect(server_copy.first.c_str(), server_copy.second,
*plogger, timeout);
// remove from global state
{
std::scoped_lock lock(local->mtx);
local->active.erase(active_tracker);
}
// successful connection
if (stream) {
std::scoped_lock lock(result->mtx);
if (!result->done.exchange(true)) {
result->stream = std::move(stream);
}
}
}
++result->count;
result->cv.notify_all();
}).detach();
}
// wait for a result, timeout, or all finished
std::unique_lock lock(result->mtx);
if (timeout == 0) {
result->cv.wait(
lock, [&] { return result->stream || result->count >= num_workers; });
} else {
auto timeout_time =
std::chrono::steady_clock::now() + std::chrono::seconds(timeout);
result->cv.wait_until(lock, timeout_time, [&] {
return result->stream || result->count >= num_workers;
});
}
// no need to wait for remaining worker threads; shared_ptr will clean up
return std::move(result->stream);
}

View File

@@ -1,230 +0,0 @@
/*
TCPStream.h
TCPStream class definition. TCPStream provides methods to transfer
data between peers over a TCP/IP connection.
------------------------------------------
Copyright (c) 2013 [Vic Hargrave - http://vichargrave.com]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#include "wpi/TCPStream.h"
#include <fcntl.h>
#ifdef _WIN32
#include <winsock2.h>
#include <ws2tcpip.h>
#else
#include <arpa/inet.h>
#include <netinet/tcp.h>
#include <unistd.h>
#endif
#include <cerrno>
using namespace wpi;
TCPStream::TCPStream(int sd, sockaddr_in* address)
: m_sd(sd), m_blocking(true) {
char ip[50];
#ifdef _WIN32
InetNtop(PF_INET, &(address->sin_addr.s_addr), ip, sizeof(ip) - 1);
#else
inet_ntop(PF_INET, reinterpret_cast<in_addr*>(&(address->sin_addr.s_addr)),
ip, sizeof(ip) - 1);
#ifdef SO_NOSIGPIPE
// disable SIGPIPE on Mac OS X
int set = 1;
setsockopt(m_sd, SOL_SOCKET, SO_NOSIGPIPE, reinterpret_cast<char*>(&set),
sizeof set);
#endif
#endif
m_peerIP = ip;
m_peerPort = ntohs(address->sin_port);
}
TCPStream::~TCPStream() {
close();
}
size_t TCPStream::send(const char* buffer, size_t len, Error* err) {
if (m_sd < 0) {
*err = kConnectionClosed;
return 0;
}
#ifdef _WIN32
WSABUF wsaBuf;
wsaBuf.buf = const_cast<char*>(buffer);
wsaBuf.len = (ULONG)len;
DWORD rv;
bool result = true;
while (WSASend(m_sd, &wsaBuf, 1, &rv, 0, nullptr, nullptr) == SOCKET_ERROR) {
if (WSAGetLastError() != WSAEWOULDBLOCK) {
result = false;
break;
}
if (!m_blocking) {
*err = kWouldBlock;
return 0;
}
Sleep(1);
}
if (!result) {
char Buffer[128];
#ifdef _MSC_VER
sprintf_s(Buffer, "Send() failed: WSA error=%d\n", WSAGetLastError());
#else
std::snprintf(Buffer, sizeof(Buffer), "Send() failed: WSA error=%d\n",
WSAGetLastError());
#endif
OutputDebugStringA(Buffer);
*err = kConnectionReset;
return 0;
}
#else
#ifdef MSG_NOSIGNAL
// disable SIGPIPE on Linux
ssize_t rv = ::send(m_sd, buffer, len, MSG_NOSIGNAL);
#else
ssize_t rv = ::send(m_sd, buffer, len, 0);
#endif
if (rv < 0) {
if (!m_blocking && (errno == EAGAIN || errno == EWOULDBLOCK)) {
*err = kWouldBlock;
} else {
*err = kConnectionReset;
}
return 0;
}
#endif
return static_cast<size_t>(rv);
}
size_t TCPStream::receive(char* buffer, size_t len, Error* err, int timeout) {
if (m_sd < 0) {
*err = kConnectionClosed;
return 0;
}
#ifdef _WIN32
int rv;
#else
ssize_t rv;
#endif
if (timeout <= 0) {
#ifdef _WIN32
rv = recv(m_sd, buffer, len, 0);
#else
rv = read(m_sd, buffer, len);
#endif
} else if (WaitForReadEvent(timeout)) {
#ifdef _WIN32
rv = recv(m_sd, buffer, len, 0);
#else
rv = read(m_sd, buffer, len);
#endif
} else {
*err = kConnectionTimedOut;
return 0;
}
if (rv < 0) {
#ifdef _WIN32
if (!m_blocking && WSAGetLastError() == WSAEWOULDBLOCK) {
#else
if (!m_blocking && (errno == EAGAIN || errno == EWOULDBLOCK)) {
#endif
*err = kWouldBlock;
} else {
*err = kConnectionReset;
}
return 0;
}
return static_cast<size_t>(rv);
}
void TCPStream::close() {
if (m_sd >= 0) {
#ifdef _WIN32
::shutdown(m_sd, SD_BOTH);
closesocket(m_sd);
#else
::shutdown(m_sd, SHUT_RDWR);
::close(m_sd);
#endif
}
m_sd = -1;
}
std::string_view TCPStream::getPeerIP() const {
return m_peerIP;
}
int TCPStream::getPeerPort() const {
return m_peerPort;
}
void TCPStream::setNoDelay() {
if (m_sd < 0) {
return;
}
int optval = 1;
setsockopt(m_sd, IPPROTO_TCP, TCP_NODELAY, reinterpret_cast<char*>(&optval),
sizeof optval);
}
bool TCPStream::setBlocking(bool enabled) {
if (m_sd < 0) {
return true; // silently accept
}
#ifdef _WIN32
u_long mode = enabled ? 0 : 1;
if (ioctlsocket(m_sd, FIONBIO, &mode) == SOCKET_ERROR) {
return false;
}
#else
int flags = fcntl(m_sd, F_GETFL, nullptr);
if (flags < 0) {
return false;
}
if (enabled) {
flags &= ~O_NONBLOCK;
} else {
flags |= O_NONBLOCK;
}
if (fcntl(m_sd, F_SETFL, flags) < 0) {
return false;
}
#endif
return true;
}
int TCPStream::getNativeHandle() const {
return m_sd;
}
bool TCPStream::WaitForReadEvent(int timeout) {
fd_set sdset;
struct timeval tv;
tv.tv_sec = timeout;
tv.tv_usec = 0;
FD_ZERO(&sdset);
FD_SET(m_sd, &sdset);
if (select(m_sd + 1, &sdset, nullptr, nullptr, &tv) > 0) {
return true;
}
return false;
}

View File

@@ -1,248 +0,0 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#include "wpi/UDPClient.h"
#ifdef _WIN32
#include <WinSock2.h>
#include <Ws2tcpip.h>
#pragma comment(lib, "Ws2_32.lib")
#else
#include <arpa/inet.h>
#include <fcntl.h>
#include <netinet/in.h>
#include <unistd.h>
#endif
#include "wpi/Logger.h"
#include "wpi/SmallString.h"
#include "wpi/SocketError.h"
using namespace wpi;
UDPClient::UDPClient(Logger& logger) : UDPClient("", logger) {}
UDPClient::UDPClient(std::string_view address, Logger& logger)
: m_lsd(0), m_port(0), m_address(address), m_logger(logger) {}
UDPClient::UDPClient(UDPClient&& other)
: m_lsd(other.m_lsd),
m_port(other.m_port),
m_address(std::move(other.m_address)),
m_logger(other.m_logger) {
other.m_lsd = 0;
other.m_port = 0;
}
UDPClient::~UDPClient() {
if (m_lsd > 0) {
shutdown();
}
}
UDPClient& UDPClient::operator=(UDPClient&& other) {
if (this == &other) {
return *this;
}
shutdown();
m_logger = other.m_logger;
m_lsd = other.m_lsd;
m_address = std::move(other.m_address);
m_port = other.m_port;
other.m_lsd = 0;
other.m_port = 0;
return *this;
}
int UDPClient::start() {
return start(0);
}
int UDPClient::start(int port) {
if (m_lsd > 0) {
return 0;
}
#ifdef _WIN32
WSAData wsaData;
WORD wVersionRequested = MAKEWORD(2, 2);
WSAStartup(wVersionRequested, &wsaData);
#endif
m_lsd = socket(AF_INET, SOCK_DGRAM, 0);
if (m_lsd < 0) {
WPI_ERROR(m_logger, "{}", "could not create socket");
return -1;
}
struct sockaddr_in addr;
std::memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
if (m_address.size() > 0) {
#ifdef _WIN32
SmallString<128> addr_copy(m_address);
addr_copy.push_back('\0');
int res = InetPton(PF_INET, addr_copy.data(), &(addr.sin_addr));
#else
int res = inet_pton(PF_INET, m_address.c_str(), &(addr.sin_addr));
#endif
if (res != 1) {
WPI_ERROR(m_logger, "could not resolve {} address", m_address);
return -1;
}
} else {
addr.sin_addr.s_addr = INADDR_ANY;
}
addr.sin_port = htons(port);
if (port != 0) {
#ifdef _WIN32
int optval = 1;
setsockopt(m_lsd, SOL_SOCKET, SO_EXCLUSIVEADDRUSE,
reinterpret_cast<char*>(&optval), sizeof optval);
#else
int optval = 1;
setsockopt(m_lsd, SOL_SOCKET, SO_REUSEADDR,
reinterpret_cast<char*>(&optval), sizeof optval);
#endif
}
int result = bind(m_lsd, reinterpret_cast<sockaddr*>(&addr), sizeof(addr));
if (result != 0) {
WPI_ERROR(m_logger, "bind() failed: {}", SocketStrerror());
return result;
}
m_port = port;
return 0;
}
void UDPClient::shutdown() {
if (m_lsd > 0) {
#ifdef _WIN32
::shutdown(m_lsd, SD_BOTH);
closesocket(m_lsd);
WSACleanup();
#else
::shutdown(m_lsd, SHUT_RDWR);
close(m_lsd);
#endif
m_lsd = 0;
m_port = 0;
}
}
int UDPClient::send(span<const uint8_t> data, std::string_view server,
int port) {
// server must be a resolvable IP address
struct sockaddr_in addr;
std::memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
SmallString<128> remoteAddr{server};
if (remoteAddr.empty()) {
WPI_ERROR(m_logger, "{}", "server must be passed");
return -1;
}
#ifdef _WIN32
int res = InetPton(AF_INET, remoteAddr.c_str(), &(addr.sin_addr));
#else
int res = inet_pton(AF_INET, remoteAddr.c_str(), &(addr.sin_addr));
#endif
if (res != 1) {
WPI_ERROR(m_logger, "could not resolve {} address", server);
return -1;
}
addr.sin_port = htons(port);
// sendto should not block
int result =
sendto(m_lsd, reinterpret_cast<const char*>(data.data()), data.size(), 0,
reinterpret_cast<sockaddr*>(&addr), sizeof(addr));
return result;
}
int UDPClient::send(std::string_view data, std::string_view server, int port) {
// server must be a resolvable IP address
struct sockaddr_in addr;
std::memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
SmallString<128> remoteAddr{server};
if (remoteAddr.empty()) {
WPI_ERROR(m_logger, "{}", "server must be passed");
return -1;
}
#ifdef _WIN32
int res = InetPton(AF_INET, remoteAddr.c_str(), &(addr.sin_addr));
#else
int res = inet_pton(AF_INET, remoteAddr.c_str(), &(addr.sin_addr));
#endif
if (res != 1) {
WPI_ERROR(m_logger, "could not resolve {} address", server);
return -1;
}
addr.sin_port = htons(port);
// sendto should not block
int result = sendto(m_lsd, data.data(), data.size(), 0,
reinterpret_cast<sockaddr*>(&addr), sizeof(addr));
return result;
}
int UDPClient::receive(uint8_t* data_received, int receive_len) {
if (m_port == 0) {
return -1; // return if not receiving
}
return recv(m_lsd, reinterpret_cast<char*>(data_received), receive_len, 0);
}
int UDPClient::receive(uint8_t* data_received, int receive_len,
SmallVectorImpl<char>* addr_received,
int* port_received) {
if (m_port == 0) {
return -1; // return if not receiving
}
struct sockaddr_in remote;
socklen_t remote_len = sizeof(remote);
std::memset(&remote, 0, sizeof(remote));
int result =
recvfrom(m_lsd, reinterpret_cast<char*>(data_received), receive_len, 0,
reinterpret_cast<sockaddr*>(&remote), &remote_len);
char ip[50];
#ifdef _WIN32
InetNtop(PF_INET, &(remote.sin_addr.s_addr), ip, sizeof(ip) - 1);
#else
inet_ntop(PF_INET, reinterpret_cast<in_addr*>(&(remote.sin_addr.s_addr)), ip,
sizeof(ip) - 1);
#endif
ip[49] = '\0';
int addr_len = std::strlen(ip);
addr_received->clear();
addr_received->append(&ip[0], &ip[addr_len]);
*port_received = ntohs(remote.sin_port);
return result;
}
int UDPClient::set_timeout(double timeout) {
if (timeout < 0) {
return -1;
}
struct timeval tv;
tv.tv_sec = timeout; // truncating will give seconds
timeout -= tv.tv_sec; // remove seconds portion
tv.tv_usec = timeout * 1000000; // fractions of a second to us
int ret = setsockopt(m_lsd, SOL_SOCKET, SO_RCVTIMEO,
reinterpret_cast<char*>(&tv), sizeof(tv));
if (ret < 0) {
WPI_ERROR(m_logger, "{}", "set timeout failed");
}
return ret;
}

View File

@@ -1,656 +0,0 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#include "wpi/WebSocket.h"
#include <random>
#include "fmt/format.h"
#include "wpi/Base64.h"
#include "wpi/HttpParser.h"
#include "wpi/SmallString.h"
#include "wpi/SmallVector.h"
#include "wpi/StringExtras.h"
#include "wpi/raw_uv_ostream.h"
#include "wpi/sha1.h"
#include "wpi/uv/Stream.h"
using namespace wpi;
namespace {
class WebSocketWriteReq : public uv::WriteReq {
public:
explicit WebSocketWriteReq(
std::function<void(span<uv::Buffer>, uv::Error)> callback)
: m_callback{std::move(callback)} {
finish.connect([this](uv::Error err) {
span<uv::Buffer> bufs{m_bufs};
for (auto&& buf : bufs.subspan(0, m_startUser)) {
buf.Deallocate();
}
m_callback(bufs.subspan(m_startUser), err);
});
}
std::function<void(span<uv::Buffer>, uv::Error)> m_callback;
SmallVector<uv::Buffer, 4> m_bufs;
size_t m_startUser;
};
} // namespace
class WebSocket::ClientHandshakeData {
public:
ClientHandshakeData() {
// key is a random nonce
static std::random_device rd;
static std::default_random_engine gen{rd()};
std::uniform_int_distribution<unsigned int> dist(0, 255);
char nonce[16]; // the nonce sent to the server
for (char& v : nonce) {
v = static_cast<char>(dist(gen));
}
raw_svector_ostream os(key);
Base64Encode(os, {nonce, 16});
}
~ClientHandshakeData() {
if (auto t = timer.lock()) {
t->Stop();
t->Close();
}
}
SmallString<64> key; // the key sent to the server
SmallVector<std::string, 2> protocols; // valid protocols
HttpParser parser{HttpParser::kResponse}; // server response parser
bool hasUpgrade = false;
bool hasConnection = false;
bool hasAccept = false;
bool hasProtocol = false;
std::weak_ptr<uv::Timer> timer;
};
static std::string_view AcceptHash(std::string_view key,
SmallVectorImpl<char>& buf) {
SHA1 hash;
hash.Update(key);
hash.Update("258EAFA5-E914-47DA-95CA-C5AB0DC85B11");
SmallString<64> hashBuf;
return Base64Encode(hash.RawFinal(hashBuf), buf);
}
WebSocket::WebSocket(uv::Stream& stream, bool server, const private_init&)
: m_stream{stream}, m_server{server} {
// Connect closed and error signals to ourselves
m_stream.closed.connect([this]() { SetClosed(1006, "handle closed"); });
m_stream.error.connect([this](uv::Error err) {
Terminate(1006, fmt::format("stream error: {}", err.name()));
});
// Start reading
m_stream.StopRead(); // we may have been reading
m_stream.StartRead();
m_stream.data.connect(
[this](uv::Buffer& buf, size_t size) { HandleIncoming(buf, size); });
m_stream.end.connect(
[this]() { Terminate(1006, "remote end closed connection"); });
}
WebSocket::~WebSocket() = default;
std::shared_ptr<WebSocket> WebSocket::CreateClient(
uv::Stream& stream, std::string_view uri, std::string_view host,
span<const std::string_view> protocols, const ClientOptions& options) {
auto ws = std::make_shared<WebSocket>(stream, false, private_init{});
stream.SetData(ws);
ws->StartClient(uri, host, protocols, options);
return ws;
}
std::shared_ptr<WebSocket> WebSocket::CreateServer(uv::Stream& stream,
std::string_view key,
std::string_view version,
std::string_view protocol) {
auto ws = std::make_shared<WebSocket>(stream, true, private_init{});
stream.SetData(ws);
ws->StartServer(key, version, protocol);
return ws;
}
void WebSocket::Close(uint16_t code, std::string_view reason) {
SendClose(code, reason);
if (m_state != FAILED && m_state != CLOSED) {
m_state = CLOSING;
}
}
void WebSocket::Fail(uint16_t code, std::string_view reason) {
if (m_state == FAILED || m_state == CLOSED) {
return;
}
SendClose(code, reason);
SetClosed(code, reason, true);
Shutdown();
}
void WebSocket::Terminate(uint16_t code, std::string_view reason) {
if (m_state == FAILED || m_state == CLOSED) {
return;
}
SetClosed(code, reason);
Shutdown();
}
void WebSocket::StartClient(std::string_view uri, std::string_view host,
span<const std::string_view> protocols,
const ClientOptions& options) {
// Create client handshake data
m_clientHandshake = std::make_unique<ClientHandshakeData>();
// Build client request
SmallVector<uv::Buffer, 4> bufs;
raw_uv_ostream os{bufs, 4096};
os << "GET " << uri << " HTTP/1.1\r\n";
os << "Host: " << host << "\r\n";
os << "Upgrade: websocket\r\n";
os << "Connection: Upgrade\r\n";
os << "Sec-WebSocket-Key: " << m_clientHandshake->key << "\r\n";
os << "Sec-WebSocket-Version: 13\r\n";
// protocols (if provided)
if (!protocols.empty()) {
os << "Sec-WebSocket-Protocol: ";
bool first = true;
for (auto protocol : protocols) {
if (!first) {
os << ", ";
} else {
first = false;
}
os << protocol;
// also save for later checking against server response
m_clientHandshake->protocols.emplace_back(protocol);
}
os << "\r\n";
}
// other headers
for (auto&& header : options.extraHeaders) {
os << header.first << ": " << header.second << "\r\n";
}
// finish headers
os << "\r\n";
// Send client request
m_stream.Write(bufs, [](auto bufs, uv::Error) {
for (auto& buf : bufs) {
buf.Deallocate();
}
});
// Set up client response handling
m_clientHandshake->parser.status.connect([this](std::string_view status) {
unsigned int code = m_clientHandshake->parser.GetStatusCode();
if (code != 101) {
Terminate(code, status);
}
});
m_clientHandshake->parser.header.connect(
[this](std::string_view name, std::string_view value) {
value = trim(value);
if (equals_lower(name, "upgrade")) {
if (!equals_lower(value, "websocket")) {
return Terminate(1002, "invalid upgrade response value");
}
m_clientHandshake->hasUpgrade = true;
} else if (equals_lower(name, "connection")) {
if (!equals_lower(value, "upgrade")) {
return Terminate(1002, "invalid connection response value");
}
m_clientHandshake->hasConnection = true;
} else if (equals_lower(name, "sec-websocket-accept")) {
// Check against expected response
SmallString<64> acceptBuf;
if (!equals(value, AcceptHash(m_clientHandshake->key, acceptBuf))) {
return Terminate(1002, "invalid accept key");
}
m_clientHandshake->hasAccept = true;
} else if (equals_lower(name, "sec-websocket-extensions")) {
// No extensions are supported
if (!value.empty()) {
return Terminate(1010, "unsupported extension");
}
} else if (equals_lower(name, "sec-websocket-protocol")) {
// Make sure it was one of the provided protocols
bool match = false;
for (auto&& protocol : m_clientHandshake->protocols) {
if (equals_lower(value, protocol)) {
match = true;
break;
}
}
if (!match) {
return Terminate(1003, "unsupported protocol");
}
m_clientHandshake->hasProtocol = true;
m_protocol = value;
}
});
m_clientHandshake->parser.headersComplete.connect([this](bool) {
if (!m_clientHandshake->hasUpgrade || !m_clientHandshake->hasConnection ||
!m_clientHandshake->hasAccept ||
(!m_clientHandshake->hasProtocol &&
!m_clientHandshake->protocols.empty())) {
return Terminate(1002, "invalid response");
}
if (m_state == CONNECTING) {
m_state = OPEN;
open(m_protocol);
}
});
// Start handshake timer if a timeout was specified
if (options.handshakeTimeout != (uv::Timer::Time::max)()) {
auto timer = uv::Timer::Create(m_stream.GetLoopRef());
timer->timeout.connect(
[this]() { Terminate(1006, "connection timed out"); });
timer->Start(options.handshakeTimeout);
m_clientHandshake->timer = timer;
}
}
void WebSocket::StartServer(std::string_view key, std::string_view version,
std::string_view protocol) {
m_protocol = protocol;
// Build server response
SmallVector<uv::Buffer, 4> bufs;
raw_uv_ostream os{bufs, 4096};
// Handle unsupported version
if (version != "13") {
os << "HTTP/1.1 426 Upgrade Required\r\n";
os << "Upgrade: WebSocket\r\n";
os << "Sec-WebSocket-Version: 13\r\n\r\n";
m_stream.Write(bufs, [this](auto bufs, uv::Error) {
for (auto& buf : bufs) {
buf.Deallocate();
}
// XXX: Should we support sending a new handshake on the same connection?
// XXX: "this->" is required by GCC 5.5 (bug)
this->Terminate(1003, "unsupported protocol version");
});
return;
}
os << "HTTP/1.1 101 Switching Protocols\r\n";
os << "Upgrade: websocket\r\n";
os << "Connection: Upgrade\r\n";
// accept hash
SmallString<64> acceptBuf;
os << "Sec-WebSocket-Accept: " << AcceptHash(key, acceptBuf) << "\r\n";
if (!protocol.empty()) {
os << "Sec-WebSocket-Protocol: " << protocol << "\r\n";
}
// end headers
os << "\r\n";
// Send server response
m_stream.Write(bufs, [this](auto bufs, uv::Error) {
for (auto& buf : bufs) {
buf.Deallocate();
}
if (m_state == CONNECTING) {
m_state = OPEN;
open(m_protocol);
}
});
}
void WebSocket::SendClose(uint16_t code, std::string_view reason) {
SmallVector<uv::Buffer, 4> bufs;
if (code != 1005) {
raw_uv_ostream os{bufs, 4096};
const uint8_t codeMsb[] = {static_cast<uint8_t>((code >> 8) & 0xff),
static_cast<uint8_t>(code & 0xff)};
os << span{codeMsb};
os << reason;
}
Send(kFlagFin | kOpClose, bufs, [](auto bufs, uv::Error) {
for (auto&& buf : bufs) {
buf.Deallocate();
}
});
}
void WebSocket::SetClosed(uint16_t code, std::string_view reason, bool failed) {
if (m_state == FAILED || m_state == CLOSED) {
return;
}
m_state = failed ? FAILED : CLOSED;
closed(code, reason);
}
void WebSocket::Shutdown() {
m_stream.Shutdown([this] { m_stream.Close(); });
}
void WebSocket::HandleIncoming(uv::Buffer& buf, size_t size) {
// ignore incoming data if we're failed or closed
if (m_state == FAILED || m_state == CLOSED) {
return;
}
std::string_view data{buf.base, size};
// Handle connecting state (mainly on client)
if (m_state == CONNECTING) {
if (m_clientHandshake) {
data = m_clientHandshake->parser.Execute(data);
// check for parser failure
if (m_clientHandshake->parser.HasError()) {
return Terminate(1003, "invalid response");
}
if (m_state != OPEN) {
return; // not done with handshake yet
}
// we're done with the handshake, so release its memory
m_clientHandshake.reset();
// fall through to process additional data after handshake
} else {
return Terminate(1003, "got data on server before response");
}
}
// Message processing
while (!data.empty()) {
if (m_frameSize == UINT64_MAX) {
// Need at least two bytes to determine header length
if (m_header.size() < 2u) {
size_t toCopy = (std::min)(2u - m_header.size(), data.size());
m_header.append(data.data(), data.data() + toCopy);
data.remove_prefix(toCopy);
if (m_header.size() < 2u) {
return; // need more data
}
// Validate RSV bits are zero
if ((m_header[0] & 0x70) != 0) {
return Fail(1002, "nonzero RSV");
}
}
// Once we have first two bytes, we can calculate the header size
if (m_headerSize == 0) {
m_headerSize = 2;
uint8_t len = m_header[1] & kLenMask;
if (len == 126) {
m_headerSize += 2;
} else if (len == 127) {
m_headerSize += 8;
}
bool masking = (m_header[1] & kFlagMasking) != 0;
if (masking) {
m_headerSize += 4; // masking key
}
// On server side, incoming messages MUST be masked
// On client side, incoming messages MUST NOT be masked
if (m_server && !masking) {
return Fail(1002, "client data not masked");
}
if (!m_server && masking) {
return Fail(1002, "server data masked");
}
}
// Need to complete header to calculate message size
if (m_header.size() < m_headerSize) {
size_t toCopy = (std::min)(m_headerSize - m_header.size(), data.size());
m_header.append(data.data(), data.data() + toCopy);
data.remove_prefix(toCopy);
if (m_header.size() < m_headerSize) {
return; // need more data
}
}
if (m_header.size() >= m_headerSize) {
// get payload length
uint8_t len = m_header[1] & kLenMask;
if (len == 126) {
m_frameSize = (static_cast<uint16_t>(m_header[2]) << 8) |
static_cast<uint16_t>(m_header[3]);
} else if (len == 127) {
m_frameSize = (static_cast<uint64_t>(m_header[2]) << 56) |
(static_cast<uint64_t>(m_header[3]) << 48) |
(static_cast<uint64_t>(m_header[4]) << 40) |
(static_cast<uint64_t>(m_header[5]) << 32) |
(static_cast<uint64_t>(m_header[6]) << 24) |
(static_cast<uint64_t>(m_header[7]) << 16) |
(static_cast<uint64_t>(m_header[8]) << 8) |
static_cast<uint64_t>(m_header[9]);
} else {
m_frameSize = len;
}
// limit maximum size
if ((m_payload.size() + m_frameSize) > m_maxMessageSize) {
return Fail(1009, "message too large");
}
}
}
if (m_frameSize != UINT64_MAX) {
size_t need = m_frameStart + m_frameSize - m_payload.size();
size_t toCopy = (std::min)(need, data.size());
m_payload.append(data.data(), data.data() + toCopy);
data.remove_prefix(toCopy);
need -= toCopy;
if (need == 0) {
// We have a complete frame
// If the message had masking, unmask it
if ((m_header[1] & kFlagMasking) != 0) {
uint8_t key[4] = {
m_header[m_headerSize - 4], m_header[m_headerSize - 3],
m_header[m_headerSize - 2], m_header[m_headerSize - 1]};
int n = 0;
for (uint8_t& ch : span{m_payload}.subspan(m_frameStart)) {
ch ^= key[n++];
if (n >= 4) {
n = 0;
}
}
}
// Handle message
bool fin = (m_header[0] & kFlagFin) != 0;
uint8_t opcode = m_header[0] & kOpMask;
switch (opcode) {
case kOpCont:
switch (m_fragmentOpcode) {
case kOpText:
if (!m_combineFragments || fin) {
text(std::string_view{reinterpret_cast<char*>(
m_payload.data()),
m_payload.size()},
fin);
}
break;
case kOpBinary:
if (!m_combineFragments || fin) {
binary(m_payload, fin);
}
break;
default:
// no preceding message?
return Fail(1002, "invalid continuation message");
}
if (fin) {
m_fragmentOpcode = 0;
}
break;
case kOpText:
if (m_fragmentOpcode != 0) {
return Fail(1002, "incomplete fragment");
}
if (!m_combineFragments || fin) {
text(std::string_view{reinterpret_cast<char*>(m_payload.data()),
m_payload.size()},
fin);
}
if (!fin) {
m_fragmentOpcode = opcode;
}
break;
case kOpBinary:
if (m_fragmentOpcode != 0) {
return Fail(1002, "incomplete fragment");
}
if (!m_combineFragments || fin) {
binary(m_payload, fin);
}
if (!fin) {
m_fragmentOpcode = opcode;
}
break;
case kOpClose: {
uint16_t code;
std::string_view reason;
if (!fin) {
code = 1002;
reason = "cannot fragment control frames";
} else if (m_payload.size() < 2) {
code = 1005;
} else {
code = (static_cast<uint16_t>(m_payload[0]) << 8) |
static_cast<uint16_t>(m_payload[1]);
reason = drop_front(
{reinterpret_cast<char*>(m_payload.data()), m_payload.size()},
2);
}
// Echo the close if we didn't previously send it
if (m_state != CLOSING) {
SendClose(code, reason);
}
SetClosed(code, reason);
// If we're the server, shutdown the connection.
if (m_server) {
Shutdown();
}
break;
}
case kOpPing:
if (!fin) {
return Fail(1002, "cannot fragment control frames");
}
ping(m_payload);
break;
case kOpPong:
if (!fin) {
return Fail(1002, "cannot fragment control frames");
}
pong(m_payload);
break;
default:
return Fail(1002, "invalid message opcode");
}
// Prepare for next message
m_header.clear();
m_headerSize = 0;
if (!m_combineFragments || fin) {
m_payload.clear();
}
m_frameStart = m_payload.size();
m_frameSize = UINT64_MAX;
}
}
}
}
void WebSocket::Send(
uint8_t opcode, span<const uv::Buffer> data,
std::function<void(span<uv::Buffer>, uv::Error)> callback) {
// If we're not open, emit an error and don't send the data
if (m_state != OPEN) {
int err;
if (m_state == CONNECTING) {
err = UV_EAGAIN;
} else {
err = UV_ESHUTDOWN;
}
SmallVector<uv::Buffer, 4> bufs{data.begin(), data.end()};
callback(bufs, uv::Error{err});
return;
}
auto req = std::make_shared<WebSocketWriteReq>(std::move(callback));
raw_uv_ostream os{req->m_bufs, 4096};
// opcode (includes FIN bit)
os << static_cast<unsigned char>(opcode);
// payload length
uint64_t size = 0;
for (auto&& buf : data) {
size += buf.len;
}
if (size < 126) {
os << static_cast<unsigned char>((m_server ? 0x00 : kFlagMasking) | size);
} else if (size <= 0xffff) {
os << static_cast<unsigned char>((m_server ? 0x00 : kFlagMasking) | 126);
const uint8_t sizeMsb[] = {static_cast<uint8_t>((size >> 8) & 0xff),
static_cast<uint8_t>(size & 0xff)};
os << span{sizeMsb};
} else {
os << static_cast<unsigned char>((m_server ? 0x00 : kFlagMasking) | 127);
const uint8_t sizeMsb[] = {static_cast<uint8_t>((size >> 56) & 0xff),
static_cast<uint8_t>((size >> 48) & 0xff),
static_cast<uint8_t>((size >> 40) & 0xff),
static_cast<uint8_t>((size >> 32) & 0xff),
static_cast<uint8_t>((size >> 24) & 0xff),
static_cast<uint8_t>((size >> 16) & 0xff),
static_cast<uint8_t>((size >> 8) & 0xff),
static_cast<uint8_t>(size & 0xff)};
os << span{sizeMsb};
}
// clients need to mask the input data
if (!m_server) {
// generate masking key
static std::random_device rd;
static std::default_random_engine gen{rd()};
std::uniform_int_distribution<unsigned int> dist(0, 255);
uint8_t key[4];
for (uint8_t& v : key) {
v = dist(gen);
}
os << span<const uint8_t>{key, 4};
// copy and mask data
int n = 0;
for (auto&& buf : data) {
for (auto&& ch : buf.data()) {
os << static_cast<unsigned char>(static_cast<uint8_t>(ch) ^ key[n++]);
if (n >= 4) {
n = 0;
}
}
}
req->m_startUser = req->m_bufs.size();
req->m_bufs.append(data.begin(), data.end());
// don't send the user bufs as we copied their data
m_stream.Write(span{req->m_bufs}.subspan(0, req->m_startUser), req);
} else {
// servers can just send the buffers directly without masking
req->m_startUser = req->m_bufs.size();
req->m_bufs.append(data.begin(), data.end());
m_stream.Write(req->m_bufs, req);
}
}

View File

@@ -1,171 +0,0 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#include "wpi/WebSocketServer.h"
#include <utility>
#include "wpi/StringExtras.h"
#include "wpi/fmt/raw_ostream.h"
#include "wpi/raw_uv_ostream.h"
#include "wpi/uv/Buffer.h"
#include "wpi/uv/Stream.h"
using namespace wpi;
WebSocketServerHelper::WebSocketServerHelper(HttpParser& req) {
req.header.connect([this](std::string_view name, std::string_view value) {
if (equals_lower(name, "host")) {
m_gotHost = true;
} else if (equals_lower(name, "upgrade")) {
if (equals_lower(value, "websocket")) {
m_websocket = true;
}
} else if (equals_lower(name, "sec-websocket-key")) {
m_key = value;
} else if (equals_lower(name, "sec-websocket-version")) {
m_version = value;
} else if (equals_lower(name, "sec-websocket-protocol")) {
// Protocols are comma delimited, repeated headers add to list
SmallVector<std::string_view, 2> protocols;
split(value, protocols, ",", -1, false);
for (auto protocol : protocols) {
protocol = trim(protocol);
if (!protocol.empty()) {
m_protocols.emplace_back(protocol);
}
}
}
});
req.headersComplete.connect([&req, this](bool) {
if (req.IsUpgrade() && IsUpgrade()) {
upgrade();
}
});
}
std::pair<bool, std::string_view> WebSocketServerHelper::MatchProtocol(
span<const std::string_view> protocols) {
if (protocols.empty() && m_protocols.empty()) {
return {true, {}};
}
for (auto protocol : protocols) {
for (auto&& clientProto : m_protocols) {
if (protocol == clientProto) {
return {true, protocol};
}
}
}
return {false, {}};
}
WebSocketServer::WebSocketServer(uv::Stream& stream,
span<const std::string_view> protocols,
ServerOptions options, const private_init&)
: m_stream{stream},
m_helper{m_req},
m_protocols{protocols.begin(), protocols.end()},
m_options{std::move(options)} {
// Header handling
m_req.header.connect([this](std::string_view name, std::string_view value) {
if (equals_lower(name, "host")) {
if (m_options.checkHost) {
if (!m_options.checkHost(value)) {
Abort(401, "Unrecognized Host");
}
}
}
});
m_req.url.connect([this](std::string_view name) {
if (m_options.checkUrl) {
if (!m_options.checkUrl(name)) {
Abort(404, "Not Found");
}
}
});
m_req.headersComplete.connect([this](bool) {
// We only accept websocket connections
if (!m_helper.IsUpgrade() || !m_req.IsUpgrade()) {
Abort(426, "Upgrade Required");
}
});
// Handle upgrade event
m_helper.upgrade.connect([this] {
if (m_aborted) {
return;
}
// Negotiate sub-protocol
SmallVector<std::string_view, 2> protocols{m_protocols.begin(),
m_protocols.end()};
std::string_view protocol = m_helper.MatchProtocol(protocols).second;
// Disconnect our header reader
m_dataConn.disconnect();
// Accepting the stream may destroy this (as it replaces the stream user
// data), so grab a shared pointer first.
auto self = shared_from_this();
// Accept the upgrade
auto ws = m_helper.Accept(m_stream, protocol);
// Connect the websocket open event to our connected event.
ws->open.connect_extended(
[self, s = ws.get()](auto conn, std::string_view) {
self->connected(self->m_req.GetUrl(), *s);
conn.disconnect(); // one-shot
});
});
// Set up stream
stream.StartRead();
m_dataConn =
stream.data.connect_connection([this](uv::Buffer& buf, size_t size) {
if (m_aborted) {
return;
}
m_req.Execute(std::string_view{buf.base, size});
if (m_req.HasError()) {
Abort(400, "Bad Request");
}
});
m_errorConn =
stream.error.connect_connection([this](uv::Error) { m_stream.Close(); });
m_endConn = stream.end.connect_connection([this] { m_stream.Close(); });
}
std::shared_ptr<WebSocketServer> WebSocketServer::Create(
uv::Stream& stream, span<const std::string_view> protocols,
const ServerOptions& options) {
auto server = std::make_shared<WebSocketServer>(stream, protocols, options,
private_init{});
stream.SetData(server);
return server;
}
void WebSocketServer::Abort(uint16_t code, std::string_view reason) {
if (m_aborted) {
return;
}
m_aborted = true;
// Build response
SmallVector<uv::Buffer, 4> bufs;
raw_uv_ostream os{bufs, 1024};
// Handle unsupported version
fmt::print(os, "HTTP/1.1 {} {}\r\n", code, reason);
if (code == 426) {
os << "Upgrade: WebSocket\r\n";
}
os << "\r\n";
m_stream.Write(bufs, [this](auto bufs, uv::Error) {
for (auto& buf : bufs) {
buf.Deallocate();
}
m_stream.Shutdown([this] { m_stream.Close(); });
});
}

View File

@@ -1,57 +0,0 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#include "wpi/hostname.h"
#include <cstdlib>
#include <string>
#include <string_view>
#include "uv.h"
#include "wpi/SmallVector.h"
namespace wpi {
std::string GetHostname() {
std::string rv;
char name[256];
size_t size = sizeof(name);
int err = uv_os_gethostname(name, &size);
if (err == 0) {
rv.assign(name, size);
} else if (err == UV_ENOBUFS) {
char* name2 = static_cast<char*>(std::malloc(size));
err = uv_os_gethostname(name2, &size);
if (err == 0) {
rv.assign(name2, size);
}
std::free(name2);
}
return rv;
}
std::string_view GetHostname(SmallVectorImpl<char>& name) {
// Use a tmp array to not require the SmallVector to be too large.
char tmpName[256];
size_t size = sizeof(tmpName);
name.clear();
int err = uv_os_gethostname(tmpName, &size);
if (err == 0) {
name.append(tmpName, tmpName + size);
} else if (err == UV_ENOBUFS) {
name.resize(size);
err = uv_os_gethostname(name.data(), &size);
if (err != 0) {
size = 0;
}
}
return {name.data(), size};
}
} // namespace wpi

File diff suppressed because it is too large Load Diff

View File

@@ -4,14 +4,8 @@
#include <jni.h>
#include "../MulticastHandleManager.h"
#include "edu_wpi_first_util_WPIUtilJNI.h"
#include "wpi/DenseMap.h"
#include "wpi/MulticastServiceAnnouncer.h"
#include "wpi/MulticastServiceResolver.h"
#include "wpi/PortForwarder.h"
#include "wpi/Synchronization.h"
#include "wpi/UidVector.h"
#include "wpi/jni_util.h"
#include "wpi/timestamp.h"
@@ -21,8 +15,6 @@ static bool mockTimeEnabled = false;
static uint64_t mockNow = 0;
static JException interruptedEx;
static JClass serviceDataCls;
static JGlobal<jobjectArray> serviceDataEmptyArray;
extern "C" {
@@ -37,17 +29,6 @@ JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved) {
return JNI_ERR;
}
serviceDataCls = JClass{env, "edu/wpi/first/util/ServiceData"};
if (!serviceDataCls) {
return JNI_ERR;
}
serviceDataEmptyArray = JGlobal<jobjectArray>{
env, env->NewObjectArray(0, serviceDataCls, nullptr)};
if (serviceDataEmptyArray == nullptr) {
return JNI_ERR;
}
return JNI_VERSION_1_6;
}
@@ -57,8 +38,6 @@ JNIEXPORT void JNICALL JNI_OnUnload(JavaVM* vm, void* reserved) {
return;
}
serviceDataEmptyArray.free(env);
serviceDataCls.free(env);
interruptedEx.free(env);
}
@@ -128,32 +107,6 @@ Java_edu_wpi_first_util_WPIUtilJNI_getSystemTime
return wpi::GetSystemTime();
}
/*
* Class: edu_wpi_first_util_WPIUtilJNI
* Method: addPortForwarder
* Signature: (ILjava/lang/String;I)V
*/
JNIEXPORT void JNICALL
Java_edu_wpi_first_util_WPIUtilJNI_addPortForwarder
(JNIEnv* env, jclass, jint port, jstring remoteHost, jint remotePort)
{
wpi::PortForwarder::GetInstance().Add(static_cast<unsigned int>(port),
JStringRef{env, remoteHost}.str(),
static_cast<unsigned int>(remotePort));
}
/*
* Class: edu_wpi_first_util_WPIUtilJNI
* Method: removePortForwarder
* Signature: (I)V
*/
JNIEXPORT void JNICALL
Java_edu_wpi_first_util_WPIUtilJNI_removePortForwarder
(JNIEnv* env, jclass, jint port)
{
wpi::PortForwarder::GetInstance().Remove(port);
}
/*
* Class: edu_wpi_first_util_WPIUtilJNI
* Method: createEvent
@@ -319,257 +272,4 @@ Java_edu_wpi_first_util_WPIUtilJNI_waitForObjectsTimeout
return MakeJIntArray(env, signaled);
}
/*
* Class: edu_wpi_first_util_WPIUtilJNI
* Method: createMulticastServiceAnnouncer
* Signature: (Ljava/lang/String;Ljava/lang/String;I[Ljava/lang/Object;[Ljava/lang/Object;)I
*/
JNIEXPORT jint JNICALL
Java_edu_wpi_first_util_WPIUtilJNI_createMulticastServiceAnnouncer
(JNIEnv* env, jclass, jstring serviceName, jstring serviceType, jint port,
jobjectArray keys, jobjectArray values)
{
auto& manager = wpi::GetMulticastManager();
std::scoped_lock lock{manager.mutex};
JStringRef serviceNameRef{env, serviceName};
JStringRef serviceTypeRef{env, serviceType};
size_t keysLen = env->GetArrayLength(keys);
wpi::SmallVector<std::pair<std::string, std::string>, 8> txtVec;
txtVec.reserve(keysLen);
for (size_t i = 0; i < keysLen; i++) {
JLocal<jstring> key{
env, static_cast<jstring>(env->GetObjectArrayElement(keys, i))};
JLocal<jstring> value{
env, static_cast<jstring>(env->GetObjectArrayElement(values, i))};
txtVec.emplace_back(std::pair<std::string, std::string>{
JStringRef{env, key}.str(), JStringRef{env, value}.str()});
}
auto announcer = std::make_unique<wpi::MulticastServiceAnnouncer>(
serviceNameRef.str(), serviceTypeRef.str(), port, txtVec);
size_t index = manager.handleIds.emplace_back(1);
manager.announcers[index] = std::move(announcer);
return static_cast<jint>(index);
}
/*
* Class: edu_wpi_first_util_WPIUtilJNI
* Method: freeMulticastServiceAnnouncer
* Signature: (I)V
*/
JNIEXPORT void JNICALL
Java_edu_wpi_first_util_WPIUtilJNI_freeMulticastServiceAnnouncer
(JNIEnv* env, jclass, jint handle)
{
auto& manager = wpi::GetMulticastManager();
std::scoped_lock lock{manager.mutex};
manager.announcers[handle] = nullptr;
manager.handleIds.erase(handle);
}
/*
* Class: edu_wpi_first_util_WPIUtilJNI
* Method: startMulticastServiceAnnouncer
* Signature: (I)V
*/
JNIEXPORT void JNICALL
Java_edu_wpi_first_util_WPIUtilJNI_startMulticastServiceAnnouncer
(JNIEnv* env, jclass, jint handle)
{
auto& manager = wpi::GetMulticastManager();
std::scoped_lock lock{manager.mutex};
auto& announcer = manager.announcers[handle];
announcer->Start();
}
/*
* Class: edu_wpi_first_util_WPIUtilJNI
* Method: stopMulticastServiceAnnouncer
* Signature: (I)V
*/
JNIEXPORT void JNICALL
Java_edu_wpi_first_util_WPIUtilJNI_stopMulticastServiceAnnouncer
(JNIEnv* env, jclass, jint handle)
{
auto& manager = wpi::GetMulticastManager();
std::scoped_lock lock{manager.mutex};
auto& announcer = manager.announcers[handle];
announcer->Stop();
}
/*
* Class: edu_wpi_first_util_WPIUtilJNI
* Method: getMulticastServiceAnnouncerHasImplementation
* Signature: (I)Z
*/
JNIEXPORT jboolean JNICALL
Java_edu_wpi_first_util_WPIUtilJNI_getMulticastServiceAnnouncerHasImplementation
(JNIEnv* env, jclass, jint handle)
{
auto& manager = wpi::GetMulticastManager();
std::scoped_lock lock{manager.mutex};
auto& announcer = manager.announcers[handle];
return announcer->HasImplementation();
}
/*
* Class: edu_wpi_first_util_WPIUtilJNI
* Method: createMulticastServiceResolver
* Signature: (Ljava/lang/String;)I
*/
JNIEXPORT jint JNICALL
Java_edu_wpi_first_util_WPIUtilJNI_createMulticastServiceResolver
(JNIEnv* env, jclass, jstring serviceType)
{
auto& manager = wpi::GetMulticastManager();
std::scoped_lock lock{manager.mutex};
JStringRef serviceTypeRef{env, serviceType};
auto resolver =
std::make_unique<wpi::MulticastServiceResolver>(serviceTypeRef.str());
size_t index = manager.handleIds.emplace_back(2);
manager.resolvers[index] = std::move(resolver);
return static_cast<jint>(index);
}
/*
* Class: edu_wpi_first_util_WPIUtilJNI
* Method: freeMulticastServiceResolver
* Signature: (I)V
*/
JNIEXPORT void JNICALL
Java_edu_wpi_first_util_WPIUtilJNI_freeMulticastServiceResolver
(JNIEnv* env, jclass, jint handle)
{
auto& manager = wpi::GetMulticastManager();
std::scoped_lock lock{manager.mutex};
manager.resolvers[handle] = nullptr;
manager.handleIds.erase(handle);
}
/*
* Class: edu_wpi_first_util_WPIUtilJNI
* Method: startMulticastServiceResolver
* Signature: (I)V
*/
JNIEXPORT void JNICALL
Java_edu_wpi_first_util_WPIUtilJNI_startMulticastServiceResolver
(JNIEnv* env, jclass, jint handle)
{
auto& manager = wpi::GetMulticastManager();
std::scoped_lock lock{manager.mutex};
auto& resolver = manager.resolvers[handle];
resolver->Start();
}
/*
* Class: edu_wpi_first_util_WPIUtilJNI
* Method: stopMulticastServiceResolver
* Signature: (I)V
*/
JNIEXPORT void JNICALL
Java_edu_wpi_first_util_WPIUtilJNI_stopMulticastServiceResolver
(JNIEnv* env, jclass, jint handle)
{
auto& manager = wpi::GetMulticastManager();
std::scoped_lock lock{manager.mutex};
auto& resolver = manager.resolvers[handle];
resolver->Stop();
}
/*
* Class: edu_wpi_first_util_WPIUtilJNI
* Method: getMulticastServiceResolverHasImplementation
* Signature: (I)Z
*/
JNIEXPORT jboolean JNICALL
Java_edu_wpi_first_util_WPIUtilJNI_getMulticastServiceResolverHasImplementation
(JNIEnv* env, jclass, jint handle)
{
auto& manager = wpi::GetMulticastManager();
std::scoped_lock lock{manager.mutex};
auto& resolver = manager.resolvers[handle];
return resolver->HasImplementation();
}
/*
* Class: edu_wpi_first_util_WPIUtilJNI
* Method: getMulticastServiceResolverEventHandle
* Signature: (I)I
*/
JNIEXPORT jint JNICALL
Java_edu_wpi_first_util_WPIUtilJNI_getMulticastServiceResolverEventHandle
(JNIEnv* env, jclass, jint handle)
{
auto& manager = wpi::GetMulticastManager();
std::scoped_lock lock{manager.mutex};
auto& resolver = manager.resolvers[handle];
return resolver->GetEventHandle();
}
/*
* Class: edu_wpi_first_util_WPIUtilJNI
* Method: getMulticastServiceResolverData
* Signature: (I)[Ljava/lang/Object;
*/
JNIEXPORT jobjectArray JNICALL
Java_edu_wpi_first_util_WPIUtilJNI_getMulticastServiceResolverData
(JNIEnv* env, jclass, jint handle)
{
static jmethodID constructor =
env->GetMethodID(serviceDataCls, "<init>",
"(JILjava/lang/String;Ljava/lang/String;[Ljava/lang/"
"String;[Ljava/lang/String;)V");
auto& manager = wpi::GetMulticastManager();
std::vector<wpi::MulticastServiceResolver::ServiceData> allData;
{
std::scoped_lock lock{manager.mutex};
auto& resolver = manager.resolvers[handle];
allData = resolver->GetData();
}
if (allData.empty()) {
return serviceDataEmptyArray;
}
JLocal<jobjectArray> returnData{
env, env->NewObjectArray(allData.size(), serviceDataCls, nullptr)};
for (auto&& data : allData) {
JLocal<jstring> serviceName{env, MakeJString(env, data.serviceName)};
JLocal<jstring> hostName{env, MakeJString(env, data.hostName)};
wpi::SmallVector<std::string_view, 8> keysRef;
wpi::SmallVector<std::string_view, 8> valuesRef;
size_t index = 0;
for (auto&& txt : data.txt) {
keysRef.emplace_back(txt.first);
valuesRef.emplace_back(txt.second);
}
JLocal<jobjectArray> keys{env, MakeJStringArray(env, keysRef)};
JLocal<jobjectArray> values{env, MakeJStringArray(env, valuesRef)};
JLocal<jobject> dataItem{
env, env->NewObject(serviceDataCls, constructor,
static_cast<jlong>(data.ipv4Address),
static_cast<jint>(data.port), serviceName.obj(),
hostName.obj(), keys.obj(), values.obj())};
env->SetObjectArrayElement(returnData, index, dataItem);
index++;
}
return returnData;
}
} // extern "C"

View File

@@ -1,33 +0,0 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#include "wpi/raw_socket_istream.h"
#include "wpi/NetworkStream.h"
using namespace wpi;
void raw_socket_istream::read_impl(void* data, size_t len) {
char* cdata = static_cast<char*>(data);
size_t pos = 0;
while (pos < len) {
NetworkStream::Error err;
size_t count = m_stream.receive(&cdata[pos], len - pos, &err, m_timeout);
if (count == 0) {
error_detected();
break;
}
pos += count;
}
set_read_count(pos);
}
void raw_socket_istream::close() {
m_stream.close();
}
size_t raw_socket_istream::in_avail() const {
return 0;
}

View File

@@ -1,42 +0,0 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#include "wpi/raw_socket_ostream.h"
#include "wpi/NetworkStream.h"
using namespace wpi;
raw_socket_ostream::~raw_socket_ostream() {
flush();
if (m_shouldClose) {
close();
}
}
void raw_socket_ostream::write_impl(const char* data, size_t len) {
size_t pos = 0;
while (pos < len) {
NetworkStream::Error err;
size_t count = m_stream.send(&data[pos], len - pos, &err);
if (count == 0) {
error_detected();
return;
}
pos += count;
}
}
uint64_t raw_socket_ostream::current_pos() const {
return 0;
}
void raw_socket_ostream::close() {
if (!m_shouldClose) {
return;
}
flush();
m_stream.close();
}

View File

@@ -1,39 +0,0 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#include "wpi/raw_uv_ostream.h"
#include <cstring>
using namespace wpi;
void raw_uv_ostream::write_impl(const char* data, size_t len) {
while (len > 0) {
// allocate additional buffers as required
if (m_left == 0) {
m_bufs.emplace_back(m_alloc());
// we want bufs() to always be valid, so set len=0 and keep track of the
// amount of space remaining separately
m_left = m_bufs.back().len;
m_bufs.back().len = 0;
assert(m_left != 0);
}
size_t amt = (std::min)(m_left, len);
auto& buf = m_bufs.back();
std::memcpy(buf.base + buf.len, data, amt);
data += amt;
len -= amt;
buf.len += amt;
m_left -= amt;
}
}
uint64_t raw_uv_ostream::current_pos() const {
uint64_t size = 0;
for (auto&& buf : m_bufs) {
size += buf.len;
}
return size;
}

View File

@@ -1,33 +0,0 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#include "wpi/uv/Async.h"
#include "wpi/uv/Loop.h"
namespace wpi::uv {
Async<>::~Async() noexcept {
if (auto loop = m_loop.lock()) {
Close();
} else {
ForceClosed();
}
}
std::shared_ptr<Async<>> Async<>::Create(const std::shared_ptr<Loop>& loop) {
auto h = std::make_shared<Async>(loop, private_init{});
int err = uv_async_init(loop->GetRaw(), h->GetRaw(), [](uv_async_t* handle) {
Async& h = *static_cast<Async*>(handle->data);
h.wakeup();
});
if (err < 0) {
loop->ReportError(err);
return nullptr;
}
h->Keep();
return h;
}
} // namespace wpi::uv

View File

@@ -1,29 +0,0 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#include "wpi/uv/Check.h"
#include "wpi/uv/Loop.h"
namespace wpi::uv {
std::shared_ptr<Check> Check::Create(Loop& loop) {
auto h = std::make_shared<Check>(private_init{});
int err = uv_check_init(loop.GetRaw(), h->GetRaw());
if (err < 0) {
loop.ReportError(err);
return nullptr;
}
h->Keep();
return h;
}
void Check::Start() {
Invoke(&uv_check_start, GetRaw(), [](uv_check_t* handle) {
Check& h = *static_cast<Check*>(handle->data);
h.check();
});
}
} // namespace wpi::uv

View File

@@ -1,63 +0,0 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#include "wpi/uv/FsEvent.h"
#include <cstdlib>
#include "wpi/SmallString.h"
#include "wpi/uv/Loop.h"
namespace wpi::uv {
std::shared_ptr<FsEvent> FsEvent::Create(Loop& loop) {
auto h = std::make_shared<FsEvent>(private_init{});
int err = uv_fs_event_init(loop.GetRaw(), h->GetRaw());
if (err < 0) {
loop.ReportError(err);
return nullptr;
}
h->Keep();
return h;
}
void FsEvent::Start(std::string_view path, unsigned int flags) {
SmallString<128> pathBuf{path};
Invoke(
&uv_fs_event_start, GetRaw(),
[](uv_fs_event_t* handle, const char* filename, int events, int status) {
FsEvent& h = *static_cast<FsEvent*>(handle->data);
if (status < 0) {
h.ReportError(status);
} else {
h.fsEvent(filename, events);
}
},
pathBuf.c_str(), flags);
}
std::string FsEvent::GetPath() {
// Per the libuv docs, GetPath() always gives us a null-terminated string.
// common case should be small
char buf[128];
size_t size = 128;
int r = uv_fs_event_getpath(GetRaw(), buf, &size);
if (r == 0) {
return buf;
} else if (r == UV_ENOBUFS) {
// need to allocate a big enough buffer
char* buf2 = static_cast<char*>(std::malloc(size));
r = uv_fs_event_getpath(GetRaw(), buf2, &size);
if (r == 0) {
std::string out{buf2};
std::free(buf2);
return out;
}
std::free(buf2);
}
ReportError(r);
return std::string{};
}
} // namespace wpi::uv

View File

@@ -1,51 +0,0 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#include "wpi/uv/GetAddrInfo.h"
#include "wpi/SmallString.h"
#include "wpi/uv/Loop.h"
#include "wpi/uv/util.h"
namespace wpi::uv {
GetAddrInfoReq::GetAddrInfoReq() {
error = [this](Error err) { GetLoop().error(err); };
}
void GetAddrInfo(Loop& loop, const std::shared_ptr<GetAddrInfoReq>& req,
std::string_view node, std::string_view service,
const addrinfo* hints) {
SmallString<128> nodeStr{node};
SmallString<128> serviceStr{service};
int err = uv_getaddrinfo(
loop.GetRaw(), req->GetRaw(),
[](uv_getaddrinfo_t* req, int status, addrinfo* res) {
auto& h = *static_cast<GetAddrInfoReq*>(req->data);
if (status < 0) {
h.ReportError(status);
} else {
h.resolved(*res);
}
uv_freeaddrinfo(res);
h.Release(); // this is always a one-shot
},
node.empty() ? nullptr : nodeStr.c_str(),
service.empty() ? nullptr : serviceStr.c_str(), hints);
if (err < 0) {
loop.ReportError(err);
} else {
req->Keep();
}
}
void GetAddrInfo(Loop& loop, std::function<void(const addrinfo&)> callback,
std::string_view node, std::string_view service,
const addrinfo* hints) {
auto req = std::make_shared<GetAddrInfoReq>();
req->resolved.connect(std::move(callback));
GetAddrInfo(loop, req, node, service, hints);
}
} // namespace wpi::uv

View File

@@ -1,94 +0,0 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#include "wpi/uv/GetNameInfo.h"
#include "wpi/uv/Loop.h"
#include "wpi/uv/util.h"
namespace wpi::uv {
GetNameInfoReq::GetNameInfoReq() {
error = [this](Error err) { GetLoop().error(err); };
}
void GetNameInfo(Loop& loop, const std::shared_ptr<GetNameInfoReq>& req,
const sockaddr& addr, int flags) {
int err = uv_getnameinfo(
loop.GetRaw(), req->GetRaw(),
[](uv_getnameinfo_t* req, int status, const char* hostname,
const char* service) {
auto& h = *static_cast<GetNameInfoReq*>(req->data);
if (status < 0) {
h.ReportError(status);
} else {
h.resolved(hostname, service);
}
h.Release(); // this is always a one-shot
},
&addr, flags);
if (err < 0) {
loop.ReportError(err);
} else {
req->Keep();
}
}
void GetNameInfo(Loop& loop,
std::function<void(const char*, const char*)> callback,
const sockaddr& addr, int flags) {
auto req = std::make_shared<GetNameInfoReq>();
req->resolved.connect(std::move(callback));
GetNameInfo(loop, req, addr, flags);
}
void GetNameInfo4(Loop& loop, const std::shared_ptr<GetNameInfoReq>& req,
std::string_view ip, unsigned int port, int flags) {
sockaddr_in addr;
int err = NameToAddr(ip, port, &addr);
if (err < 0) {
loop.ReportError(err);
} else {
GetNameInfo(loop, req, reinterpret_cast<const sockaddr&>(addr), flags);
}
}
void GetNameInfo4(Loop& loop,
std::function<void(const char*, const char*)> callback,
std::string_view ip, unsigned int port, int flags) {
sockaddr_in addr;
int err = NameToAddr(ip, port, &addr);
if (err < 0) {
loop.ReportError(err);
} else {
GetNameInfo(loop, std::move(callback),
reinterpret_cast<const sockaddr&>(addr), flags);
}
}
void GetNameInfo6(Loop& loop, const std::shared_ptr<GetNameInfoReq>& req,
std::string_view ip, unsigned int port, int flags) {
sockaddr_in6 addr;
int err = NameToAddr(ip, port, &addr);
if (err < 0) {
loop.ReportError(err);
} else {
GetNameInfo(loop, req, reinterpret_cast<const sockaddr&>(addr), flags);
}
}
void GetNameInfo6(Loop& loop,
std::function<void(const char*, const char*)> callback,
std::string_view ip, unsigned int port, int flags) {
sockaddr_in6 addr;
int err = NameToAddr(ip, port, &addr);
if (err < 0) {
loop.ReportError(err);
} else {
GetNameInfo(loop, std::move(callback),
reinterpret_cast<const sockaddr&>(addr), flags);
}
}
} // namespace wpi::uv

View File

@@ -1,35 +0,0 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#include "wpi/uv/Handle.h"
using namespace wpi::uv;
Handle::~Handle() noexcept {
if (!m_closed && m_uv_handle->type != UV_UNKNOWN_HANDLE) {
uv_close(m_uv_handle, [](uv_handle_t* uv_handle) { std::free(uv_handle); });
} else {
std::free(m_uv_handle);
}
}
void Handle::Close() noexcept {
if (!IsClosing()) {
uv_close(m_uv_handle, [](uv_handle_t* handle) {
Handle& h = *static_cast<Handle*>(handle->data);
h.closed();
h.Release(); // free ourselves
});
m_closed = true;
}
}
void Handle::AllocBuf(uv_handle_t* handle, size_t size, uv_buf_t* buf) {
auto& h = *static_cast<Handle*>(handle->data);
*buf = h.m_allocBuf(size);
}
void Handle::DefaultFreeBuf(Buffer& buf) {
buf.Deallocate();
}

View File

@@ -1,29 +0,0 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#include "wpi/uv/Idle.h"
#include "wpi/uv/Loop.h"
namespace wpi::uv {
std::shared_ptr<Idle> Idle::Create(Loop& loop) {
auto h = std::make_shared<Idle>(private_init{});
int err = uv_idle_init(loop.GetRaw(), h->GetRaw());
if (err < 0) {
loop.ReportError(err);
return nullptr;
}
h->Keep();
return h;
}
void Idle::Start() {
Invoke(&uv_idle_start, GetRaw(), [](uv_idle_t* handle) {
Idle& h = *static_cast<Idle*>(handle->data);
h.idle();
});
}
} // namespace wpi::uv

View File

@@ -1,70 +0,0 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#include "wpi/uv/Loop.h"
using namespace wpi::uv;
Loop::Loop(const private_init&) noexcept {
#ifndef _WIN32
// Ignore SIGPIPE (see https://github.com/joyent/libuv/issues/1254)
static bool once = []() {
signal(SIGPIPE, SIG_IGN);
return true;
}();
(void)once;
#endif
}
Loop::~Loop() noexcept {
if (m_loop) {
m_loop->data = nullptr;
Close();
}
}
std::shared_ptr<Loop> Loop::Create() {
auto loop = std::make_shared<Loop>(private_init{});
if (uv_loop_init(&loop->m_loopStruct) < 0) {
return nullptr;
}
loop->m_loop = &loop->m_loopStruct;
loop->m_loop->data = loop.get();
return loop;
}
std::shared_ptr<Loop> Loop::GetDefault() {
static std::shared_ptr<Loop> loop = std::make_shared<Loop>(private_init{});
loop->m_loop = uv_default_loop();
if (!loop->m_loop) {
return nullptr;
}
loop->m_loop->data = loop.get();
return loop;
}
void Loop::Close() {
int err = uv_loop_close(m_loop);
if (err < 0) {
ReportError(err);
}
}
void Loop::Walk(function_ref<void(Handle&)> callback) {
uv_walk(
m_loop,
[](uv_handle_t* handle, void* func) {
auto& h = *static_cast<Handle*>(handle->data);
auto& f = *static_cast<function_ref<void(Handle&)>*>(func);
f(h);
},
&callback);
}
void Loop::Fork() {
int err = uv_loop_fork(m_loop);
if (err < 0) {
ReportError(err);
}
}

View File

@@ -1,59 +0,0 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#include "wpi/uv/util.h" // NOLINT(build/include_order)
#include <cstring>
#include "wpi/SmallString.h"
namespace wpi::uv {
int NameToAddr(std::string_view ip, unsigned int port, sockaddr_in* addr) {
if (ip.empty()) {
std::memset(addr, 0, sizeof(sockaddr_in));
addr->sin_family = PF_INET;
addr->sin_addr.s_addr = INADDR_ANY;
addr->sin_port = htons(port);
return 0;
} else {
SmallString<128> ipBuf{ip};
return uv_ip4_addr(ipBuf.c_str(), port, addr);
}
}
int NameToAddr(std::string_view ip, unsigned int port, sockaddr_in6* addr) {
if (ip.empty()) {
std::memset(addr, 0, sizeof(sockaddr_in6));
addr->sin6_family = PF_INET6;
addr->sin6_addr = in6addr_any;
addr->sin6_port = htons(port);
return 0;
} else {
SmallString<128> ipBuf{ip};
return uv_ip6_addr(ipBuf.c_str(), port, addr);
}
}
int NameToAddr(std::string_view ip, in_addr* addr) {
if (ip.empty()) {
addr->s_addr = INADDR_ANY;
return 0;
} else {
SmallString<128> ipBuf{ip};
return uv_inet_pton(AF_INET, ipBuf.c_str(), addr);
}
}
int NameToAddr(std::string_view ip, in6_addr* addr) {
if (ip.empty()) {
*addr = in6addr_any;
return 0;
} else {
SmallString<128> ipBuf{ip};
return uv_inet_pton(AF_INET6, ipBuf.c_str(), addr);
}
}
} // namespace wpi::uv

View File

@@ -1,30 +0,0 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#include "wpi/uv/NetworkStream.h"
namespace wpi::uv {
ConnectReq::ConnectReq() {
error = [this](Error err) { GetStream().error(err); };
}
void NetworkStream::Listen(int backlog) {
Invoke(&uv_listen, GetRawStream(), backlog,
[](uv_stream_t* handle, int status) {
auto& h = *static_cast<NetworkStream*>(handle->data);
if (status < 0) {
h.ReportError(status);
} else {
h.connection();
}
});
}
void NetworkStream::Listen(std::function<void()> callback, int backlog) {
connection.connect(std::move(callback));
Listen(backlog);
}
} // namespace wpi::uv

View File

@@ -1,138 +0,0 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#include "wpi/uv/Pipe.h"
#include <cstdlib>
#include "wpi/SmallString.h"
namespace wpi::uv {
std::shared_ptr<Pipe> Pipe::Create(Loop& loop, bool ipc) {
auto h = std::make_shared<Pipe>(private_init{});
int err = uv_pipe_init(loop.GetRaw(), h->GetRaw(), ipc ? 1 : 0);
if (err < 0) {
loop.ReportError(err);
return nullptr;
}
h->Keep();
return h;
}
void Pipe::Reuse(std::function<void()> callback, bool ipc) {
if (IsClosing()) {
return;
}
if (!m_reuseData) {
m_reuseData = std::make_unique<ReuseData>();
}
m_reuseData->callback = std::move(callback);
m_reuseData->ipc = ipc;
uv_close(GetRawHandle(), [](uv_handle_t* handle) {
Pipe& h = *static_cast<Pipe*>(handle->data);
if (!h.m_reuseData) {
return;
}
auto data = std::move(h.m_reuseData);
auto err =
uv_pipe_init(h.GetLoopRef().GetRaw(), h.GetRaw(), data->ipc ? 1 : 0);
if (err < 0) {
h.ReportError(err);
return;
}
data->callback();
});
}
std::shared_ptr<Pipe> Pipe::Accept() {
auto client = Create(GetLoopRef(), GetRaw()->ipc);
if (!client) {
return nullptr;
}
if (!Accept(client)) {
client->Release();
return nullptr;
}
return client;
}
Pipe* Pipe::DoAccept() {
return Accept().get();
}
void Pipe::Bind(std::string_view name) {
SmallString<128> nameBuf{name};
Invoke(&uv_pipe_bind, GetRaw(), nameBuf.c_str());
}
void Pipe::Connect(std::string_view name,
const std::shared_ptr<PipeConnectReq>& req) {
SmallString<128> nameBuf{name};
uv_pipe_connect(req->GetRaw(), GetRaw(), nameBuf.c_str(),
[](uv_connect_t* req, int status) {
auto& h = *static_cast<PipeConnectReq*>(req->data);
if (status < 0) {
h.ReportError(status);
} else {
h.connected();
}
h.Release(); // this is always a one-shot
});
req->Keep();
}
void Pipe::Connect(std::string_view name, std::function<void()> callback) {
auto req = std::make_shared<PipeConnectReq>();
req->connected.connect(std::move(callback));
Connect(name, req);
}
std::string Pipe::GetSock() {
// Per libuv docs, the returned buffer is NOT null terminated.
// common case should be small
char buf[128];
size_t size = 128;
int r = uv_pipe_getsockname(GetRaw(), buf, &size);
if (r == 0) {
return std::string{buf, size};
} else if (r == UV_ENOBUFS) {
// need to allocate a big enough buffer
char* buf2 = static_cast<char*>(std::malloc(size));
r = uv_pipe_getsockname(GetRaw(), buf2, &size);
if (r == 0) {
std::string out{buf2, size};
std::free(buf2);
return out;
}
std::free(buf2);
}
ReportError(r);
return std::string{};
}
std::string Pipe::GetPeer() {
// Per libuv docs, the returned buffer is NOT null terminated.
// common case should be small
char buf[128];
size_t size = 128;
int r = uv_pipe_getpeername(GetRaw(), buf, &size);
if (r == 0) {
return std::string{buf, size};
} else if (r == UV_ENOBUFS) {
// need to allocate a big enough buffer
char* buf2 = static_cast<char*>(std::malloc(size));
r = uv_pipe_getpeername(GetRaw(), buf2, &size);
if (r == 0) {
std::string out{buf2, size};
std::free(buf2);
return out;
}
std::free(buf2);
}
ReportError(r);
return std::string{};
}
} // namespace wpi::uv

View File

@@ -1,95 +0,0 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#include "wpi/uv/Poll.h"
#include "wpi/uv/Loop.h"
namespace wpi::uv {
std::shared_ptr<Poll> Poll::Create(Loop& loop, int fd) {
auto h = std::make_shared<Poll>(private_init{});
int err = uv_poll_init(loop.GetRaw(), h->GetRaw(), fd);
if (err < 0) {
loop.ReportError(err);
return nullptr;
}
h->Keep();
return h;
}
std::shared_ptr<Poll> Poll::CreateSocket(Loop& loop, uv_os_sock_t sock) {
auto h = std::make_shared<Poll>(private_init{});
int err = uv_poll_init_socket(loop.GetRaw(), h->GetRaw(), sock);
if (err < 0) {
loop.ReportError(err);
return nullptr;
}
h->Keep();
return h;
}
void Poll::Reuse(int fd, std::function<void()> callback) {
if (IsClosing()) {
return;
}
if (!m_reuseData) {
m_reuseData = std::make_unique<ReuseData>();
}
m_reuseData->callback = std::move(callback);
m_reuseData->isSocket = false;
m_reuseData->fd = fd;
uv_close(GetRawHandle(), [](uv_handle_t* handle) {
Poll& h = *static_cast<Poll*>(handle->data);
if (!h.m_reuseData || h.m_reuseData->isSocket) {
return; // just in case
}
auto data = std::move(h.m_reuseData);
int err = uv_poll_init(h.GetLoopRef().GetRaw(), h.GetRaw(), data->fd);
if (err < 0) {
h.ReportError(err);
return;
}
data->callback();
});
}
void Poll::ReuseSocket(uv_os_sock_t sock, std::function<void()> callback) {
if (IsClosing()) {
return;
}
if (!m_reuseData) {
m_reuseData = std::make_unique<ReuseData>();
}
m_reuseData->callback = std::move(callback);
m_reuseData->isSocket = true;
m_reuseData->sock = sock;
uv_close(GetRawHandle(), [](uv_handle_t* handle) {
Poll& h = *static_cast<Poll*>(handle->data);
if (!h.m_reuseData || !h.m_reuseData->isSocket) {
return; // just in case
}
auto data = std::move(h.m_reuseData);
int err = uv_poll_init(h.GetLoopRef().GetRaw(), h.GetRaw(), data->sock);
if (err < 0) {
h.ReportError(err);
return;
}
data->callback();
});
}
void Poll::Start(int events) {
Invoke(&uv_poll_start, GetRaw(), events,
[](uv_poll_t* handle, int status, int events) {
Poll& h = *static_cast<Poll*>(handle->data);
if (status < 0) {
h.ReportError(status);
} else {
h.pollEvent(events);
}
});
}
} // namespace wpi::uv

View File

@@ -1,29 +0,0 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#include "wpi/uv/Prepare.h"
#include "wpi/uv/Loop.h"
namespace wpi::uv {
std::shared_ptr<Prepare> Prepare::Create(Loop& loop) {
auto h = std::make_shared<Prepare>(private_init{});
int err = uv_prepare_init(loop.GetRaw(), h->GetRaw());
if (err < 0) {
loop.ReportError(err);
return nullptr;
}
h->Keep();
return h;
}
void Prepare::Start() {
Invoke(&uv_prepare_start, GetRaw(), [](uv_prepare_t* handle) {
Prepare& h = *static_cast<Prepare*>(handle->data);
h.prepare();
});
}
} // namespace wpi::uv

View File

@@ -1,133 +0,0 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#include "wpi/uv/Process.h"
#include "wpi/SmallString.h"
#include "wpi/uv/Loop.h"
#include "wpi/uv/Pipe.h"
namespace wpi::uv {
std::shared_ptr<Process> Process::SpawnArray(Loop& loop, std::string_view file,
span<const Option> options) {
// convert Option array to libuv structure
uv_process_options_t coptions;
coptions.exit_cb = [](uv_process_t* handle, int64_t status, int signal) {
auto& h = *static_cast<Process*>(handle->data);
h.exited(status, signal);
};
SmallString<128> fileBuf{file};
coptions.file = fileBuf.c_str();
coptions.cwd = nullptr;
coptions.flags = 0;
coptions.uid = 0;
coptions.gid = 0;
SmallVector<char*, 4> argsBuf;
SmallVector<char*, 4> envBuf;
struct StdioContainer : public uv_stdio_container_t {
StdioContainer() {
flags = UV_IGNORE;
data.fd = 0;
}
};
SmallVector<StdioContainer, 4> stdioBuf;
for (auto&& o : options) {
switch (o.m_type) {
case Option::kArg:
argsBuf.push_back(const_cast<char*>(o.m_data.str));
break;
case Option::kEnv:
envBuf.push_back(const_cast<char*>(o.m_data.str));
break;
case Option::kCwd:
coptions.cwd = o.m_data.str[0] == '\0' ? nullptr : o.m_data.str;
break;
case Option::kUid:
coptions.uid = o.m_data.uid;
coptions.flags |= UV_PROCESS_SETUID;
break;
case Option::kGid:
coptions.gid = o.m_data.gid;
coptions.flags |= UV_PROCESS_SETGID;
break;
case Option::kSetFlags:
coptions.flags |= o.m_data.flags;
break;
case Option::kClearFlags:
coptions.flags &= ~o.m_data.flags;
break;
case Option::kStdioIgnore: {
size_t index = o.m_data.stdio.index;
if (index >= stdioBuf.size()) {
stdioBuf.resize(index + 1);
}
stdioBuf[index].flags = UV_IGNORE;
stdioBuf[index].data.fd = 0;
break;
}
case Option::kStdioInheritFd: {
size_t index = o.m_data.stdio.index;
if (index >= stdioBuf.size()) {
stdioBuf.resize(index + 1);
}
stdioBuf[index].flags = UV_INHERIT_FD;
stdioBuf[index].data.fd = o.m_data.stdio.fd;
break;
}
case Option::kStdioInheritPipe: {
size_t index = o.m_data.stdio.index;
if (index >= stdioBuf.size()) {
stdioBuf.resize(index + 1);
}
stdioBuf[index].flags = UV_INHERIT_STREAM;
stdioBuf[index].data.stream = o.m_data.stdio.pipe->GetRawStream();
break;
}
case Option::kStdioCreatePipe: {
size_t index = o.m_data.stdio.index;
if (index >= stdioBuf.size()) {
stdioBuf.resize(index + 1);
}
stdioBuf[index].flags =
static_cast<uv_stdio_flags>(UV_CREATE_PIPE | o.m_data.stdio.flags);
stdioBuf[index].data.stream = o.m_data.stdio.pipe->GetRawStream();
break;
}
default:
break;
}
}
if (argsBuf.empty()) {
argsBuf.push_back(const_cast<char*>(coptions.file));
}
argsBuf.push_back(nullptr);
coptions.args = argsBuf.data();
if (envBuf.empty()) {
coptions.env = nullptr;
} else {
envBuf.push_back(nullptr);
coptions.env = envBuf.data();
}
coptions.stdio_count = stdioBuf.size();
coptions.stdio = static_cast<uv_stdio_container_t*>(stdioBuf.data());
auto h = std::make_shared<Process>(private_init{});
int err = uv_spawn(loop.GetRaw(), h->GetRaw(), &coptions);
if (err < 0) {
loop.ReportError(err);
return nullptr;
}
h->Keep();
return h;
}
} // namespace wpi::uv

View File

@@ -1,32 +0,0 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#include "wpi/uv/Signal.h"
#include "wpi/uv/Loop.h"
namespace wpi::uv {
std::shared_ptr<Signal> Signal::Create(Loop& loop) {
auto h = std::make_shared<Signal>(private_init{});
int err = uv_signal_init(loop.GetRaw(), h->GetRaw());
if (err < 0) {
loop.ReportError(err);
return nullptr;
}
h->Keep();
return h;
}
void Signal::Start(int signum) {
Invoke(
&uv_signal_start, GetRaw(),
[](uv_signal_t* handle, int signum) {
Signal& h = *static_cast<Signal*>(handle->data);
h.signal(signum);
},
signum);
}
} // namespace wpi::uv

View File

@@ -1,109 +0,0 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#include "wpi/uv/Stream.h"
#include "wpi/SmallVector.h"
using namespace wpi;
using namespace wpi::uv;
namespace {
class CallbackWriteReq : public WriteReq {
public:
CallbackWriteReq(span<const Buffer> bufs,
std::function<void(span<Buffer>, Error)> callback)
: m_bufs{bufs.begin(), bufs.end()} {
finish.connect(
[this, f = std::move(callback)](Error err) { f(m_bufs, err); });
}
private:
SmallVector<Buffer, 4> m_bufs;
};
} // namespace
namespace wpi::uv {
ShutdownReq::ShutdownReq() {
error = [this](Error err) { GetStream().error(err); };
}
WriteReq::WriteReq() {
error = [this](Error err) { GetStream().error(err); };
}
void Stream::Shutdown(const std::shared_ptr<ShutdownReq>& req) {
if (Invoke(&uv_shutdown, req->GetRaw(), GetRawStream(),
[](uv_shutdown_t* req, int status) {
auto& h = *static_cast<ShutdownReq*>(req->data);
if (status < 0) {
h.ReportError(status);
} else {
h.complete();
}
h.Release(); // this is always a one-shot
})) {
req->Keep();
}
}
void Stream::Shutdown(std::function<void()> callback) {
auto req = std::make_shared<ShutdownReq>();
if (callback) {
req->complete.connect(std::move(callback));
}
Shutdown(req);
}
void Stream::StartRead() {
Invoke(&uv_read_start, GetRawStream(), &Handle::AllocBuf,
[](uv_stream_t* stream, ssize_t nread, const uv_buf_t* buf) {
auto& h = *static_cast<Stream*>(stream->data);
Buffer data = *buf;
// nread=0 is simply ignored
if (nread == UV_EOF) {
h.end();
} else if (nread > 0) {
h.data(data, static_cast<size_t>(nread));
} else if (nread < 0) {
h.ReportError(nread);
}
// free the buffer
h.FreeBuf(data);
});
}
void Stream::Write(span<const Buffer> bufs,
const std::shared_ptr<WriteReq>& req) {
if (Invoke(&uv_write, req->GetRaw(), GetRawStream(), bufs.data(), bufs.size(),
[](uv_write_t* r, int status) {
auto& h = *static_cast<WriteReq*>(r->data);
if (status < 0) {
h.ReportError(status);
}
h.finish(Error(status));
h.Release(); // this is always a one-shot
})) {
req->Keep();
}
}
void Stream::Write(span<const Buffer> bufs,
std::function<void(span<Buffer>, Error)> callback) {
Write(bufs, std::make_shared<CallbackWriteReq>(bufs, std::move(callback)));
}
int Stream::TryWrite(span<const Buffer> bufs) {
int val = uv_try_write(GetRawStream(), bufs.data(), bufs.size());
if (val < 0) {
this->ReportError(val);
return 0;
}
return val;
}
} // namespace wpi::uv

View File

@@ -1,170 +0,0 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#include "wpi/uv/Tcp.h"
#include <cstring>
#include "wpi/uv/util.h"
namespace wpi::uv {
std::shared_ptr<Tcp> Tcp::Create(Loop& loop, unsigned int flags) {
auto h = std::make_shared<Tcp>(private_init{});
int err = uv_tcp_init_ex(loop.GetRaw(), h->GetRaw(), flags);
if (err < 0) {
loop.ReportError(err);
return nullptr;
}
h->Keep();
return h;
}
void Tcp::Reuse(std::function<void()> callback, unsigned int flags) {
if (IsClosing()) {
return;
}
if (!m_reuseData) {
m_reuseData = std::make_unique<ReuseData>();
}
m_reuseData->callback = std::move(callback);
m_reuseData->flags = flags;
uv_close(GetRawHandle(), [](uv_handle_t* handle) {
Tcp& h = *static_cast<Tcp*>(handle->data);
if (!h.m_reuseData) {
return; // just in case
}
auto data = std::move(h.m_reuseData);
int err = uv_tcp_init_ex(h.GetLoopRef().GetRaw(), h.GetRaw(), data->flags);
if (err < 0) {
h.ReportError(err);
return;
}
data->callback();
});
}
std::shared_ptr<Tcp> Tcp::Accept() {
auto client = Create(GetLoopRef());
if (!client) {
return nullptr;
}
if (!Accept(client)) {
client->Release();
return nullptr;
}
return client;
}
Tcp* Tcp::DoAccept() {
return Accept().get();
}
void Tcp::Bind(std::string_view ip, unsigned int port, unsigned int flags) {
sockaddr_in addr;
int err = NameToAddr(ip, port, &addr);
if (err < 0) {
ReportError(err);
} else {
Bind(reinterpret_cast<const sockaddr&>(addr), flags);
}
}
void Tcp::Bind6(std::string_view ip, unsigned int port, unsigned int flags) {
sockaddr_in6 addr;
int err = NameToAddr(ip, port, &addr);
if (err < 0) {
ReportError(err);
} else {
Bind(reinterpret_cast<const sockaddr&>(addr), flags);
}
}
sockaddr_storage Tcp::GetSock() {
sockaddr_storage name;
int len = sizeof(name);
if (!Invoke(&uv_tcp_getsockname, GetRaw(), reinterpret_cast<sockaddr*>(&name),
&len)) {
std::memset(&name, 0, sizeof(name));
}
return name;
}
sockaddr_storage Tcp::GetPeer() {
sockaddr_storage name;
int len = sizeof(name);
if (!Invoke(&uv_tcp_getpeername, GetRaw(), reinterpret_cast<sockaddr*>(&name),
&len)) {
std::memset(&name, 0, sizeof(name));
}
return name;
}
void Tcp::Connect(const sockaddr& addr,
const std::shared_ptr<TcpConnectReq>& req) {
if (Invoke(&uv_tcp_connect, req->GetRaw(), GetRaw(), &addr,
[](uv_connect_t* req, int status) {
auto& h = *static_cast<TcpConnectReq*>(req->data);
if (status < 0) {
h.ReportError(status);
} else {
h.connected();
}
h.Release(); // this is always a one-shot
})) {
req->Keep();
}
}
void Tcp::Connect(const sockaddr& addr, std::function<void()> callback) {
auto req = std::make_shared<TcpConnectReq>();
req->connected.connect(std::move(callback));
Connect(addr, req);
}
void Tcp::Connect(std::string_view ip, unsigned int port,
const std::shared_ptr<TcpConnectReq>& req) {
sockaddr_in addr;
int err = NameToAddr(ip, port, &addr);
if (err < 0) {
ReportError(err);
} else {
Connect(reinterpret_cast<const sockaddr&>(addr), req);
}
}
void Tcp::Connect(std::string_view ip, unsigned int port,
std::function<void()> callback) {
sockaddr_in addr;
int err = NameToAddr(ip, port, &addr);
if (err < 0) {
ReportError(err);
} else {
Connect(reinterpret_cast<const sockaddr&>(addr), std::move(callback));
}
}
void Tcp::Connect6(std::string_view ip, unsigned int port,
const std::shared_ptr<TcpConnectReq>& req) {
sockaddr_in6 addr;
int err = NameToAddr(ip, port, &addr);
if (err < 0) {
ReportError(err);
} else {
Connect(reinterpret_cast<const sockaddr&>(addr), req);
}
}
void Tcp::Connect6(std::string_view ip, unsigned int port,
std::function<void()> callback) {
sockaddr_in6 addr;
int err = NameToAddr(ip, port, &addr);
if (err < 0) {
ReportError(err);
} else {
Connect(reinterpret_cast<const sockaddr&>(addr), std::move(callback));
}
}
} // namespace wpi::uv

View File

@@ -1,44 +0,0 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#include "wpi/uv/Timer.h"
#include "wpi/uv/Loop.h"
namespace wpi::uv {
std::shared_ptr<Timer> Timer::Create(Loop& loop) {
auto h = std::make_shared<Timer>(private_init{});
int err = uv_timer_init(loop.GetRaw(), h->GetRaw());
if (err < 0) {
loop.ReportError(err);
return nullptr;
}
h->Keep();
return h;
}
void Timer::SingleShot(Loop& loop, Time timeout, std::function<void()> func) {
auto h = Create(loop);
if (!h) {
return;
}
h->timeout.connect([theTimer = h.get(), f = std::move(func)]() {
f();
theTimer->Close();
});
h->Start(timeout);
}
void Timer::Start(Time timeout, Time repeat) {
Invoke(
&uv_timer_start, GetRaw(),
[](uv_timer_t* handle) {
Timer& h = *static_cast<Timer*>(handle->data);
h.timeout();
},
timeout.count(), repeat.count());
}
} // namespace wpi::uv

View File

@@ -1,22 +0,0 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#include "wpi/uv/Tty.h"
#include "wpi/uv/Loop.h"
namespace wpi::uv {
std::shared_ptr<Tty> Tty::Create(Loop& loop, uv_file fd, bool readable) {
auto h = std::make_shared<Tty>(private_init{});
int err = uv_tty_init(loop.GetRaw(), h->GetRaw(), fd, readable ? 1 : 0);
if (err < 0) {
loop.ReportError(err);
return nullptr;
}
h->Keep();
return h;
}
} // namespace wpi::uv

View File

@@ -1,184 +0,0 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#include "wpi/uv/Udp.h"
#include <cstring>
#include "wpi/SmallString.h"
#include "wpi/SmallVector.h"
#include "wpi/uv/util.h"
namespace {
using namespace wpi;
using namespace wpi::uv;
class CallbackUdpSendReq : public UdpSendReq {
public:
CallbackUdpSendReq(span<const Buffer> bufs,
std::function<void(span<Buffer>, Error)> callback)
: m_bufs{bufs.begin(), bufs.end()} {
complete.connect(
[this, f = std::move(callback)](Error err) { f(m_bufs, err); });
}
private:
SmallVector<Buffer, 4> m_bufs;
};
} // namespace
namespace wpi::uv {
UdpSendReq::UdpSendReq() {
error = [this](Error err) { GetUdp().error(err); };
}
std::shared_ptr<Udp> Udp::Create(Loop& loop, unsigned int flags) {
auto h = std::make_shared<Udp>(private_init{});
int err = uv_udp_init_ex(loop.GetRaw(), h->GetRaw(), flags);
if (err < 0) {
loop.ReportError(err);
return nullptr;
}
h->Keep();
return h;
}
void Udp::Bind(std::string_view ip, unsigned int port, unsigned int flags) {
sockaddr_in addr;
int err = NameToAddr(ip, port, &addr);
if (err < 0) {
ReportError(err);
} else {
Bind(reinterpret_cast<const sockaddr&>(addr), flags);
}
}
void Udp::Bind6(std::string_view ip, unsigned int port, unsigned int flags) {
sockaddr_in6 addr;
int err = NameToAddr(ip, port, &addr);
if (err < 0) {
ReportError(err);
} else {
Bind(reinterpret_cast<const sockaddr&>(addr), flags);
}
}
void Udp::Connect(std::string_view ip, unsigned int port) {
sockaddr_in addr;
int err = NameToAddr(ip, port, &addr);
if (err < 0) {
ReportError(err);
} else {
Connect(reinterpret_cast<const sockaddr&>(addr));
}
}
void Udp::Connect6(std::string_view ip, unsigned int port) {
sockaddr_in6 addr;
int err = NameToAddr(ip, port, &addr);
if (err < 0) {
ReportError(err);
} else {
Connect(reinterpret_cast<const sockaddr&>(addr));
}
}
sockaddr_storage Udp::GetPeer() {
sockaddr_storage name;
int len = sizeof(name);
if (!Invoke(&uv_udp_getpeername, GetRaw(), reinterpret_cast<sockaddr*>(&name),
&len)) {
std::memset(&name, 0, sizeof(name));
}
return name;
}
sockaddr_storage Udp::GetSock() {
sockaddr_storage name;
int len = sizeof(name);
if (!Invoke(&uv_udp_getsockname, GetRaw(), reinterpret_cast<sockaddr*>(&name),
&len)) {
std::memset(&name, 0, sizeof(name));
}
return name;
}
void Udp::SetMembership(std::string_view multicastAddr,
std::string_view interfaceAddr,
uv_membership membership) {
SmallString<128> multicastAddrBuf{multicastAddr};
SmallString<128> interfaceAddrBuf{interfaceAddr};
Invoke(&uv_udp_set_membership, GetRaw(), multicastAddrBuf.c_str(),
interfaceAddrBuf.c_str(), membership);
}
void Udp::SetMulticastInterface(std::string_view interfaceAddr) {
SmallString<128> interfaceAddrBuf{interfaceAddr};
Invoke(&uv_udp_set_multicast_interface, GetRaw(), interfaceAddrBuf.c_str());
}
void Udp::Send(const sockaddr& addr, span<const Buffer> bufs,
const std::shared_ptr<UdpSendReq>& req) {
if (Invoke(&uv_udp_send, req->GetRaw(), GetRaw(), bufs.data(), bufs.size(),
&addr, [](uv_udp_send_t* r, int status) {
auto& h = *static_cast<UdpSendReq*>(r->data);
if (status < 0) {
h.ReportError(status);
}
h.complete(Error(status));
h.Release(); // this is always a one-shot
})) {
req->Keep();
}
}
void Udp::Send(const sockaddr& addr, span<const Buffer> bufs,
std::function<void(span<Buffer>, Error)> callback) {
Send(addr, bufs,
std::make_shared<CallbackUdpSendReq>(bufs, std::move(callback)));
}
void Udp::Send(span<const Buffer> bufs,
const std::shared_ptr<UdpSendReq>& req) {
if (Invoke(&uv_udp_send, req->GetRaw(), GetRaw(), bufs.data(), bufs.size(),
nullptr, [](uv_udp_send_t* r, int status) {
auto& h = *static_cast<UdpSendReq*>(r->data);
if (status < 0) {
h.ReportError(status);
}
h.complete(Error(status));
h.Release(); // this is always a one-shot
})) {
req->Keep();
}
}
void Udp::Send(span<const Buffer> bufs,
std::function<void(span<Buffer>, Error)> callback) {
Send(bufs, std::make_shared<CallbackUdpSendReq>(bufs, std::move(callback)));
}
void Udp::StartRecv() {
Invoke(&uv_udp_recv_start, GetRaw(), &AllocBuf,
[](uv_udp_t* handle, ssize_t nread, const uv_buf_t* buf,
const sockaddr* addr, unsigned flags) {
auto& h = *static_cast<Udp*>(handle->data);
Buffer data = *buf;
// nread=0 is simply ignored
if (nread > 0) {
h.received(data, static_cast<size_t>(nread), *addr, flags);
} else if (nread < 0) {
h.ReportError(nread);
}
// free the buffer
h.FreeBuf(data);
});
}
} // namespace wpi::uv

View File

@@ -1,50 +0,0 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#include "wpi/uv/Work.h"
#include "wpi/uv/Loop.h"
namespace wpi::uv {
WorkReq::WorkReq() {
error = [this](Error err) { GetLoop().error(err); };
}
void QueueWork(Loop& loop, const std::shared_ptr<WorkReq>& req) {
int err = uv_queue_work(
loop.GetRaw(), req->GetRaw(),
[](uv_work_t* req) {
auto& h = *static_cast<WorkReq*>(req->data);
h.work();
},
[](uv_work_t* req, int status) {
auto& h = *static_cast<WorkReq*>(req->data);
if (status < 0) {
h.ReportError(status);
} else {
h.afterWork();
}
h.Release(); // this is always a one-shot
});
if (err < 0) {
loop.ReportError(err);
} else {
req->Keep();
}
}
void QueueWork(Loop& loop, std::function<void()> work,
std::function<void()> afterWork) {
auto req = std::make_shared<WorkReq>();
if (work) {
req->work.connect(std::move(work));
}
if (afterWork) {
req->afterWork.connect(std::move(afterWork));
}
QueueWork(loop, req);
}
} // namespace wpi::uv