[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

View File

@@ -1,55 +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 <string>
#include <string_view>
#include "wpi/Signal.h"
namespace wpi {
class Logger;
namespace uv {
class Loop;
class Tcp;
class Timer;
} // namespace uv
class DsClient : public std::enable_shared_from_this<DsClient> {
struct private_init {};
public:
static std::shared_ptr<DsClient> Create(wpi::uv::Loop& loop,
wpi::Logger& logger) {
return std::make_shared<DsClient>(loop, logger, private_init{});
}
DsClient(wpi::uv::Loop& loop, wpi::Logger& logger, const private_init&);
~DsClient();
DsClient(const DsClient&) = delete;
DsClient& operator=(const DsClient&) = delete;
void Close();
sig::Signal<std::string_view> setIp;
sig::Signal<> clearIp;
private:
void Connect();
void HandleIncoming(std::string_view in);
void ParseJson();
wpi::Logger& m_logger;
std::shared_ptr<wpi::uv::Tcp> m_tcp;
std::shared_ptr<wpi::uv::Timer> m_timer;
std::string m_json;
};
} // namespace wpi

View File

@@ -1,62 +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.
#ifndef WPIUTIL_WPI_EVENTLOOPRUNNER_H_
#define WPIUTIL_WPI_EVENTLOOPRUNNER_H_
#include <functional>
#include <memory>
#include "wpi/SafeThread.h"
#include "wpi/uv/Loop.h"
namespace wpi {
/**
* Executes an event loop on a separate thread.
*/
class EventLoopRunner {
public:
using LoopFunc = std::function<void(uv::Loop&)>;
EventLoopRunner();
virtual ~EventLoopRunner();
/**
* Stop the loop. Once the loop is stopped it cannot be restarted.
* This function does not return until the loop has exited.
*/
void Stop();
/**
* Run a function asynchronously (once) on the loop.
* This is safe to call from any thread, but is NOT safe to call from the
* provided function (it will deadlock).
* @param func function to execute on the loop
*/
void ExecAsync(LoopFunc func);
/**
* Run a function synchronously (once) on the loop.
* This is safe to call from any thread, but is NOT safe to call from the
* provided function (it will deadlock).
* This does not return until the function finishes executing.
* @param func function to execute on the loop
*/
void ExecSync(LoopFunc func);
/**
* Get the loop. If the loop thread is not running, returns nullptr.
* @return The loop
*/
std::shared_ptr<uv::Loop> GetLoop();
private:
class Thread;
SafeThreadOwner<Thread> m_owner;
};
} // namespace wpi
#endif // WPIUTIL_WPI_EVENTLOOPRUNNER_H_

View File

@@ -1,227 +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.
#ifndef WPIUTIL_WPI_HTTPPARSER_H_
#define WPIUTIL_WPI_HTTPPARSER_H_
#include <stdint.h>
#include <string_view>
#include "wpi/Signal.h"
#include "wpi/SmallString.h"
#include "wpi/http_parser.h"
namespace wpi {
/**
* HTTP protocol parser. Performs incremental parsing with callbacks for each
* part of the HTTP protocol. As this is incremental, it's suitable for use
* with event based frameworks that provide arbitrary chunks of data.
*/
class HttpParser {
public:
enum Type {
kRequest = HTTP_REQUEST,
kResponse = HTTP_RESPONSE,
kBoth = HTTP_BOTH
};
/**
* Returns the library version. Bits 16-23 contain the major version number,
* bits 8-15 the minor version number and bits 0-7 the patch level.
*/
static uint32_t GetParserVersion();
/**
* Constructor.
* @param type Type of parser (request or response or both)
*/
explicit HttpParser(Type type);
/**
* Reset the parser to initial state.
* This allows reusing the same parser object from request to request.
* @param type Type of parser (request or response or both)
*/
void Reset(Type type);
/**
* Set the maximum accepted length for URLs, field names, and field values.
* The default is 1024.
* @param len maximum length
*/
void SetMaxLength(size_t len) { m_maxLength = len; }
/**
* Executes the parser. An empty input is treated as EOF.
* @param in input data
* @return Trailing input data after the parse.
*/
std::string_view Execute(std::string_view in) {
in.remove_prefix(
http_parser_execute(&m_parser, &m_settings, in.data(), in.size()));
return in;
}
/**
* Get HTTP major version.
*/
unsigned int GetMajor() const { return m_parser.http_major; }
/**
* Get HTTP minor version.
*/
unsigned int GetMinor() const { return m_parser.http_minor; }
/**
* Get HTTP status code. Valid only on responses. Valid in and after
* the OnStatus() callback has been called.
*/
unsigned int GetStatusCode() const { return m_parser.status_code; }
/**
* Get HTTP method. Valid only on requests.
*/
http_method GetMethod() const {
return static_cast<http_method>(m_parser.method);
}
/**
* Determine if an error occurred.
* @return False if no error.
*/
bool HasError() const { return m_parser.http_errno != HPE_OK; }
/**
* Get error number.
*/
http_errno GetError() const {
return static_cast<http_errno>(m_parser.http_errno);
}
/**
* Abort the parse. Call this from a callback handler to indicate an error.
* This will result in GetError() returning one of the callback-related
* errors (e.g. HPE_CB_message_begin).
*/
void Abort() { m_aborted = true; }
/**
* Determine if an upgrade header was present and the parser has exited
* because of that. Should be checked when Execute() returns in addition to
* checking GetError().
* @return True if upgrade header, false otherwise.
*/
bool IsUpgrade() const { return m_parser.upgrade; }
/**
* If this returns false in the headersComplete or messageComplete
* callback, then this should be the last message on the connection.
* If you are the server, respond with the "Connection: close" header.
* If you are the client, close the connection.
*/
bool ShouldKeepAlive() const { return http_should_keep_alive(&m_parser); }
/**
* Pause the parser.
* @param paused True to pause, false to unpause.
*/
void Pause(bool paused) { http_parser_pause(&m_parser, paused); }
/**
* Checks if this is the final chunk of the body.
*/
bool IsBodyFinal() const { return http_body_is_final(&m_parser); }
/**
* Get URL. Valid in and after the url callback has been called.
*/
std::string_view GetUrl() const { return m_urlBuf.str(); }
/**
* Message begin callback.
*/
sig::Signal<> messageBegin;
/**
* URL callback.
*
* The parameter to the callback is the complete URL string.
*/
sig::Signal<std::string_view> url;
/**
* Status callback.
*
* The parameter to the callback is the complete status string.
* GetStatusCode() can be used to get the numeric status code.
*/
sig::Signal<std::string_view> status;
/**
* Header field callback.
*
* The parameters to the callback are the field name and field value.
*/
sig::Signal<std::string_view, std::string_view> header;
/**
* Headers complete callback.
*
* The parameter to the callback is whether the connection should be kept
* alive. If this is false, then this should be the last message on the
* connection. If you are the server, respond with the "Connection: close"
* header. If you are the client, close the connection.
*/
sig::Signal<bool> headersComplete;
/**
* Body data callback.
*
* The parameters to the callback is the data chunk and whether this is the
* final chunk of data in the message. Note this callback will be called
* multiple times arbitrarily (e.g. it's possible that it may be called with
* just a few characters at a time).
*/
sig::Signal<std::string_view, bool> body;
/**
* Headers complete callback.
*
* The parameter to the callback is whether the connection should be kept
* alive. If this is false, then this should be the last message on the
* connection. If you are the server, respond with the "Connection: close"
* header. If you are the client, close the connection.
*/
sig::Signal<bool> messageComplete;
/**
* Chunk header callback.
*
* The parameter to the callback is the chunk size.
*/
sig::Signal<uint64_t> chunkHeader;
/**
* Chunk complete callback.
*/
sig::Signal<> chunkComplete;
private:
http_parser m_parser;
http_parser_settings m_settings;
size_t m_maxLength = 1024;
enum { kStart, kUrl, kStatus, kField, kValue } m_state = kStart;
SmallString<128> m_urlBuf;
SmallString<32> m_fieldBuf;
SmallString<128> m_valueBuf;
bool m_aborted = false;
};
} // namespace wpi
#endif // WPIUTIL_WPI_HTTPPARSER_H_

View File

@@ -1,152 +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.
#ifndef WPIUTIL_WPI_HTTPSERVERCONNECTION_H_
#define WPIUTIL_WPI_HTTPSERVERCONNECTION_H_
#include <memory>
#include <string_view>
#include "wpi/HttpParser.h"
#include "wpi/span.h"
#include "wpi/uv/Stream.h"
namespace wpi {
class raw_ostream;
class HttpServerConnection {
public:
explicit HttpServerConnection(std::shared_ptr<uv::Stream> stream);
virtual ~HttpServerConnection() = default;
protected:
/**
* Process an incoming HTTP request. This is called after the incoming
* message completes (e.g. from the HttpParser::messageComplete callback).
*
* The implementation should read request details from m_request and call the
* appropriate Send() functions to send a response back to the client.
*/
virtual void ProcessRequest() = 0;
/**
* Build common response headers.
*
* Called by SendHeader() to send headers common to every response.
* Each line must be terminated with "\r\n".
*
* The default implementation sends the following:
* "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"
*
* These parameters should ensure the browser does not cache the response.
* A browser should connect for each file and not serve files from its cache.
*
* @param os response stream
*/
virtual void BuildCommonHeaders(raw_ostream& os);
/**
* Build HTTP response header, along with other header information like
* mimetype. Calls BuildCommonHeaders().
*
* @param os response stream
* @param code HTTP response code (e.g. 200)
* @param codeText HTTP response code text (e.g. "OK")
* @param contentType MIME content type (e.g. "text/plain")
* @param contentLength Length of content. If 0 is provided, m_keepAlive will
* be set to false.
* @param extra Extra HTTP headers to send, including final "\r\n"
*/
virtual void BuildHeader(raw_ostream& os, int code, std::string_view codeText,
std::string_view contentType, uint64_t contentLength,
std::string_view extra = {});
/**
* Send data to client.
*
* This is a convenience wrapper around m_stream.Write() to provide
* auto-close functionality.
*
* @param bufs Buffers to write. Deallocate() will be called on each
* buffer after the write completes. If different behavior
* is desired, call m_stream.Write() directly instead.
* @param closeAfter close the connection after the write completes
*/
void SendData(span<const uv::Buffer> bufs, bool closeAfter = false);
/**
* Send HTTP response, along with other header information like mimetype.
* Calls BuildHeader().
*
* @param code HTTP response code (e.g. 200)
* @param codeText HTTP response code text (e.g. "OK")
* @param contentType MIME content type (e.g. "text/plain")
* @param content Response message content
* @param extraHeader Extra HTTP headers to send, including final "\r\n"
*/
virtual void SendResponse(int code, std::string_view codeText,
std::string_view contentType,
std::string_view content,
std::string_view extraHeader = {});
/**
* Send HTTP response from static data, along with other header information
* like mimetype. Calls BuildHeader(). Supports gzip pre-compressed data
* (it will decompress if the client does not accept gzip encoded data).
* Unlike SendResponse(), content is not copied and its contents must remain
* valid for an unspecified lifetime.
*
* @param code HTTP response code (e.g. 200)
* @param codeText HTTP response code text (e.g. "OK")
* @param contentType MIME content type (e.g. "text/plain")
* @param content Response message content
* @param gzipped True if content is gzip compressed
* @param extraHeader Extra HTTP headers to send, including final "\r\n"
*/
virtual void SendStaticResponse(int code, std::string_view codeText,
std::string_view contentType,
std::string_view content, bool gzipped,
std::string_view extraHeader = {});
/**
* Send error header and message.
* This provides standard code responses for 400, 401, 403, 404, 500, and 503.
* Other codes will be reported as 501. For arbitrary code handling, use
* SendResponse() instead.
*
* @param code HTTP error code (e.g. 404)
* @param message Additional message text
*/
virtual void SendError(int code, std::string_view message = {});
/** The HTTP request. */
HttpParser m_request{HttpParser::kRequest};
/** Whether the connection should be kept alive. */
bool m_keepAlive = false;
/** If gzip is an acceptable encoding for responses. */
bool m_acceptGzip = false;
/** The underlying stream for the connection. */
uv::Stream& m_stream;
/** The header reader connection. */
sig::ScopedConnection m_dataConn;
/** The end stream connection. */
sig::ScopedConnection m_endConn;
/** The message complete connection. */
sig::Connection m_messageCompleteConn;
};
} // namespace wpi
#endif // WPIUTIL_WPI_HTTPSERVERCONNECTION_H_

View File

@@ -1,422 +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.
#ifndef WPIUTIL_WPI_HTTPUTIL_H_
#define WPIUTIL_WPI_HTTPUTIL_H_
#include <initializer_list>
#include <memory>
#include <optional>
#include <string>
#include <string_view>
#include <utility>
#include <vector>
#include "wpi/NetworkStream.h"
#include "wpi/SmallString.h"
#include "wpi/SmallVector.h"
#include "wpi/StringMap.h"
#include "wpi/raw_istream.h"
#include "wpi/raw_socket_istream.h"
#include "wpi/raw_socket_ostream.h"
#include "wpi/span.h"
namespace wpi {
// Unescape a %xx-encoded URI.
// @param buf Buffer for output
// @param error Set to true if an error occurred
// @return Escaped string
std::string_view UnescapeURI(std::string_view str, SmallVectorImpl<char>& buf,
bool* error);
// Escape a string with %xx-encoding.
// @param buf Buffer for output
// @param spacePlus If true, encodes spaces to '+' rather than "%20"
// @return Escaped string
std::string_view EscapeURI(std::string_view str, SmallVectorImpl<char>& buf,
bool spacePlus = true);
// Parse a set of HTTP headers. Saves just the Content-Type and Content-Length
// fields.
// @param is Input stream
// @param contentType If not null, Content-Type contents are saved here.
// @param contentLength If not null, Content-Length contents are saved here.
// @return False if error occurred in input stream
bool ParseHttpHeaders(raw_istream& is, SmallVectorImpl<char>* contentType,
SmallVectorImpl<char>* contentLength);
// Look for a MIME multi-part boundary. On return, the input stream will
// be located at the character following the boundary (usually "\r\n").
// @param is Input stream
// @param boundary Boundary string to scan for (not including "--" prefix)
// @param saveBuf If not null, all scanned characters up to but not including
// the boundary are saved to this string
// @return False if error occurred on input stream, true if boundary found.
bool FindMultipartBoundary(wpi::raw_istream& is, std::string_view boundary,
std::string* saveBuf);
/**
* Map for looking up elements of the query portion of a URI. Does not
* handle multiple elements with the same name. This is a reference type;
* it does not make a copy of the query string, so it is important that the
* query string has a lifetime at least as long as this object.
*/
class HttpQueryMap {
public:
/**
* Constructs an empty map (with no entries).
*/
HttpQueryMap() = default;
/**
* Constructs from an escaped query string. Note: does not make a copy of
* the query string, so it must not be a temporary.
*
* @param query query string
*/
explicit HttpQueryMap(std::string_view query);
/**
* Gets an element of the query string. Both the name and the returned
* value are unescaped strings.
*
* @param name name (unescaped)
* @param buf result buffer for value
* @return Optional unescaped value. Returns an empty optional if the
* name is not present in the query map.
*/
std::optional<std::string_view> Get(std::string_view name,
SmallVectorImpl<char>& buf) const;
private:
StringMap<std::string_view> m_elems;
};
class HttpPathRef;
/**
* Class for HTTP path matching. A root path is represented as a single
* empty element, otherwise the path consists of each non-empty element
* between the '/' characters:
* - "" -> []
* - "/" -> [""]
* - "/foo" -> ["foo"]
* - "/foo/bar" -> ["foo", "bar"]
* - "/foo//bar/" -> ["foo", "bar"]
*
* All path elements are unescaped.
*/
class HttpPath {
public:
/**
* Constructs an empty HTTP path.
*/
HttpPath() = default;
/**
* Constructs a HTTP path from an escaped path string. Makes a copy of the
* path, so it's safe to be a temporary.
*/
explicit HttpPath(std::string_view path);
/**
* Evaluates to true if the path is not empty.
*/
explicit operator bool() const { return !empty(); }
/**
* Returns true if the path has no elements.
*/
bool empty() const { return m_pathEnds.empty(); }
/**
* Returns number of elements in the path.
*/
size_t size() const { return m_pathEnds.size(); }
/**
* Returns true if the path exactly matches the provided match list.
*
* @param match match list
* @return True if path equals match list
*/
bool equals(std::initializer_list<std::string_view> match) const {
return equals(0, {match.begin(), match.end()});
}
bool equals(span<const std::string_view> match) const {
return equals(0, match);
}
bool equals(std::string_view match) const { return equals(0, {match}); }
/**
* Returns true if the elements of the path starting at the "start" element
* match the provided match list, and there are no additional elements.
*
* @param start element to start matching at
* @param match match list
* @return True if match
*/
bool equals(size_t start,
std::initializer_list<std::string_view> match) const {
return equals(start, {match.begin(), match.end()});
}
bool equals(size_t start, span<const std::string_view> match) const {
if (m_pathEnds.size() != (start + match.size())) {
return false;
}
return startswith(start, match);
}
bool equals(size_t start, std::string_view match) const {
return equals(start, {match});
}
/**
* Returns true if the first elements of the path match the provided match
* list. The path may have additional elements.
*
* @param match match list
* @return True if path starts with match list
*/
bool startswith(std::initializer_list<std::string_view> match) const {
return startswith(0, {match.begin(), match.end()});
}
bool startswith(span<const std::string_view> match) const {
return startswith(0, match);
}
bool startswith(std::string_view match) const {
return startswith(0, {match});
}
/**
* Returns true if the elements of the path starting at the "start" element
* match the provided match list. The path may have additional elements.
*
* @param start element to start matching at
* @param match match list
* @return True if path starting at the start element matches the match list
*/
bool startswith(size_t start,
std::initializer_list<std::string_view> match) const {
return startswith(start, {match.begin(), match.end()});
}
bool startswith(size_t start, span<const std::string_view> match) const;
bool startswith(size_t start, std::string_view match) const {
return startswith(start, {match});
}
/**
* Gets a single element of the path.
*/
std::string_view operator[](size_t n) const;
/**
* Returns a path reference with the first N elements of the path removed.
*/
HttpPathRef drop_front(size_t n) const;
private:
SmallString<128> m_pathBuf;
SmallVector<size_t, 16> m_pathEnds;
};
/**
* Proxy reference object for a portion of a HttpPath.
*/
class HttpPathRef {
public:
HttpPathRef() = default;
/*implicit*/ HttpPathRef(const HttpPath& path, // NOLINT
size_t start = 0)
: m_path(&path), m_start(start) {}
explicit operator bool() const { return !empty(); }
bool empty() const { return m_path && m_path->size() == m_start; }
size_t size() const { return m_path ? m_path->size() - m_start : 0; }
bool equals(std::initializer_list<std::string_view> match) const {
return equals(0, {match.begin(), match.end()});
}
bool equals(span<const std::string_view> match) const {
return equals(0, match);
}
bool equals(std::string_view match) const { return equals(0, {match}); }
bool equals(size_t start,
std::initializer_list<std::string_view> match) const {
return equals(start, {match.begin(), match.end()});
}
bool equals(size_t start, span<const std::string_view> match) const {
return m_path ? m_path->equals(m_start + start, match) : false;
}
bool equals(size_t start, std::string_view match) const {
return equals(start, {match});
}
bool startswith(std::initializer_list<std::string_view> match) const {
return startswith(0, {match.begin(), match.end()});
}
bool startswith(span<const std::string_view> match) const {
return startswith(0, match);
}
bool startswith(std::string_view match) const {
return startswith(0, {match});
}
bool startswith(size_t start,
std::initializer_list<std::string_view> match) const {
return startswith(start, {match.begin(), match.end()});
}
bool startswith(size_t start, span<const std::string_view> match) const {
return m_path ? m_path->startswith(m_start + start, match) : false;
}
bool startswith(size_t start, std::string_view match) const {
return startswith(start, {match});
}
std::string_view operator[](size_t n) const {
return m_path ? m_path->operator[](m_start + n) : std::string_view{};
}
HttpPathRef drop_front(size_t n) const {
return m_path ? m_path->drop_front(m_start + n) : HttpPathRef{};
}
private:
const HttpPath* m_path = nullptr;
size_t m_start = 0;
};
class HttpLocation {
public:
HttpLocation() = default;
HttpLocation(std::string_view url_, bool* error, std::string* errorMsg);
std::string url; // retain copy
std::string user; // unescaped
std::string password; // unescaped
std::string host;
int port;
std::string path; // escaped, not including leading '/'
std::vector<std::pair<std::string, std::string>> params; // unescaped
std::string fragment;
};
class HttpRequest {
public:
HttpRequest() = default;
explicit HttpRequest(const HttpLocation& loc)
: host{loc.host}, port{loc.port} {
SetPath(loc.path, loc.params);
SetAuth(loc);
}
template <typename T>
HttpRequest(const HttpLocation& loc, const T& extraParams);
HttpRequest(const HttpLocation& loc, std::string_view path_)
: host{loc.host}, port{loc.port}, path{path_} {
SetAuth(loc);
}
template <typename T>
HttpRequest(const HttpLocation& loc, std::string_view path_, const T& params)
: host{loc.host}, port{loc.port} {
SetPath(path_, params);
SetAuth(loc);
}
SmallString<128> host;
int port;
std::string auth;
SmallString<128> path;
private:
void SetAuth(const HttpLocation& loc);
template <typename T>
void SetPath(std::string_view path_, const T& params);
template <typename T>
static std::string_view GetFirst(const T& elem) {
return elem.first;
}
template <typename T>
static std::string_view GetFirst(const StringMapEntry<T>& elem) {
return elem.getKey();
}
template <typename T>
static std::string_view GetSecond(const T& elem) {
return elem.second;
}
};
class HttpConnection {
public:
HttpConnection(std::unique_ptr<wpi::NetworkStream> stream_, int timeout)
: stream{std::move(stream_)}, is{*stream, timeout}, os{*stream, true} {}
bool Handshake(const HttpRequest& request, std::string* warnMsg);
std::unique_ptr<wpi::NetworkStream> stream;
wpi::raw_socket_istream is;
wpi::raw_socket_ostream os;
// Valid after Handshake() is successful
SmallString<64> contentType;
SmallString<64> contentLength;
explicit operator bool() const { return stream && !is.has_error(); }
};
class HttpMultipartScanner {
public:
explicit HttpMultipartScanner(std::string_view boundary,
bool saveSkipped = false) {
Reset(saveSkipped);
SetBoundary(boundary);
}
// Change the boundary. This is only safe to do when IsDone() is true (or
// immediately after construction).
void SetBoundary(std::string_view boundary);
// Reset the scanner. This allows reuse of internal buffers.
void Reset(bool saveSkipped = false);
// Execute the scanner. Will automatically call Reset() on entry if IsDone()
// is true.
// @param in input data
// @return the input not consumed; empty if all input consumed
std::string_view Execute(std::string_view in);
// Returns true when the boundary has been found.
bool IsDone() const { return m_state == kDone; }
// Get the skipped data. Will be empty if saveSkipped was false.
std::string_view GetSkipped() const {
return m_saveSkipped ? std::string_view{m_buf} : std::string_view{};
}
private:
SmallString<64> m_boundaryWith, m_boundaryWithout;
// Internal state
enum State { kBoundary, kPadding, kDone };
State m_state;
size_t m_posWith, m_posWithout;
enum Dashes { kUnknown, kWith, kWithout };
Dashes m_dashes;
// Buffer
bool m_saveSkipped;
std::string m_buf;
};
} // namespace wpi
#include "HttpUtil.inc"
#endif // WPIUTIL_WPI_HTTPUTIL_H_

View File

@@ -1,55 +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.
#ifndef WPIUTIL_WPI_HTTPUTIL_INC_
#define WPIUTIL_WPI_HTTPUTIL_INC_
#include <utility>
#include "wpi/HttpUtil.h"
namespace wpi {
inline HttpPathRef HttpPath::drop_front(size_t n) const {
return HttpPathRef(*this, n);
}
template <typename T>
HttpRequest::HttpRequest(const HttpLocation& loc, const T& extraParams)
: host{loc.host}, port{loc.port} {
StringMap<std::string_view> params;
for (const auto& p : loc.params) {
params.insert(std::make_pair(GetFirst(p), GetSecond(p)));
}
for (const auto& p : extraParams) {
params.insert(std::make_pair(GetFirst(p), GetSecond(p)));
}
SetPath(loc.path, params);
SetAuth(loc);
}
template <typename T>
void HttpRequest::SetPath(std::string_view path_, const T& params) {
// Build location including query string
raw_svector_ostream pathOs{path};
pathOs << path_;
bool first = true;
for (const auto& param : params) {
if (first) {
pathOs << '?';
first = false;
} else {
pathOs << '&';
}
SmallString<64> escapeBuf;
pathOs << EscapeURI(GetFirst(param), escapeBuf, false);
if (!GetSecond(param).empty()) {
pathOs << '=' << EscapeURI(GetSecond(param), escapeBuf, false);
}
}
}
} // namespace wpi
#endif // WPIUTIL_WPI_HTTPUTIL_INC_

View File

@@ -1,92 +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.
#ifndef WPIUTIL_WPI_HTTPWEBSOCKETSERVERCONNECTION_H_
#define WPIUTIL_WPI_HTTPWEBSOCKETSERVERCONNECTION_H_
#include <initializer_list>
#include <memory>
#include <string>
#include <string_view>
#include "wpi/HttpServerConnection.h"
#include "wpi/SmallVector.h"
#include "wpi/WebSocket.h"
#include "wpi/WebSocketServer.h"
#include "wpi/span.h"
#include "wpi/uv/Stream.h"
namespace wpi {
/**
* A server-side HTTP connection that also accepts WebSocket upgrades.
*
* @tparam Derived derived class for std::enable_shared_from_this.
*/
template <typename Derived>
class HttpWebSocketServerConnection
: public HttpServerConnection,
public std::enable_shared_from_this<Derived> {
public:
/**
* Constructor.
*
* @param stream network stream
* @param protocols Acceptable subprotocols
*/
HttpWebSocketServerConnection(std::shared_ptr<uv::Stream> stream,
span<const std::string_view> protocols);
/**
* Constructor.
*
* @param stream network stream
* @param protocols Acceptable subprotocols
*/
HttpWebSocketServerConnection(
std::shared_ptr<uv::Stream> stream,
std::initializer_list<std::string_view> protocols)
: HttpWebSocketServerConnection(stream,
{protocols.begin(), protocols.end()}) {}
protected:
/**
* Check that an incoming WebSocket upgrade is okay. This is called prior
* to accepting the upgrade (so prior to ProcessWsUpgrade()).
*
* The implementation should check other headers and return true if the
* WebSocket connection should be accepted.
*
* @param protocol negotiated subprotocol
*/
virtual bool IsValidWsUpgrade(std::string_view protocol) { return true; }
/**
* Process an incoming WebSocket upgrade. This is called after the header
* reader has been disconnected and the websocket has been accepted.
*
* The implementation should set up appropriate callbacks on the websocket
* object to continue communication.
*
* @note When a WebSocket upgrade occurs, the stream user data is replaced
* with the websocket, and the websocket user data points to "this".
* Replace the websocket user data with caution!
*/
virtual void ProcessWsUpgrade() = 0;
/**
* WebSocket connection; not valid until ProcessWsUpgrade is called.
*/
WebSocket* m_websocket = nullptr;
private:
WebSocketServerHelper m_helper;
SmallVector<std::string, 2> m_protocols;
};
} // namespace wpi
#include "HttpWebSocketServerConnection.inc"
#endif // WPIUTIL_WPI_HTTPWEBSOCKETSERVERCONNECTION_H_

View File

@@ -1,56 +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.
#ifndef WPIUTIL_WPI_HTTPWEBSOCKETSERVERCONNECTION_INC_
#define WPIUTIL_WPI_HTTPWEBSOCKETSERVERCONNECTION_INC_
#include <memory>
#include "wpi/HttpWebSocketServerConnection.h"
namespace wpi {
template <typename Derived>
HttpWebSocketServerConnection<Derived>::HttpWebSocketServerConnection(
std::shared_ptr<uv::Stream> stream, span<const std::string_view> protocols)
: HttpServerConnection{stream},
m_helper{m_request},
m_protocols{protocols.begin(), protocols.end()} {
// Handle upgrade event
m_helper.upgrade.connect([this] {
// Negotiate sub-protocol
SmallVector<std::string_view, 2> protocols{m_protocols.begin(),
m_protocols.end()};
std::string_view protocol = m_helper.MatchProtocol(protocols).second;
// Check that the upgrade is valid
if (!IsValidWsUpgrade(protocol)) {
return;
}
// Disconnect HttpServerConnection header reader
m_dataConn.disconnect();
m_messageCompleteConn.disconnect();
// Accepting the stream may destroy this (as it replaces the stream user
// data), so grab a shared pointer first.
auto self = this->shared_from_this();
// Accept the upgrade
auto ws = m_helper.Accept(m_stream, protocol);
// Set this as the websocket user data to keep it around
ws->SetData(self);
// Store in member
m_websocket = ws.get();
// Call derived class function
ProcessWsUpgrade();
});
}
} // namespace wpi
#endif // WPIUTIL_WPI_HTTPWEBSOCKETSERVERCONNECTION_INC_

View File

@@ -1,16 +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.
#ifndef WPIUTIL_WPI_MIMETYPES_H_
#define WPIUTIL_WPI_MIMETYPES_H_
#include <string_view>
namespace wpi {
std::string_view MimeTypeFromPath(std::string_view path);
} // namespace wpi
#endif // WPIUTIL_WPI_MIMETYPES_H_

View File

@@ -1,61 +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 <stdint.h>
#ifdef __cplusplus
#include <memory>
#include <string>
#include <string_view>
#include <utility>
#include "wpi/span.h"
namespace wpi {
class MulticastServiceAnnouncer {
public:
MulticastServiceAnnouncer(
std::string_view serviceName, std::string_view serviceType, int port,
wpi::span<const std::pair<std::string, std::string>> txt);
MulticastServiceAnnouncer(
std::string_view serviceName, std::string_view serviceType, int port,
wpi::span<const std::pair<std::string_view, std::string_view>> txt);
~MulticastServiceAnnouncer() noexcept;
void Start();
void Stop();
bool HasImplementation() const;
struct Impl;
private:
std::unique_ptr<Impl> pImpl;
};
} // namespace wpi
#endif
#ifdef __cplusplus
extern "C" {
#endif
typedef unsigned int WPI_MulticastServiceAnnouncerHandle; // NOLINT
WPI_MulticastServiceAnnouncerHandle WPI_CreateMulticastServiceAnnouncer(
const char* serviceName, const char* serviceType, int32_t port,
int32_t txtCount, const char** keys, const char** values);
void WPI_FreeMulticastServiceAnnouncer(
WPI_MulticastServiceAnnouncerHandle handle);
void WPI_StartMulticastServiceAnnouncer(
WPI_MulticastServiceAnnouncerHandle handle);
void WPI_StopMulticastServiceAnnouncer(
WPI_MulticastServiceAnnouncerHandle handle);
int32_t WPI_GetMulticastServiceAnnouncerHasImplementation(
WPI_MulticastServiceAnnouncerHandle handle);
#ifdef __cplusplus
} // extern "C"
#endif

View File

@@ -1,102 +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 "wpi/Synchronization.h"
#ifdef __cplusplus
#include <functional>
#include <memory>
#include <string>
#include <string_view>
#include <utility>
#include <vector>
#include "wpi/mutex.h"
#include "wpi/span.h"
namespace wpi {
class MulticastServiceResolver {
public:
explicit MulticastServiceResolver(std::string_view serviceType);
~MulticastServiceResolver() noexcept;
struct ServiceData {
unsigned int ipv4Address;
int port;
std::string serviceName;
std::string hostName;
std::vector<std::pair<std::string, std::string>> txt;
};
void Start();
void Stop();
WPI_EventHandle GetEventHandle() const { return event.GetHandle(); }
std::vector<ServiceData> GetData() {
std::scoped_lock lock{mutex};
event.Reset();
if (queue.empty()) {
return {};
}
std::vector<ServiceData> ret;
queue.swap(ret);
return ret;
}
bool HasImplementation() const;
struct Impl;
private:
void PushData(ServiceData&& data) {
std::scoped_lock lock{mutex};
queue.emplace_back(std::forward<ServiceData>(data));
event.Set();
}
wpi::Event event{true};
std::vector<ServiceData> queue;
wpi::mutex mutex;
std::unique_ptr<Impl> pImpl;
};
} // namespace wpi
#endif
#ifdef __cplusplus
extern "C" {
#endif
typedef unsigned int WPI_MulticastServiceResolverHandle; // NOLINT
WPI_MulticastServiceResolverHandle WPI_CreateMulticastServiceResolver(
const char* serviceType);
void WPI_FreeMulticastServiceResolver(
WPI_MulticastServiceResolverHandle handle);
void WPI_StartMulticastServiceResolver(
WPI_MulticastServiceResolverHandle handle);
void WPI_StopMulticastServiceResolver(
WPI_MulticastServiceResolverHandle handle);
int32_t WPI_GetMulticastServiceResolverHasImplementation(
WPI_MulticastServiceResolverHandle handle);
WPI_EventHandle WPI_GetMulticastServiceResolverEventHandle(
WPI_MulticastServiceResolverHandle handle);
typedef struct WPI_ServiceData { // NOLINT
uint32_t ipv4Address;
int32_t port;
const char* serviceName;
const char* hostName;
int32_t txtCount;
const char** txtKeys;
const char** txtValues;
} WPI_ServiceData;
WPI_ServiceData* WPI_GetMulticastServiceResolverData(
WPI_MulticastServiceResolverHandle handle, int32_t* dataCount);
void WPI_FreeServiceData(WPI_ServiceData* serviceData, int32_t length);
#ifdef __cplusplus
} // extern "C"
#endif

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.
#ifndef WPIUTIL_WPI_NETWORKACCEPTOR_H_
#define WPIUTIL_WPI_NETWORKACCEPTOR_H_
#include <memory>
#include "wpi/NetworkStream.h"
namespace wpi {
class NetworkAcceptor {
public:
NetworkAcceptor() = default;
virtual ~NetworkAcceptor() = default;
virtual int start() = 0;
virtual void shutdown() = 0;
virtual std::unique_ptr<NetworkStream> accept() = 0;
NetworkAcceptor(const NetworkAcceptor&) = delete;
NetworkAcceptor& operator=(const NetworkAcceptor&) = delete;
};
} // namespace wpi
#endif // WPIUTIL_WPI_NETWORKACCEPTOR_H_

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.
#ifndef WPIUTIL_WPI_NETWORKSTREAM_H_
#define WPIUTIL_WPI_NETWORKSTREAM_H_
#include <cstddef>
#include <string_view>
namespace wpi {
class NetworkStream {
public:
NetworkStream() = default;
virtual ~NetworkStream() = default;
enum Error {
kConnectionClosed = 0,
kConnectionReset = -1,
kConnectionTimedOut = -2,
kWouldBlock = -3
};
virtual size_t send(const char* buffer, size_t len, Error* err) = 0;
virtual size_t receive(char* buffer, size_t len, Error* err,
int timeout = 0) = 0;
virtual void close() = 0;
virtual std::string_view getPeerIP() const = 0;
virtual int getPeerPort() const = 0;
virtual void setNoDelay() = 0;
// returns false on failure
virtual bool setBlocking(bool enabled) = 0;
virtual int getNativeHandle() const = 0;
NetworkStream(const NetworkStream&) = delete;
NetworkStream& operator=(const NetworkStream&) = delete;
};
} // namespace wpi
#endif // WPIUTIL_WPI_NETWORKSTREAM_H_

View File

@@ -1,119 +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 <stdint.h>
#include <functional>
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include "wpi/span.h"
#include "wpi/uv/Timer.h"
namespace wpi {
class Logger;
namespace uv {
class GetAddrInfoReq;
class Loop;
class Tcp;
class Timer;
} // namespace uv
/**
* Parallel TCP connector. Attempts parallel resolution and connection to
* multiple servers with automatic retry if none connect.
*
* Each successful TCP connection results in a call to the connected callback.
* For correct operation, the consuming code (either the connected callback or
* e.g. task it starts) must call Succeeded() to indicate if the connection has
* succeeded prior to the reconnect rate timeout. A successful connection
* results in the connector terminating all other connection attempts.
*
* After the reconnect rate times out, all remaining active connection attempts
* are canceled and new ones started.
*/
class ParallelTcpConnector
: public std::enable_shared_from_this<ParallelTcpConnector> {
struct private_init {};
public:
/**
* Create.
*
* @param loop loop
* @param reconnectRate how long to wait after starting connection attempts
* to cancel and attempt connecting again
* @param logger logger
* @param connected callback function when a connection succeeds; may be
* called multiple times if it does not call Succeeded()
* before returning
* @return Parallel connector
*/
static std::shared_ptr<ParallelTcpConnector> Create(
wpi::uv::Loop& loop, wpi::uv::Timer::Time reconnectRate,
wpi::Logger& logger, std::function<void(wpi::uv::Tcp& tcp)> connected) {
return std::make_shared<ParallelTcpConnector>(
loop, reconnectRate, logger, std::move(connected), private_init{});
}
ParallelTcpConnector(wpi::uv::Loop& loop, wpi::uv::Timer::Time reconnectRate,
wpi::Logger& logger,
std::function<void(wpi::uv::Tcp& tcp)> connected,
const private_init&);
~ParallelTcpConnector();
ParallelTcpConnector(const ParallelTcpConnector&) = delete;
ParallelTcpConnector& operator=(const ParallelTcpConnector&) = delete;
/**
* Closes resources, canceling all pending action attempts.
*/
void Close();
/**
* Changes the servers/ports to connect to. Starts connection attempts if not
* already connected.
*
* @param servers array of server/port pairs
*/
void SetServers(
wpi::span<const std::pair<std::string, unsigned int>> servers);
/**
* Tells the parallel connector that the current connection has terminated and
* it is necessary to start reconnection attempts.
*/
void Disconnected();
/**
* Tells the parallel connector that a particular connection has succeeded and
* it should stop trying to connect.
*
* @param tcp connection passed to connected callback
*/
void Succeeded(wpi::uv::Tcp& tcp);
private:
bool IsConnected() const { return m_isConnected || m_servers.empty(); }
void Connect();
void CancelAll(wpi::uv::Tcp* except = nullptr);
wpi::uv::Loop& m_loop;
wpi::Logger& m_logger;
wpi::uv::Timer::Time m_reconnectRate;
std::function<void(wpi::uv::Tcp& tcp)> m_connected;
std::shared_ptr<wpi::uv::Timer> m_reconnectTimer;
std::vector<std::pair<std::string, unsigned int>> m_servers;
std::vector<std::weak_ptr<wpi::uv::GetAddrInfoReq>> m_resolvers;
std::vector<std::weak_ptr<wpi::uv::Tcp>> m_attempts;
bool m_isConnected{false};
};
} // namespace wpi

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.
#ifndef WPIUTIL_WPI_PORTFORWARDER_H_
#define WPIUTIL_WPI_PORTFORWARDER_H_
#pragma once
#include <memory>
#include <string_view>
namespace wpi {
/**
* Forward ports to another host. This is primarily useful for accessing
* Ethernet-connected devices from a computer tethered to the RoboRIO USB port.
*/
class PortForwarder {
public:
PortForwarder(const PortForwarder&) = delete;
PortForwarder& operator=(const PortForwarder&) = delete;
/**
* Get an instance of the PortForwarder class.
*
* This is a singleton to guarantee that there is only a single instance
* regardless of how many times GetInstance is called.
*/
static PortForwarder& GetInstance();
/**
* Forward a local TCP port to a remote host and port.
* Note that local ports less than 1024 won't work as a normal user.
*
* @param port local port number
* @param remoteHost remote IP address / DNS name
* @param remotePort remote port number
*/
void Add(unsigned int port, std::string_view remoteHost,
unsigned int remotePort);
/**
* Stop TCP forwarding on a port.
*
* @param port local port number
*/
void Remove(unsigned int port);
private:
PortForwarder();
struct Impl;
std::unique_ptr<Impl> m_impl;
};
} // namespace wpi
#endif // WPIUTIL_WPI_PORTFORWARDER_H_

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.
#ifndef WPIUTIL_WPI_SOCKETERROR_H_
#define WPIUTIL_WPI_SOCKETERROR_H_
#include <string>
namespace wpi {
int SocketErrno();
std::string SocketStrerror(int code);
inline std::string SocketStrerror() {
return SocketStrerror(SocketErrno());
}
} // namespace wpi
#endif // WPIUTIL_WPI_SOCKETERROR_H_

View File

@@ -1,58 +0,0 @@
/*
TCPAcceptor.h
TCPAcceptor class interface. 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.
*/
#ifndef WPIUTIL_WPI_TCPACCEPTOR_H_
#define WPIUTIL_WPI_TCPACCEPTOR_H_
#include <atomic>
#include <memory>
#include <string>
#include <string_view>
#include "wpi/NetworkAcceptor.h"
#include "wpi/TCPStream.h"
namespace wpi {
class Logger;
class TCPAcceptor : public NetworkAcceptor {
int m_lsd;
int m_port;
std::string m_address;
bool m_listening;
std::atomic_bool m_shutdown;
Logger& m_logger;
public:
TCPAcceptor(int port, std::string_view address, Logger& logger);
~TCPAcceptor() override;
int start() override;
void shutdown() final;
std::unique_ptr<NetworkStream> accept() override;
};
} // namespace wpi
#endif // WPIUTIL_WPI_TCPACCEPTOR_H_

View File

@@ -1,49 +0,0 @@
/*
TCPConnector.h
TCPConnector class interface. 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
*/
#ifndef WPIUTIL_WPI_TCPCONNECTOR_H_
#define WPIUTIL_WPI_TCPCONNECTOR_H_
#include <memory>
#include <utility>
#include "wpi/NetworkStream.h"
#include "wpi/span.h"
namespace wpi {
class Logger;
class TCPConnector {
public:
static std::unique_ptr<NetworkStream> connect(const char* server, int port,
Logger& logger,
int timeout = 0);
static std::unique_ptr<NetworkStream> connect_parallel(
span<const std::pair<const char*, int>> servers, Logger& logger,
int timeout = 0);
};
} // namespace wpi
#endif // WPIUTIL_WPI_TCPCONNECTOR_H_

View File

@@ -1,72 +0,0 @@
/*
TCPStream.h
TCPStream class interface. 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.
*/
#ifndef WPIUTIL_WPI_TCPSTREAM_H_
#define WPIUTIL_WPI_TCPSTREAM_H_
#include <cstddef>
#include <string>
#include <string_view>
#include "wpi/NetworkStream.h"
struct sockaddr_in;
namespace wpi {
class TCPStream : public NetworkStream {
int m_sd;
std::string m_peerIP;
int m_peerPort;
bool m_blocking;
public:
friend class TCPAcceptor;
friend class TCPConnector;
~TCPStream() override;
size_t send(const char* buffer, size_t len, Error* err) override;
size_t receive(char* buffer, size_t len, Error* err,
int timeout = 0) override;
void close() final;
std::string_view getPeerIP() const override;
int getPeerPort() const override;
void setNoDelay() override;
bool setBlocking(bool enabled) override;
int getNativeHandle() const override;
TCPStream(const TCPStream& stream) = delete;
TCPStream& operator=(const TCPStream&) = delete;
private:
bool WaitForReadEvent(int timeout);
TCPStream(int sd, sockaddr_in* address);
TCPStream() = delete;
};
} // namespace wpi
#endif // WPIUTIL_WPI_TCPSTREAM_H_

View File

@@ -1,49 +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.
#ifndef WPIUTIL_WPI_UDPCLIENT_H_
#define WPIUTIL_WPI_UDPCLIENT_H_
#include <string>
#include <string_view>
#include "wpi/SmallVector.h"
#include "wpi/mutex.h"
#include "wpi/span.h"
namespace wpi {
class Logger;
class UDPClient {
int m_lsd;
int m_port;
std::string m_address;
Logger& m_logger;
public:
explicit UDPClient(Logger& logger);
UDPClient(std::string_view address, Logger& logger);
UDPClient(const UDPClient& other) = delete;
UDPClient(UDPClient&& other);
~UDPClient();
UDPClient& operator=(const UDPClient& other) = delete;
UDPClient& operator=(UDPClient&& other);
int start();
int start(int port);
void shutdown();
// The passed in address MUST be a resolved IP address.
int send(span<const uint8_t> data, std::string_view server, int port);
int send(std::string_view data, std::string_view server, int port);
int receive(uint8_t* data_received, int receive_len);
int receive(uint8_t* data_received, int receive_len,
SmallVectorImpl<char>* addr_received, int* port_received);
int set_timeout(double timeout);
};
} // namespace wpi
#endif // WPIUTIL_WPI_UDPCLIENT_H_

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.
#ifndef WPIUTIL_WPI_URLPARSER_H_
#define WPIUTIL_WPI_URLPARSER_H_
#include <string_view>
#include "wpi/StringExtras.h"
#include "wpi/http_parser.h"
namespace wpi {
/**
* Parses a URL into its constiuent components.
* `schema://userinfo@host:port/the/path?query#fragment`
*/
class UrlParser {
public:
/**
* Parse a URL.
* @param in input
* @param isConnect
*/
UrlParser(std::string_view in, bool isConnect) {
m_data = in;
http_parser_url_init(&m_url);
m_error = http_parser_parse_url(in.data(), in.size(), isConnect, &m_url);
}
/**
* Determine if the URL is valid (e.g. the parse was successful).
*/
bool IsValid() const { return !m_error; }
bool HasSchema() const { return (m_url.field_set & (1 << UF_SCHEMA)) != 0; }
bool HasHost() const { return (m_url.field_set & (1 << UF_HOST)) != 0; }
bool HasPort() const { return (m_url.field_set & (1 << UF_PORT)) != 0; }
bool HasPath() const { return (m_url.field_set & (1 << UF_PATH)) != 0; }
bool HasQuery() const { return (m_url.field_set & (1 << UF_QUERY)) != 0; }
bool HasFragment() const {
return (m_url.field_set & (1 << UF_FRAGMENT)) != 0;
}
bool HasUserInfo() const {
return (m_url.field_set & (1 << UF_USERINFO)) != 0;
}
std::string_view GetSchema() const {
return wpi::substr(m_data, m_url.field_data[UF_SCHEMA].off,
m_url.field_data[UF_SCHEMA].len);
}
std::string_view GetHost() const {
return wpi::substr(m_data, m_url.field_data[UF_HOST].off,
m_url.field_data[UF_HOST].len);
}
unsigned int GetPort() const { return m_url.port; }
std::string_view GetPath() const {
return wpi::substr(m_data, m_url.field_data[UF_PATH].off,
m_url.field_data[UF_PATH].len);
}
std::string_view GetQuery() const {
return wpi::substr(m_data, m_url.field_data[UF_QUERY].off,
m_url.field_data[UF_QUERY].len);
}
std::string_view GetFragment() const {
return wpi::substr(m_data, m_url.field_data[UF_FRAGMENT].off,
m_url.field_data[UF_FRAGMENT].len);
}
std::string_view GetUserInfo() const {
return wpi::substr(m_data, m_url.field_data[UF_USERINFO].off,
m_url.field_data[UF_USERINFO].len);
}
private:
bool m_error;
std::string_view m_data;
http_parser_url m_url;
};
} // namespace wpi
#endif // WPIUTIL_WPI_URLPARSER_H_

View File

@@ -1,477 +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.
#ifndef WPIUTIL_WPI_WEBSOCKET_H_
#define WPIUTIL_WPI_WEBSOCKET_H_
#include <stdint.h>
#include <functional>
#include <initializer_list>
#include <memory>
#include <string>
#include <string_view>
#include <utility>
#include "wpi/Signal.h"
#include "wpi/SmallVector.h"
#include "wpi/span.h"
#include "wpi/uv/Buffer.h"
#include "wpi/uv/Error.h"
#include "wpi/uv/Timer.h"
namespace wpi {
namespace uv {
class Stream;
} // namespace uv
/**
* RFC 6455 compliant WebSocket client and server implementation.
*/
class WebSocket : public std::enable_shared_from_this<WebSocket> {
struct private_init {};
static constexpr uint8_t kOpCont = 0x00;
static constexpr uint8_t kOpText = 0x01;
static constexpr uint8_t kOpBinary = 0x02;
static constexpr uint8_t kOpClose = 0x08;
static constexpr uint8_t kOpPing = 0x09;
static constexpr uint8_t kOpPong = 0x0A;
static constexpr uint8_t kOpMask = 0x0F;
static constexpr uint8_t kFlagFin = 0x80;
static constexpr uint8_t kFlagMasking = 0x80;
static constexpr uint8_t kLenMask = 0x7f;
public:
WebSocket(uv::Stream& stream, bool server, const private_init&);
WebSocket(const WebSocket&) = delete;
WebSocket(WebSocket&&) = delete;
WebSocket& operator=(const WebSocket&) = delete;
WebSocket& operator=(WebSocket&&) = delete;
~WebSocket();
/**
* Connection states.
*/
enum State {
/** The connection is not yet open. */
CONNECTING = 0,
/** The connection is open and ready to communicate. */
OPEN,
/** The connection is in the process of closing. */
CLOSING,
/** The connection failed. */
FAILED,
/** The connection is closed. */
CLOSED
};
/**
* Client connection options.
*/
struct ClientOptions {
ClientOptions() : handshakeTimeout{(uv::Timer::Time::max)()} {}
/** Timeout for the handshake request. */
uv::Timer::Time handshakeTimeout; // NOLINT
/** Additional headers to include in handshake. */
span<const std::pair<std::string_view, std::string_view>> extraHeaders;
};
/**
* Starts a client connection by performing the initial client handshake.
* An open event is emitted when the handshake completes.
* This sets the stream user data to the websocket.
* @param stream Connection stream
* @param uri The Request-URI to send
* @param host The host or host:port to send
* @param protocols The list of subprotocols
* @param options Handshake options
*/
static std::shared_ptr<WebSocket> CreateClient(
uv::Stream& stream, std::string_view uri, std::string_view host,
span<const std::string_view> protocols = {},
const ClientOptions& options = {});
/**
* Starts a client connection by performing the initial client handshake.
* An open event is emitted when the handshake completes.
* This sets the stream user data to the websocket.
* @param stream Connection stream
* @param uri The Request-URI to send
* @param host The host or host:port to send
* @param protocols The list of subprotocols
* @param options Handshake options
*/
static std::shared_ptr<WebSocket> CreateClient(
uv::Stream& stream, std::string_view uri, std::string_view host,
std::initializer_list<std::string_view> protocols,
const ClientOptions& options = {}) {
return CreateClient(stream, uri, host, {protocols.begin(), protocols.end()},
options);
}
/**
* Starts a server connection by performing the initial server side handshake.
* This should be called after the HTTP headers have been received.
* An open event is emitted when the handshake completes.
* This sets the stream user data to the websocket.
* @param stream Connection stream
* @param key The value of the Sec-WebSocket-Key header field in the client
* request
* @param version The value of the Sec-WebSocket-Version header field in the
* client request
* @param protocol The subprotocol to send to the client (in the
* Sec-WebSocket-Protocol header field).
*/
static std::shared_ptr<WebSocket> CreateServer(
uv::Stream& stream, std::string_view key, std::string_view version,
std::string_view protocol = {});
/**
* Get connection state.
*/
State GetState() const { return m_state; }
/**
* Return if the connection is open. Messages can only be sent on open
* connections.
*/
bool IsOpen() const { return m_state == OPEN; }
/**
* Get the underlying stream.
*/
uv::Stream& GetStream() const { return m_stream; }
/**
* Get the selected sub-protocol. Only valid in or after the open() event.
*/
std::string_view GetProtocol() const { return m_protocol; }
/**
* Set the maximum message size. Default is 128 KB. If configured to combine
* fragments this maximum applies to the entire message (all combined
* fragments).
* @param size Maximum message size in bytes
*/
void SetMaxMessageSize(size_t size) { m_maxMessageSize = size; }
/**
* Set whether or not fragmented frames should be combined. Default is to
* combine. If fragmented frames are combined, the text and binary callbacks
* will always have the second parameter (fin) set to true.
* @param combine True if fragmented frames should be combined.
*/
void SetCombineFragments(bool combine) { m_combineFragments = combine; }
/**
* Initiate a closing handshake.
* @param code A numeric status code (defaults to 1005, no status code)
* @param reason A human-readable string explaining why the connection is
* closing (optional).
*/
void Close(uint16_t code = 1005, std::string_view reason = {});
/**
* Send a text message.
* @param data UTF-8 encoded data to send
* @param callback Callback which is invoked when the write completes.
*/
void SendText(span<const uv::Buffer> data,
std::function<void(span<uv::Buffer>, uv::Error)> callback) {
Send(kFlagFin | kOpText, data, std::move(callback));
}
/**
* Send a text message.
* @param data UTF-8 encoded data to send
* @param callback Callback which is invoked when the write completes.
*/
void SendText(std::initializer_list<uv::Buffer> data,
std::function<void(span<uv::Buffer>, uv::Error)> callback) {
SendText({data.begin(), data.end()}, std::move(callback));
}
/**
* Send a binary message.
* @param data Data to send
* @param callback Callback which is invoked when the write completes.
*/
void SendBinary(span<const uv::Buffer> data,
std::function<void(span<uv::Buffer>, uv::Error)> callback) {
Send(kFlagFin | kOpBinary, data, std::move(callback));
}
/**
* Send a binary message.
* @param data Data to send
* @param callback Callback which is invoked when the write completes.
*/
void SendBinary(std::initializer_list<uv::Buffer> data,
std::function<void(span<uv::Buffer>, uv::Error)> callback) {
SendBinary({data.begin(), data.end()}, std::move(callback));
}
/**
* Send a text message fragment. This must be followed by one or more
* SendFragment() calls, where the last one has fin=True, to complete the
* message.
* @param data UTF-8 encoded data to send
* @param callback Callback which is invoked when the write completes.
*/
void SendTextFragment(
span<const uv::Buffer> data,
std::function<void(span<uv::Buffer>, uv::Error)> callback) {
Send(kOpText, data, std::move(callback));
}
/**
* Send a text message fragment. This must be followed by one or more
* SendFragment() calls, where the last one has fin=True, to complete the
* message.
* @param data UTF-8 encoded data to send
* @param callback Callback which is invoked when the write completes.
*/
void SendTextFragment(
std::initializer_list<uv::Buffer> data,
std::function<void(span<uv::Buffer>, uv::Error)> callback) {
SendTextFragment({data.begin(), data.end()}, std::move(callback));
}
/**
* Send a text message fragment. This must be followed by one or more
* SendFragment() calls, where the last one has fin=True, to complete the
* message.
* @param data Data to send
* @param callback Callback which is invoked when the write completes.
*/
void SendBinaryFragment(
span<const uv::Buffer> data,
std::function<void(span<uv::Buffer>, uv::Error)> callback) {
Send(kOpBinary, data, std::move(callback));
}
/**
* Send a text message fragment. This must be followed by one or more
* SendFragment() calls, where the last one has fin=True, to complete the
* message.
* @param data Data to send
* @param callback Callback which is invoked when the write completes.
*/
void SendBinaryFragment(
std::initializer_list<uv::Buffer> data,
std::function<void(span<uv::Buffer>, uv::Error)> callback) {
SendBinaryFragment({data.begin(), data.end()}, std::move(callback));
}
/**
* Send a continuation frame. This is used to send additional parts of a
* message started with SendTextFragment() or SendBinaryFragment().
* @param data Data to send
* @param fin Set to true if this is the final fragment of the message
* @param callback Callback which is invoked when the write completes.
*/
void SendFragment(span<const uv::Buffer> data, bool fin,
std::function<void(span<uv::Buffer>, uv::Error)> callback) {
Send(kOpCont | (fin ? kFlagFin : 0), data, std::move(callback));
}
/**
* Send a continuation frame. This is used to send additional parts of a
* message started with SendTextFragment() or SendBinaryFragment().
* @param data Data to send
* @param fin Set to true if this is the final fragment of the message
* @param callback Callback which is invoked when the write completes.
*/
void SendFragment(std::initializer_list<uv::Buffer> data, bool fin,
std::function<void(span<uv::Buffer>, uv::Error)> callback) {
SendFragment({data.begin(), data.end()}, fin, std::move(callback));
}
/**
* Send a ping frame with no data.
* @param callback Optional callback which is invoked when the ping frame
* write completes.
*/
void SendPing(std::function<void(uv::Error)> callback = nullptr) {
SendPing({}, [f = std::move(callback)](auto bufs, uv::Error err) {
if (f) {
f(err);
}
});
}
/**
* Send a ping frame.
* @param data Data to send in the ping frame
* @param callback Callback which is invoked when the ping frame
* write completes.
*/
void SendPing(span<const uv::Buffer> data,
std::function<void(span<uv::Buffer>, uv::Error)> callback) {
Send(kFlagFin | kOpPing, data, std::move(callback));
}
/**
* Send a ping frame.
* @param data Data to send in the ping frame
* @param callback Callback which is invoked when the ping frame
* write completes.
*/
void SendPing(std::initializer_list<uv::Buffer> data,
std::function<void(span<uv::Buffer>, uv::Error)> callback) {
SendPing({data.begin(), data.end()}, std::move(callback));
}
/**
* Send a pong frame with no data.
* @param callback Optional callback which is invoked when the pong frame
* write completes.
*/
void SendPong(std::function<void(uv::Error)> callback = nullptr) {
SendPong({}, [f = std::move(callback)](auto bufs, uv::Error err) {
if (f) {
f(err);
}
});
}
/**
* Send a pong frame.
* @param data Data to send in the pong frame
* @param callback Callback which is invoked when the pong frame
* write completes.
*/
void SendPong(span<const uv::Buffer> data,
std::function<void(span<uv::Buffer>, uv::Error)> callback) {
Send(kFlagFin | kOpPong, data, std::move(callback));
}
/**
* Send a pong frame.
* @param data Data to send in the pong frame
* @param callback Callback which is invoked when the pong frame
* write completes.
*/
void SendPong(std::initializer_list<uv::Buffer> data,
std::function<void(span<uv::Buffer>, uv::Error)> callback) {
SendPong({data.begin(), data.end()}, std::move(callback));
}
/**
* Fail the connection.
*/
void Fail(uint16_t code = 1002, std::string_view reason = "protocol error");
/**
* Forcibly close the connection.
*/
void Terminate(uint16_t code = 1006, std::string_view reason = "terminated");
/**
* Gets user-defined data.
* @return User-defined data if any, nullptr otherwise.
*/
template <typename T = void>
std::shared_ptr<T> GetData() const {
return std::static_pointer_cast<T>(m_data);
}
/**
* Sets user-defined data.
* @param data User-defined arbitrary data.
*/
void SetData(std::shared_ptr<void> data) { m_data = std::move(data); }
/**
* Shuts down and closes the underlying stream.
*/
void Shutdown();
/**
* Open event. Emitted when the connection is open and ready to communicate.
* The parameter is the selected subprotocol.
*/
sig::Signal<std::string_view> open;
/**
* Close event. Emitted when the connection is closed. The first parameter
* is a numeric value indicating the status code explaining why the connection
* has been closed. The second parameter is a human-readable string
* explaining the reason why the connection has been closed.
*/
sig::Signal<uint16_t, std::string_view> closed;
/**
* Text message event. Emitted when a text message is received.
* The first parameter is the data, the second parameter is true if the
* data is the last fragment of the message.
*/
sig::Signal<std::string_view, bool> text;
/**
* Binary message event. Emitted when a binary message is received.
* The first parameter is the data, the second parameter is true if the
* data is the last fragment of the message.
*/
sig::Signal<span<const uint8_t>, bool> binary;
/**
* Ping event. Emitted when a ping message is received.
*/
sig::Signal<span<const uint8_t>> ping;
/**
* Pong event. Emitted when a pong message is received.
*/
sig::Signal<span<const uint8_t>> pong;
private:
// user data
std::shared_ptr<void> m_data;
// constructor parameters
uv::Stream& m_stream;
bool m_server;
// subprotocol, set via constructor (server) or handshake (client)
std::string m_protocol;
// user-settable configuration
size_t m_maxMessageSize = 128 * 1024;
bool m_combineFragments = true;
// operating state
State m_state = CONNECTING;
// incoming message buffers/state
SmallVector<uint8_t, 14> m_header;
size_t m_headerSize = 0;
SmallVector<uint8_t, 1024> m_payload;
size_t m_frameStart = 0;
uint64_t m_frameSize = UINT64_MAX;
uint8_t m_fragmentOpcode = 0;
// temporary data used only during client handshake
class ClientHandshakeData;
std::unique_ptr<ClientHandshakeData> m_clientHandshake;
void StartClient(std::string_view uri, std::string_view host,
span<const std::string_view> protocols,
const ClientOptions& options);
void StartServer(std::string_view key, std::string_view version,
std::string_view protocol);
void SendClose(uint16_t code, std::string_view reason);
void SetClosed(uint16_t code, std::string_view reason, bool failed = false);
void HandleIncoming(uv::Buffer& buf, size_t size);
void Send(uint8_t opcode, span<const uv::Buffer> data,
std::function<void(span<uv::Buffer>, uv::Error)> callback);
};
} // namespace wpi
#endif // WPIUTIL_WPI_WEBSOCKET_H_

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.
#ifndef WPIUTIL_WPI_WEBSOCKETSERVER_H_
#define WPIUTIL_WPI_WEBSOCKETSERVER_H_
#include <functional>
#include <initializer_list>
#include <memory>
#include <string>
#include <string_view>
#include <utility>
#include "wpi/HttpParser.h"
#include "wpi/Signal.h"
#include "wpi/SmallString.h"
#include "wpi/SmallVector.h"
#include "wpi/WebSocket.h"
#include "wpi/span.h"
namespace wpi {
namespace uv {
class Stream;
} // namespace uv
/**
* WebSocket HTTP server helper. Handles websocket-specific headers. User
* must provide the HttpParser.
*/
class WebSocketServerHelper {
public:
/**
* Constructor.
* @param req HttpParser for request
*/
explicit WebSocketServerHelper(HttpParser& req);
/**
* Get whether or not this was a websocket upgrade.
* Only valid during and after the upgrade event.
*/
bool IsWebsocket() const { return m_websocket; }
/**
* Try to find a match to the list of sub-protocols provided by the client.
* The list is priority ordered, so the first match wins.
* Only valid during and after the upgrade event.
* @param protocols Acceptable protocols
* @return Pair; first item is true if a match was made, false if not.
* Second item is the matched protocol if a match was made, otherwise
* is empty.
*/
std::pair<bool, std::string_view> MatchProtocol(
span<const std::string_view> protocols);
/**
* Try to find a match to the list of sub-protocols provided by the client.
* The list is priority ordered, so the first match wins.
* Only valid during and after the upgrade event.
* @param protocols Acceptable protocols
* @return Pair; first item is true if a match was made, false if not.
* Second item is the matched protocol if a match was made, otherwise
* is empty.
*/
std::pair<bool, std::string_view> MatchProtocol(
std::initializer_list<std::string_view> protocols) {
return MatchProtocol({protocols.begin(), protocols.end()});
}
/**
* Accept the upgrade. Disconnect other readers (such as the HttpParser
* reader) before calling this. See also WebSocket::CreateServer().
* @param stream Connection stream
* @param protocol The subprotocol to send to the client
*/
std::shared_ptr<WebSocket> Accept(uv::Stream& stream,
std::string_view protocol = {}) {
return WebSocket::CreateServer(stream, m_key, m_version, protocol);
}
bool IsUpgrade() const { return m_gotHost && m_websocket; }
/**
* Upgrade event. Call Accept() to accept the upgrade.
*/
sig::Signal<> upgrade;
private:
bool m_gotHost = false;
bool m_websocket = false;
SmallVector<std::string, 2> m_protocols;
SmallString<64> m_key;
SmallString<16> m_version;
};
/**
* Dedicated WebSocket server.
*/
class WebSocketServer : public std::enable_shared_from_this<WebSocketServer> {
struct private_init {};
public:
/**
* Server options.
*/
struct ServerOptions {
/**
* Checker for URL. Return true if URL should be accepted. By default all
* URLs are accepted.
*/
std::function<bool(std::string_view)> checkUrl;
/**
* Checker for Host header. Return true if Host should be accepted. By
* default all hosts are accepted.
*/
std::function<bool(std::string_view)> checkHost;
};
/**
* Private constructor.
*/
WebSocketServer(uv::Stream& stream, span<const std::string_view> protocols,
ServerOptions options, const private_init&);
/**
* Starts a dedicated WebSocket server on the provided connection. The
* connection should be an accepted client stream.
* This also sets the stream user data to the socket server.
* A connected event is emitted when the connection is opened.
* @param stream Connection stream
* @param protocols Acceptable subprotocols
* @param options Handshake options
*/
static std::shared_ptr<WebSocketServer> Create(
uv::Stream& stream, span<const std::string_view> protocols = {},
const ServerOptions& options = {});
/**
* Starts a dedicated WebSocket server on the provided connection. The
* connection should be an accepted client stream.
* This also sets the stream user data to the socket server.
* A connected event is emitted when the connection is opened.
* @param stream Connection stream
* @param protocols Acceptable subprotocols
* @param options Handshake options
*/
static std::shared_ptr<WebSocketServer> Create(
uv::Stream& stream, std::initializer_list<std::string_view> protocols,
const ServerOptions& options = {}) {
return Create(stream, {protocols.begin(), protocols.end()}, options);
}
/**
* Connected event. First parameter is the URL, second is the websocket.
*/
sig::Signal<std::string_view, WebSocket&> connected;
private:
uv::Stream& m_stream;
HttpParser m_req{HttpParser::kRequest};
WebSocketServerHelper m_helper;
SmallVector<std::string, 2> m_protocols;
ServerOptions m_options;
bool m_aborted = false;
sig::ScopedConnection m_dataConn;
sig::ScopedConnection m_errorConn;
sig::ScopedConnection m_endConn;
void Abort(uint16_t code, std::string_view reason);
};
} // namespace wpi
#endif // WPIUTIL_WPI_WEBSOCKETSERVER_H_

View File

@@ -1,285 +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.
#ifndef WPIUTIL_WPI_WORKERTHREAD_H_
#define WPIUTIL_WPI_WORKERTHREAD_H_
#include <functional>
#include <memory>
#include <tuple>
#include <utility>
#include <vector>
#include "wpi/SafeThread.h"
#include "wpi/future.h"
#include "wpi/uv/Async.h"
namespace wpi {
namespace detail {
template <typename R>
struct WorkerThreadAsync {
using AfterWorkFunction = std::function<void(R)>;
~WorkerThreadAsync() { UnsetLoop(); }
void SetLoop(uv::Loop& loop) {
auto async = uv::Async<AfterWorkFunction, R>::Create(loop);
async->wakeup.connect(
[](AfterWorkFunction func, R result) { func(result); });
m_async = async;
}
void UnsetLoop() {
if (auto async = m_async.lock()) {
async->Close();
m_async.reset();
}
}
std::weak_ptr<uv::Async<AfterWorkFunction, R>> m_async;
};
template <>
struct WorkerThreadAsync<void> {
using AfterWorkFunction = std::function<void()>;
~WorkerThreadAsync() { RemoveLoop(); }
void SetLoop(uv::Loop& loop) {
auto async = uv::Async<AfterWorkFunction>::Create(loop);
async->wakeup.connect([](AfterWorkFunction func) { func(); });
m_async = async;
}
void RemoveLoop() {
if (auto async = m_async.lock()) {
async->Close();
m_async.reset();
}
}
std::weak_ptr<uv::Async<AfterWorkFunction>> m_async;
};
template <typename R, typename... T>
struct WorkerThreadRequest {
using WorkFunction = std::function<R(T...)>;
using AfterWorkFunction = typename WorkerThreadAsync<R>::AfterWorkFunction;
WorkerThreadRequest() = default;
WorkerThreadRequest(uint64_t promiseId_, WorkFunction work_,
std::tuple<T...> params_)
: promiseId(promiseId_),
work(std::move(work_)),
params(std::move(params_)) {}
WorkerThreadRequest(WorkFunction work_, AfterWorkFunction afterWork_,
std::tuple<T...> params_)
: promiseId(0),
work(std::move(work_)),
afterWork(std::move(afterWork_)),
params(std::move(params_)) {}
uint64_t promiseId;
WorkFunction work;
AfterWorkFunction afterWork;
std::tuple<T...> params;
};
template <typename R, typename... T>
class WorkerThreadThread : public SafeThread {
public:
using Request = WorkerThreadRequest<R, T...>;
void Main() override;
std::vector<Request> m_requests;
PromiseFactory<R> m_promises;
detail::WorkerThreadAsync<R> m_async;
};
template <typename R, typename... T>
void RunWorkerThreadRequest(WorkerThreadThread<R, T...>& thr,
WorkerThreadRequest<R, T...>& req) {
R result = std::apply(req.work, std::move(req.params));
if (req.afterWork) {
if (auto async = thr.m_async.m_async.lock()) {
async->Send(std::move(req.afterWork), std::move(result));
}
} else {
thr.m_promises.SetValue(req.promiseId, std::move(result));
}
}
template <typename... T>
void RunWorkerThreadRequest(WorkerThreadThread<void, T...>& thr,
WorkerThreadRequest<void, T...>& req) {
std::apply(req.work, req.params);
if (req.afterWork) {
if (auto async = thr.m_async.m_async.lock()) {
async->Send(std::move(req.afterWork));
}
} else {
thr.m_promises.SetValue(req.promiseId);
}
}
template <typename R, typename... T>
void WorkerThreadThread<R, T...>::Main() {
std::vector<Request> requests;
while (m_active) {
std::unique_lock lock(m_mutex);
m_cond.wait(lock, [&] { return !m_active || !m_requests.empty(); });
if (!m_active) {
break;
}
// don't want to hold the lock while executing the callbacks
requests.swap(m_requests);
lock.unlock();
for (auto&& req : requests) {
if (!m_active) {
break; // requests may be long-running
}
RunWorkerThreadRequest(*this, req);
}
requests.clear();
m_promises.Notify();
}
}
} // namespace detail
template <typename T>
class WorkerThread;
template <typename R, typename... T>
class WorkerThread<R(T...)> final {
using Thread = detail::WorkerThreadThread<R, T...>;
public:
using WorkFunction = std::function<R(T...)>;
using AfterWorkFunction =
typename detail::WorkerThreadAsync<R>::AfterWorkFunction;
WorkerThread() { m_owner.Start(); }
/**
* Set the loop. This must be called from the loop thread.
* Subsequent calls to QueueWorkThen will run afterWork on the provided
* loop (via an async handle).
*
* @param loop the loop to use for running afterWork routines
*/
void SetLoop(uv::Loop& loop) {
if (auto thr = m_owner.GetThread()) {
thr->m_async.SetLoop(loop);
}
}
/**
* Set the loop. This must be called from the loop thread.
* Subsequent calls to QueueWorkThen will run afterWork on the provided
* loop (via an async handle).
*
* @param loop the loop to use for running afterWork routines
*/
void SetLoop(std::shared_ptr<uv::Loop> loop) { SetLoop(*loop); }
/**
* Unset the loop. This must be called from the loop thread.
* Subsequent calls to QueueWorkThen will no longer run afterWork.
*/
void UnsetLoop() {
if (auto thr = m_owner.GetThread()) {
thr->m_async.UnsetLoop();
}
}
/**
* Get the handle used by QueueWorkThen() to run afterWork.
* This handle is set by SetLoop().
* Calling Close() on this handle is the same as calling UnsetLoop().
*
* @return The handle (if nullptr, no handle is set)
*/
std::shared_ptr<uv::Handle> GetHandle() const {
if (auto thr = m_owner.GetThread()) {
return thr->m_async.m_async.lock();
} else {
return nullptr;
}
}
/**
* Wakeup the worker thread, call the work function, and return a future for
* the result.
*
* Its safe to call this function from any thread.
* The work function will be called on the worker thread.
*
* The future will return a default-constructed result if this class is
* destroyed while waiting for a result.
*
* @param work Work function (called on worker thread)
* @param u Arguments to work function
*/
template <typename... U>
future<R> QueueWork(WorkFunction work, U&&... u) {
if (auto thr = m_owner.GetThread()) {
// create the future
uint64_t req = thr->m_promises.CreateRequest();
// add the parameters to the input queue
thr->m_requests.emplace_back(
req, std::move(work), std::forward_as_tuple(std::forward<U>(u)...));
// signal the thread
thr->m_cond.notify_one();
// return future
return thr->m_promises.CreateFuture(req);
}
// XXX: is this the right thing to do?
return future<R>();
}
/**
* Wakeup the worker thread, call the work function, and call the afterWork
* function with the result on the loop set by SetLoop().
*
* Its safe to call this function from any thread.
* The work function will be called on the worker thread, and the afterWork
* function will be called on the loop thread.
*
* SetLoop() must be called prior to calling this function for afterWork to
* be called.
*
* @param work Work function (called on worker thread)
* @param afterWork After work function (called on loop thread)
* @param u Arguments to work function
*/
template <typename... U>
void QueueWorkThen(WorkFunction work, AfterWorkFunction afterWork, U&&... u) {
if (auto thr = m_owner.GetThread()) {
// add the parameters to the input queue
thr->m_requests.emplace_back(
std::move(work), std::move(afterWork),
std::forward_as_tuple(std::forward<U>(u)...));
// signal the thread
thr->m_cond.notify_one();
}
}
private:
SafeThreadOwner<Thread> m_owner;
};
} // namespace wpi
#endif // WPIUTIL_WPI_WORKERTHREAD_H_

View File

@@ -1,19 +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.
#ifndef WPIUTIL_WPI_HOSTNAME_H_
#define WPIUTIL_WPI_HOSTNAME_H_
#include <string>
#include <string_view>
namespace wpi {
template <typename T>
class SmallVectorImpl;
std::string GetHostname();
std::string_view GetHostname(SmallVectorImpl<char>& name);
} // namespace wpi
#endif // WPIUTIL_WPI_HOSTNAME_H_

View File

@@ -1,421 +0,0 @@
/* Copyright Joyent, Inc. and other Node contributors. All rights reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*/
#ifndef wpi_http_parser_h
#define wpi_http_parser_h
/* Also update SONAME in the Makefile whenever you change these. */
#define HTTP_PARSER_VERSION_MAJOR 2
#define HTTP_PARSER_VERSION_MINOR 8
#define HTTP_PARSER_VERSION_PATCH 1
#include <stddef.h>
#include <stdint.h>
/* Compile with -DHTTP_PARSER_STRICT=0 to make less checks, but run
* faster
*/
#ifndef HTTP_PARSER_STRICT
# define HTTP_PARSER_STRICT 1
#endif
/* Maximium header size allowed. If the macro is not defined
* before including this header then the default is used. To
* change the maximum header size, define the macro in the build
* environment (e.g. -DHTTP_MAX_HEADER_SIZE=<value>). To remove
* the effective limit on the size of the header, define the macro
* to a very large number (e.g. -DHTTP_MAX_HEADER_SIZE=0x7fffffff)
*/
#ifndef HTTP_MAX_HEADER_SIZE
# define HTTP_MAX_HEADER_SIZE (80*1024)
#endif
namespace wpi {
struct http_parser;
struct http_parser_settings;
/* Callbacks should return non-zero to indicate an error. The parser will
* then halt execution.
*
* The one exception is on_headers_complete. In a HTTP_RESPONSE parser
* returning '1' from on_headers_complete will tell the parser that it
* should not expect a body. This is used when receiving a response to a
* HEAD request which may contain 'Content-Length' or 'Transfer-Encoding:
* chunked' headers that indicate the presence of a body.
*
* Returning `2` from on_headers_complete will tell parser that it should not
* expect neither a body nor any further responses on this connection. This is
* useful for handling responses to a CONNECT request which may not contain
* `Upgrade` or `Connection: upgrade` headers.
*
* http_data_cb does not return data chunks. It will be called arbitrarily
* many times for each string. E.G. you might get 10 callbacks for "on_url"
* each providing just a few characters more data.
*/
typedef int (*http_data_cb) (http_parser*, const char *at, size_t length);
typedef int (*http_cb) (http_parser*);
/* Status Codes */
#define HTTP_STATUS_MAP(XX) \
XX(100, CONTINUE, Continue) \
XX(101, SWITCHING_PROTOCOLS, Switching Protocols) \
XX(102, PROCESSING, Processing) \
XX(200, OK, OK) \
XX(201, CREATED, Created) \
XX(202, ACCEPTED, Accepted) \
XX(203, NON_AUTHORITATIVE_INFORMATION, Non-Authoritative Information) \
XX(204, NO_CONTENT, No Content) \
XX(205, RESET_CONTENT, Reset Content) \
XX(206, PARTIAL_CONTENT, Partial Content) \
XX(207, MULTI_STATUS, Multi-Status) \
XX(208, ALREADY_REPORTED, Already Reported) \
XX(226, IM_USED, IM Used) \
XX(300, MULTIPLE_CHOICES, Multiple Choices) \
XX(301, MOVED_PERMANENTLY, Moved Permanently) \
XX(302, FOUND, Found) \
XX(303, SEE_OTHER, See Other) \
XX(304, NOT_MODIFIED, Not Modified) \
XX(305, USE_PROXY, Use Proxy) \
XX(307, TEMPORARY_REDIRECT, Temporary Redirect) \
XX(308, PERMANENT_REDIRECT, Permanent Redirect) \
XX(400, BAD_REQUEST, Bad Request) \
XX(401, UNAUTHORIZED, Unauthorized) \
XX(402, PAYMENT_REQUIRED, Payment Required) \
XX(403, FORBIDDEN, Forbidden) \
XX(404, NOT_FOUND, Not Found) \
XX(405, METHOD_NOT_ALLOWED, Method Not Allowed) \
XX(406, NOT_ACCEPTABLE, Not Acceptable) \
XX(407, PROXY_AUTHENTICATION_REQUIRED, Proxy Authentication Required) \
XX(408, REQUEST_TIMEOUT, Request Timeout) \
XX(409, CONFLICT, Conflict) \
XX(410, GONE, Gone) \
XX(411, LENGTH_REQUIRED, Length Required) \
XX(412, PRECONDITION_FAILED, Precondition Failed) \
XX(413, PAYLOAD_TOO_LARGE, Payload Too Large) \
XX(414, URI_TOO_LONG, URI Too Long) \
XX(415, UNSUPPORTED_MEDIA_TYPE, Unsupported Media Type) \
XX(416, RANGE_NOT_SATISFIABLE, Range Not Satisfiable) \
XX(417, EXPECTATION_FAILED, Expectation Failed) \
XX(421, MISDIRECTED_REQUEST, Misdirected Request) \
XX(422, UNPROCESSABLE_ENTITY, Unprocessable Entity) \
XX(423, LOCKED, Locked) \
XX(424, FAILED_DEPENDENCY, Failed Dependency) \
XX(426, UPGRADE_REQUIRED, Upgrade Required) \
XX(428, PRECONDITION_REQUIRED, Precondition Required) \
XX(429, TOO_MANY_REQUESTS, Too Many Requests) \
XX(431, REQUEST_HEADER_FIELDS_TOO_LARGE, Request Header Fields Too Large) \
XX(451, UNAVAILABLE_FOR_LEGAL_REASONS, Unavailable For Legal Reasons) \
XX(500, INTERNAL_SERVER_ERROR, Internal Server Error) \
XX(501, NOT_IMPLEMENTED, Not Implemented) \
XX(502, BAD_GATEWAY, Bad Gateway) \
XX(503, SERVICE_UNAVAILABLE, Service Unavailable) \
XX(504, GATEWAY_TIMEOUT, Gateway Timeout) \
XX(505, HTTP_VERSION_NOT_SUPPORTED, HTTP Version Not Supported) \
XX(506, VARIANT_ALSO_NEGOTIATES, Variant Also Negotiates) \
XX(507, INSUFFICIENT_STORAGE, Insufficient Storage) \
XX(508, LOOP_DETECTED, Loop Detected) \
XX(510, NOT_EXTENDED, Not Extended) \
XX(511, NETWORK_AUTHENTICATION_REQUIRED, Network Authentication Required) \
enum http_status
{
#define XX(num, name, string) HTTP_STATUS_##name = num,
HTTP_STATUS_MAP(XX)
#undef XX
};
/* Request Methods */
#define HTTP_METHOD_MAP(XX) \
XX(0, DELETE, DELETE) \
XX(1, GET, GET) \
XX(2, HEAD, HEAD) \
XX(3, POST, POST) \
XX(4, PUT, PUT) \
/* pathological */ \
XX(5, CONNECT, CONNECT) \
XX(6, OPTIONS, OPTIONS) \
XX(7, TRACE, TRACE) \
/* WebDAV */ \
XX(8, COPY, COPY) \
XX(9, LOCK, LOCK) \
XX(10, MKCOL, MKCOL) \
XX(11, MOVE, MOVE) \
XX(12, PROPFIND, PROPFIND) \
XX(13, PROPPATCH, PROPPATCH) \
XX(14, SEARCH, SEARCH) \
XX(15, UNLOCK, UNLOCK) \
XX(16, BIND, BIND) \
XX(17, REBIND, REBIND) \
XX(18, UNBIND, UNBIND) \
XX(19, ACL, ACL) \
/* subversion */ \
XX(20, REPORT, REPORT) \
XX(21, MKACTIVITY, MKACTIVITY) \
XX(22, CHECKOUT, CHECKOUT) \
XX(23, MERGE, MERGE) \
/* upnp */ \
XX(24, MSEARCH, M-SEARCH) \
XX(25, NOTIFY, NOTIFY) \
XX(26, SUBSCRIBE, SUBSCRIBE) \
XX(27, UNSUBSCRIBE, UNSUBSCRIBE) \
/* RFC-5789 */ \
XX(28, PATCH, PATCH) \
XX(29, PURGE, PURGE) \
/* CalDAV */ \
XX(30, MKCALENDAR, MKCALENDAR) \
/* RFC-2068, section 19.6.1.2 */ \
XX(31, LINK, LINK) \
XX(32, UNLINK, UNLINK) \
/* icecast */ \
XX(33, SOURCE, SOURCE) \
enum http_method
{
#define XX(num, name, string) HTTP_##name = num,
HTTP_METHOD_MAP(XX)
#undef XX
};
enum http_parser_type { HTTP_REQUEST, HTTP_RESPONSE, HTTP_BOTH };
/* Flag values for http_parser.flags field */
enum flags
{ F_CHUNKED = 1 << 0
, F_CONNECTION_KEEP_ALIVE = 1 << 1
, F_CONNECTION_CLOSE = 1 << 2
, F_CONNECTION_UPGRADE = 1 << 3
, F_TRAILING = 1 << 4
, F_UPGRADE = 1 << 5
, F_SKIPBODY = 1 << 6
, F_CONTENTLENGTH = 1 << 7
};
/* Map for errno-related constants
*
* The provided argument should be a macro that takes 2 arguments.
*/
#define HTTP_ERRNO_MAP(XX) \
/* No error */ \
XX(OK, "success") \
\
/* Callback-related errors */ \
XX(CB_message_begin, "the on_message_begin callback failed") \
XX(CB_url, "the on_url callback failed") \
XX(CB_header_field, "the on_header_field callback failed") \
XX(CB_header_value, "the on_header_value callback failed") \
XX(CB_headers_complete, "the on_headers_complete callback failed") \
XX(CB_body, "the on_body callback failed") \
XX(CB_message_complete, "the on_message_complete callback failed") \
XX(CB_status, "the on_status callback failed") \
XX(CB_chunk_header, "the on_chunk_header callback failed") \
XX(CB_chunk_complete, "the on_chunk_complete callback failed") \
\
/* Parsing-related errors */ \
XX(INVALID_EOF_STATE, "stream ended at an unexpected time") \
XX(HEADER_OVERFLOW, \
"too many header bytes seen; overflow detected") \
XX(CLOSED_CONNECTION, \
"data received after completed connection: close message") \
XX(INVALID_VERSION, "invalid HTTP version") \
XX(INVALID_STATUS, "invalid HTTP status code") \
XX(INVALID_METHOD, "invalid HTTP method") \
XX(INVALID_URL, "invalid URL") \
XX(INVALID_HOST, "invalid host") \
XX(INVALID_PORT, "invalid port") \
XX(INVALID_PATH, "invalid path") \
XX(INVALID_QUERY_STRING, "invalid query string") \
XX(INVALID_FRAGMENT, "invalid fragment") \
XX(LF_EXPECTED, "LF character expected") \
XX(INVALID_HEADER_TOKEN, "invalid character in header") \
XX(INVALID_CONTENT_LENGTH, \
"invalid character in content-length header") \
XX(UNEXPECTED_CONTENT_LENGTH, \
"unexpected content-length header") \
XX(INVALID_CHUNK_SIZE, \
"invalid character in chunk size header") \
XX(INVALID_CONSTANT, "invalid constant string") \
XX(INVALID_INTERNAL_STATE, "encountered unexpected internal state")\
XX(STRICT, "strict mode assertion failed") \
XX(PAUSED, "parser is paused") \
XX(UNKNOWN, "an unknown error occurred")
/* Define HPE_* values for each errno value above */
#define HTTP_ERRNO_GEN(n, s) HPE_##n,
enum http_errno {
HTTP_ERRNO_MAP(HTTP_ERRNO_GEN)
};
#undef HTTP_ERRNO_GEN
/* Get an http_errno value from an http_parser */
#define HTTP_PARSER_ERRNO(p) ((::wpi::http_errno) (p)->http_errno)
struct http_parser {
/** PRIVATE **/
unsigned int type : 2; /* enum http_parser_type */
unsigned int flags : 8; /* F_* values from 'flags' enum; semi-public */
unsigned int state : 7; /* enum state from http_parser.c */
unsigned int header_state : 7; /* enum header_state from http_parser.c */
unsigned int index : 7; /* index into current matcher */
unsigned int lenient_http_headers : 1;
uint32_t nread; /* # bytes read in various scenarios */
uint64_t content_length; /* # bytes in body (0 if no Content-Length header) */
/** READ-ONLY **/
unsigned short http_major;
unsigned short http_minor;
unsigned int status_code : 16; /* responses only */
unsigned int method : 8; /* requests only */
unsigned int http_errno : 7;
/* 1 = Upgrade header was present and the parser has exited because of that.
* 0 = No upgrade header present.
* Should be checked when http_parser_execute() returns in addition to
* error checking.
*/
unsigned int upgrade : 1;
/** PUBLIC **/
void *data; /* A pointer to get hook to the "connection" or "socket" object */
};
struct http_parser_settings {
http_cb on_message_begin;
http_data_cb on_url;
http_data_cb on_status;
http_data_cb on_header_field;
http_data_cb on_header_value;
http_cb on_headers_complete;
http_data_cb on_body;
http_cb on_message_complete;
/* When on_chunk_header is called, the current chunk length is stored
* in parser->content_length.
*/
http_cb on_chunk_header;
http_cb on_chunk_complete;
};
enum http_parser_url_fields
{ UF_SCHEMA = 0
, UF_HOST = 1
, UF_PORT = 2
, UF_PATH = 3
, UF_QUERY = 4
, UF_FRAGMENT = 5
, UF_USERINFO = 6
, UF_MAX = 7
};
/* Result structure for http_parser_parse_url().
*
* Callers should index into field_data[] with UF_* values iff field_set
* has the relevant (1 << UF_*) bit set. As a courtesy to clients (and
* because we probably have padding left over), we convert any port to
* a uint16_t.
*/
struct http_parser_url {
uint16_t field_set; /* Bitmask of (1 << UF_*) values */
uint16_t port; /* Converted UF_PORT string */
struct {
uint16_t off; /* Offset into buffer in which field starts */
uint16_t len; /* Length of run in buffer */
} field_data[UF_MAX];
};
/* Returns the library version. Bits 16-23 contain the major version number,
* bits 8-15 the minor version number and bits 0-7 the patch level.
* Usage example:
*
* unsigned long version = http_parser_version();
* unsigned major = (version >> 16) & 255;
* unsigned minor = (version >> 8) & 255;
* unsigned patch = version & 255;
* printf("http_parser v%u.%u.%u\n", major, minor, patch);
*/
unsigned long http_parser_version(void);
void http_parser_init(http_parser *parser, enum http_parser_type type);
/* Initialize http_parser_settings members to 0
*/
void http_parser_settings_init(http_parser_settings *settings);
/* Executes the parser. Returns number of parsed bytes. Sets
* `parser->http_errno` on error. */
size_t http_parser_execute(http_parser *parser,
const http_parser_settings *settings,
const char *data,
size_t len);
/* If http_should_keep_alive() in the on_headers_complete or
* on_message_complete callback returns 0, then this should be
* the last message on the connection.
* If you are the server, respond with the "Connection: close" header.
* If you are the client, close the connection.
*/
int http_should_keep_alive(const http_parser *parser);
/* Returns a string version of the HTTP method. */
const char *http_method_str(enum http_method m);
/* Returns a string version of the HTTP status code. */
const char *http_status_str(enum http_status s);
/* Return a string name of the given error */
const char *http_errno_name(enum http_errno err);
/* Return a string description of the given error */
const char *http_errno_description(enum http_errno err);
/* Initialize all http_parser_url members to 0 */
void http_parser_url_init(struct http_parser_url *u);
/* Parse a URL; return nonzero on failure */
int http_parser_parse_url(const char *buf, size_t buflen,
int is_connect,
struct http_parser_url *u);
/* Pause or un-pause the parser; a nonzero value pauses */
void http_parser_pause(http_parser *parser, int paused);
/* Checks if this is the final chunk of the body. */
int http_body_is_final(const http_parser *parser);
} // namespace wpi
#endif

View File

@@ -1,31 +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.
#ifndef WPIUTIL_WPI_RAW_SOCKET_ISTREAM_H_
#define WPIUTIL_WPI_RAW_SOCKET_ISTREAM_H_
#include "wpi/raw_istream.h"
namespace wpi {
class NetworkStream;
class raw_socket_istream : public raw_istream {
public:
explicit raw_socket_istream(NetworkStream& stream, int timeout = 0)
: m_stream(stream), m_timeout(timeout) {}
void close() override;
size_t in_avail() const override;
private:
void read_impl(void* data, size_t len) override;
NetworkStream& m_stream;
int m_timeout;
};
} // namespace wpi
#endif // WPIUTIL_WPI_RAW_SOCKET_ISTREAM_H_

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.
#ifndef WPIUTIL_WPI_RAW_SOCKET_OSTREAM_H_
#define WPIUTIL_WPI_RAW_SOCKET_OSTREAM_H_
#include "wpi/raw_ostream.h"
namespace wpi {
class NetworkStream;
class raw_socket_ostream : public raw_ostream {
public:
raw_socket_ostream(NetworkStream& stream, bool shouldClose)
: m_stream(stream), m_shouldClose(shouldClose) {}
~raw_socket_ostream() override;
void close();
bool has_error() const { return m_error; }
void clear_error() { m_error = false; }
protected:
void error_detected() { m_error = true; }
private:
void write_impl(const char* data, size_t len) override;
uint64_t current_pos() const override;
NetworkStream& m_stream;
bool m_error = false;
bool m_shouldClose;
};
} // namespace wpi
#endif // WPIUTIL_WPI_RAW_SOCKET_OSTREAM_H_

View File

@@ -1,74 +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.
#ifndef WPIUTIL_WPI_RAW_UV_OSTREAM_H_
#define WPIUTIL_WPI_RAW_UV_OSTREAM_H_
#include <functional>
#include <utility>
#include "wpi/SmallVector.h"
#include "wpi/raw_ostream.h"
#include "wpi/span.h"
#include "wpi/uv/Buffer.h"
namespace wpi {
/**
* raw_ostream style output to a SmallVector of uv::Buffer buffers. Fixed-size
* buffers are allocated and appended as necessary to fit the data being output.
* The SmallVector need not be empty at start.
*/
class raw_uv_ostream : public raw_ostream {
public:
/**
* Construct a new raw_uv_ostream.
* @param bufs Buffers vector. NOT cleared on construction.
* @param allocSize Size to allocate for each buffer; allocation will be
* performed using Buffer::Allocate().
*/
raw_uv_ostream(SmallVectorImpl<uv::Buffer>& bufs, size_t allocSize)
: m_bufs(bufs), m_alloc([=] { return uv::Buffer::Allocate(allocSize); }) {
SetUnbuffered();
}
/**
* Construct a new raw_uv_ostream.
* @param bufs Buffers vector. NOT cleared on construction.
* @param alloc Allocator.
*/
raw_uv_ostream(SmallVectorImpl<uv::Buffer>& bufs,
std::function<uv::Buffer()> alloc)
: m_bufs(bufs), m_alloc(std::move(alloc)) {
SetUnbuffered();
}
~raw_uv_ostream() override = default;
/**
* Returns an span to the buffers.
*/
span<uv::Buffer> bufs() { return m_bufs; }
void flush() = delete;
/**
* Resets the amount of allocated space.
*/
void reset() { m_left = 0; }
private:
void write_impl(const char* data, size_t len) override;
uint64_t current_pos() const override;
SmallVectorImpl<uv::Buffer>& m_bufs;
std::function<uv::Buffer()> m_alloc;
// How much allocated space is left in the current buffer.
size_t m_left = 0;
};
} // namespace wpi
#endif // WPIUTIL_WPI_RAW_UV_OSTREAM_H_

View File

@@ -1,174 +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.
#ifndef WPIUTIL_WPI_UV_ASYNC_H_
#define WPIUTIL_WPI_UV_ASYNC_H_
#include <uv.h>
#include <memory>
#include <thread>
#include <tuple>
#include <utility>
#include <vector>
#include "wpi/Signal.h"
#include "wpi/mutex.h"
#include "wpi/uv/Handle.h"
#include "wpi/uv/Loop.h"
namespace wpi::uv {
/**
* Async handle.
* Async handles allow the user to "wakeup" the event loop and have a signal
* generated from another thread.
*
* Data may be passed into the callback called on the event loop by using
* template parameters. If data parameters are used, the async callback will
* be called once for every call to Send(). If no data parameters are used,
* the async callback may or may not be called for every call to Send() (e.g.
* the calls may be coaleasced).
*/
template <typename... T>
class Async final : public HandleImpl<Async<T...>, uv_async_t> {
struct private_init {};
public:
Async(const std::shared_ptr<Loop>& loop, const private_init&)
: m_loop{loop} {}
~Async() noexcept override {
if (auto loop = m_loop.lock()) {
this->Close();
} else {
this->ForceClosed();
}
}
/**
* Create an async handle.
*
* @param loop Loop object where this handle runs.
*/
static std::shared_ptr<Async> Create(Loop& loop) {
return Create(loop.shared_from_this());
}
/**
* Create an async handle.
*
* @param loop Loop object where this handle runs.
*/
static std::shared_ptr<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) {
auto& h = *static_cast<Async*>(handle->data);
std::scoped_lock lock(h.m_mutex);
for (auto&& v : h.m_data) {
std::apply(h.wakeup, v);
}
h.m_data.clear();
});
if (err < 0) {
loop->ReportError(err);
return nullptr;
}
h->Keep();
return h;
}
/**
* Wakeup the event loop and emit the event.
*
* Its safe to call this function from any thread including the loop thread.
* An async event will be emitted on the loop thread.
*/
template <typename... U>
void Send(U&&... u) {
auto loop = m_loop.lock();
if (loop && loop->GetThreadId() == std::this_thread::get_id()) {
// called from within the loop, just call the function directly
wakeup(std::forward<U>(u)...);
return;
}
{
std::scoped_lock lock(m_mutex);
m_data.emplace_back(std::forward_as_tuple(std::forward<U>(u)...));
}
if (loop) {
this->Invoke(&uv_async_send, this->GetRaw());
}
}
/**
* Signal generated (on event loop thread) when the async event occurs.
*/
sig::Signal<T...> wakeup;
private:
wpi::mutex m_mutex;
std::vector<std::tuple<T...>> m_data;
std::weak_ptr<Loop> m_loop;
};
/**
* Async specialization for no data parameters. The async callback may or may
* not be called for every call to Send() (e.g. the calls may be coaleasced).
*/
template <>
class Async<> final : public HandleImpl<Async<>, uv_async_t> {
struct private_init {};
public:
Async(const std::shared_ptr<Loop>& loop, const private_init&)
: m_loop(loop) {}
~Async() noexcept override;
/**
* Create an async handle.
*
* @param loop Loop object where this handle runs.
*/
static std::shared_ptr<Async> Create(Loop& loop) {
return Create(loop.shared_from_this());
}
/**
* Create an async handle.
*
* @param loop Loop object where this handle runs.
*/
static std::shared_ptr<Async> Create(const std::shared_ptr<Loop>& loop);
/**
* Wakeup the event loop and emit the event.
*
* Its safe to call this function from any thread.
* An async event will be emitted on the loop thread.
*/
void Send() {
if (auto loop = m_loop.lock()) {
if (loop->GetThreadId() == std::this_thread::get_id()) {
// called from within the loop, just call the function directly
wakeup();
} else {
Invoke(&uv_async_send, GetRaw());
}
}
}
/**
* Signal generated (on event loop thread) when the async event occurs.
*/
sig::Signal<> wakeup;
private:
std::weak_ptr<Loop> m_loop;
};
} // namespace wpi::uv
#endif // WPIUTIL_WPI_UV_ASYNC_H_

