mirror of
https://github.com/wpilibsuite/allwpilib
synced 2026-06-21 01:01:43 +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
|
||||
Reference in New Issue
Block a user