mirror of
https://github.com/wpilibsuite/allwpilib
synced 2026-07-02 02:51:42 +00:00
[wpinet] Move network portions of wpiutil into new wpinet library (#4077)
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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"
|
||||
@@ -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"
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -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
|
||||
@@ -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));
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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(); });
|
||||
});
|
||||
}
|
||||
@@ -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
@@ -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"
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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_
|
||||
@@ -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_
|
||||
@@ -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_
|
||||
@@ -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_
|
||||
@@ -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_
|
||||
@@ -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_
|
||||
@@ -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_
|
||||
@@ -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_
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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_
|
||||
@@ -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_
|
||||
@@ -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
|
||||
@@ -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_
|
||||
@@ -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_
|
||||
@@ -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_
|
||||
@@ -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_
|
||||
@@ -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_
|
||||
@@ -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_
|
||||
@@ -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_
|
||||
@@ -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_
|
||||
@@ -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_
|
||||
@@ -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.
|
||||
*
|
||||
* It’s 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().
|
||||
*
|
||||
* It’s 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_
|
||||
@@ -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_
|
||||
@@ -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
|
||||
@@ -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_
|
||||
@@ -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_
|
||||
@@ -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_
|
||||
@@ -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.
|
||||
*
|
||||
* It’s 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.
|
||||
*
|
||||
* It’s 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_
|
||||
@@ -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.
|
||||
*
|
||||
* It’s 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_
|
||||
@@ -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_
|
||||
@@ -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_
|
||||
@@ -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_
|
||||
@@ -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_
|
||||
@@ -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_
|
||||
@@ -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_
|
||||
@@ -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 it’s 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_
|
||||
@@ -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_
|
||||
@@ -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 won’t 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 loop’s 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.
|
||||
* Don’t 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 loop’s 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 won’t 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 don’t 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_
|
||||
@@ -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_
|
||||
@@ -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_
|
||||
@@ -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_
|
||||
@@ -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_
|
||||
@@ -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_
|
||||
@@ -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_
|
||||
@@ -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_
|
||||
@@ -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 won’t queue a write request if it can’t 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 won’t queue a write request if it can’t 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_
|
||||
@@ -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_
|
||||
@@ -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_
|
||||
@@ -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_
|
||||
@@ -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_
|
||||
@@ -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
Reference in New Issue
Block a user