View File

@@ -1,167 +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.
#ifndef WPIUTIL_WPI_UV_ASYNCFUNCTION_H_
#define WPIUTIL_WPI_UV_ASYNCFUNCTION_H_
#include <stdint.h>
#include <uv.h>
#include <functional>
#include <memory>
#include <thread>
#include <tuple>
#include <utility>
#include <vector>
#include "wpi/future.h"
#include "wpi/mutex.h"
#include "wpi/uv/Handle.h"
#include "wpi/uv/Loop.h"
namespace wpi::uv {
template <typename T>
class AsyncFunction;
/**
* Function async handle.
* Async handles allow the user to "wakeup" the event loop and have a function
* called from another thread that returns a result to the calling thread.
*/
template <typename R, typename... T>
class AsyncFunction<R(T...)> final
: public HandleImpl<AsyncFunction<R(T...)>, uv_async_t> {
struct private_init {};
public:
AsyncFunction(const std::shared_ptr<Loop>& loop,
std::function<void(promise<R>, T...)> func, const private_init&)
: wakeup{std::move(func)}, m_loop{loop} {}
~AsyncFunction() noexcept override {
if (auto loop = m_loop.lock()) {
this->Close();
} else {
this->ForceClosed();
}
}
/**
* Create an async handle.
*
* @param loop Loop object where this handle runs.
* @param func wakeup function to be called (sets wakeup value); the function
* needs to return void, and its first parameter is the promise
* for the result. If no value is set on the promise by the
* wakeup function, a default-constructed value is "returned".
*/
static std::shared_ptr<AsyncFunction> Create(
Loop& loop, std::function<void(promise<R>, T...)> func = nullptr) {
return Create(loop.shared_from_this(), std::move(func));
}
/**
* Create an async handle.
*
* @param loop Loop object where this handle runs.
* @param func wakeup function to be called (sets wakeup value); the function
* needs to return void, and its first parameter is the promise
* for the result. If no value is set on the promise by the
* wakeup function, a default-constructed value is "returned".
*/
static std::shared_ptr<AsyncFunction> Create(
const std::shared_ptr<Loop>& loop,
std::function<void(promise<R>, T...)> func = nullptr) {
auto h =
std::make_shared<AsyncFunction>(loop, std::move(func), private_init{});
int err =
uv_async_init(loop->GetRaw(), h->GetRaw(), [](uv_async_t* handle) {
auto& h = *static_cast<AsyncFunction*>(handle->data);
std::unique_lock lock(h.m_mutex);
if (!h.m_params.empty()) {
// for each set of parameters in the input queue, call the wakeup
// function and put the result in the output queue if the caller is
// waiting for it
for (auto&& v : h.m_params) {
auto p = h.m_promises.CreatePromise(v.first);
if (h.wakeup) {
std::apply(h.wakeup,
std::tuple_cat(std::make_tuple(std::move(p)),
std::move(v.second)));
}
}
h.m_params.clear();
// wake up any threads that might be waiting for the result
lock.unlock();
h.m_promises.Notify();
}
});
if (err < 0) {
loop->ReportError(err);
return nullptr;
}
h->Keep();
return h;
}
/**
* Wakeup the event loop, call the async function, and return a future for
* the result.
*
* Its safe to call this function from any thread including the loop thread.
* The async function will be called on the loop thread.
*
* The future will return a default-constructed result if this handle is
* destroyed while waiting for a result.
*/
template <typename... U>
future<R> Call(U&&... u) {
// create the future
uint64_t req = m_promises.CreateRequest();
auto loop = m_loop.lock();
if (loop && loop->GetThreadId() == std::this_thread::get_id()) {
// called from within the loop, just call the function directly
wakeup(m_promises.CreatePromise(req), std::forward<U>(u)...);
return m_promises.CreateFuture(req);
}
// add the parameters to the input queue
{
std::scoped_lock lock(m_mutex);
m_params.emplace_back(std::piecewise_construct,
std::forward_as_tuple(req),
std::forward_as_tuple(std::forward<U>(u)...));
}
// signal the loop
if (loop) {
this->Invoke(&uv_async_send, this->GetRaw());
}
// return future
return m_promises.CreateFuture(req);
}
template <typename... U>
future<R> operator()(U&&... u) {
return Call(std::forward<U>(u)...);
}
/**
* Function called (on event loop thread) when the async is called.
*/
std::function<void(promise<R>, T...)> wakeup;
private:
wpi::mutex m_mutex;
std::vector<std::pair<uint64_t, std::tuple<T...>>> m_params;
PromiseFactory<R> m_promises;
std::weak_ptr<Loop> m_loop;
};
} // namespace wpi::uv
#endif // WPIUTIL_WPI_UV_ASYNCFUNCTION_H_

View File

@@ -1,163 +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.
#ifndef WPIUTIL_WPI_UV_BUFFER_H_
#define WPIUTIL_WPI_UV_BUFFER_H_
#include <uv.h>
#include <cstring>
#include <initializer_list>
#include <string_view>
#include <utility>
#include "wpi/SmallVector.h"
#include "wpi/span.h"
namespace wpi::uv {
/**
* Data buffer. Convenience wrapper around uv_buf_t.
*/
class Buffer : public uv_buf_t {
public:
Buffer() {
base = nullptr;
len = 0;
}
/*implicit*/ Buffer(const uv_buf_t& oth) { // NOLINT
base = oth.base;
len = oth.len;
}
/*implicit*/ Buffer(std::string_view str) // NOLINT
: Buffer{str.data(), str.size()} {}
/*implicit*/ Buffer(span<const uint8_t> arr) // NOLINT
: Buffer{reinterpret_cast<const char*>(arr.data()), arr.size()} {}
Buffer(char* base_, size_t len_) {
base = base_;
len = static_cast<decltype(len)>(len_);
}
Buffer(const char* base_, size_t len_) {
base = const_cast<char*>(base_);
len = static_cast<decltype(len)>(len_);
}
span<const char> data() const { return {base, len}; }
span<char> data() { return {base, len}; }
operator span<const char>() const { return data(); } // NOLINT
operator span<char>() { return data(); } // NOLINT
static Buffer Allocate(size_t size) { return Buffer{new char[size], size}; }
static Buffer Dup(std::string_view in) {
Buffer buf = Allocate(in.size());
std::memcpy(buf.base, in.data(), in.size());
return buf;
}
static Buffer Dup(span<const uint8_t> in) {
Buffer buf = Allocate(in.size());
std::memcpy(buf.base, in.begin(), in.size());
return buf;
}
Buffer Dup() const {
Buffer buf = Allocate(len);
std::memcpy(buf.base, base, len);
return buf;
}
void Deallocate() {
delete[] base;
base = nullptr;
len = 0;
}
Buffer Move() {
Buffer buf = *this;
base = nullptr;
len = 0;
return buf;
}
friend void swap(Buffer& a, Buffer& b) {
using std::swap;
swap(a.base, b.base);
swap(a.len, b.len);
}
};
/**
* A simple pool allocator for Buffers.
* Buffers are allocated individually but are reused rather than returned
* to the heap.
* @tparam DEPTH depth of pool
*/
template <size_t DEPTH = 4>
class SimpleBufferPool {
public:
/**
* Constructor.
* @param size Size of each buffer to allocate.
*/
explicit SimpleBufferPool(size_t size = 4096) : m_size{size} {}
~SimpleBufferPool() { Clear(); }
SimpleBufferPool(const SimpleBufferPool& other) = delete;
SimpleBufferPool& operator=(const SimpleBufferPool& other) = delete;
/**
* Allocate a buffer.
*/
Buffer Allocate() {
if (m_pool.empty()) {
return Buffer::Allocate(m_size);
}
auto buf = m_pool.back();
m_pool.pop_back();
buf.len = m_size;
return buf;
}
/**
* Allocate a buffer.
*/
Buffer operator()() { return Allocate(); }
/**
* Release allocated buffers back into the pool.
* This is NOT safe to use with arbitrary buffers unless they were
* allocated with the same size as the buffer pool allocation size.
*/
void Release(span<Buffer> bufs) {
for (auto& buf : bufs) {
m_pool.emplace_back(buf.Move());
}
}
/**
* Clear the pool, releasing all buffers.
*/
void Clear() {
for (auto& buf : m_pool) {
buf.Deallocate();
}
m_pool.clear();
}
/**
* Get number of buffers left in the pool before a new buffer will be
* allocated from the heap.
*/
size_t Remaining() const { return m_pool.size(); }
private:
SmallVector<Buffer, DEPTH> m_pool;
size_t m_size; // NOLINT
};
} // namespace wpi::uv
#endif // WPIUTIL_WPI_UV_BUFFER_H_

View File

@@ -1,65 +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.
#ifndef WPIUTIL_WPI_UV_CHECK_H_
#define WPIUTIL_WPI_UV_CHECK_H_
#include <uv.h>
#include <memory>
#include "wpi/Signal.h"
#include "wpi/uv/Handle.h"
namespace wpi::uv {
class Loop;
/**
* Check handle.
* Check handles will generate a signal once per loop iteration, right
* after polling for I/O.
*/
class Check final : public HandleImpl<Check, uv_check_t> {
struct private_init {};
public:
explicit Check(const private_init&) {}
~Check() noexcept override = default;
/**
* Create a check handle.
*
* @param loop Loop object where this handle runs.
*/
static std::shared_ptr<Check> Create(Loop& loop);
/**
* Create a check handle.
*
* @param loop Loop object where this handle runs.
*/
static std::shared_ptr<Check> Create(const std::shared_ptr<Loop>& loop) {
return Create(*loop);
}
/**
* Start the handle.
*/
void Start();
/**
* Stop the handle. The signal will no longer be generated.
*/
void Stop() { Invoke(&uv_check_stop, GetRaw()); }
/**
* Signal generated once per loop iteration after polling for I/O.
*/
sig::Signal<> check;
};
} // namespace wpi::uv
#endif // WPIUTIL_WPI_UV_CHECK_H_

View File

@@ -1,46 +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.
#ifndef WPIUTIL_WPI_UV_ERROR_H_
#define WPIUTIL_WPI_UV_ERROR_H_
#include <uv.h>
namespace wpi::uv {
/**
* Error code.
*/
class Error {
public:
Error() = default;
explicit Error(int err) : m_err(err) {}
/**
* Boolean conversion. Returns true if error, false if ok.
*/
explicit operator bool() const { return m_err < 0; }
/**
* Returns the error code.
*/
int code() const { return m_err; }
/**
* Returns the error message.
*/
const char* str() const { return uv_strerror(m_err); }
/**
* Returns the error name.
*/
const char* name() const { return uv_err_name(m_err); }
private:
int m_err{UV_UNKNOWN};
};
} // namespace wpi::uv
#endif // WPIUTIL_WPI_UV_ERROR_H_

View File

@@ -1,79 +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.
#ifndef WPIUTIL_WPI_UV_FSEVENT_H_
#define WPIUTIL_WPI_UV_FSEVENT_H_
#include <uv.h>
#include <memory>
#include <string>
#include <string_view>
#include "wpi/Signal.h"
#include "wpi/uv/Handle.h"
namespace wpi::uv {
class Loop;
/**
* Filesystem Event handle.
*/
class FsEvent final : public HandleImpl<FsEvent, uv_fs_event_t> {
struct private_init {};
public:
explicit FsEvent(const private_init&) {}
~FsEvent() noexcept override = default;
/**
* Create a filesystem event handle.
*
* @param loop Loop object where this handle runs.
*/
static std::shared_ptr<FsEvent> Create(Loop& loop);
/**
* Create a filesystem event handle.
*
* @param loop Loop object where this handle runs.
*/
static std::shared_ptr<FsEvent> Create(const std::shared_ptr<Loop>& loop) {
return Create(*loop);
}
/**
* Start watching the specified path for changes.
*
* @param path Path to watch for changes
* @param events Bitmask of event flags. Only UV_FS_EVENT_RECURSIVE is
* supported (and only on OSX and Windows).
*/
void Start(std::string_view path, unsigned int flags = 0);
/**
* Stop watching for changes.
*/
void Stop() { Invoke(&uv_fs_event_stop, GetRaw()); }
/**
* Get the path being monitored. Signals error and returns empty string if
* an error occurs.
* @return Monitored path.
*/
std::string GetPath();
/**
* Signal generated when a filesystem change occurs. The first parameter
* is the filename (if a directory was passed to Start(), the filename is
* relative to that directory). The second parameter is an ORed mask of
* UV_RENAME and UV_CHANGE.
*/
sig::Signal<const char*, int> fsEvent;
};
} // namespace wpi::uv
#endif // WPIUTIL_WPI_UV_FSEVENT_H_

View File

@@ -1,119 +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.
#ifndef WPIUTIL_WPI_UV_GETADDRINFO_H_
#define WPIUTIL_WPI_UV_GETADDRINFO_H_
#include <uv.h>
#include <functional>
#include <memory>
#include <string_view>
#include <utility>
#include "wpi/Signal.h"
#include "wpi/uv/Request.h"
namespace wpi::uv {
class Loop;
/**
* GetAddrInfo request.
* For use with `GetAddrInfo()` function family.
*/
class GetAddrInfoReq : public RequestImpl<GetAddrInfoReq, uv_getaddrinfo_t> {
public:
GetAddrInfoReq();
Loop& GetLoop() const { return *static_cast<Loop*>(GetRaw()->loop->data); }
/**
* Resolved lookup signal.
* Parameter is resolved address info.
*/
sig::Signal<const addrinfo&> resolved;
};
/**
* Asynchronous getaddrinfo(3). HandleResolvedAddress() is called on the
* request when the resolution completes. HandleError() is called on the
* request if any errors occur.
*
* Either node or service may be empty but not both.
*
* @param loop Event loop
* @param req request
* @param node Either a numerical network address or a network hostname.
* @param service Either a service name or a port number as a string.
* @param hints Optional `addrinfo` data structure with additional address
* type constraints.
*/
void GetAddrInfo(Loop& loop, const std::shared_ptr<GetAddrInfoReq>& req,
std::string_view node, std::string_view service = {},
const addrinfo* hints = nullptr);
/**
* Asynchronous getaddrinfo(3). HandleResolvedAddress() is called on the
* request when the resolution completes. HandleError() is called on the
* request if any errors occur.
*
* Either node or service may be empty but not both.
*
* @param loop Event loop
* @param req request
* @param node Either a numerical network address or a network hostname.
* @param service Either a service name or a port number as a string.
* @param hints Optional `addrinfo` data structure with additional address
* type constraints.
*/
inline void GetAddrInfo(const std::shared_ptr<Loop>& loop,
const std::shared_ptr<GetAddrInfoReq>& req,
std::string_view node, std::string_view service = {},
const addrinfo* hints = nullptr) {
GetAddrInfo(*loop, req, node, service, hints);
}
/**
* Asynchronous getaddrinfo(3). The callback is called when the resolution
* completes, and errors are forwarded to the loop. This is a convenience
* wrapper.
*
* Either node or service may be empty but not both.
*
* @param loop Event loop
* @param callback Callback function to call when resolution completes
* @param node Either a numerical network address or a network hostname.
* @param service Either a service name or a port number as a string.
* @param hints Optional `addrinfo` data structure with additional address
* type constraints.
*/
void GetAddrInfo(Loop& loop, std::function<void(const addrinfo&)> callback,
std::string_view node, std::string_view service = {},
const addrinfo* hints = nullptr);
/**
* Asynchronous getaddrinfo(3). The callback is called when the resolution
* completes, and errors are forwarded to the loop. This is a convenience
* wrapper.
*
* Either node or service may be empty but not both.
*
* @param loop Event loop
* @param callback Callback function to call when resolution completes
* @param node Either a numerical network address or a network hostname.
* @param service Either a service name or a port number as a string.
* @param hints Optional `addrinfo` data structure with additional address
* type constraints.
*/
inline void GetAddrInfo(const std::shared_ptr<Loop>& loop,
std::function<void(const addrinfo&)> callback,
std::string_view node, std::string_view service = {},
const addrinfo* hints = nullptr) {
GetAddrInfo(*loop, std::move(callback), node, service, hints);
}
} // namespace wpi::uv
#endif // WPIUTIL_WPI_UV_GETADDRINFO_H_

View File

@@ -1,228 +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.
#ifndef WPIUTIL_WPI_UV_GETNAMEINFO_H_
#define WPIUTIL_WPI_UV_GETNAMEINFO_H_
#include <uv.h>
#include <functional>
#include <memory>
#include <string_view>
#include <utility>
#include "wpi/Signal.h"
#include "wpi/uv/Request.h"
namespace wpi::uv {
class Loop;
/**
* GetNameInfo request.
* For use with `GetNameInfo()` function family.
*/
class GetNameInfoReq : public RequestImpl<GetNameInfoReq, uv_getnameinfo_t> {
public:
GetNameInfoReq();
Loop& GetLoop() const { return *static_cast<Loop*>(GetRaw()->loop->data); }
/**
* Resolved lookup signal.
* Parameters are hostname and service.
*/
sig::Signal<const char*, const char*> resolved;
};
/**
* Asynchronous getnameinfo(3). HandleResolvedName() is called on the
* request when the resolution completes. HandleError() is called on the
* request if any errors occur.
*
* @param loop Event loop
* @param req request
* @param addr Initialized `sockaddr_in` or `sockaddr_in6` data structure.
* @param flags Optional flags to modify the behavior of `getnameinfo`.
*/
void GetNameInfo(Loop& loop, const std::shared_ptr<GetNameInfoReq>& req,
const sockaddr& addr, int flags = 0);
/**
* Asynchronous getnameinfo(3). HandleResolvedName() is called on the
* request when the resolution completes. HandleError() is called on the
* request if any errors occur.
*
* @param loop Event loop
* @param req request
* @param addr Initialized `sockaddr_in` or `sockaddr_in6` data structure.
* @param flags Optional flags to modify the behavior of `getnameinfo`.
*/
inline void GetNameInfo(const std::shared_ptr<Loop>& loop,
const std::shared_ptr<GetNameInfoReq>& req,
const sockaddr& addr, int flags = 0) {
GetNameInfo(*loop, req, addr, flags);
}
/**
* Asynchronous getnameinfo(3). The callback is called when the resolution
* completes, and errors are forwarded to the loop.
*
* @param loop Event loop
* @param callback Callback function to call when resolution completes
* @param addr Initialized `sockaddr_in` or `sockaddr_in6` data structure.
* @param flags Optional flags to modify the behavior of `getnameinfo`.
*/
void GetNameInfo(Loop& loop,
std::function<void(const char*, const char*)> callback,
const sockaddr& addr, int flags = 0);
/**
* Asynchronous getnameinfo(3). The callback is called when the resolution
* completes, and errors are forwarded to the loop.
*
* @param loop Event loop
* @param callback Callback function to call when resolution completes
* @param addr Initialized `sockaddr_in` or `sockaddr_in6` data structure.
* @param flags Optional flags to modify the behavior of `getnameinfo`.
* @return Connection object for the callback
*/
inline void GetNameInfo(const std::shared_ptr<Loop>& loop,
std::function<void(const char*, const char*)> callback,
const sockaddr& addr, int flags = 0) {
GetNameInfo(*loop, std::move(callback), addr, flags);
}
/**
* Asynchronous IPv4 getnameinfo(3). HandleResolvedName() is called on the
* request when the resolution completes. HandleError() is called on the
* request if any errors occur.
*
* @param loop Event loop
* @param req request
* @param ip A valid IPv4 address
* @param port A valid port number
* @param flags Optional flags to modify the behavior of `getnameinfo`.
*/
void GetNameInfo4(Loop& loop, const std::shared_ptr<GetNameInfoReq>& req,
std::string_view ip, unsigned int port, int flags = 0);
/**
* Asynchronous IPv4 getnameinfo(3). HandleResolvedName() is called on the
* request when the resolution completes. HandleError() is called on the
* request if any errors occur.
*
* @param loop Event loop
* @param req request
* @param ip A valid IPv4 address
* @param port A valid port number
* @param flags Optional flags to modify the behavior of `getnameinfo`.
*/
inline void GetNameInfo4(const std::shared_ptr<Loop>& loop,
const std::shared_ptr<GetNameInfoReq>& req,
std::string_view ip, unsigned int port,
int flags = 0) {
return GetNameInfo4(*loop, req, ip, port, flags);
}
/**
* Asynchronous IPv4 getnameinfo(3). The callback is called when the resolution
* completes, and errors are forwarded to the loop.
*
* @param loop Event loop
* @param callback Callback function to call when resolution completes
* @param ip A valid IPv4 address
* @param port A valid port number
* @param flags Optional flags to modify the behavior of `getnameinfo`.
*/
void GetNameInfo4(Loop& loop,
std::function<void(const char*, const char*)> callback,
std::string_view ip, unsigned int port, int flags = 0);
/**
* Asynchronous IPv4 getnameinfo(3). The callback is called when the resolution
* completes, and errors are forwarded to the loop. This is a convenience
* wrapper.
*
* @param loop Event loop
* @param ip A valid IPv4 address
* @param port A valid port number
* @param callback Callback function to call when resolution completes
* @param flags Optional flags to modify the behavior of `getnameinfo`.
*/
inline void GetNameInfo4(const std::shared_ptr<Loop>& loop,
std::function<void(const char*, const char*)> callback,
std::string_view ip, unsigned int port,
int flags = 0) {
return GetNameInfo4(*loop, std::move(callback), ip, port, flags);
}
/**
* Asynchronous IPv6 getnameinfo(3). HandleResolvedName() is called on the
* request when the resolution completes. HandleError() is called on the
* request if any errors occur.
*
* @param loop Event loop
* @param req request
* @param ip A valid IPv6 address
* @param port A valid port number
* @param flags Optional flags to modify the behavior of `getnameinfo`.
*/
void GetNameInfo6(Loop& loop, const std::shared_ptr<GetNameInfoReq>& req,
std::string_view ip, unsigned int port, int flags = 0);
/**
* Asynchronous IPv6 getnameinfo(3). HandleResolvedName() is called on the
* request when the resolution completes. HandleError() is called on the
* request if any errors occur.
*
* @param loop Event loop
* @param req request
* @param ip A valid IPv6 address
* @param port A valid port number
* @param flags Optional flags to modify the behavior of `getnameinfo`.
*/
inline void GetNameInfo6(const std::shared_ptr<Loop>& loop,
const std::shared_ptr<GetNameInfoReq>& req,
std::string_view ip, unsigned int port,
int flags = 0) {
GetNameInfo6(*loop, req, ip, port, flags);
}
/**
* Asynchronous IPv6 getnameinfo(3). The callback is called when the resolution
* completes, and errors are forwarded to the loop. This is a convenience
* wrapper.
*
* @param loop Event loop
* @param callback Callback function to call when resolution completes
* @param ip A valid IPv6 address
* @param port A valid port number
* @param flags Optional flags to modify the behavior of `getnameinfo`.
*/
void GetNameInfo6(Loop& loop,
std::function<void(const char*, const char*)> callback,
std::string_view ip, unsigned int port, int flags = 0);
/**
* Asynchronous IPv6 getnameinfo(3). The callback is called when the resolution
* completes, and errors are forwarded to the loop. This is a convenience
* wrapper.
*
* @param loop Event loop
* @param callback Callback function to call when resolution completes
* @param ip A valid IPv6 address
* @param port A valid port number
* @param flags Optional flags to modify the behavior of `getnameinfo`.
*/
inline void GetNameInfo6(const std::shared_ptr<Loop>& loop,
std::function<void(const char*, const char*)> callback,
std::string_view ip, unsigned int port,
int flags = 0) {
return GetNameInfo6(*loop, std::move(callback), ip, port, flags);
}
} // namespace wpi::uv
#endif // WPIUTIL_WPI_UV_GETNAMEINFO_H_

View File

@@ -1,297 +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.
#ifndef WPIUTIL_WPI_UV_HANDLE_H_
#define WPIUTIL_WPI_UV_HANDLE_H_
#include <uv.h>
#include <cstdlib>
#include <functional>
#include <memory>
#include <string_view>
#include <utility>
#include "wpi/Signal.h"
#include "wpi/uv/Buffer.h"
#include "wpi/uv/Error.h"
#include "wpi/uv/Loop.h"
namespace wpi::uv {
/**
* Handle.
* Handles are not moveable or copyable and cannot be directly constructed.
* This class provides shared_ptr ownership and shared_from_this.
* Use the specific handle type Create() functions to create handles.
*/
class Handle : public std::enable_shared_from_this<Handle> {
public:
using Type = uv_handle_type;
Handle(const Handle&) = delete;
Handle(Handle&&) = delete;
Handle& operator=(const Handle&) = delete;
Handle& operator=(Handle&&) = delete;
virtual ~Handle() noexcept;
/**
* Get the type of the handle.
*
* A base handle offers no functionality to promote it to the actual handle
* type. By means of this function, the type of the underlying handle as
* specified by Type is made available.
*
* @return The actual type of the handle.
*/
Type GetType() const noexcept { return m_uv_handle->type; }
/**
* Get the name of the type of the handle. E.g. "pipe" for pipe handles.
*/
std::string_view GetTypeName() const noexcept {
return uv_handle_type_name(m_uv_handle->type);
}
/**
* Get the loop where this handle runs.
*
* @return The loop.
*/
std::shared_ptr<Loop> GetLoop() const noexcept {
return GetLoopRef().shared_from_this();
}
/**
* Get the loop where this handle runs.
*
* @return The loop.
*/
Loop& GetLoopRef() const noexcept {
return *static_cast<Loop*>(m_uv_handle->loop->data);
}
/**
* Check if the handle is active.
*
* What _active_ means depends on the type of handle:
*
* * An AsyncHandle handle is always active and cannot be deactivated,
* except by closing it with uv_close().
* * A PipeHandle, TcpHandle, UDPHandle, etc. handle - basically any handle
* that deals with I/O - is active when it is doing something that involves
* I/O, like reading, writing, connecting, accepting new connections, etc.
* * A CheckHandle, IdleHandle, TimerHandle, etc. handle is active when it
* has been started with a call to `Start()`.
*
* Rule of thumb: if a handle of type `FooHandle` has a `Start()` member
* method, then its active from the moment that method is called. Likewise,
* `Stop()` deactivates the handle again.
*
* @return True if the handle is active, false otherwise.
*/
bool IsActive() const noexcept { return uv_is_active(m_uv_handle) != 0; }
/**
* Check if a handle is closing or closed.
*
* This function should only be used between the initialization of the
* handle and the arrival of the close callback.
*
* @return True if the handle is closing or closed, false otherwise.
*/
bool IsClosing() const noexcept {
return m_closed || uv_is_closing(m_uv_handle) != 0;
}
/**
* Request handle to be closed.
*
* This **must** be called on each handle before memory is released.
* In-progress requests are cancelled and this can result in error() being
* emitted.
*
* The handle will emit closed() when finished.
*/
void Close() noexcept;
/**
* Set if the loop is closing.
*
* This is set during EventLoopRunner.Stop(), and can be used for other cases
* to indicate the loop should be closing. For instance for a uv_walk loop can
* use this to close existing handles.
*
* @param loopClosing true to set the loop currently in closing stages.
*/
void SetLoopClosing(bool loopClosing) noexcept {
m_loopClosing = loopClosing;
}
/**
* Get the loop closing status.
*
* This can be used from closed() in order to tell if a closing loop is the
* reason for the close, or another reason.
*
* @return true if the loop is closing, otherwise false.
*/
bool IsLoopClosing() const noexcept { return m_loopClosing; }
/**
* Reference the given handle.
*
* References are idempotent, that is, if a handle is already referenced
* calling this function again will have no effect.
*/
void Reference() noexcept { uv_ref(m_uv_handle); }
/**
* Unreference the given handle.
*
* References are idempotent, that is, if a handle is not referenced calling
* this function again will have no effect.
*/
void Unreference() noexcept { uv_unref(m_uv_handle); }
/**
* Check if the given handle is referenced.
* @return True if the handle is referenced, false otherwise.
*/
bool HasReference() const noexcept { return uv_has_ref(m_uv_handle) != 0; }
/**
* Return the size of the underlying handle type.
* @return The size of the underlying handle type.
*/
size_t RawSize() const noexcept { return uv_handle_size(m_uv_handle->type); }
/**
* Get the underlying handle data structure.
*
* @return The underlying handle data structure.
*/
uv_handle_t* GetRawHandle() const noexcept { return m_uv_handle; }
/**
* Set the functions used for allocating and releasing buffers. The size
* passed to the allocator function is a "suggested" size--it's just an
* indication, not related in any way to the pending data to be read. The
* user is free to allocate the amount of memory they decide. For example,
* applications with custom allocation schemes may decide to use a different
* size which matches the memory chunks they already have for other purposes.
*
* @warning Be very careful changing the allocator after the loop has started
* running; there are no interlocks between this and buffers currently in
* flight.
*
* @param alloc Allocation function
* @param dealloc Deallocation function
*/
void SetBufferAllocator(std::function<Buffer(size_t)> alloc,
std::function<void(Buffer&)> dealloc) {
m_allocBuf = std::move(alloc);
m_freeBuf = std::move(dealloc);
}
/**
* Free a buffer. Uses the function provided to SetBufFree() or
* Buffer::Deallocate by default.
*
* @param buf The buffer
*/
void FreeBuf(Buffer& buf) const noexcept { m_freeBuf(buf); }
/**
* Gets user-defined data.
* @return User-defined data if any, nullptr otherwise.
*/
template <typename T = void>
std::shared_ptr<T> GetData() const {
return std::static_pointer_cast<T>(m_data);
}
/**
* Sets user-defined data.
* @param data User-defined arbitrary data.
*/
void SetData(std::shared_ptr<void> data) { m_data = std::move(data); }
/**
* Error signal
*/
sig::Signal<Error> error;
/**
* Closed signal
*/
sig::Signal<> closed;
/**
* Report an error.
* @param err Error code
*/
void ReportError(int err) const { error(Error(err)); }
protected:
explicit Handle(uv_handle_t* uv_handle) : m_uv_handle{uv_handle} {
m_uv_handle->data = this;
}
void Keep() noexcept { m_self = shared_from_this(); }
void Release() noexcept { m_self.reset(); }
void ForceClosed() noexcept { m_closed = true; }
static void AllocBuf(uv_handle_t* handle, size_t size, uv_buf_t* buf);
static void DefaultFreeBuf(Buffer& buf);
template <typename F, typename... Args>
bool Invoke(F&& f, Args&&... args) const {
auto err = std::forward<F>(f)(std::forward<Args>(args)...);
if (err < 0) {
ReportError(err);
}
return err == 0;
}
private:
std::shared_ptr<Handle> m_self;
uv_handle_t* m_uv_handle;
bool m_closed = false;
bool m_loopClosing = false;
std::function<Buffer(size_t)> m_allocBuf{&Buffer::Allocate};
std::function<void(Buffer&)> m_freeBuf{&DefaultFreeBuf};
std::shared_ptr<void> m_data;
};
/**
* Handle.
*/
template <typename T, typename U>
class HandleImpl : public Handle {
public:
std::shared_ptr<T> shared_from_this() {
return std::static_pointer_cast<T>(Handle::shared_from_this());
}
std::shared_ptr<const T> shared_from_this() const {
return std::static_pointer_cast<const T>(Handle::shared_from_this());
}
/**
* Get the underlying handle data structure.
*
* @return The underlying handle data structure.
*/
U* GetRaw() const noexcept {
return reinterpret_cast<U*>(this->GetRawHandle());
}
protected:
HandleImpl() : Handle{static_cast<uv_handle_t*>(std::malloc(sizeof(U)))} {}
};
} // namespace wpi::uv
#endif // WPIUTIL_WPI_UV_HANDLE_H_

View File

@@ -1,74 +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.
#ifndef WPIUTIL_WPI_UV_IDLE_H_
#define WPIUTIL_WPI_UV_IDLE_H_
#include <uv.h>
#include <memory>
#include "wpi/Signal.h"
#include "wpi/uv/Handle.h"
namespace wpi::uv {
class Loop;
/**
* Idle handle.
*
* Idle handles will generate a signal once per loop iteration, right
* before the Prepare handles.
*
* The notable difference with Prepare handles is that when there are active
* idle handles, the loop will perform a zero timeout poll instead of blocking
* for I/O.
*
* @warning Despite the name, idle handles will signal every loop iteration,
* not when the loop is actually "idle". This also means they can easly become
* CPU hogs.
*/
class Idle final : public HandleImpl<Idle, uv_idle_t> {
struct private_init {};
public:
explicit Idle(const private_init&) {}
~Idle() noexcept override = default;
/**
* Create an idle handle.
*
* @param loop Loop object where this handle runs.
*/
static std::shared_ptr<Idle> Create(Loop& loop);
/**
* Create an idle handle.
*
* @param loop Loop object where this handle runs.
*/
static std::shared_ptr<Idle> Create(const std::shared_ptr<Loop>& loop) {
return Create(*loop);
}
/**
* Start the handle.
*/
void Start();
/**
* Stop the handle. The signal will no longer be generated.
*/
void Stop() { Invoke(&uv_idle_stop, GetRaw()); }
/**
* Signal generated once per loop iteration prior to Prepare signals.
*/
sig::Signal<> idle;
};
} // namespace wpi::uv
#endif // WPIUTIL_WPI_UV_IDLE_H_

View File

@@ -1,253 +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.
#ifndef WPIUTIL_WPI_UV_LOOP_H_
#define WPIUTIL_WPI_UV_LOOP_H_
#include <uv.h>
#include <atomic>
#include <chrono>
#include <functional>
#include <memory>
#include <thread>
#include <utility>
#include "wpi/Signal.h"
#include "wpi/function_ref.h"
#include "wpi/uv/Error.h"
namespace wpi::uv {
class Handle;
/**
* Event loop.
*
* The event loop is the central part of uv functionality. It takes care of
* polling for I/O and scheduling signals to be generated based on different
* sources of events.
*
* The event loop is not moveable, copyable, or directly constructible. Use
* Create() to create an event loop, or GetDefault() to get the default loop
* if you know your program will only have a single one.
*/
class Loop final : public std::enable_shared_from_this<Loop> {
struct private_init {};
public:
using Time = std::chrono::duration<uint64_t, std::milli>;
enum Mode {
kDefault = UV_RUN_DEFAULT,
kOnce = UV_RUN_ONCE,
kNoWait = UV_RUN_NOWAIT
};
explicit Loop(const private_init&) noexcept;
Loop(const Loop&) = delete;
Loop& operator=(const Loop&) = delete;
Loop(Loop&& oth) = delete;
Loop& operator=(Loop&& oth) = delete;
~Loop() noexcept;
/**
* Create a new event loop. The created loop is not the default event loop.
*
* @return The newly created loop. May return nullptr if a failure occurs.
*/
static std::shared_ptr<Loop> Create();
/**
* Create the default event loop. Only use this event loop if a single loop
* is needed for the entire application.
*
* @return The newly created loop. May return nullptr if a failure occurs.
*/
static std::shared_ptr<Loop> GetDefault();
/**
* Release all internal loop resources.
*
* Call this function only when the loop has finished executing and all open
* handles and requests have been closed, or the loop will emit an error.
*
* error() will be emitted in case of errors.
*/
void Close();
/**
* Run the event loop.
*
* Available modes are:
*
* * `Loop::kDefault`: Run the event loop until there are no
* active and referenced handles or requests.
* * `Loop::kOnce`: Run a single event loop iteration. Note that this
* function blocksif there are no pending callbacks.
* * `Loop::kNoWait`: Run a single event loop iteration, but don't block
* if there are no pending callbacks.
*
* @return True when done, false in all other cases.
*/
bool Run(Mode mode = kDefault) {
m_tid = std::this_thread::get_id();
int rv = uv_run(m_loop, static_cast<uv_run_mode>(static_cast<int>(mode)));
m_tid = std::thread::id{};
return rv == 0;
}
/**
* Check if there are active resources.
*
* @return True if there are active resources in the loop.
*/
bool IsAlive() const noexcept { return uv_loop_alive(m_loop) != 0; }
/**
* Stop the event loop.
*
* This will cause Run() to end as soon as possible.
* This will happen not sooner than the next loop iteration.
* If this function was called before blocking for I/O, the loop wont block
* for I/O on this iteration.
*/
void Stop() noexcept { uv_stop(m_loop); }
/**
* Get backend file descriptor.
*
* Only kqueue, epoll and event ports are supported.
* This can be used in conjunction with `run(Loop::kNoWait)` to poll
* in one thread and run the event loops callbacks in another.
*
* @return The backend file descriptor.
*/
int GetDescriptor() const noexcept { return uv_backend_fd(m_loop); }
/**
* Get the poll timeout.
*
* @return A `std::pair` composed of a boolean value that is true in case of
* valid timeout, false otherwise, and the timeout
* (`std::chrono::duration<uint64_t, std::milli>`).
*/
std::pair<bool, Time> GetTimeout() const noexcept {
auto to = uv_backend_timeout(m_loop);
return std::make_pair(to == -1, Time{to});
}
/**
* Return the current timestamp in milliseconds.
*
* The timestamp is cached at the start of the event loop tick.
* The timestamp increases monotonically from some arbitrary point in
* time.
* Dont make assumptions about the starting point, you will only get
* disappointed.
*
* @return The current timestamp in milliseconds (actual type is
* `std::chrono::duration<uint64_t, std::milli>`).
*/
Time Now() const noexcept { return Time{uv_now(m_loop)}; }
/**
* Update the event loops concept of _now_.
*
* The current time is cached at the start of the event loop tick in order
* to reduce the number of time-related system calls.
* You wont normally need to call this function unless you have callbacks
* that block the event loop for longer periods of time, where _longer_ is
* somewhat subjective but probably on the order of a millisecond or more.
*/
void UpdateTime() noexcept { uv_update_time(m_loop); }
/**
* Walk the list of handles.
*
* The callback will be executed once for each handle that is still active.
*
* @param callback A function to be invoked once for each active handle.
*/
void Walk(function_ref<void(Handle&)> callback);
/**
* Reinitialize any kernel state necessary in the child process after
* a fork(2) system call.
*
* Previously started watchers will continue to be started in the child
* process.
*
* It is necessary to explicitly call this function on every event loop
* created in the parent process that you plan to continue to use in the
* child, including the default loop (even if you dont continue to use it
* in the parent). This function must be called before calling any API
* function using the loop in the child. Failure to do so will result in
* undefined behaviour, possibly including duplicate events delivered to
* both parent and child or aborting the child process.
*
* When possible, it is preferred to create a new loop in the child process
* instead of reusing a loop created in the parent. New loops created in the
* child process after the fork should not use this function.
*
* Note that this function is not implemented on Windows.
* Note also that this function is experimental in `libuv`. It may contain
* bugs, and is subject to change or removal. API and ABI stability is not
* guaranteed.
*
* error() will be emitted in case of errors.
*/
void Fork();
/**
* Get the underlying event loop data structure.
*
* @return The underlying event loop data structure.
*/
uv_loop_t* GetRaw() const noexcept { return m_loop; }
/**
* Gets user-defined data.
* @return User-defined data if any, nullptr otherwise.
*/
template <typename T = void>
std::shared_ptr<T> GetData() const {
return std::static_pointer_cast<T>(m_data);
}
/**
* Sets user-defined data.
* @param data User-defined arbitrary data.
*/
void SetData(std::shared_ptr<void> data) { m_data = std::move(data); }
/**
* Get the thread id of the loop thread. If the loop is not currently
* running, returns default-constructed thread id.
*/
std::thread::id GetThreadId() const { return m_tid; }
/**
* Error signal
*/
sig::Signal<Error> error;
/**
* Reports error.
* @param err Error code
*/
void ReportError(int err) { error(Error(err)); }
private:
std::shared_ptr<void> m_data;
uv_loop_t* m_loop;
uv_loop_t m_loopStruct;
std::atomic<std::thread::id> m_tid;
};
} // namespace wpi::uv
#endif // WPIUTIL_WPI_UV_LOOP_H_

View File

@@ -1,153 +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.
#ifndef WPIUTIL_WPI_UV_NETWORKSTREAM_H_
#define WPIUTIL_WPI_UV_NETWORKSTREAM_H_
#include <uv.h>
#include <cstdlib>
#include <functional>
#include <memory>
#include "wpi/Signal.h"
#include "wpi/uv/Stream.h"
namespace wpi::uv {
class NetworkStream;
/**
* Connection request.
*/
class ConnectReq : public RequestImpl<ConnectReq, uv_connect_t> {
public:
ConnectReq();
NetworkStream& GetStream() const {
return *static_cast<NetworkStream*>(GetRaw()->handle->data);
}
/**
* Connection completed signal.
*/
sig::Signal<> connected;
};
/**
* Network stream handle.
* This is an abstract type; there are two network stream implementations (Tcp
* and Pipe).
*/
class NetworkStream : public Stream {
public:
static constexpr int kDefaultBacklog = 128;
std::shared_ptr<NetworkStream> shared_from_this() {
return std::static_pointer_cast<NetworkStream>(Handle::shared_from_this());
}
std::shared_ptr<const NetworkStream> shared_from_this() const {
return std::static_pointer_cast<const NetworkStream>(
Handle::shared_from_this());
}
/**
* Start listening for incoming connections. When a new incoming connection
* is received the connection signal is generated.
* @param backlog the number of connections the kernel might queue, same as
* listen(2).
*/
void Listen(int backlog = kDefaultBacklog);
/**
* Start listening for incoming connections. This is a convenience wrapper
* around `Listen(int)` that also connects a callback to the connection
* signal. When a new incoming connection is received the connection signal
* is generated (and the callback is called).
* @param callback the callback to call when a connection is received.
* `Accept()` should be called from this callback.
* @param backlog the number of connections the kernel might queue, same as
* listen(2).
*/
void Listen(std::function<void()> callback, int backlog = kDefaultBacklog);
/**
* Accept incoming connection.
*
* This call is used in conjunction with `Listen()` to accept incoming
* connections. Call this function after receiving a ListenEvent event to
* accept the connection.
* An error signal will be emitted in case of errors.
*
* When the connection signal is emitted it is guaranteed that this
* function will complete successfully the first time. If you attempt to use
* it more than once, it may fail.
* It is suggested to only call this function once per connection signal.
*
* @return The stream handle for the accepted connection, or nullptr on error.
*/
std::shared_ptr<NetworkStream> Accept() {
return DoAccept()->shared_from_this();
}
/**
* Accept incoming connection.
*
* This call is used in conjunction with `Listen()` to accept incoming
* connections. Call this function after receiving a connection signal to
* accept the connection.
* An error signal will be emitted in case of errors.
*
* When the connection signal is emitted it is guaranteed that this
* function will complete successfully the first time. If you attempt to use
* it more than once, it may fail.
* It is suggested to only call this function once per connection signal.
*
* @param client Client stream object.
* @return False on error.
*/
bool Accept(const std::shared_ptr<NetworkStream>& client) {
return Invoke(&uv_accept, GetRawStream(), client->GetRawStream());
}
/**
* Signal generated when an incoming connection is received.
*/
sig::Signal<> connection;
protected:
explicit NetworkStream(uv_stream_t* uv_stream) : Stream{uv_stream} {}
virtual NetworkStream* DoAccept() = 0;
};
template <typename T, typename U>
class NetworkStreamImpl : public NetworkStream {
public:
std::shared_ptr<T> shared_from_this() {
return std::static_pointer_cast<T>(Handle::shared_from_this());
}
std::shared_ptr<const T> shared_from_this() const {
return std::static_pointer_cast<const T>(Handle::shared_from_this());
}
/**
* Get the underlying handle data structure.
*
* @return The underlying handle data structure.
*/
U* GetRaw() const noexcept {
return reinterpret_cast<U*>(this->GetRawHandle());
}
protected:
NetworkStreamImpl()
: NetworkStream{static_cast<uv_stream_t*>(std::malloc(sizeof(U)))} {}
};
} // namespace wpi::uv
#endif // WPIUTIL_WPI_UV_NETWORKSTREAM_H_

View File

@@ -1,208 +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.
#ifndef WPIUTIL_WPI_UV_PIPE_H_
#define WPIUTIL_WPI_UV_PIPE_H_
#include <uv.h>
#include <functional>
#include <memory>
#include <string>
#include <string_view>
#include "wpi/uv/NetworkStream.h"
namespace wpi::uv {
class Loop;
class PipeConnectReq;
/**
* Pipe handle.
* Pipe handles provide an abstraction over local domain sockets on Unix and
* named pipes on Windows.
*/
class Pipe final : public NetworkStreamImpl<Pipe, uv_pipe_t> {
struct private_init {};
public:
explicit Pipe(const private_init&) {}
~Pipe() noexcept override = default;
/**
* Create a pipe handle.
*
* @param loop Loop object where this handle runs.
* @param ipc Indicates if this pipe will be used for handle passing between
* processes.
*/
static std::shared_ptr<Pipe> Create(Loop& loop, bool ipc = false);
/**
* Create a pipe handle.
*
* @param loop Loop object where this handle runs.
* @param ipc Indicates if this pipe will be used for handle passing between
* processes.
*/
static std::shared_ptr<Pipe> Create(const std::shared_ptr<Loop>& loop,
bool ipc = false) {
return Create(*loop, ipc);
}
/**
* Reuse this handle. This closes the handle, and after the close completes,
* reinitializes it (identically to Create) and calls the provided callback.
* Unlike Close(), it does NOT emit the closed signal, however, IsClosing()
* will return true until the callback is called. This does nothing if
* IsClosing() is true (e.g. if Close() was called).
*
* @param ipc IPC
* @param callback Callback
*/
void Reuse(std::function<void()> callback, bool ipc = false);
/**
* Accept incoming connection.
*
* This call is used in conjunction with `Listen()` to accept incoming
* connections. Call this function after receiving a ListenEvent event to
* accept the connection.
* An error signal will be emitted in case of errors.
*
* When the connection signal is emitted it is guaranteed that this
* function will complete successfully the first time. If you attempt to use
* it more than once, it may fail.
* It is suggested to only call this function once per connection signal.
*
* @return The stream handle for the accepted connection, or nullptr on error.
*/
std::shared_ptr<Pipe> Accept();
/**
* Accept incoming connection.
*
* This call is used in conjunction with `Listen()` to accept incoming
* connections. Call this function after receiving a connection signal to
* accept the connection.
* An error signal will be emitted in case of errors.
*
* When the connection signal is emitted it is guaranteed that this
* function will complete successfully the first time. If you attempt to use
* it more than once, it may fail.
* It is suggested to only call this function once per connection signal.
*
* @param client Client stream object.
* @return False on error.
*/
bool Accept(const std::shared_ptr<Pipe>& client) {
return NetworkStream::Accept(client);
}
/**
* Open an existing file descriptor or HANDLE as a pipe.
*
* @note The passed file descriptor or HANDLE is not checked for its type, but
* it's required that it represents a valid pipe.
*
* @param file A valid file handle (either a file descriptor or a HANDLE).
*/
void Open(uv_file file) { Invoke(&uv_pipe_open, GetRaw(), file); }
/**
* Bind the pipe to a file path (Unix) or a name (Windows).
*
* @note Paths on Unix get truncated to `sizeof(sockaddr_un.sun_path)` bytes,
* typically between 92 and 108 bytes.
*
* @param name File path (Unix) or name (Windows).
*/
void Bind(std::string_view name);
/**
* Connect to the Unix domain socket or the named pipe.
*
* @note Paths on Unix get truncated to `sizeof(sockaddr_un.sun_path)` bytes,
* typically between 92 and 108 bytes.
*
* HandleConnected() is called on the request when the connection has been
* established.
* HandleError() is called on the request in case of errors during the
* connection.
*
* @param name File path (Unix) or name (Windows).
* @param req connection request
*/
void Connect(std::string_view name,
const std::shared_ptr<PipeConnectReq>& req);
/**
* Connect to the Unix domain socket or the named pipe.
*
* @note Paths on Unix get truncated to `sizeof(sockaddr_un.sun_path)` bytes,
* typically between 92 and 108 bytes.
*
* The callback is called when the connection has been established. Errors
* are reported to the stream error handler.
*
* @param name File path (Unix) or name (Windows).
* @param callback Callback function to call when connection established
*/
void Connect(std::string_view name, std::function<void()> callback);
/**
* Get the name of the Unix domain socket or the named pipe.
* @return The name (will be empty if an error occurred).
*/
std::string GetSock();
/**
* Get the name of the Unix domain socket or the named pipe to which the
* handle is connected.
* @return The name (will be empty if an error occurred).
*/
std::string GetPeer();
/**
* Set the number of pending pipe instance handles when the pipe server is
* waiting for connections.
* @note This setting applies to Windows only.
* @param count Number of pending handles.
*/
void SetPendingInstances(int count) {
uv_pipe_pending_instances(GetRaw(), count);
}
/**
* Alters pipe permissions, allowing it to be accessed from processes run
* by different users. Makes the pipe writable or readable by all users.
* Mode can be UV_WRITABLE, UV_READABLE, or both. This function is blocking.
* @param flags chmod flags
*/
void Chmod(int flags) { Invoke(&uv_pipe_chmod, GetRaw(), flags); }
private:
Pipe* DoAccept() override;
struct ReuseData {
std::function<void()> callback;
bool ipc;
};
std::unique_ptr<ReuseData> m_reuseData;
};
/**
* Pipe connection request.
*/
class PipeConnectReq : public ConnectReq {
public:
Pipe& GetStream() const {
return *static_cast<Pipe*>(&ConnectReq::GetStream());
}
};
} // namespace wpi::uv
#endif // WPIUTIL_WPI_UV_PIPE_H_

View File

@@ -1,121 +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.
#ifndef WPIUTIL_WPI_UV_POLL_H_
#define WPIUTIL_WPI_UV_POLL_H_
#include <uv.h>
#include <memory>
#include "wpi/Signal.h"
#include "wpi/uv/Handle.h"
namespace wpi::uv {
class Loop;
/**
* Poll handle.
*/
class Poll final : public HandleImpl<Poll, uv_poll_t> {
struct private_init {};
public:
explicit Poll(const private_init&) {}
~Poll() noexcept override = default;
/**
* Create a poll handle using a file descriptor.
*
* @param loop Loop object where this handle runs.
* @param fd File descriptor.
*/
static std::shared_ptr<Poll> Create(Loop& loop, int fd);
/**
* Create a poll handle using a file descriptor.
*
* @param loop Loop object where this handle runs.
* @param fd File descriptor.
*/
static std::shared_ptr<Poll> Create(const std::shared_ptr<Loop>& loop,
int fd) {
return Create(*loop, fd);
}
/**
* Create a poll handle using a socket descriptor.
*
* @param loop Loop object where this handle runs.
* @param sock Socket descriptor.
*/
static std::shared_ptr<Poll> CreateSocket(Loop& loop, uv_os_sock_t sock);
/**
* Create a poll handle using a socket descriptor.
*
* @param loop Loop object where this handle runs.
* @param sock Socket descriptor.
*/
static std::shared_ptr<Poll> CreateSocket(const std::shared_ptr<Loop>& loop,
uv_os_sock_t sock) {
return CreateSocket(*loop, sock);
}
/**
* Reuse this handle. This closes the handle, and after the close completes,
* reinitializes it (identically to Create) and calls the provided callback.
* Unlike Close(), it does NOT emit the closed signal, however, IsClosing()
* will return true until the callback is called. This does nothing if
* IsClosing() is true (e.g. if Close() was called).
*
* @param fd File descriptor
* @param callback Callback
*/
void Reuse(int fd, std::function<void()> callback);
/**
* Reuse this handle. This closes the handle, and after the close completes,
* reinitializes it (identically to CreateSocket) and calls the provided
* callback. Unlike Close(), it does NOT emit the closed signal, however,
* IsClosing() will return true until the callback is called. This does
* nothing if IsClosing() is true (e.g. if Close() was called).
*
* @param sock Socket descriptor.
* @param callback Callback
*/
void ReuseSocket(uv_os_sock_t sock, std::function<void()> callback);
/**
* Start polling the file descriptor.
*
* @param events Bitmask of events (UV_READABLE, UV_WRITEABLE, UV_PRIORITIZED,
* and UV_DISCONNECT).
*/
void Start(int events);
/**
* Stop polling the file descriptor.
*/
void Stop() { Invoke(&uv_poll_stop, GetRaw()); }
/**
* Signal generated when a poll event occurs.
*/
sig::Signal<int> pollEvent;
private:
struct ReuseData {
std::function<void()> callback;
bool isSocket;
int fd;
uv_os_sock_t sock;
};
std::unique_ptr<ReuseData> m_reuseData;
};
} // namespace wpi::uv
#endif // WPIUTIL_WPI_UV_POLL_H_

View File

@@ -1,65 +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.
#ifndef WPIUTIL_WPI_UV_PREPARE_H_
#define WPIUTIL_WPI_UV_PREPARE_H_
#include <uv.h>
#include <memory>
#include "wpi/Signal.h"
#include "wpi/uv/Handle.h"
namespace wpi::uv {
class Loop;
/**
* Prepare handle.
* Prepare handles will generate a signal once per loop iteration, right
* before polling for I/O.
*/
class Prepare final : public HandleImpl<Prepare, uv_prepare_t> {
struct private_init {};
public:
explicit Prepare(const private_init&) {}
~Prepare() noexcept override = default;
/**
* Create a prepare handle.
*
* @param loop Loop object where this handle runs.
*/
static std::shared_ptr<Prepare> Create(Loop& loop);
/**
* Create a prepare handle.
*
* @param loop Loop object where this handle runs.
*/
static std::shared_ptr<Prepare> Create(const std::shared_ptr<Loop>& loop) {
return Create(*loop);
}
/**
* Start the handle.
*/
void Start();
/**
* Stop the handle. The signal will no longer be generated.
*/
void Stop() { Invoke(&uv_prepare_stop, GetRaw()); }
/**
* Signal generated once per loop iteration prior to polling for I/O.
*/
sig::Signal<> prepare;
};
} // namespace wpi::uv
#endif // WPIUTIL_WPI_UV_PREPARE_H_

View File

@@ -1,310 +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.
#ifndef WPIUTIL_WPI_UV_PROCESS_H_
#define WPIUTIL_WPI_UV_PROCESS_H_
#include <uv.h>
#include <initializer_list>
#include <memory>
#include <string>
#include <string_view>
#include "wpi/Signal.h"
#include "wpi/SmallVector.h"
#include "wpi/span.h"
#include "wpi/uv/Handle.h"
namespace wpi::uv {
class Loop;
class Pipe;
/**
* Process handle.
* Process handles will spawn a new process and allow the user to control it
* and establish communication channels with it using streams.
*/
class Process final : public HandleImpl<Process, uv_process_t> {
struct private_init {};
public:
explicit Process(const private_init&) {}
~Process() noexcept override = default;
/**
* Structure for Spawn() option temporaries. This is a reference type, so if
* this value is stored outside of a temporary, be careful about overwriting
* what it points to.
*/
struct Option {
enum Type {
kNone,
kArg,
kEnv,
kCwd,
kUid,
kGid,
kSetFlags,
kClearFlags,
kStdioIgnore,
kStdioInheritFd,
kStdioInheritPipe,
kStdioCreatePipe
};
Option() : m_type(kNone) {}
/*implicit*/ Option(const char* arg) { // NOLINT
m_data.str = arg;
}
/*implicit*/ Option(const std::string& arg) { // NOLINT
m_data.str = arg.data();
}
/*implicit*/ Option(std::string_view arg) // NOLINT
: m_strData(arg) {
m_data.str = m_strData.c_str();
}
/*implicit*/ Option(const SmallVectorImpl<char>& arg) // NOLINT
: m_strData(arg.data(), arg.size()) {
m_data.str = m_strData.c_str();
}
explicit Option(Type type) : m_type(type) {}
Type m_type = kArg;
std::string m_strData;
union {
const char* str;
uv_uid_t uid;
uv_gid_t gid;
unsigned int flags;
struct {
size_t index;
union {
int fd;
Pipe* pipe;
};
unsigned int flags;
} stdio;
} m_data;
};
/**
* Set environment variable for the subprocess. If not set, the parent's
* environment is used.
* @param env environment variable
*/
static Option Env(std::string_view env) {
Option o(Option::kEnv);
o.m_strData = env;
o.m_data.str = o.m_strData.c_str();
return o;
}
/**
* Set the current working directory for the subprocess.
* @param cwd current working directory
*/
static Option Cwd(std::string_view cwd) {
Option o(Option::kCwd);
o.m_strData = cwd;
o.m_data.str = o.m_strData.c_str();
return o;
}
/**
* Set the child process' user id.
* @param uid user id
*/
static Option Uid(uv_uid_t uid) {
Option o(Option::kUid);
o.m_data.uid = uid;
return o;
}
/**
* Set the child process' group id.
* @param gid group id
*/
static Option Gid(uv_gid_t gid) {
Option o(Option::kGid);
o.m_data.gid = gid;
return o;
}
/**
* Set spawn flags.
* @param flags Bitmask values from uv_process_flags.
*/
static Option SetFlags(unsigned int flags) {
Option o(Option::kSetFlags);
o.m_data.flags = flags;
return o;
}
/**
* Clear spawn flags.
* @param flags Bitmask values from uv_process_flags.
*/
static Option ClearFlags(unsigned int flags) {
Option o(Option::kClearFlags);
o.m_data.flags = flags;
return o;
}
/**
* Explicitly ignore a stdio.
* @param index stdio index
*/
static Option StdioIgnore(size_t index) {
Option o(Option::kStdioIgnore);
o.m_data.stdio.index = index;
return o;
}
/**
* Inherit a file descriptor from the parent process.
* @param index stdio index
* @param fd parent file descriptor
*/
static Option StdioInherit(size_t index, int fd) {
Option o(Option::kStdioInheritFd);
o.m_data.stdio.index = index;
o.m_data.stdio.fd = fd;
return o;
}
/**
* Inherit a pipe from the parent process.
* @param index stdio index
* @param pipe pipe
*/
static Option StdioInherit(size_t index, Pipe& pipe) {
Option o(Option::kStdioInheritPipe);
o.m_data.stdio.index = index;
o.m_data.stdio.pipe = &pipe;
return o;
}
/**
* Create a pipe between the child and the parent.
* @param index stdio index
* @param pipe pipe
* @param flags Some combination of UV_READABLE_PIPE, UV_WRITABLE_PIPE, and
* UV_OVERLAPPED_PIPE (Windows only, ignored on Unix).
*/
static Option StdioCreatePipe(size_t index, Pipe& pipe, unsigned int flags) {
Option o(Option::kStdioCreatePipe);
o.m_data.stdio.index = index;
o.m_data.stdio.pipe = &pipe;
o.m_data.stdio.flags = flags;
return o;
}
/**
* Disables inheritance for file descriptors / handles that this process
* inherited from its parent. The effect is that child processes spawned
* by this process don't accidentally inherit these handles.
*
* It is recommended to call this function as early in your program as
* possible, before the inherited file descriptors can be closed or
* duplicated.
*/
static void DisableStdioInheritance() { uv_disable_stdio_inheritance(); }
/**
* Starts a process. If the process is not successfully spawned, an error
* is generated on the loop and this function returns nullptr.
*
* Possible reasons for failing to spawn would include (but not be limited to)
* the file to execute not existing, not having permissions to use the setuid
* or setgid specified, or not having enough memory to allocate for the new
* process.
*
* @param loop Loop object where this handle runs.
* @param file Path pointing to the program to be executed
* @param options Process options
*/
static std::shared_ptr<Process> SpawnArray(Loop& loop, std::string_view file,
span<const Option> options);
static std::shared_ptr<Process> SpawnArray(
Loop& loop, std::string_view file,
std::initializer_list<Option> options) {
return SpawnArray(loop, file, {options.begin(), options.end()});
}
template <typename... Args>
static std::shared_ptr<Process> Spawn(Loop& loop, std::string_view file,
const Args&... options) {
return SpawnArray(loop, file, {options...});
}
/**
* Starts a process. If the process is not successfully spawned, an error
* is generated on the loop and this function returns nullptr.
*
* Possible reasons for failing to spawn would include (but not be limited to)
* the file to execute not existing, not having permissions to use the setuid
* or setgid specified, or not having enough memory to allocate for the new
* process.
*
* @param loop Loop object where this handle runs.
* @param file Path pointing to the program to be executed
* @param options Process options
*/
static std::shared_ptr<Process> SpawnArray(const std::shared_ptr<Loop>& loop,
std::string_view file,
span<const Option> options) {
return SpawnArray(*loop, file, options);
}
static std::shared_ptr<Process> SpawnArray(
const std::shared_ptr<Loop>& loop, std::string_view file,
std::initializer_list<Option> options) {
return SpawnArray(*loop, file, options);
}
template <typename... Args>
static std::shared_ptr<Process> Spawn(const std::shared_ptr<Loop>& loop,
std::string_view file,
const Args&... options) {
return SpawnArray(*loop, file, {options...});
}
/**
* Sends the specified signal to the process.
* @param signum signal number
*/
void Kill(int signum) { Invoke(&uv_process_kill, GetRaw(), signum); }
/**
* Sends the specified signal to the given PID.
* @param pid process ID
* @param signum signal number
* @return 0 on success, otherwise error code.
*/
static int Kill(int pid, int signum) noexcept { return uv_kill(pid, signum); }
/**
* Get the process ID.
* @return Process ID.
*/
uv_pid_t GetPid() const noexcept { return GetRaw()->pid; }
/**
* Signal generated when the process exits. The parameters are the exit
* status and the signal that caused the process to terminate, if any.
*/
sig::Signal<int64_t, int> exited;
};
} // namespace wpi::uv
#endif // WPIUTIL_WPI_UV_PROCESS_H_

View File

@@ -1,166 +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.
#ifndef WPIUTIL_WPI_UV_REQUEST_H_
#define WPIUTIL_WPI_UV_REQUEST_H_
#include <uv.h>
#include <functional>
#include <memory>
#include "wpi/uv/Error.h"
namespace wpi::uv {
/**
* Request. Requests are not moveable or copyable.
* This class provides shared_ptr ownership and shared_from_this.
*/
class Request : public std::enable_shared_from_this<Request> {
public:
using Type = uv_req_type;
Request(const Request&) = delete;
Request(Request&&) = delete;
Request& operator=(const Request&) = delete;
Request& operator=(Request&&) = delete;
virtual ~Request() noexcept = default;
/**
* Get the type of the request.
*
* A base request offers no functionality to promote it to the actual request
* type. By means of this function, the type of the underlying request as
* specified by Type is made available.
*
* @return The actual type of the request.
*/
Type GetType() const noexcept { return m_uv_req->type; }
/**
* Get the name of the type of the request. E.g. "connect" for connect.
*/
const char* GetTypeName() const noexcept {
return uv_req_type_name(m_uv_req->type);
}
/**
* Cancel a pending request.
*
* This method fails if the request is executing or has finished
* executing.
* It can emit an error signal in case of errors.
*
* @return True in case of success, false otherwise.
*/
bool Cancel() { return uv_cancel(m_uv_req) == 0; }
/**
* Return the size of the underlying request type.
* @return The size of the underlying request type.
*/
size_t RawSize() const noexcept { return uv_req_size(m_uv_req->type); }
/**
* Get the underlying request data structure.
*
* @return The underlying request data structure.
*/
uv_req_t* GetRawReq() noexcept { return m_uv_req; }
/**
* Get the underlying request data structure.
*
* @return The underlying request data structure.
*/
const uv_req_t* GetRawReq() const noexcept { return m_uv_req; }
/**
* Keep this request in memory even if no outside shared_ptr references
* remain. To release call Release().
*
* Derived classes can override this method for different memory management
* approaches (e.g. pooled storage of requests).
*/
virtual void Keep() noexcept { m_self = shared_from_this(); }
/**
* No longer force holding this request in memory. Does not immediately
* destroy the object unless no outside shared_ptr references remain.
*
* Derived classes can override this method for different memory management
* approaches (e.g. pooled storage of requests).
*/
virtual void Release() noexcept { m_self.reset(); }
/**
* Error callback. By default, this is set up to report errors to the handle
* that created this request.
* @param err error code
*/
std::function<void(Error)> error;
/**
* Report an error.
* @param err Error code
*/
void ReportError(int err) { error(Error(err)); }
protected:
/**
* Constructor.
*/
explicit Request(uv_req_t* uv_req) : m_uv_req{uv_req} {
m_uv_req->data = this;
}
private:
std::shared_ptr<Request> m_self;
uv_req_t* m_uv_req;
};
/**
* Request. Requests are not moveable or copyable.
* @tparam T CRTP derived class
* @tparam U underlying libuv request type
*/
template <typename T, typename U>
class RequestImpl : public Request {
public:
std::shared_ptr<T> shared_from_this() {
return std::static_pointer_cast<T>(this->shared_from_this());
}
std::shared_ptr<const T> shared_from_this() const {
return std::static_pointer_cast<const T>(this->shared_from_this());
}
/**
* Get the underlying request data structure.
*
* @return The underlying request data structure.
*/
U* GetRaw() noexcept { return &m_uv_req; }
/**
* Get the underlying request data structure.
*
* @return The underlying request data structure.
*/
const U* GetRaw() const noexcept { return &m_uv_req; }
protected:
/**
* Constructor.
*/
RequestImpl() : Request{reinterpret_cast<uv_req_t*>(&m_uv_req)} {}
private:
U m_uv_req;
};
} // namespace wpi::uv
#endif // WPIUTIL_WPI_UV_REQUEST_H_

View File

@@ -1,79 +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.
#ifndef WPIUTIL_WPI_UV_SIGNAL_H_
#define WPIUTIL_WPI_UV_SIGNAL_H_
#include <uv.h>
#include <memory>
#include "wpi/Signal.h"
#include "wpi/uv/Handle.h"
namespace wpi::uv {
class Loop;
/**
* Signal handle.
*/
class Signal final : public HandleImpl<Signal, uv_signal_t> {
struct private_init {};
public:
explicit Signal(const private_init&) {}
~Signal() noexcept override = default;
/**
* Create a signal handle.
*
* @param loop Loop object where this handle runs.
*/
static std::shared_ptr<Signal> Create(Loop& loop);
/**
* Create a signal handle.
*
* @param loop Loop object where this handle runs.
*/
static std::shared_ptr<Signal> Create(const std::shared_ptr<Loop>& loop) {
return Create(*loop);
}
/**
* Start watching for the given signal.
*
* @param signum Signal to watch for.
*/
void Start(int signum);
/**
* Start watching for the given signal. Same as Start() but the signal
* handler is reset the moment the signal is received.
*
* @param signum Signal to watch for.
*/
void StartOneshot(int signum);
/**
* Stop watching for the signal.
*/
void Stop() { Invoke(&uv_signal_stop, GetRaw()); }
/**
* Get the signal being monitored.
* @return Signal number.
*/
int GetSignal() const { return GetRaw()->signum; }
/**
* Signal generated when a signal occurs.
*/
sig::Signal<int> signal;
};
} // namespace wpi::uv
#endif // WPIUTIL_WPI_UV_SIGNAL_H_

View File

@@ -1,302 +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.
#ifndef WPIUTIL_WPI_UV_STREAM_H_
#define WPIUTIL_WPI_UV_STREAM_H_
#include <uv.h>
#include <cstdlib>
#include <functional>
#include <initializer_list>
#include <memory>
#include <utility>
#include "wpi/Signal.h"
#include "wpi/span.h"
#include "wpi/uv/Buffer.h"
#include "wpi/uv/Handle.h"
#include "wpi/uv/Request.h"
namespace wpi::uv {
class Stream;
/**
* Shutdown request.
*/
class ShutdownReq : public RequestImpl<ShutdownReq, uv_shutdown_t> {
public:
ShutdownReq();
Stream& GetStream() const {
return *static_cast<Stream*>(GetRaw()->handle->data);
}
/**
* Shutdown completed signal.
*/
sig::Signal<> complete;
};
/**
* Write request.
*/
class WriteReq : public RequestImpl<WriteReq, uv_write_t> {
public:
WriteReq();
Stream& GetStream() const {
return *static_cast<Stream*>(GetRaw()->handle->data);
}
/**
* Write completed signal. This is called even if an error occurred.
* @param err error value
*/
sig::Signal<Error> finish;
};
/**
* Stream handle.
* Stream handles provide an abstraction of a duplex communication channel.
* This is an abstract type; there are three stream implementations (Tcp,
* Pipe, and Tty).
*/
class Stream : public Handle {
public:
std::shared_ptr<Stream> shared_from_this() {
return std::static_pointer_cast<Stream>(Handle::shared_from_this());
}
std::shared_ptr<const Stream> shared_from_this() const {
return std::static_pointer_cast<const Stream>(Handle::shared_from_this());
}
/**
* Shutdown the outgoing (write) side of a duplex stream. It waits for pending
* write requests to complete. HandleShutdownComplete() is called on the
* request after shutdown is complete.
*
* @param req shutdown request
*/
void Shutdown(const std::shared_ptr<ShutdownReq>& req);
/**
* Shutdown the outgoing (write) side of a duplex stream. It waits for pending
* write requests to complete. The callback is called after shutdown is
* complete. Errors will be reported to the stream error handler.
*
* @param callback Callback function to call when shutdown completes
* @return Connection object for the callback
*/
void Shutdown(std::function<void()> callback = nullptr);
/**
* Start reading data from an incoming stream.
*
* This will only succeed after a connection has been established.
*
* A data signal will be emitted several times until there is no more
* data to read or `StopRead()` is called.
* An end signal will be emitted when there is no more data to read.
*/
void StartRead();
/**
* Stop reading data from the stream.
*
* This function is idempotent and may be safely called on a stopped stream.
*/
void StopRead() { Invoke(&uv_read_stop, GetRawStream()); }
/**
* Write data to the stream.
*
* Data are written in order. The lifetime of the data pointers passed in
* the `bufs` parameter must exceed the lifetime of the write request.
* An easy way to ensure this is to have the write request keep track of
* the data and use either its Complete() function or destructor to free the
* data.
*
* The finish signal will be emitted on the request object when the data
* has been written (or if an error occurs).
* The error signal will be emitted on the request object in case of errors.
*
* @param bufs The buffers to be written to the stream.
* @param req write request
*/
void Write(span<const Buffer> bufs, const std::shared_ptr<WriteReq>& req);
/**
* Write data to the stream.
*
* Data are written in order. The lifetime of the data pointers passed in
* the `bufs` parameter must exceed the lifetime of the write request.
* An easy way to ensure this is to have the write request keep track of
* the data and use either its Complete() function or destructor to free the
* data.
*
* The finish signal will be emitted on the request object when the data
* has been written (or if an error occurs).
* The error signal will be emitted on the request object in case of errors.
*
* @param bufs The buffers to be written to the stream.
* @param req write request
*/
void Write(std::initializer_list<Buffer> bufs,
const std::shared_ptr<WriteReq>& req) {
Write({bufs.begin(), bufs.end()}, req);
}
/**
* Write data to the stream.
*
* Data are written in order. The lifetime of the data pointers passed in
* the `bufs` parameter must exceed the lifetime of the write request.
* The callback can be used to free data after the request completes.
*
* The callback will be called when the data has been written (even if an
* error occurred). Errors will be reported to the stream error handler.
*
* @param bufs The buffers to be written to the stream.
* @param callback Callback function to call when the write completes
*/
void Write(span<const Buffer> bufs,
std::function<void(span<Buffer>, Error)> callback);
/**
* Write data to the stream.
*
* Data are written in order. The lifetime of the data pointers passed in
* the `bufs` parameter must exceed the lifetime of the write request.
* The callback can be used to free data after the request completes.
*
* The callback will be called when the data has been written (even if an
* error occurred). Errors will be reported to the stream error handler.
*
* @param bufs The buffers to be written to the stream.
* @param callback Callback function to call when the write completes
*/
void Write(std::initializer_list<Buffer> bufs,
std::function<void(span<Buffer>, Error)> callback) {
Write({bufs.begin(), bufs.end()}, std::move(callback));
}
/**
* Queue a write request if it can be completed immediately.
*
* Same as `Write()`, but wont queue a write request if it cant be
* completed immediately.
* An error signal will be emitted in case of errors.
*
* @param bufs The buffers to be written to the stream.
* @return Number of bytes written.
*/
int TryWrite(span<const Buffer> bufs);
/**
* Queue a write request if it can be completed immediately.
*
* Same as `Write()`, but wont queue a write request if it cant be
* completed immediately.
* An error signal will be emitted in case of errors.
*
* @param bufs The buffers to be written to the stream.
* @return Number of bytes written.
*/
int TryWrite(std::initializer_list<Buffer> bufs) {
return TryWrite({bufs.begin(), bufs.end()});
}
/**
* Check if the stream is readable.
* @return True if the stream is readable, false otherwise.
*/
bool IsReadable() const noexcept {
return uv_is_readable(GetRawStream()) == 1;
}
/**
* @brief Checks if the stream is writable.
* @return True if the stream is writable, false otherwise.
*/
bool IsWritable() const noexcept {
return uv_is_writable(GetRawStream()) == 1;
}
/**
* Enable or disable blocking mode for a stream.
*
* When blocking mode is enabled all writes complete synchronously. The
* interface remains unchanged otherwise, e.g. completion or failure of the
* operation will still be reported through events which are emitted
* asynchronously.
*
* @param enable True to enable blocking mode, false otherwise.
* @return True in case of success, false otherwise.
*/
bool SetBlocking(bool enable) noexcept {
return uv_stream_set_blocking(GetRawStream(), enable) == 0;
}
/**
* Gets the amount of queued bytes waiting to be sent.
* @return Amount of queued bytes waiting to be sent.
*/
size_t GetWriteQueueSize() const noexcept {
return GetRawStream()->write_queue_size;
}
/**
* Get the underlying stream data structure.
*
* @return The underlying stream data structure.
*/
uv_stream_t* GetRawStream() const noexcept {
return reinterpret_cast<uv_stream_t*>(GetRawHandle());
}
/**
* Signal generated when data was read on a stream.
*/
sig::Signal<Buffer&, size_t> data;
/**
* Signal generated when no more read data is available.
*/
sig::Signal<> end;
protected:
explicit Stream(uv_stream_t* uv_stream)
: Handle{reinterpret_cast<uv_handle_t*>(uv_stream)} {}
};
template <typename T, typename U>
class StreamImpl : public Stream {
public:
std::shared_ptr<T> shared_from_this() {
return std::static_pointer_cast<T>(Handle::shared_from_this());
}
std::shared_ptr<const T> shared_from_this() const {
return std::static_pointer_cast<const T>(Handle::shared_from_this());
}
/**
* Get the underlying handle data structure.
*
* @return The underlying handle data structure.
*/
U* GetRaw() const noexcept {
return reinterpret_cast<U*>(this->GetRawHandle());
}
protected:
StreamImpl() : Stream{static_cast<uv_stream_t*>(std::malloc(sizeof(U)))} {}
};
} // namespace wpi::uv
#endif // WPIUTIL_WPI_UV_STREAM_H_

View File

@@ -1,363 +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.
#ifndef WPIUTIL_WPI_UV_TCP_H_
#define WPIUTIL_WPI_UV_TCP_H_
#include <uv.h>
#include <chrono>
#include <functional>
#include <memory>
#include <string_view>
#include <utility>
#include "wpi/uv/NetworkStream.h"
namespace wpi::uv {
class Loop;
class TcpConnectReq;
/**
* TCP handle.
* TCP handles are used to represent both TCP streams and servers.
*/
class Tcp final : public NetworkStreamImpl<Tcp, uv_tcp_t> {
struct private_init {};
public:
using Time = std::chrono::duration<uint64_t, std::milli>;
explicit Tcp(const private_init&) {}
~Tcp() noexcept override = default;
/**
* Create a TCP handle.
*
* @param loop Loop object where this handle runs.
* @param flags Flags
*/
static std::shared_ptr<Tcp> Create(Loop& loop,
unsigned int flags = AF_UNSPEC);
/**
* Create a TCP handle.
*
* @param loop Loop object where this handle runs.
* @param flags Flags
*/
static std::shared_ptr<Tcp> Create(const std::shared_ptr<Loop>& loop,
unsigned int flags = AF_UNSPEC) {
return Create(*loop, flags);
}
/**
* Reuse this handle. This closes the handle, and after the close completes,
* reinitializes it (identically to Create) and calls the provided callback.
* Unlike Close(), it does NOT emit the closed signal, however, IsClosing()
* will return true until the callback is called. This does nothing if
* IsClosing() is true (e.g. if Close() was called).
*
* @param flags Flags
* @param callback Callback
*/
void Reuse(std::function<void()> callback, unsigned int flags = AF_UNSPEC);
/**
* Accept incoming connection.
*
* This call is used in conjunction with `Listen()` to accept incoming
* connections. Call this function after receiving a ListenEvent event to
* accept the connection.
* An error signal will be emitted in case of errors.
*
* When the connection signal is emitted it is guaranteed that this
* function will complete successfully the first time. If you attempt to use
* it more than once, it may fail.
* It is suggested to only call this function once per connection signal.
*
* @return The stream handle for the accepted connection, or nullptr on error.
*/
std::shared_ptr<Tcp> Accept();
/**
* Accept incoming connection.
*
* This call is used in conjunction with `Listen()` to accept incoming
* connections. Call this function after receiving a connection signal to
* accept the connection.
* An error signal will be emitted in case of errors.
*
* When the connection signal is emitted it is guaranteed that this
* function will complete successfully the first time. If you attempt to use
* it more than once, it may fail.
* It is suggested to only call this function once per connection signal.
*
* @param client Client stream object.
* @return False on error.
*/
bool Accept(const std::shared_ptr<Tcp>& client) {
return NetworkStream::Accept(client);
}
/**
* Open an existing file descriptor or SOCKET as a TCP handle.
*
* @note The passed file descriptor or SOCKET is not checked for its type, but
* it's required that it represents a valid stream socket.
*
* @param sock A valid socket handle (either a file descriptor or a SOCKET).
*/
void Open(uv_os_sock_t sock) { Invoke(&uv_tcp_open, GetRaw(), sock); }
/**
* Enable no delay operation (turns off Nagle's algorithm).
* @param enable True to enable it, false otherwise.
* @return True in case of success, false otherwise.
*/
bool SetNoDelay(bool enable) { return uv_tcp_nodelay(GetRaw(), enable) == 0; }
/**
* Enable/Disable TCP keep-alive.
* @param enable True to enable it, false otherwise.
* @param time Initial delay in seconds (use
* `std::chrono::duration<unsigned int>`).
* @return True in case of success, false otherwise.
*/
bool SetKeepAlive(bool enable, Time time = Time{0}) {
return uv_tcp_keepalive(GetRaw(), enable,
static_cast<unsigned>(time.count())) == 0;
}
/**
* Enable/Disable simultaneous asynchronous accept requests.
*
* Enable/Disable simultaneous asynchronous accept requests that are
* queued by the operating system when listening for new TCP
* connections.
* This setting is used to tune a TCP server for the desired performance.
* Having simultaneous accepts can significantly improve the rate of
* accepting connections (which is why it is enabled by default) but may
* lead to uneven load distribution in multi-process setups.
*
* @param enable True to enable it, false otherwise.
* @return True in case of success, false otherwise.
*/
bool SetSimultaneousAccepts(bool enable) {
return uv_tcp_simultaneous_accepts(GetRaw(), enable) == 0;
}
/**
* Bind the handle to an IPv4 or IPv6 address and port.
*
* A successful call to this function does not guarantee that the call to
* `Listen()` or `Connect()` will work properly.
* An error signal can be emitted because of either this function or the
* ones mentioned above.
*
* @param addr Initialized `sockaddr_in` or `sockaddr_in6` data structure.
* @param flags Optional additional flags.
*/
void Bind(const sockaddr& addr, unsigned int flags = 0) {
Invoke(&uv_tcp_bind, GetRaw(), &addr, flags);
}
void Bind(const sockaddr_in& addr, unsigned int flags = 0) {
Bind(reinterpret_cast<const sockaddr&>(addr), flags);
}
void Bind(const sockaddr_in6& addr, unsigned int flags = 0) {
Bind(reinterpret_cast<const sockaddr&>(addr), flags);
}
/**
* Bind the handle to an IPv4 address and port.
*
* A successful call to this function does not guarantee that the call to
* `Listen()` or `Connect()` will work properly.
* An error signal can be emitted because of either this function or the
* ones mentioned above.
*
* Available flags are:
*
* @param ip The address to which to bind.
* @param port The port to which to bind.
* @param flags Optional additional flags.
*/
void Bind(std::string_view ip, unsigned int port, unsigned int flags = 0);
/**
* Bind the handle to an IPv6 address and port.
*
* A successful call to this function does not guarantee that the call to
* `Listen()` or `Connect()` will work properly.
* An error signal can be emitted because of either this function or the
* ones mentioned above.
*
* Available flags are:
*
* @param ip The address to which to bind.
* @param port The port to which to bind.
* @param flags Optional additional flags.
*/
void Bind6(std::string_view ip, unsigned int port, unsigned int flags = 0);
/**
* Get the current address to which the handle is bound.
* @return The address (will be zeroed if an error occurred).
*/
sockaddr_storage GetSock();
/**
* Get the address of the peer connected to the handle.
* @return The address (will be zeroed if an error occurred).
*/
sockaddr_storage GetPeer();
/**
* Establish an IPv4 or IPv6 TCP connection.
*
* On Windows if the addr is initialized to point to an unspecified address
* (`0.0.0.0` or `::`) it will be changed to point to localhost. This is
* done to match the behavior of Linux systems.
*
* The connected signal is emitted on the request when the connection has been
* established.
* The error signal is emitted on the request in case of errors during the
* connection.
*
* @param addr Initialized `sockaddr_in` or `sockaddr_in6` data structure.
* @param req connection request
*/
void Connect(const sockaddr& addr, const std::shared_ptr<TcpConnectReq>& req);
void Connect(const sockaddr_in& addr,
const std::shared_ptr<TcpConnectReq>& req) {
Connect(reinterpret_cast<const sockaddr&>(addr), req);
}
void Connect(const sockaddr_in6& addr,
const std::shared_ptr<TcpConnectReq>& req) {
Connect(reinterpret_cast<const sockaddr&>(addr), req);
}
/**
* Establish an IPv4 or IPv6 TCP connection.
*
* On Windows if the addr is initialized to point to an unspecified address
* (`0.0.0.0` or `::`) it will be changed to point to localhost. This is
* done to match the behavior of Linux systems.
*
* The callback is called when the connection has been established. Errors
* are reported to the stream error handler.
*
* @param addr Initialized `sockaddr_in` or `sockaddr_in6` data structure.
* @param callback Callback function to call when connection established
*/
void Connect(const sockaddr& addr, std::function<void()> callback);
void Connect(const sockaddr_in& addr, std::function<void()> callback) {
Connect(reinterpret_cast<const sockaddr&>(addr), std::move(callback));
}
void Connect(const sockaddr_in6& addr, std::function<void()> callback) {
Connect(reinterpret_cast<const sockaddr&>(addr), std::move(callback));
}
/**
* Establish an IPv4 TCP connection.
*
* On Windows if the addr is initialized to point to an unspecified address
* (`0.0.0.0` or `::`) it will be changed to point to localhost. This is
* done to match the behavior of Linux systems.
*
* The connected signal is emitted on the request when the connection has been
* established.
* The error signal is emitted on the request in case of errors during the
* connection.
*
* @param ip The address to which to connect to.
* @param port The port to which to connect to.
* @param req connection request
*/
void Connect(std::string_view ip, unsigned int port,
const std::shared_ptr<TcpConnectReq>& req);
/**
* Establish an IPv4 TCP connection.
*
* On Windows if the addr is initialized to point to an unspecified address
* (`0.0.0.0` or `::`) it will be changed to point to localhost. This is
* done to match the behavior of Linux systems.
*
* The callback is called when the connection has been established. Errors
* are reported to the stream error handler.
*
* @param ip The address to which to connect to.
* @param port The port to which to connect to.
* @param callback Callback function to call when connection established
*/
void Connect(std::string_view ip, unsigned int port,
std::function<void()> callback);
/**
* Establish an IPv6 TCP connection.
*
* On Windows if the addr is initialized to point to an unspecified address
* (`0.0.0.0` or `::`) it will be changed to point to localhost. This is
* done to match the behavior of Linux systems.
*
* The connected signal is emitted on the request when the connection has been
* established.
* The error signal is emitted on the request in case of errors during the
* connection.
*
* @param ip The address to which to connect to.
* @param port The port to which to connect to.
* @param req connection request
*/
void Connect6(std::string_view ip, unsigned int port,
const std::shared_ptr<TcpConnectReq>& req);
/**
* Establish an IPv6 TCP connection.
*
* On Windows if the addr is initialized to point to an unspecified address
* (`0.0.0.0` or `::`) it will be changed to point to localhost. This is
* done to match the behavior of Linux systems.
*
* The callback is called when the connection has been established. Errors
* are reported to the stream error handler.
*
* @param ip The address to which to connect to.
* @param port The port to which to connect to.
* @param callback Callback function to call when connection established
*/
void Connect6(std::string_view ip, unsigned int port,
std::function<void()> callback);
private:
Tcp* DoAccept() override;
struct ReuseData {
std::function<void()> callback;
unsigned int flags;
};
std::unique_ptr<ReuseData> m_reuseData;
};
/**
* TCP connection request.
*/
class TcpConnectReq : public ConnectReq {
public:
Tcp& GetStream() const {
return *static_cast<Tcp*>(&ConnectReq::GetStream());
}
};
} // namespace wpi::uv
#endif // WPIUTIL_WPI_UV_TCP_H_

View File

@@ -1,135 +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.
#ifndef WPIUTIL_WPI_UV_TIMER_H_
#define WPIUTIL_WPI_UV_TIMER_H_
#include <uv.h>
#include <chrono>
#include <functional>
#include <memory>
#include <utility>
#include "wpi/Signal.h"
#include "wpi/uv/Handle.h"
namespace wpi::uv {
class Loop;
/**
* Timer handle.
* Timer handles are used to schedule signals to be called in the future.
*/
class Timer final : public HandleImpl<Timer, uv_timer_t> {
struct private_init {};
public:
using Time = std::chrono::duration<uint64_t, std::milli>;
explicit Timer(const private_init&) {}
~Timer() noexcept override = default;
/**
* Create a timer handle.
*
* @param loop Loop object where this handle runs.
*/
static std::shared_ptr<Timer> Create(Loop& loop);
/**
* Create a timer handle.
*
* @param loop Loop object where this handle runs.
*/
static std::shared_ptr<Timer> Create(const std::shared_ptr<Loop>& loop) {
return Create(*loop);
}
/**
* Create a timer that calls a functor after a given time interval.
*
* @param loop Loop object where the timer should run.
* @param timeout Time interval
* @param func Functor
*/
static void SingleShot(Loop& loop, Time timeout, std::function<void()> func);
/**
* Create a timer that calls a functor after a given time interval.
*
* @param loop Loop object where the timer should run.
* @param timeout Time interval
* @param func Functor
*/
static void SingleShot(const std::shared_ptr<Loop>& loop, Time timeout,
std::function<void()> func) {
return SingleShot(*loop, timeout, std::move(func));
}
/**
* Start the timer.
*
* If timeout is zero, an event is emitted on the next event loop
* iteration. If repeat is non-zero, an event is emitted first
* after timeout milliseconds and then repeatedly after repeat milliseconds.
*
* @param timeout Milliseconds before to emit an event (use
* `std::chrono::duration<uint64_t, std::milli>`).
* @param repeat Milliseconds between successive events (use
* `std::chrono::duration<uint64_t, std::milli>`).
*/
void Start(Time timeout, Time repeat = Time{0});
/**
* Stop the timer.
*/
void Stop() { Invoke(&uv_timer_stop, GetRaw()); }
/**
* Stop the timer and restart it if it was repeating.
*
* Stop the timer, and if it is repeating restart it using the repeat value
* as the timeout.
* If the timer has never been started before it emits sigError.
*/
void Again() { Invoke(&uv_timer_again, GetRaw()); }
/**
* Set the repeat interval value.
*
* The timer will be scheduled to run on the given interval and will follow
* normal timer semantics in the case of a time-slice overrun.
* For example, if a 50ms repeating timer first runs for 17ms, it will be
* scheduled to run again 33ms later. If other tasks consume more than the
* 33ms following the first timer event, then another event will be emitted
* as soon as possible.
*
* If the repeat value is set from a listener bound to an event, it does
* not immediately take effect. If the timer was non-repeating before, it
* will have been stopped. If it was repeating, then the old repeat value
* will have been used to schedule the next timeout.
*
* @param repeat Repeat interval in milliseconds (use
* `std::chrono::duration<uint64_t, std::milli>`).
*/
void SetRepeat(Time repeat) { uv_timer_set_repeat(GetRaw(), repeat.count()); }
/**
* Get the timer repeat value.
* @return Timer repeat value in milliseconds (as a
* `std::chrono::duration<uint64_t, std::milli>`).
*/
Time GetRepeat() const { return Time{uv_timer_get_repeat(GetRaw())}; }
/**
* Signal generated when the timeout event occurs.
*/
sig::Signal<> timeout;
};
} // namespace wpi::uv
#endif // WPIUTIL_WPI_UV_TIMER_H_

View File

@@ -1,85 +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.
#ifndef WPIUTIL_WPI_UV_TTY_H_
#define WPIUTIL_WPI_UV_TTY_H_
#include <uv.h>
#include <memory>
#include <utility>
#include "wpi/uv/Stream.h"
namespace wpi::uv {
class Loop;
class Tty;
/**
* TTY handle.
* TTY handles represent a stream for the console.
*/
class Tty final : public StreamImpl<Tty, uv_tty_t> {
struct private_init {};
public:
explicit Tty(const private_init&) {}
~Tty() noexcept override = default;
/**
* Create a TTY handle.
*
* @param loop Loop object where this handle runs.
* @param fd File descriptor, usually 0=stdin, 1=stdout, 2=stderr
* @param readable Specifies if you plan on calling StartRead(). stdin is
* readable, stdout is not.
*/
static std::shared_ptr<Tty> Create(Loop& loop, uv_file fd, bool readable);
/**
* Create a TTY handle.
*
* @param loop Loop object where this handle runs.
* @param fd File descriptor, usually 0=stdin, 1=stdout, 2=stderr
* @param readable Specifies if you plan on calling StartRead(). stdin is
* readable, stdout is not.
*/
static std::shared_ptr<Tty> Create(const std::shared_ptr<Loop>& loop,
uv_file fd, bool readable) {
return Create(*loop, fd, readable);
}
/**
* Set the TTY using the specified terminal mode.
*
* @param mode terminal mode
*/
void SetMode(uv_tty_mode_t mode) {
int err = uv_tty_set_mode(GetRaw(), mode);
if (err < 0) {
ReportError(err);
}
}
/**
* Reset TTY settings to default values for the next process to take over.
* Typically called when the program exits.
*/
void ResetMode() { Invoke(&uv_tty_reset_mode); }
/**
* Gets the current window size.
* @return Window size (pair of width and height).
*/
std::pair<int, int> GetWindowSize() {
int width = 0, height = 0;
Invoke(&uv_tty_get_winsize, GetRaw(), &width, &height);
return std::make_pair(width, height);
}
};
} // namespace wpi::uv
#endif // WPIUTIL_WPI_UV_TTY_H_

View File

@@ -1,378 +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.
#ifndef WPIUTIL_WPI_UV_UDP_H_
#define WPIUTIL_WPI_UV_UDP_H_
#include <uv.h>
#include <functional>
#include <memory>
#include <string_view>
#include <utility>
#include "wpi/Signal.h"
#include "wpi/span.h"
#include "wpi/uv/Handle.h"
#include "wpi/uv/Request.h"
namespace wpi::uv {
class Loop;
class Udp;
/**
* UDP send request.
*/
class UdpSendReq : public RequestImpl<UdpSendReq, uv_udp_send_t> {
public:
UdpSendReq();
Udp& GetUdp() const { return *static_cast<Udp*>(GetRaw()->handle->data); }
/**
* Send completed signal. This is called even if an error occurred.
* @param err error value
*/
sig::Signal<Error> complete;
};
/**
* UDP handle.
* UDP handles encapsulate UDP communication for both clients and servers.
*/
class Udp final : public HandleImpl<Udp, uv_udp_t> {
struct private_init {};
public:
explicit Udp(const private_init&) {}
~Udp() noexcept override = default;
/**
* Create a UDP handle.
*
* @param loop Loop object where this handle runs.
* @param flags Flags
*/
static std::shared_ptr<Udp> Create(Loop& loop,
unsigned int flags = AF_UNSPEC);
/**
* Create a UDP handle.
*
* @param loop Loop object where this handle runs.
* @param flags Flags
*/
static std::shared_ptr<Udp> Create(const std::shared_ptr<Loop>& loop,
unsigned int flags = AF_UNSPEC) {
return Create(*loop, flags);
}
/**
* Open an existing file descriptor or SOCKET as a UDP handle.
*
* @param sock A valid socket handle (either a file descriptor or a SOCKET).
*/
void Open(uv_os_sock_t sock) { Invoke(&uv_udp_open, GetRaw(), sock); }
/**
* Bind the handle to an IPv4 or IPv6 address and port.
*
* @param addr Initialized `sockaddr_in` or `sockaddr_in6` data structure.
* @param flags Optional additional flags.
*/
void Bind(const sockaddr& addr, unsigned int flags = 0) {
Invoke(&uv_udp_bind, GetRaw(), &addr, flags);
}
void Bind(const sockaddr_in& addr, unsigned int flags = 0) {
Bind(reinterpret_cast<const sockaddr&>(addr), flags);
}
void Bind(const sockaddr_in6& addr, unsigned int flags = 0) {
Bind(reinterpret_cast<const sockaddr&>(addr), flags);
}
/**
* Bind the handle to an IPv4 address and port.
*
* @param ip The address to which to bind.
* @param port The port to which to bind.
* @param flags Optional additional flags.
*/
void Bind(std::string_view ip, unsigned int port, unsigned int flags = 0);
/**
* Bind the handle to an IPv6 address and port.
*
* @param ip The address to which to bind.
* @param port The port to which to bind.
* @param flags Optional additional flags.
*/
void Bind6(std::string_view ip, unsigned int port, unsigned int flags = 0);
/**
* Associate the handle to a remote address and port, so every message sent
* by this handle is automatically sent to that destination.
*
* @param addr Initialized `sockaddr_in` or `sockaddr_in6` data structure.
*/
void Connect(const sockaddr& addr) {
Invoke(&uv_udp_connect, GetRaw(), &addr);
}
void Connect(const sockaddr_in& addr) {
Connect(reinterpret_cast<const sockaddr&>(addr));
}
void Connect(const sockaddr_in6& addr) {
Connect(reinterpret_cast<const sockaddr&>(addr));
}
/**
* Associate the handle to an IPv4 address and port, so every message sent
* by this handle is automatically sent to that destination.
*
* @param ip The address to which to bind.
* @param port The port to which to bind.
*/
void Connect(std::string_view ip, unsigned int port);
/**
* Associate the handle to an IPv6 address and port, so every message sent
* by this handle is automatically sent to that destination.
*
* @param ip The address to which to bind.
* @param port The port to which to bind.
* @param flags Optional additional flags.
*/
void Connect6(std::string_view ip, unsigned int port);
/**
* Get the remote IP and port on connected UDP handles.
* @return The address (will be zeroed if an error occurred).
*/
sockaddr_storage GetPeer();
/**
* Get the current address to which the handle is bound.
* @return The address (will be zeroed if an error occurred).
*/
sockaddr_storage GetSock();
/**
* Set membership for a multicast address.
*
* @param multicastAddr Multicast address to set membership for
* @param interfaceAddr Interface address
* @param membership Should be UV_JOIN_GROUP or UV_LEAVE_GROUP
*/
void SetMembership(std::string_view multicastAddr,
std::string_view interfaceAddr, uv_membership membership);
/**
* Set IP multicast loop flag. Makes multicast packets loop back to local
* sockets.
*
* @param enabled True for enabled, false for disabled
*/
void SetMulticastLoop(bool enabled) {
Invoke(&uv_udp_set_multicast_loop, GetRaw(), enabled ? 1 : 0);
}
/**
* Set the multicast TTL.
*
* @param ttl Time to live (1-255)
*/
void SetMulticastTtl(int ttl) {
Invoke(&uv_udp_set_multicast_ttl, GetRaw(), ttl);
}
/**
* Set the multicast interface to send or receive data on.
*
* @param interfaceAddr Interface address
*/
void SetMulticastInterface(std::string_view interfaceAddr);
/**
* Set broadcast on or off.
*
* @param enabled True for enabled, false for disabled
*/
void SetBroadcast(bool enabled) {
Invoke(&uv_udp_set_broadcast, GetRaw(), enabled ? 1 : 0);
}
/**
* Set the time to live (TTL).
*
* @param ttl Time to live (1-255)
*/
void SetTtl(int ttl) { Invoke(&uv_udp_set_ttl, GetRaw(), ttl); }
/**
* Send data over the UDP socket. If the socket has not previously been bound
* with Bind() it will be bound to 0.0.0.0 (the "all interfaces" IPv4 address)
* and a random port number.
*
* Data are written in order. The lifetime of the data pointers passed in
* the `bufs` parameter must exceed the lifetime of the send request.
* The callback can be used to free data after the request completes.
*
* HandleSendComplete() will be called on the request object when the data
* has been written. HandleSendComplete() is called even if an error occurs.
* HandleError() will be called on the request object in case of errors.
*
* @param addr sockaddr_in or sockaddr_in6 with the address and port of the
* remote peer.
* @param bufs The buffers to be written to the stream.
* @param req write request
*/
void Send(const sockaddr& addr, span<const Buffer> bufs,
const std::shared_ptr<UdpSendReq>& req);
void Send(const sockaddr_in& addr, span<const Buffer> bufs,
const std::shared_ptr<UdpSendReq>& req) {
Send(reinterpret_cast<const sockaddr&>(addr), bufs, req);
}
void Send(const sockaddr_in6& addr, span<const Buffer> bufs,
const std::shared_ptr<UdpSendReq>& req) {
Send(reinterpret_cast<const sockaddr&>(addr), bufs, req);
}
/**
* Variant of Send() for connected sockets. Cannot be used with
* connectionless sockets.
*
* @param bufs The buffers to be written to the stream.
* @param req write request
*/
void Send(span<const Buffer> bufs, const std::shared_ptr<UdpSendReq>& req);
/**
* Send data over the UDP socket. If the socket has not previously been bound
* with Bind() it will be bound to 0.0.0.0 (the "all interfaces" IPv4 address)
* and a random port number.
*
* Data are written in order. The lifetime of the data pointers passed in
* the `bufs` parameter must exceed the lifetime of the send request.
* The callback can be used to free data after the request completes.
*
* The callback will be called when the data has been sent. Errors will
* be reported via the error signal.
*
* @param addr sockaddr_in or sockaddr_in6 with the address and port of the
* remote peer.
* @param bufs The buffers to be sent.
* @param callback Callback function to call when the data has been sent.
*/
void Send(const sockaddr& addr, span<const Buffer> bufs,
std::function<void(span<Buffer>, Error)> callback);
void Send(const sockaddr_in& addr, span<const Buffer> bufs,
std::function<void(span<Buffer>, Error)> callback) {
Send(reinterpret_cast<const sockaddr&>(addr), bufs, std::move(callback));
}
void Send(const sockaddr_in6& addr, span<const Buffer> bufs,
std::function<void(span<Buffer>, Error)> callback) {
Send(reinterpret_cast<const sockaddr&>(addr), bufs, std::move(callback));
}
/**
* Variant of Send() for connected sockets. Cannot be used with
* connectionless sockets.
*
* @param bufs The buffers to be written to the stream.
* @param callback Callback function to call when the data has been sent.
*/
void Send(span<const Buffer> bufs,
std::function<void(span<Buffer>, Error)> callback);
/**
* Same as Send(), but won't queue a send request if it can't be completed
* immediately.
*
* @param addr sockaddr_in or sockaddr_in6 with the address and port of the
* remote peer.
* @param bufs The buffers to be send.
* @return Number of bytes sent.
*/
int TrySend(const sockaddr& addr, span<const Buffer> bufs) {
int val = uv_udp_try_send(GetRaw(), bufs.data(),
static_cast<unsigned>(bufs.size()), &addr);
if (val < 0) {
this->ReportError(val);
return 0;
}
return val;
}
int TrySend(const sockaddr_in& addr, span<const Buffer> bufs) {
return TrySend(reinterpret_cast<const sockaddr&>(addr), bufs);
}
int TrySend(const sockaddr_in6& addr, span<const Buffer> bufs) {
return TrySend(reinterpret_cast<const sockaddr&>(addr), bufs);
}
/**
* Variant of TrySend() for connected sockets. Cannot be used with
* connectionless sockets.
*
* @param bufs The buffers to be written to the stream.
* @return Number of bytes sent.
*/
int TrySend(span<const Buffer> bufs) {
int val = uv_udp_try_send(GetRaw(), bufs.data(),
static_cast<unsigned>(bufs.size()), nullptr);
if (val < 0) {
this->ReportError(val);
return 0;
}
return val;
}
/**
* Prepare for receiving data. If the socket has not previously been bound
* with Bind() it is bound to 0.0.0.0 (the "all interfaces" IPv4 address) and
* a random port number.
*
* A received signal will be emitted for each received data packet until
* `StopRecv()` is called.
*/
void StartRecv();
/**
* Stop listening for incoming datagrams.
*/
void StopRecv() { Invoke(&uv_udp_recv_stop, GetRaw()); }
/**
* Gets the amount of queued bytes waiting to be sent.
* @return Amount of queued bytes waiting to be sent.
*/
size_t GetSendQueueSize() const noexcept { return GetRaw()->send_queue_size; }
/**
* Gets the amount of queued packets waiting to be sent.
* @return Amount of queued packets waiting to be sent.
*/
size_t GetSendQueueCount() const noexcept {
return GetRaw()->send_queue_count;
}
/**
* Signal generated for each incoming datagram. Parameters are the buffer,
* the number of bytes received, the address of the sender, and flags.
*/
sig::Signal<Buffer&, size_t, const sockaddr&, unsigned> received;
};
} // namespace wpi::uv
#endif // WPIUTIL_WPI_UV_UDP_H_

View File

@@ -1,93 +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.
#ifndef WPIUTIL_WPI_UV_WORK_H_
#define WPIUTIL_WPI_UV_WORK_H_
#include <uv.h>
#include <functional>
#include <memory>
#include <utility>
#include "wpi/Signal.h"
#include "wpi/uv/Request.h"
namespace wpi::uv {
class Loop;
/**
* Work request.
* For use with `QueueWork()` function family.
*/
class WorkReq : public RequestImpl<WorkReq, uv_work_t> {
public:
WorkReq();
Loop& GetLoop() const { return *static_cast<Loop*>(GetRaw()->loop->data); }
/**
* Function(s) that will be run on the thread pool.
*/
sig::Signal<> work;
/**
* Function(s) that will be run on the loop thread after the work on the
* thread pool has been completed by the work callback.
*/
sig::Signal<> afterWork;
};
/**
* Initializes a work request which will run on the thread pool.
*
* @param loop Event loop
* @param req request
*/
void QueueWork(Loop& loop, const std::shared_ptr<WorkReq>& req);
/**
* Initializes a work request which will run on the thread pool.
*
* @param loop Event loop
* @param req request
*/
inline void QueueWork(const std::shared_ptr<Loop>& loop,
const std::shared_ptr<WorkReq>& req) {
QueueWork(*loop, req);
}
/**
* Initializes a work request which will run on the thread pool. The work
* callback will be run on a thread from the thread pool, and the afterWork
* callback will be called on the loop thread after the work completes. Any
* errors are forwarded to the loop.
*
* @param loop Event loop
* @param work Work callback (called from separate thread)
* @param afterWork After work callback (called on loop thread)
*/
void QueueWork(Loop& loop, std::function<void()> work,
std::function<void()> afterWork);
/**
* Initializes a work request which will run on the thread pool. The work
* callback will be run on a thread from the thread pool, and the afterWork
* callback will be called on the loop thread after the work completes. Any
* errors are forwarded to the loop.
*
* @param loop Event loop
* @param work Work callback (called from separate thread)
* @param afterWork After work callback (called on loop thread)
*/
inline void QueueWork(const std::shared_ptr<Loop>& loop,
std::function<void()> work,
std::function<void()> afterWork) {
QueueWork(*loop, std::move(work), std::move(afterWork));
}
} // namespace wpi::uv
#endif // WPIUTIL_WPI_UV_WORK_H_

Some files were not shown because too many files have changed in this diff Show More