SCRIPT Move cc files

This commit is contained in:
PJ Reiniger
2025-11-07 19:55:39 -05:00
committed by Peter Johnson
parent 10b4a0c971
commit 7ca1be9bae
1197 changed files with 0 additions and 0 deletions

View File

@@ -0,0 +1,55 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#pragma once
#include <memory>
#include <string>
#include <string_view>
#include <wpi/Signal.h>
namespace wpi {
class Logger;
namespace uv {
class Loop;
class Tcp;
class Timer;
} // namespace uv
class DsClient : public std::enable_shared_from_this<DsClient> {
struct private_init {};
public:
static std::shared_ptr<DsClient> Create(wpi::uv::Loop& loop,
wpi::Logger& logger) {
return std::make_shared<DsClient>(loop, logger, private_init{});
}
DsClient(wpi::uv::Loop& loop, wpi::Logger& logger, const private_init&);
~DsClient();
DsClient(const DsClient&) = delete;
DsClient& operator=(const DsClient&) = delete;
void Close();
sig::Signal<std::string_view> setIp;
sig::Signal<> clearIp;
private:
void Connect();
void HandleIncoming(std::string_view in);
void ParseJson();
wpi::Logger& m_logger;
std::shared_ptr<wpi::uv::Tcp> m_tcp;
std::shared_ptr<wpi::uv::Timer> m_timer;
std::string m_json;
};
} // namespace wpi

View File

@@ -0,0 +1,63 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#ifndef WPINET_EVENTLOOPRUNNER_H_
#define WPINET_EVENTLOOPRUNNER_H_
#include <functional>
#include <memory>
#include <wpi/SafeThread.h>
#include "wpinet/uv/Loop.h"
namespace wpi {
/**
* Executes an event loop on a separate thread.
*/
class EventLoopRunner {
public:
using LoopFunc = std::function<void(uv::Loop&)>;
EventLoopRunner();
virtual ~EventLoopRunner();
/**
* Stop the loop. Once the loop is stopped it cannot be restarted.
* This function does not return until the loop has exited.
*/
void Stop();
/**
* Run a function asynchronously (once) on the loop.
* This is safe to call from any thread, but is NOT safe to call from the
* provided function (it will deadlock).
* @param func function to execute on the loop
*/
void ExecAsync(LoopFunc func);
/**
* Run a function synchronously (once) on the loop.
* This is safe to call from any thread, but is NOT safe to call from the
* provided function (it will deadlock).
* This does not return until the function finishes executing.
* @param func function to execute on the loop
*/
void ExecSync(LoopFunc func);
/**
* Get the loop. If the loop thread is not running, returns nullptr.
* @return The loop
*/
std::shared_ptr<uv::Loop> GetLoop();
private:
class Thread;
SafeThreadOwner<Thread> m_owner;
};
} // namespace wpi
#endif // WPINET_EVENTLOOPRUNNER_H_

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,127 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#ifndef WPINET_HTTPWEBSOCKETSERVERCONNECTION_H_
#define WPINET_HTTPWEBSOCKETSERVERCONNECTION_H_
#include <initializer_list>
#include <memory>
#include <span>
#include <string>
#include <string_view>
#include <wpi/SmallVector.h>
#include "wpinet/HttpServerConnection.h"
#include "wpinet/WebSocket.h"
#include "wpinet/WebSocketServer.h"
#include "wpinet/uv/Stream.h"
namespace wpi {
/**
* A server-side HTTP connection that also accepts WebSocket upgrades.
*
* @tparam Derived derived class for std::enable_shared_from_this.
*/
template <typename Derived>
class HttpWebSocketServerConnection
: public HttpServerConnection,
public std::enable_shared_from_this<Derived> {
public:
/**
* Constructor.
*
* @param stream network stream
* @param protocols Acceptable subprotocols
*/
HttpWebSocketServerConnection(std::shared_ptr<uv::Stream> stream,
std::span<const std::string_view> protocols)
: HttpServerConnection{stream},
m_helper{m_request},
m_protocols{protocols.begin(), protocols.end()} {
// Handle upgrade event
m_helper.upgrade.connect([this] {
// Negotiate sub-protocol
SmallVector<std::string_view, 2> protocols{m_protocols.begin(),
m_protocols.end()};
std::string_view protocol = m_helper.MatchProtocol(protocols).second;
// Check that the upgrade is valid
if (!IsValidWsUpgrade(protocol)) {
return;
}
// Disconnect HttpServerConnection header reader
m_dataConn.disconnect();
m_messageCompleteConn.disconnect();
// Accepting the stream may destroy this (as it replaces the stream user
// data), so grab a shared pointer first.
auto self = this->shared_from_this();
// Accept the upgrade
auto ws = m_helper.Accept(m_stream, protocol);
// Set this as the websocket user data to keep it around
ws->SetData(self);
// Store in member
m_websocket = ws.get();
// Call derived class function
ProcessWsUpgrade();
});
}
/**
* Constructor.
*
* @param stream network stream
* @param protocols Acceptable subprotocols
*/
HttpWebSocketServerConnection(
std::shared_ptr<uv::Stream> stream,
std::initializer_list<std::string_view> protocols)
: HttpWebSocketServerConnection(stream,
{protocols.begin(), protocols.end()}) {}
protected:
/**
* Check that an incoming WebSocket upgrade is okay. This is called prior
* to accepting the upgrade (so prior to ProcessWsUpgrade()).
*
* The implementation should check other headers and return true if the
* WebSocket connection should be accepted.
*
* @param protocol negotiated subprotocol
*/
virtual bool IsValidWsUpgrade(std::string_view protocol) { return true; }
/**
* Process an incoming WebSocket upgrade. This is called after the header
* reader has been disconnected and the websocket has been accepted.
*
* The implementation should set up appropriate callbacks on the websocket
* object to continue communication.
*
* @note When a WebSocket upgrade occurs, the stream user data is replaced
* with the websocket, and the websocket user data points to "this".
* Replace the websocket user data with caution!
*/
virtual void ProcessWsUpgrade() = 0;
/**
* WebSocket connection; not valid until ProcessWsUpgrade is called.
*/
WebSocket* m_websocket = nullptr;
private:
WebSocketServerHelper m_helper;
SmallVector<std::string, 2> m_protocols;
};
} // namespace wpi
#endif // WPINET_HTTPWEBSOCKETSERVERCONNECTION_H_

View File

@@ -0,0 +1,16 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#ifndef WPINET_MIMETYPES_H_
#define WPINET_MIMETYPES_H_
#include <string_view>
namespace wpi {
std::string_view MimeTypeFromPath(std::string_view path);
} // namespace wpi
#endif // WPINET_MIMETYPES_H_

View File

@@ -0,0 +1,93 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#pragma once
#include <stdint.h>
#ifdef __cplusplus
#include <memory>
#include <span>
#include <string>
#include <string_view>
#include <utility>
namespace wpi {
class MulticastServiceAnnouncer {
public:
/**
* Creates a MulticastServiceAnnouncer.
*
* @param serviceName service name
* @param serviceType service type
* @param port port
* @param txt txt
*/
MulticastServiceAnnouncer(
std::string_view serviceName, std::string_view serviceType, int port,
std::span<const std::pair<std::string, std::string>> txt);
/**
* Creates a MulticastServiceAnnouncer.
*
* @param serviceName service name
* @param serviceType service type
* @param port port
* @param txt txt
*/
MulticastServiceAnnouncer(
std::string_view serviceName, std::string_view serviceType, int port,
std::span<const std::pair<std::string_view, std::string_view>> txt);
/**
* Creates a MulticastServiceAnnouncer.
*
* @param serviceName service name
* @param serviceType service type
* @param port port
*/
MulticastServiceAnnouncer(std::string_view serviceName,
std::string_view serviceType, int port);
~MulticastServiceAnnouncer() noexcept;
/**
* Starts multicast service announcer.
*/
void Start();
/**
* Stops multicast service announcer.
*/
void Stop();
/**
* Returns true if there's a multicast service announcer implementation.
*
* @return True if there's a multicast service announcer implementation.
*/
bool HasImplementation() const;
struct Impl;
private:
std::unique_ptr<Impl> pImpl;
};
} // namespace wpi
extern "C" {
#endif
typedef unsigned int WPI_MulticastServiceAnnouncerHandle; // NOLINT
WPI_MulticastServiceAnnouncerHandle WPI_CreateMulticastServiceAnnouncer(
const char* serviceName, const char* serviceType, int32_t port,
int32_t txtCount, const char** keys, const char** values);
void WPI_FreeMulticastServiceAnnouncer(
WPI_MulticastServiceAnnouncerHandle handle);
void WPI_StartMulticastServiceAnnouncer(
WPI_MulticastServiceAnnouncerHandle handle);
void WPI_StopMulticastServiceAnnouncer(
WPI_MulticastServiceAnnouncerHandle handle);
int32_t WPI_GetMulticastServiceAnnouncerHasImplementation(
WPI_MulticastServiceAnnouncerHandle handle);
#ifdef __cplusplus
} // extern "C"
#endif

View File

@@ -0,0 +1,149 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#pragma once
#include <wpi/Synchronization.h>
#ifdef __cplusplus
#include <functional>
#include <memory>
#include <string>
#include <string_view>
#include <utility>
#include <vector>
#include <wpi/mutex.h>
namespace wpi {
class MulticastServiceResolver {
public:
explicit MulticastServiceResolver(std::string_view serviceType);
~MulticastServiceResolver() noexcept;
struct ServiceData {
/// IPv4 address in host order.
unsigned int ipv4Address;
/// Port number in host order.
int port;
/// Service name.
std::string serviceName;
/// Host name.
std::string hostName;
/// Service data payload.
std::vector<std::pair<std::string, std::string>> txt;
};
/**
* Set a copy callback to be called when a service is resolved.
* Takes presidence over the move callback. Return true to
* not send the data to the event queue.
*/
bool SetCopyCallback(std::function<bool(const ServiceData&)> callback);
/**
* Set a move callback to be called when a service is resolved.
* Data is moved into the function and cannot be added to the event queue.
*/
bool SetMoveCallback(std::function<void(ServiceData&&)> callback);
/**
* Starts multicast service resolver.
*/
void Start();
/**
* Stops multicast service resolver.
*/
void Stop();
/**
* Returns event handle.
*
* @return Event handle.
*/
WPI_EventHandle GetEventHandle() const { return event.GetHandle(); }
/**
* Returns multicast service resolver data.
*
* @return Multicast service resolver data.
*/
std::vector<ServiceData> GetData() {
std::scoped_lock lock{mutex};
event.Reset();
if (queue.empty()) {
return {};
}
std::vector<ServiceData> ret;
queue.swap(ret);
return ret;
}
/**
* Returns true if there's a multicast service resolver implementation.
*
* @return True if there's a multicast service resolver implementation.
*/
bool HasImplementation() const;
struct Impl;
private:
void PushData(ServiceData&& data) {
std::scoped_lock lock{mutex};
if (copyCallback) {
if (!copyCallback(data)) {
queue.emplace_back(std::forward<ServiceData>(data));
event.Set();
}
} else if (moveCallback) {
moveCallback(std::move(data));
} else {
queue.emplace_back(std::forward<ServiceData>(data));
event.Set();
}
}
wpi::Event event{true};
std::vector<ServiceData> queue;
wpi::mutex mutex;
std::function<bool(const ServiceData&)> copyCallback;
std::function<void(ServiceData&&)> moveCallback;
std::unique_ptr<Impl> pImpl;
};
} // namespace wpi
#endif
#ifdef __cplusplus
extern "C" {
#endif
typedef unsigned int WPI_MulticastServiceResolverHandle; // NOLINT
WPI_MulticastServiceResolverHandle WPI_CreateMulticastServiceResolver(
const char* serviceType);
void WPI_FreeMulticastServiceResolver(
WPI_MulticastServiceResolverHandle handle);
void WPI_StartMulticastServiceResolver(
WPI_MulticastServiceResolverHandle handle);
void WPI_StopMulticastServiceResolver(
WPI_MulticastServiceResolverHandle handle);
int32_t WPI_GetMulticastServiceResolverHasImplementation(
WPI_MulticastServiceResolverHandle handle);
WPI_EventHandle WPI_GetMulticastServiceResolverEventHandle(
WPI_MulticastServiceResolverHandle handle);
typedef struct WPI_ServiceData { // NOLINT
uint32_t ipv4Address;
int32_t port;
const char* serviceName;
const char* hostName;
int32_t txtCount;
const char** txtKeys;
const char** txtValues;
} WPI_ServiceData;
WPI_ServiceData* WPI_GetMulticastServiceResolverData(
WPI_MulticastServiceResolverHandle handle, int32_t* dataCount);
void WPI_FreeServiceData(WPI_ServiceData* serviceData, int32_t length);
#ifdef __cplusplus
} // extern "C"
#endif

View File

@@ -0,0 +1,29 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#ifndef WPINET_NETWORKACCEPTOR_H_
#define WPINET_NETWORKACCEPTOR_H_
#include <memory>
#include "wpinet/NetworkStream.h"
namespace wpi {
class NetworkAcceptor {
public:
NetworkAcceptor() = default;
virtual ~NetworkAcceptor() = default;
virtual int start() = 0;
virtual void shutdown() = 0;
virtual std::unique_ptr<NetworkStream> accept() = 0;
NetworkAcceptor(const NetworkAcceptor&) = delete;
NetworkAcceptor& operator=(const NetworkAcceptor&) = delete;
};
} // namespace wpi
#endif // WPINET_NETWORKACCEPTOR_H_

View File

@@ -0,0 +1,44 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#ifndef WPINET_NETWORKSTREAM_H_
#define WPINET_NETWORKSTREAM_H_
#include <cstddef>
#include <string_view>
namespace wpi {
class NetworkStream {
public:
NetworkStream() = default;
virtual ~NetworkStream() = default;
enum Error {
kConnectionClosed = 0,
kConnectionReset = -1,
kConnectionTimedOut = -2,
kWouldBlock = -3
};
virtual size_t send(const char* buffer, size_t len, Error* err) = 0;
virtual size_t receive(char* buffer, size_t len, Error* err,
int timeout = 0) = 0;
virtual void close() = 0;
virtual std::string_view getPeerIP() const = 0;
virtual int getPeerPort() const = 0;
virtual void setNoDelay() = 0;
// returns false on failure
virtual bool setBlocking(bool enabled) = 0;
virtual int getNativeHandle() const = 0;
NetworkStream(const NetworkStream&) = delete;
NetworkStream& operator=(const NetworkStream&) = delete;
};
} // namespace wpi
#endif // WPINET_NETWORKSTREAM_H_

View File

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

View File

@@ -0,0 +1,59 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#ifndef WPINET_PORTFORWARDER_H_
#define WPINET_PORTFORWARDER_H_
#pragma once
#include <memory>
#include <string_view>
namespace wpi {
/**
* Forward ports to another host. This is primarily useful for accessing
* Ethernet-connected devices from a computer tethered to the RoboRIO USB port.
*/
class PortForwarder {
public:
PortForwarder(const PortForwarder&) = delete;
PortForwarder& operator=(const PortForwarder&) = delete;
/**
* Get an instance of the PortForwarder class.
*
* This is a singleton to guarantee that there is only a single instance
* regardless of how many times GetInstance is called.
*/
static PortForwarder& GetInstance();
/**
* Forward a local TCP port to a remote host and port.
* Note that local ports less than 1024 won't work as a normal user.
*
* @param port local port number
* @param remoteHost remote IP address / DNS name
* @param remotePort remote port number
*/
void Add(unsigned int port, std::string_view remoteHost,
unsigned int remotePort);
/**
* Stop TCP forwarding on a port.
*
* @param port local port number
*/
void Remove(unsigned int port);
private:
PortForwarder();
struct Impl;
std::unique_ptr<Impl> m_impl;
};
} // namespace wpi
#endif // WPINET_PORTFORWARDER_H_

View File

@@ -0,0 +1,22 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#ifndef WPINET_SOCKETERROR_H_
#define WPINET_SOCKETERROR_H_
#include <string>
namespace wpi {
int SocketErrno();
std::string SocketStrerror(int code);
inline std::string SocketStrerror() {
return SocketStrerror(SocketErrno());
}
} // namespace wpi
#endif // WPINET_SOCKETERROR_H_

View File

@@ -0,0 +1,49 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#ifndef WPINET_UDPCLIENT_H_
#define WPINET_UDPCLIENT_H_
#include <span>
#include <string>
#include <string_view>
#include <wpi/SmallVector.h>
#include <wpi/mutex.h>
namespace wpi {
class Logger;
class UDPClient {
int m_lsd;
int m_port;
std::string m_address;
Logger& m_logger;
public:
explicit UDPClient(Logger& logger);
UDPClient(std::string_view address, Logger& logger);
UDPClient(const UDPClient& other) = delete;
UDPClient(UDPClient&& other);
~UDPClient();
UDPClient& operator=(const UDPClient& other) = delete;
UDPClient& operator=(UDPClient&& other);
int start();
int start(int port);
void shutdown();
// The passed in address MUST be a resolved IP address.
int send(std::span<const uint8_t> data, std::string_view server, int port);
int send(std::string_view data, std::string_view server, int port);
int receive(uint8_t* data_received, int receive_len);
int receive(uint8_t* data_received, int receive_len,
SmallVectorImpl<char>* addr_received, int* port_received);
int set_timeout(double timeout);
};
} // namespace wpi
#endif // WPINET_UDPCLIENT_H_

View File

@@ -0,0 +1,96 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#ifndef WPINET_URLPARSER_H_
#define WPINET_URLPARSER_H_
#include <string_view>
#include <wpi/StringExtras.h>
#include "wpinet/http_parser.h"
namespace wpi {
/**
* Parses a URL into its constituent components.
* `schema://userinfo@host:port/the/path?query#fragment`
*/
class UrlParser {
public:
/**
* Parse a URL.
* @param in input
* @param isConnect
*/
UrlParser(std::string_view in, bool isConnect) {
m_data = in;
http_parser_url_init(&m_url);
m_error = http_parser_parse_url(in.data(), in.size(), isConnect, &m_url);
}
/**
* Determine if the URL is valid (e.g. the parse was successful).
*/
bool IsValid() const { return !m_error; }
bool HasSchema() const { return (m_url.field_set & (1 << UF_SCHEMA)) != 0; }
bool HasHost() const { return (m_url.field_set & (1 << UF_HOST)) != 0; }
bool HasPort() const { return (m_url.field_set & (1 << UF_PORT)) != 0; }
bool HasPath() const { return (m_url.field_set & (1 << UF_PATH)) != 0; }
bool HasQuery() const { return (m_url.field_set & (1 << UF_QUERY)) != 0; }
bool HasFragment() const {
return (m_url.field_set & (1 << UF_FRAGMENT)) != 0;
}
bool HasUserInfo() const {
return (m_url.field_set & (1 << UF_USERINFO)) != 0;
}
std::string_view GetSchema() const {
return wpi::substr(m_data, m_url.field_data[UF_SCHEMA].off,
m_url.field_data[UF_SCHEMA].len);
}
std::string_view GetHost() const {
return wpi::substr(m_data, m_url.field_data[UF_HOST].off,
m_url.field_data[UF_HOST].len);
}
unsigned int GetPort() const { return m_url.port; }
std::string_view GetPath() const {
return wpi::substr(m_data, m_url.field_data[UF_PATH].off,
m_url.field_data[UF_PATH].len);
}
std::string_view GetQuery() const {
return wpi::substr(m_data, m_url.field_data[UF_QUERY].off,
m_url.field_data[UF_QUERY].len);
}
std::string_view GetFragment() const {
return wpi::substr(m_data, m_url.field_data[UF_FRAGMENT].off,
m_url.field_data[UF_FRAGMENT].len);
}
std::string_view GetUserInfo() const {
return wpi::substr(m_data, m_url.field_data[UF_USERINFO].off,
m_url.field_data[UF_USERINFO].len);
}
private:
bool m_error;
std::string_view m_data;
http_parser_url m_url;
};
} // namespace wpi
#endif // WPINET_URLPARSER_H_

View File

@@ -0,0 +1,58 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#ifndef WPINET_WEBSERVER_H_
#define WPINET_WEBSERVER_H_
#pragma once
#include <memory>
#include <string_view>
namespace wpi {
/**
* A web server using the HTTP protocol.
*/
class WebServer {
public:
WebServer(const WebServer&) = delete;
WebServer& operator=(const WebServer&) = delete;
/**
* Get an instance of the WebServer class.
*
* This is a singleton to guarantee that there is only a single instance
* regardless of how many times GetInstance is called.
*/
static WebServer& GetInstance();
/**
* Create a web server at the given port.
* Note that local ports less than 1024 won't work as a normal user. Also,
* many ports are blocked by the FRC robot radio; check the game manual for
* what is allowed through the radio firewall.
*
* @param port local port number
* @param path local path to document root
*/
void Start(unsigned int port, std::string_view path);
/**
* Stop web server running at the given port.
*
* @param port local port number
*/
void Stop(unsigned int port);
private:
WebServer();
struct Impl;
std::unique_ptr<Impl> m_impl;
};
} // namespace wpi
#endif // WPINET_WEBSERVER_H_

View File

@@ -0,0 +1,565 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#ifndef WPINET_WEBSOCKET_H_
#define WPINET_WEBSOCKET_H_
#include <stdint.h>
#include <functional>
#include <initializer_list>
#include <memory>
#include <span>
#include <string>
#include <string_view>
#include <utility>
#include <wpi/Signal.h>
#include <wpi/SmallVector.h>
#include "wpinet/uv/Buffer.h"
#include "wpinet/uv/Error.h"
#include "wpinet/uv/Timer.h"
namespace wpi {
namespace uv {
class Stream;
} // namespace uv
/**
* RFC 6455 compliant WebSocket client and server implementation.
*/
class WebSocket : public std::enable_shared_from_this<WebSocket> {
struct private_init {};
public:
static constexpr uint8_t kOpCont = 0x00;
static constexpr uint8_t kOpText = 0x01;
static constexpr uint8_t kOpBinary = 0x02;
static constexpr uint8_t kOpClose = 0x08;
static constexpr uint8_t kOpPing = 0x09;
static constexpr uint8_t kOpPong = 0x0A;
static constexpr uint8_t kOpMask = 0x0F;
static constexpr uint8_t kFlagFin = 0x80;
static constexpr uint8_t kFlagControl = 0x08;
WebSocket(uv::Stream& stream, bool server, const private_init&);
WebSocket(const WebSocket&) = delete;
WebSocket(WebSocket&&) = delete;
WebSocket& operator=(const WebSocket&) = delete;
WebSocket& operator=(WebSocket&&) = delete;
~WebSocket();
/**
* Connection states.
*/
enum State {
/** The connection is not yet open. */
CONNECTING = 0,
/** The connection is open and ready to communicate. */
OPEN,
/** The connection is in the process of closing. */
CLOSING,
/** The connection failed. */
FAILED,
/** The connection is closed. */
CLOSED
};
/**
* Client connection options.
*/
struct ClientOptions {
ClientOptions() : handshakeTimeout{(uv::Timer::Time::max)()} {}
/** Timeout for the handshake request. */
uv::Timer::Time handshakeTimeout; // NOLINT
/** Additional headers to include in handshake. */
std::span<const std::pair<std::string_view, std::string_view>> extraHeaders;
};
/**
* Frame. Used by SendFrames().
*/
struct Frame {
static constexpr uint8_t kText = kFlagFin | kOpText;
static constexpr uint8_t kBinary = kFlagFin | kOpBinary;
static constexpr uint8_t kTextFragment = kOpText;
static constexpr uint8_t kBinaryFragment = kOpBinary;
static constexpr uint8_t kFragment = kOpCont;
static constexpr uint8_t kFinalFragment = kFlagFin | kOpCont;
static constexpr uint8_t kPing = kFlagFin | kOpPing;
static constexpr uint8_t kPong = kFlagFin | kOpPong;
constexpr Frame(uint8_t opcode, std::span<const uv::Buffer> data)
: opcode{opcode}, data{data} {}
uint8_t opcode;
std::span<const uv::Buffer> data;
};
/**
* Starts a client connection by performing the initial client handshake.
* An open event is emitted when the handshake completes.
* This sets the stream user data to the websocket.
* @param stream Connection stream
* @param uri The Request-URI to send
* @param host The host or host:port to send
* @param protocols The list of subprotocols
* @param options Handshake options
*/
static std::shared_ptr<WebSocket> CreateClient(
uv::Stream& stream, std::string_view uri, std::string_view host,
std::span<const std::string_view> protocols = {},
const ClientOptions& options = {});
/**
* Starts a client connection by performing the initial client handshake.
* An open event is emitted when the handshake completes.
* This sets the stream user data to the websocket.
* @param stream Connection stream
* @param uri The Request-URI to send
* @param host The host or host:port to send
* @param protocols The list of subprotocols
* @param options Handshake options
*/
static std::shared_ptr<WebSocket> CreateClient(
uv::Stream& stream, std::string_view uri, std::string_view host,
std::initializer_list<std::string_view> protocols,
const ClientOptions& options = {}) {
return CreateClient(stream, uri, host, {protocols.begin(), protocols.end()},
options);
}
/**
* Starts a server connection by performing the initial server side handshake.
* This should be called after the HTTP headers have been received.
* An open event is emitted when the handshake completes.
* This sets the stream user data to the websocket.
* @param stream Connection stream
* @param key The value of the Sec-WebSocket-Key header field in the client
* request
* @param version The value of the Sec-WebSocket-Version header field in the
* client request
* @param protocol The subprotocol to send to the client (in the
* Sec-WebSocket-Protocol header field).
*/
static std::shared_ptr<WebSocket> CreateServer(
uv::Stream& stream, std::string_view key, std::string_view version,
std::string_view protocol = {});
/**
* Get connection state.
*/
State GetState() const { return m_state; }
/**
* Return if the connection is open. Messages can only be sent on open
* connections.
*/
bool IsOpen() const { return m_state == OPEN; }
/**
* Get the underlying stream.
*/
uv::Stream& GetStream() const { return m_stream; }
/**
* Get the selected sub-protocol. Only valid in or after the open() event.
*/
std::string_view GetProtocol() const { return m_protocol; }
/**
* Set the maximum message size. Default is 128 KB. If configured to combine
* fragments this maximum applies to the entire message (all combined
* fragments).
* @param size Maximum message size in bytes
*/
void SetMaxMessageSize(size_t size) { m_maxMessageSize = size; }
/**
* Set whether or not fragmented frames should be combined. Default is to
* combine. If fragmented frames are combined, the text and binary callbacks
* will always have the second parameter (fin) set to true.
* @param combine True if fragmented frames should be combined.
*/
void SetCombineFragments(bool combine) { m_combineFragments = combine; }
/**
* Initiate a closing handshake.
* @param code A numeric status code (defaults to 1005, no status code)
* @param reason A human-readable string explaining why the connection is
* closing (optional).
*/
void Close(uint16_t code = 1005, std::string_view reason = {});
/**
* Send a text message.
* @param data UTF-8 encoded data to send
* @param callback Callback which is invoked when the write completes.
*/
void SendText(
std::span<const uv::Buffer> data,
std::function<void(std::span<uv::Buffer>, uv::Error)> callback) {
Send(kFlagFin | kOpText, data, std::move(callback));
}
/**
* Send a text message.
* @param data UTF-8 encoded data to send
* @param callback Callback which is invoked when the write completes.
*/
void SendText(
std::initializer_list<uv::Buffer> data,
std::function<void(std::span<uv::Buffer>, uv::Error)> callback) {
SendText({data.begin(), data.end()}, std::move(callback));
}
/**
* Send a binary message.
* @param data Data to send
* @param callback Callback which is invoked when the write completes.
*/
void SendBinary(
std::span<const uv::Buffer> data,
std::function<void(std::span<uv::Buffer>, uv::Error)> callback) {
Send(kFlagFin | kOpBinary, data, std::move(callback));
}
/**
* Send a binary message.
* @param data Data to send
* @param callback Callback which is invoked when the write completes.
*/
void SendBinary(
std::initializer_list<uv::Buffer> data,
std::function<void(std::span<uv::Buffer>, uv::Error)> callback) {
SendBinary({data.begin(), data.end()}, std::move(callback));
}
/**
* Send a text message fragment. This must be followed by one or more
* SendFragment() calls, where the last one has fin=True, to complete the
* message.
* @param data UTF-8 encoded data to send
* @param callback Callback which is invoked when the write completes.
*/
void SendTextFragment(
std::span<const uv::Buffer> data,
std::function<void(std::span<uv::Buffer>, uv::Error)> callback) {
Send(kOpText, data, std::move(callback));
}
/**
* Send a text message fragment. This must be followed by one or more
* SendFragment() calls, where the last one has fin=True, to complete the
* message.
* @param data UTF-8 encoded data to send
* @param callback Callback which is invoked when the write completes.
*/
void SendTextFragment(
std::initializer_list<uv::Buffer> data,
std::function<void(std::span<uv::Buffer>, uv::Error)> callback) {
SendTextFragment({data.begin(), data.end()}, std::move(callback));
}
/**
* Send a text message fragment. This must be followed by one or more
* SendFragment() calls, where the last one has fin=True, to complete the
* message.
* @param data Data to send
* @param callback Callback which is invoked when the write completes.
*/
void SendBinaryFragment(
std::span<const uv::Buffer> data,
std::function<void(std::span<uv::Buffer>, uv::Error)> callback) {
Send(kOpBinary, data, std::move(callback));
}
/**
* Send a text message fragment. This must be followed by one or more
* SendFragment() calls, where the last one has fin=True, to complete the
* message.
* @param data Data to send
* @param callback Callback which is invoked when the write completes.
*/
void SendBinaryFragment(
std::initializer_list<uv::Buffer> data,
std::function<void(std::span<uv::Buffer>, uv::Error)> callback) {
SendBinaryFragment({data.begin(), data.end()}, std::move(callback));
}
/**
* Send a continuation frame. This is used to send additional parts of a
* message started with SendTextFragment() or SendBinaryFragment().
* @param data Data to send
* @param fin Set to true if this is the final fragment of the message
* @param callback Callback which is invoked when the write completes.
*/
void SendFragment(
std::span<const uv::Buffer> data, bool fin,
std::function<void(std::span<uv::Buffer>, uv::Error)> callback) {
Send(kOpCont | (fin ? kFlagFin : 0), data, std::move(callback));
}
/**
* Send a continuation frame. This is used to send additional parts of a
* message started with SendTextFragment() or SendBinaryFragment().
* @param data Data to send
* @param fin Set to true if this is the final fragment of the message
* @param callback Callback which is invoked when the write completes.
*/
void SendFragment(
std::initializer_list<uv::Buffer> data, bool fin,
std::function<void(std::span<uv::Buffer>, uv::Error)> callback) {
SendFragment({data.begin(), data.end()}, fin, std::move(callback));
}
/**
* Send a ping frame with no data.
* @param callback Optional callback which is invoked when the ping frame
* write completes.
*/
void SendPing(std::function<void(uv::Error)> callback = nullptr) {
SendPing({}, [f = std::move(callback)](auto bufs, uv::Error err) {
if (f) {
f(err);
}
});
}
/**
* Send a ping frame.
* @param data Data to send in the ping frame
* @param callback Callback which is invoked when the ping frame
* write completes.
*/
void SendPing(
std::span<const uv::Buffer> data,
std::function<void(std::span<uv::Buffer>, uv::Error)> callback) {
SendControl(kFlagFin | kOpPing, data, std::move(callback));
}
/**
* Send a ping frame.
* @param data Data to send in the ping frame
* @param callback Callback which is invoked when the ping frame
* write completes.
*/
void SendPing(
std::initializer_list<uv::Buffer> data,
std::function<void(std::span<uv::Buffer>, uv::Error)> callback) {
SendPing({data.begin(), data.end()}, std::move(callback));
}
/**
* Send a pong frame with no data.
* @param callback Optional callback which is invoked when the pong frame
* write completes.
*/
void SendPong(std::function<void(uv::Error)> callback = nullptr) {
SendPong({}, [f = std::move(callback)](auto bufs, uv::Error err) {
if (f) {
f(err);
}
});
}
/**
* Send a pong frame.
* @param data Data to send in the pong frame
* @param callback Callback which is invoked when the pong frame
* write completes.
*/
void SendPong(
std::span<const uv::Buffer> data,
std::function<void(std::span<uv::Buffer>, uv::Error)> callback) {
SendControl(kFlagFin | kOpPong, data, std::move(callback));
}
/**
* Send a pong frame.
* @param data Data to send in the pong frame
* @param callback Callback which is invoked when the pong frame
* write completes.
*/
void SendPong(
std::initializer_list<uv::Buffer> data,
std::function<void(std::span<uv::Buffer>, uv::Error)> callback) {
SendPong({data.begin(), data.end()}, std::move(callback));
}
/**
* Send multiple frames.
*
* @param frames Frame type/data pairs
* @param callback Callback which is invoked when the write completes.
*/
void SendFrames(
std::span<const Frame> frames,
std::function<void(std::span<uv::Buffer>, uv::Error)> callback);
/**
* Try to send multiple frames. Tries to send as many frames as possible
* immediately, and only queues the "last" frame it can (as the network queue
* will almost always fill partway through a frame). The frames following
* the last frame will NOT be queued for transmission; the caller is
* responsible for how to handle (e.g. re-send) those frames (e.g. when the
* callback is called).
*
* @param frames Frame type/data pairs
* @param callback Callback which is invoked when the write completes of the
* last frame that is not returned.
* @return Remaining frames that will not be sent
*/
std::span<const Frame> TrySendFrames(
std::span<const Frame> frames,
std::function<void(std::span<uv::Buffer>, uv::Error)> callback);
/**
* Returns whether or not a previous TrySendFrames is still in progress.
* Calling TrySendFrames if this returns true will return all frames.
*
* @return True if a TryWrite is in progress
*/
bool IsWriteInProgress() const { return m_writeInProgress; }
/**
* Fail the connection.
*/
void Fail(uint16_t code = 1002, std::string_view reason = "protocol error");
/**
* Forcibly close the connection.
*/
void Terminate(uint16_t code = 1006, std::string_view reason = "terminated");
/**
* Gets user-defined data.
* @return User-defined data if any, nullptr otherwise.
*/
template <typename T = void>
std::shared_ptr<T> GetData() const {
return std::static_pointer_cast<T>(m_data);
}
/**
* Sets user-defined data.
* @param data User-defined arbitrary data.
*/
void SetData(std::shared_ptr<void> data) { m_data = std::move(data); }
/**
* Shuts down and closes the underlying stream.
*/
void Shutdown();
/**
* Gets the last time data was received on the stream.
* @return Timestamp
*/
uint64_t GetLastReceivedTime() const { return m_lastReceivedTime; }
/**
* Open event. Emitted when the connection is open and ready to communicate.
* The parameter is the selected subprotocol.
*/
sig::Signal<std::string_view> open;
/**
* Close event. Emitted when the connection is closed. The first parameter
* is a numeric value indicating the status code explaining why the connection
* has been closed. The second parameter is a human-readable string
* explaining the reason why the connection has been closed.
*/
sig::Signal<uint16_t, std::string_view> closed;
/**
* Text message event. Emitted when a text message is received.
* The first parameter is the data, the second parameter is true if the
* data is the last fragment of the message.
*/
sig::Signal<std::string_view, bool> text;
/**
* Binary message event. Emitted when a binary message is received.
* The first parameter is the data, the second parameter is true if the
* data is the last fragment of the message.
*/
sig::Signal<std::span<const uint8_t>, bool> binary;
/**
* Ping event. Emitted when a ping message is received. A pong message is
* automatically sent in response, so this is simply a notification.
*/
sig::Signal<std::span<const uint8_t>> ping;
/**
* Pong event. Emitted when a pong message is received.
*/
sig::Signal<std::span<const uint8_t>> pong;
private:
// user data
std::shared_ptr<void> m_data;
// constructor parameters
uv::Stream& m_stream;
bool m_server;
// subprotocol, set via constructor (server) or handshake (client)
std::string m_protocol;
// user-settable configuration
size_t m_maxMessageSize = 128 * 1024;
bool m_combineFragments = true;
// outgoing write request
bool m_writeInProgress = false;
class WriteReq;
std::weak_ptr<WriteReq> m_curWriteReq;
std::weak_ptr<WriteReq> m_lastWriteReq;
// operating state
State m_state = CONNECTING;
// incoming message buffers/state
uint64_t m_lastReceivedTime = 0;
SmallVector<uint8_t, 14> m_header;
size_t m_headerSize = 0;
SmallVector<uint8_t, 1024> m_payload;
SmallVector<uint8_t, 64> m_controlPayload;
size_t m_frameStart = 0;
uint64_t m_frameSize = UINT64_MAX;
uint8_t m_fragmentOpcode = 0;
// temporary data used only during client handshake
class ClientHandshakeData;
std::unique_ptr<ClientHandshakeData> m_clientHandshake;
void StartClient(std::string_view uri, std::string_view host,
std::span<const std::string_view> protocols,
const ClientOptions& options);
void StartServer(std::string_view key, std::string_view version,
std::string_view protocol);
void SendClose(uint16_t code, std::string_view reason);
void SetClosed(uint16_t code, std::string_view reason, bool failed = false);
void HandleIncoming(uv::Buffer& buf, size_t size);
void SendControl(
uint8_t opcode, std::span<const uv::Buffer> data,
std::function<void(std::span<uv::Buffer>, uv::Error)> callback);
void Send(uint8_t opcode, std::span<const uv::Buffer> data,
std::function<void(std::span<uv::Buffer>, uv::Error)> callback) {
SendFrames({{Frame{opcode, data}}}, std::move(callback));
}
void SendError(
std::span<const Frame> frames,
const std::function<void(std::span<uv::Buffer>, uv::Error)>& callback);
};
} // namespace wpi
#endif // WPINET_WEBSOCKET_H_

View File

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

View File

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

View File

@@ -0,0 +1,19 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#ifndef WPINET_HOSTNAME_H_
#define WPINET_HOSTNAME_H_
#include <string>
#include <string_view>
namespace wpi {
template <typename T>
class SmallVectorImpl;
std::string GetHostname();
std::string_view GetHostname(SmallVectorImpl<char>& name);
} // namespace wpi
#endif // WPINET_HOSTNAME_H_

View File

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

View File

@@ -0,0 +1,31 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#ifndef WPINET_RAW_SOCKET_ISTREAM_H_
#define WPINET_RAW_SOCKET_ISTREAM_H_
#include <wpi/raw_istream.h>
namespace wpi {
class NetworkStream;
class raw_socket_istream : public raw_istream {
public:
explicit raw_socket_istream(NetworkStream& stream, int timeout = 0)
: m_stream(stream), m_timeout(timeout) {}
void close() override;
size_t in_avail() const override;
private:
void read_impl(void* data, size_t len) override;
NetworkStream& m_stream;
int m_timeout;
};
} // namespace wpi
#endif // WPINET_RAW_SOCKET_ISTREAM_H_

View File

@@ -0,0 +1,39 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#ifndef WPINET_RAW_SOCKET_OSTREAM_H_
#define WPINET_RAW_SOCKET_OSTREAM_H_
#include <wpi/raw_ostream.h>
namespace wpi {
class NetworkStream;
class raw_socket_ostream : public raw_ostream {
public:
raw_socket_ostream(NetworkStream& stream, bool shouldClose)
: m_stream(stream), m_shouldClose(shouldClose) {}
~raw_socket_ostream() override;
void close();
bool has_error() const { return m_error; }
void clear_error() { m_error = false; }
protected:
void error_detected() { m_error = true; }
private:
void write_impl(const char* data, size_t len) override;
uint64_t current_pos() const override;
NetworkStream& m_stream;
bool m_error = false;
bool m_shouldClose;
};
} // namespace wpi
#endif // WPINET_RAW_SOCKET_OSTREAM_H_

View File

@@ -0,0 +1,75 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#ifndef WPINET_RAW_UV_OSTREAM_H_
#define WPINET_RAW_UV_OSTREAM_H_
#include <functional>
#include <span>
#include <utility>
#include <wpi/SmallVector.h>
#include <wpi/raw_ostream.h>
#include "wpinet/uv/Buffer.h"
namespace wpi {
/**
* raw_ostream style output to a SmallVector of uv::Buffer buffers. Fixed-size
* buffers are allocated and appended as necessary to fit the data being output.
* The SmallVector need not be empty at start.
*/
class raw_uv_ostream : public raw_ostream {
public:
/**
* Construct a new raw_uv_ostream.
* @param bufs Buffers vector. NOT cleared on construction.
* @param allocSize Size to allocate for each buffer; allocation will be
* performed using Buffer::Allocate().
*/
raw_uv_ostream(SmallVectorImpl<uv::Buffer>& bufs, size_t allocSize)
: m_bufs(bufs), m_alloc([=] { return uv::Buffer::Allocate(allocSize); }) {
SetUnbuffered();
}
/**
* Construct a new raw_uv_ostream.
* @param bufs Buffers vector. NOT cleared on construction.
* @param alloc Allocator.
*/
raw_uv_ostream(SmallVectorImpl<uv::Buffer>& bufs,
std::function<uv::Buffer()> alloc)
: m_bufs(bufs), m_alloc(std::move(alloc)) {
SetUnbuffered();
}
~raw_uv_ostream() override = default;
/**
* Returns an span to the buffers.
*/
std::span<uv::Buffer> bufs() { return m_bufs; }
void flush() = delete;
/**
* Resets the amount of allocated space.
*/
void reset() { m_left = 0; }
private:
void write_impl(const char* data, size_t len) override;
uint64_t current_pos() const override;
SmallVectorImpl<uv::Buffer>& m_bufs;
std::function<uv::Buffer()> m_alloc;
// How much allocated space is left in the current buffer.
size_t m_left = 0;
};
} // namespace wpi
#endif // WPINET_RAW_UV_OSTREAM_H_

View File

@@ -0,0 +1,202 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#ifndef WPINET_UV_ASYNC_H_
#define WPINET_UV_ASYNC_H_
#include <uv.h>
#include <memory>
#include <thread>
#include <tuple>
#include <utility>
#include <vector>
#include <wpi/Signal.h>
#include <wpi/mutex.h>
#include "wpinet/uv/Handle.h"
#include "wpinet/uv/Loop.h"
namespace wpi::uv {
/**
* Async handle.
* Async handles allow the user to "wakeup" the event loop and have a signal
* generated from another thread.
*
* Data may be passed into the callback called on the event loop by using
* template parameters. If data parameters are used, the async callback will
* be called once for every call to Send(). If no data parameters are used,
* the async callback may or may not be called for every call to Send() (e.g.
* the calls may be coalesced).
*/
template <typename... T>
class Async final : public HandleImpl<Async<T...>, uv_async_t> {
struct private_init {};
public:
Async(const std::shared_ptr<Loop>& loop, const private_init&)
: m_loop{loop} {}
~Async() noexcept override {
if (auto loop = m_loop.lock()) {
this->Close();
} else {
this->ForceClosed();
}
}
/**
* Create an async handle.
*
* @param loop Loop object where this handle runs.
*/
static std::shared_ptr<Async> Create(Loop& loop) {
return Create(loop.shared_from_this());
}
/**
* Create an async handle.
*
* @param loop Loop object where this handle runs.
*/
static std::shared_ptr<Async> Create(const std::shared_ptr<Loop>& loop) {
if (loop->IsClosing()) {
return nullptr;
}
auto h = std::make_shared<Async>(loop, private_init{});
int err =
uv_async_init(loop->GetRaw(), h->GetRaw(), [](uv_async_t* handle) {
auto& h = *static_cast<Async*>(handle->data);
std::scoped_lock lock(h.m_mutex);
for (auto&& v : h.m_data) {
std::apply(h.wakeup, v);
}
h.m_data.clear();
});
if (err < 0) {
loop->ReportError(err);
return nullptr;
}
h->Keep();
return h;
}
/**
* Wakeup the event loop and emit the event.
*
* Its safe to call this function from any thread including the loop thread.
* An async event will be emitted on the loop thread.
*/
template <typename... U>
void Send(U&&... u) {
auto loop = m_loop.lock();
if (loop->IsClosing()) {
return;
}
if (loop && loop->GetThreadId() == std::this_thread::get_id()) {
// called from within the loop, just call the function directly
wakeup(std::forward<U>(u)...);
return;
}
{
std::scoped_lock lock(m_mutex);
m_data.emplace_back(std::forward_as_tuple(std::forward<U>(u)...));
}
if (loop) {
this->Invoke(&uv_async_send, this->GetRaw());
}
}
/**
* Wakeup the event loop and emit the event.
* This function assumes the loop still exists, which makes it a bit faster.
*
* Its safe to call this function from any thread.
* An async event will be emitted on the loop thread.
*/
void UnsafeSend() { Invoke(&uv_async_send, this->GetRaw()); }
/**
* Signal generated (on event loop thread) when the async event occurs.
*/
sig::Signal<T...> wakeup;
private:
wpi::mutex m_mutex;
std::vector<std::tuple<T...>> m_data;
std::weak_ptr<Loop> m_loop;
};
/**
* Async specialization for no data parameters. The async callback may or may
* not be called for every call to Send() (e.g. the calls may be coalesced).
*/
template <>
class Async<> final : public HandleImpl<Async<>, uv_async_t> {
struct private_init {};
public:
Async(const std::shared_ptr<Loop>& loop, const private_init&)
: m_loop(loop) {}
~Async() noexcept override;
/**
* Create an async handle.
*
* @param loop Loop object where this handle runs.
*/
static std::shared_ptr<Async> Create(Loop& loop) {
return Create(loop.shared_from_this());
}
/**
* Create an async handle.
*
* @param loop Loop object where this handle runs.
*/
static std::shared_ptr<Async> Create(const std::shared_ptr<Loop>& loop);
/**
* Wakeup the event loop and emit the event.
*
* Its safe to call this function from any thread.
* An async event will be emitted on the loop thread.
*/
void Send() {
if (auto loop = m_loop.lock()) {
if (loop->IsClosing()) {
return;
}
if (loop->GetThreadId() == std::this_thread::get_id()) {
// called from within the loop, just call the function directly
wakeup();
} else {
Invoke(&uv_async_send, GetRaw());
}
}
}
/**
* Wakeup the event loop and emit the event.
* This function assumes the loop still exists, which makes it a bit faster.
*
* Its safe to call this function from any thread.
* An async event will be emitted on the loop thread.
*/
void UnsafeSend() { Invoke(&uv_async_send, GetRaw()); }
/**
* Signal generated (on event loop thread) when the async event occurs.
*/
sig::Signal<> wakeup;
private:
std::weak_ptr<Loop> m_loop;
};
} // namespace wpi::uv
#endif // WPINET_UV_ASYNC_H_

View File

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

View File

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

View File

@@ -0,0 +1,66 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#ifndef WPINET_UV_CHECK_H_
#define WPINET_UV_CHECK_H_
#include <uv.h>
#include <memory>
#include <wpi/Signal.h>
#include "wpinet/uv/Handle.h"
namespace wpi::uv {
class Loop;
/**
* Check handle.
* Check handles will generate a signal once per loop iteration, right
* after polling for I/O.
*/
class Check final : public HandleImpl<Check, uv_check_t> {
struct private_init {};
public:
explicit Check(const private_init&) {}
~Check() noexcept override = default;
/**
* Create a check handle.
*
* @param loop Loop object where this handle runs.
*/
static std::shared_ptr<Check> Create(Loop& loop);
/**
* Create a check handle.
*
* @param loop Loop object where this handle runs.
*/
static std::shared_ptr<Check> Create(const std::shared_ptr<Loop>& loop) {
return Create(*loop);
}
/**
* Start the handle.
*/
void Start();
/**
* Stop the handle. The signal will no longer be generated.
*/
void Stop() { Invoke(&uv_check_stop, GetRaw()); }
/**
* Signal generated once per loop iteration after polling for I/O.
*/
sig::Signal<> check;
};
} // namespace wpi::uv
#endif // WPINET_UV_CHECK_H_

View File

@@ -0,0 +1,46 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#ifndef WPINET_UV_ERROR_H_
#define WPINET_UV_ERROR_H_
#include <uv.h>
namespace wpi::uv {
/**
* Error code.
*/
class Error {
public:
Error() = default;
explicit Error(int err) : m_err(err) {}
/**
* Boolean conversion. Returns true if error, false if ok.
*/
explicit operator bool() const { return m_err < 0; }
/**
* Returns the error code.
*/
int code() const { return m_err; }
/**
* Returns the error message.
*/
const char* str() const { return uv_strerror(m_err); }
/**
* Returns the error name.
*/
const char* name() const { return uv_err_name(m_err); }
private:
int m_err{0};
};
} // namespace wpi::uv
#endif // WPINET_UV_ERROR_H_

View File

@@ -0,0 +1,80 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#ifndef WPINET_UV_FSEVENT_H_
#define WPINET_UV_FSEVENT_H_
#include <uv.h>
#include <memory>
#include <string>
#include <string_view>
#include <wpi/Signal.h>
#include "wpinet/uv/Handle.h"
namespace wpi::uv {
class Loop;
/**
* Filesystem Event handle.
*/
class FsEvent final : public HandleImpl<FsEvent, uv_fs_event_t> {
struct private_init {};
public:
explicit FsEvent(const private_init&) {}
~FsEvent() noexcept override = default;
/**
* Create a filesystem event handle.
*
* @param loop Loop object where this handle runs.
*/
static std::shared_ptr<FsEvent> Create(Loop& loop);
/**
* Create a filesystem event handle.
*
* @param loop Loop object where this handle runs.
*/
static std::shared_ptr<FsEvent> Create(const std::shared_ptr<Loop>& loop) {
return Create(*loop);
}
/**
* Start watching the specified path for changes.
*
* @param path Path to watch for changes
* @param flags Bitmask of event flags. Only UV_FS_EVENT_RECURSIVE is
* supported (and only on OSX and Windows).
*/
void Start(std::string_view path, unsigned int flags = 0);
/**
* Stop watching for changes.
*/
void Stop() { Invoke(&uv_fs_event_stop, GetRaw()); }
/**
* Get the path being monitored. Signals error and returns empty string if
* an error occurs.
* @return Monitored path.
*/
std::string GetPath();
/**
* Signal generated when a filesystem change occurs. The first parameter
* is the filename (if a directory was passed to Start(), the filename is
* relative to that directory). The second parameter is an ORed mask of
* UV_RENAME and UV_CHANGE.
*/
sig::Signal<const char*, int> fsEvent;
};
} // namespace wpi::uv
#endif // WPINET_UV_FSEVENT_H_

View File

@@ -0,0 +1,121 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#ifndef WPINET_UV_GETADDRINFO_H_
#define WPINET_UV_GETADDRINFO_H_
#include <uv.h>
#include <functional>
#include <memory>
#include <optional>
#include <string_view>
#include <utility>
#include <wpi/Signal.h>
#include "wpinet/uv/Request.h"
namespace wpi::uv {
class Loop;
/**
* GetAddrInfo request.
* For use with `GetAddrInfo()` function family.
*/
class GetAddrInfoReq : public RequestImpl<GetAddrInfoReq, uv_getaddrinfo_t> {
public:
GetAddrInfoReq();
Loop& GetLoop() const { return *static_cast<Loop*>(GetRaw()->loop->data); }
/**
* Resolved lookup signal.
* Parameter is resolved address info.
*/
sig::Signal<const addrinfo&> resolved;
};
/**
* Asynchronous getaddrinfo(3). HandleResolvedAddress() is called on the
* request when the resolution completes. HandleError() is called on the
* request if any errors occur.
*
* Either node or service may be empty but not both.
*
* @param loop Event loop
* @param req request
* @param node Either a numerical network address or a network hostname.
* @param service Either a service name or a port number as a string.
* @param hints Optional `addrinfo` data structure with additional address
* type constraints.
*/
void GetAddrInfo(Loop& loop, const std::shared_ptr<GetAddrInfoReq>& req,
std::string_view node, std::string_view service = {},
std::optional<addrinfo> hints = {});
/**
* Asynchronous getaddrinfo(3). HandleResolvedAddress() is called on the
* request when the resolution completes. HandleError() is called on the
* request if any errors occur.
*
* Either node or service may be empty but not both.
*
* @param loop Event loop
* @param req request
* @param node Either a numerical network address or a network hostname.
* @param service Either a service name or a port number as a string.
* @param hints Optional `addrinfo` data structure with additional address
* type constraints.
*/
inline void GetAddrInfo(const std::shared_ptr<Loop>& loop,
const std::shared_ptr<GetAddrInfoReq>& req,
std::string_view node, std::string_view service = {},
std::optional<addrinfo> hints = {}) {
GetAddrInfo(*loop, req, node, service, hints);
}
/**
* Asynchronous getaddrinfo(3). The callback is called when the resolution
* completes, and errors are forwarded to the loop. This is a convenience
* wrapper.
*
* Either node or service may be empty but not both.
*
* @param loop Event loop
* @param callback Callback function to call when resolution completes
* @param node Either a numerical network address or a network hostname.
* @param service Either a service name or a port number as a string.
* @param hints Optional `addrinfo` data structure with additional address
* type constraints.
*/
void GetAddrInfo(Loop& loop, std::function<void(const addrinfo&)> callback,
std::string_view node, std::string_view service = {},
std::optional<addrinfo> hints = {});
/**
* Asynchronous getaddrinfo(3). The callback is called when the resolution
* completes, and errors are forwarded to the loop. This is a convenience
* wrapper.
*
* Either node or service may be empty but not both.
*
* @param loop Event loop
* @param callback Callback function to call when resolution completes
* @param node Either a numerical network address or a network hostname.
* @param service Either a service name or a port number as a string.
* @param hints Optional `addrinfo` data structure with additional address
* type constraints.
*/
inline void GetAddrInfo(const std::shared_ptr<Loop>& loop,
std::function<void(const addrinfo&)> callback,
std::string_view node, std::string_view service = {},
std::optional<addrinfo> hints = {}) {
GetAddrInfo(*loop, std::move(callback), node, service, hints);
}
} // namespace wpi::uv
#endif // WPINET_UV_GETADDRINFO_H_

View File

@@ -0,0 +1,228 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#ifndef WPINET_UV_GETNAMEINFO_H_
#define WPINET_UV_GETNAMEINFO_H_
#include <uv.h>
#include <functional>
#include <memory>
#include <string_view>
#include <utility>
#include <wpi/Signal.h>
#include "wpinet/uv/Request.h"
namespace wpi::uv {
class Loop;
/**
* GetNameInfo request.
* For use with `GetNameInfo()` function family.
*/
class GetNameInfoReq : public RequestImpl<GetNameInfoReq, uv_getnameinfo_t> {
public:
GetNameInfoReq();
Loop& GetLoop() const { return *static_cast<Loop*>(GetRaw()->loop->data); }
/**
* Resolved lookup signal.
* Parameters are hostname and service.
*/
sig::Signal<const char*, const char*> resolved;
};
/**
* Asynchronous getnameinfo(3). HandleResolvedName() is called on the
* request when the resolution completes. HandleError() is called on the
* request if any errors occur.
*
* @param loop Event loop
* @param req request
* @param addr Initialized `sockaddr_in` or `sockaddr_in6` data structure.
* @param flags Optional flags to modify the behavior of `getnameinfo`.
*/
void GetNameInfo(Loop& loop, const std::shared_ptr<GetNameInfoReq>& req,
const sockaddr& addr, int flags = 0);
/**
* Asynchronous getnameinfo(3). HandleResolvedName() is called on the
* request when the resolution completes. HandleError() is called on the
* request if any errors occur.
*
* @param loop Event loop
* @param req request
* @param addr Initialized `sockaddr_in` or `sockaddr_in6` data structure.
* @param flags Optional flags to modify the behavior of `getnameinfo`.
*/
inline void GetNameInfo(const std::shared_ptr<Loop>& loop,
const std::shared_ptr<GetNameInfoReq>& req,
const sockaddr& addr, int flags = 0) {
GetNameInfo(*loop, req, addr, flags);
}
/**
* Asynchronous getnameinfo(3). The callback is called when the resolution
* completes, and errors are forwarded to the loop.
*
* @param loop Event loop
* @param callback Callback function to call when resolution completes
* @param addr Initialized `sockaddr_in` or `sockaddr_in6` data structure.
* @param flags Optional flags to modify the behavior of `getnameinfo`.
*/
void GetNameInfo(Loop& loop,
std::function<void(const char*, const char*)> callback,
const sockaddr& addr, int flags = 0);
/**
* Asynchronous getnameinfo(3). The callback is called when the resolution
* completes, and errors are forwarded to the loop.
*
* @param loop Event loop
* @param callback Callback function to call when resolution completes
* @param addr Initialized `sockaddr_in` or `sockaddr_in6` data structure.
* @param flags Optional flags to modify the behavior of `getnameinfo`.
*/
inline void GetNameInfo(const std::shared_ptr<Loop>& loop,
std::function<void(const char*, const char*)> callback,
const sockaddr& addr, int flags = 0) {
GetNameInfo(*loop, std::move(callback), addr, flags);
}
/**
* Asynchronous IPv4 getnameinfo(3). HandleResolvedName() is called on the
* request when the resolution completes. HandleError() is called on the
* request if any errors occur.
*
* @param loop Event loop
* @param req request
* @param ip A valid IPv4 address
* @param port A valid port number
* @param flags Optional flags to modify the behavior of `getnameinfo`.
*/
void GetNameInfo4(Loop& loop, const std::shared_ptr<GetNameInfoReq>& req,
std::string_view ip, unsigned int port, int flags = 0);
/**
* Asynchronous IPv4 getnameinfo(3). HandleResolvedName() is called on the
* request when the resolution completes. HandleError() is called on the
* request if any errors occur.
*
* @param loop Event loop
* @param req request
* @param ip A valid IPv4 address
* @param port A valid port number
* @param flags Optional flags to modify the behavior of `getnameinfo`.
*/
inline void GetNameInfo4(const std::shared_ptr<Loop>& loop,
const std::shared_ptr<GetNameInfoReq>& req,
std::string_view ip, unsigned int port,
int flags = 0) {
return GetNameInfo4(*loop, req, ip, port, flags);
}
/**
* Asynchronous IPv4 getnameinfo(3). The callback is called when the resolution
* completes, and errors are forwarded to the loop.
*
* @param loop Event loop
* @param callback Callback function to call when resolution completes
* @param ip A valid IPv4 address
* @param port A valid port number
* @param flags Optional flags to modify the behavior of `getnameinfo`.
*/
void GetNameInfo4(Loop& loop,
std::function<void(const char*, const char*)> callback,
std::string_view ip, unsigned int port, int flags = 0);
/**
* Asynchronous IPv4 getnameinfo(3). The callback is called when the resolution
* completes, and errors are forwarded to the loop. This is a convenience
* wrapper.
*
* @param loop Event loop
* @param ip A valid IPv4 address
* @param port A valid port number
* @param callback Callback function to call when resolution completes
* @param flags Optional flags to modify the behavior of `getnameinfo`.
*/
inline void GetNameInfo4(const std::shared_ptr<Loop>& loop,
std::function<void(const char*, const char*)> callback,
std::string_view ip, unsigned int port,
int flags = 0) {
return GetNameInfo4(*loop, std::move(callback), ip, port, flags);
}
/**
* Asynchronous IPv6 getnameinfo(3). HandleResolvedName() is called on the
* request when the resolution completes. HandleError() is called on the
* request if any errors occur.
*
* @param loop Event loop
* @param req request
* @param ip A valid IPv6 address
* @param port A valid port number
* @param flags Optional flags to modify the behavior of `getnameinfo`.
*/
void GetNameInfo6(Loop& loop, const std::shared_ptr<GetNameInfoReq>& req,
std::string_view ip, unsigned int port, int flags = 0);
/**
* Asynchronous IPv6 getnameinfo(3). HandleResolvedName() is called on the
* request when the resolution completes. HandleError() is called on the
* request if any errors occur.
*
* @param loop Event loop
* @param req request
* @param ip A valid IPv6 address
* @param port A valid port number
* @param flags Optional flags to modify the behavior of `getnameinfo`.
*/
inline void GetNameInfo6(const std::shared_ptr<Loop>& loop,
const std::shared_ptr<GetNameInfoReq>& req,
std::string_view ip, unsigned int port,
int flags = 0) {
GetNameInfo6(*loop, req, ip, port, flags);
}
/**
* Asynchronous IPv6 getnameinfo(3). The callback is called when the resolution
* completes, and errors are forwarded to the loop. This is a convenience
* wrapper.
*
* @param loop Event loop
* @param callback Callback function to call when resolution completes
* @param ip A valid IPv6 address
* @param port A valid port number
* @param flags Optional flags to modify the behavior of `getnameinfo`.
*/
void GetNameInfo6(Loop& loop,
std::function<void(const char*, const char*)> callback,
std::string_view ip, unsigned int port, int flags = 0);
/**
* Asynchronous IPv6 getnameinfo(3). The callback is called when the resolution
* completes, and errors are forwarded to the loop. This is a convenience
* wrapper.
*
* @param loop Event loop
* @param callback Callback function to call when resolution completes
* @param ip A valid IPv6 address
* @param port A valid port number
* @param flags Optional flags to modify the behavior of `getnameinfo`.
*/
inline void GetNameInfo6(const std::shared_ptr<Loop>& loop,
std::function<void(const char*, const char*)> callback,
std::string_view ip, unsigned int port,
int flags = 0) {
return GetNameInfo6(*loop, std::move(callback), ip, port, flags);
}
} // namespace wpi::uv
#endif // WPINET_UV_GETNAMEINFO_H_

View File

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

View File

@@ -0,0 +1,75 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#ifndef WPINET_UV_IDLE_H_
#define WPINET_UV_IDLE_H_
#include <uv.h>
#include <memory>
#include <wpi/Signal.h>
#include "wpinet/uv/Handle.h"
namespace wpi::uv {
class Loop;
/**
* Idle handle.
*
* Idle handles will generate a signal once per loop iteration, right
* before the Prepare handles.
*
* The notable difference with Prepare handles is that when there are active
* idle handles, the loop will perform a zero timeout poll instead of blocking
* for I/O.
*
* @warning Despite the name, idle handles will signal every loop iteration,
* not when the loop is actually "idle". This also means they can easily become
* CPU hogs.
*/
class Idle final : public HandleImpl<Idle, uv_idle_t> {
struct private_init {};
public:
explicit Idle(const private_init&) {}
~Idle() noexcept override = default;
/**
* Create an idle handle.
*
* @param loop Loop object where this handle runs.
*/
static std::shared_ptr<Idle> Create(Loop& loop);
/**
* Create an idle handle.
*
* @param loop Loop object where this handle runs.
*/
static std::shared_ptr<Idle> Create(const std::shared_ptr<Loop>& loop) {
return Create(*loop);
}
/**
* Start the handle.
*/
void Start();
/**
* Stop the handle. The signal will no longer be generated.
*/
void Stop() { Invoke(&uv_idle_stop, GetRaw()); }
/**
* Signal generated once per loop iteration prior to Prepare signals.
*/
sig::Signal<> idle;
};
} // namespace wpi::uv
#endif // WPINET_UV_IDLE_H_

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,123 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#ifndef WPINET_UV_POLL_H_
#define WPINET_UV_POLL_H_
#include <uv.h>
#include <functional>
#include <memory>
#include <wpi/Signal.h>
#include "wpinet/uv/Handle.h"
namespace wpi::uv {
class Loop;
/**
* Poll handle.
*/
class Poll final : public HandleImpl<Poll, uv_poll_t> {
struct private_init {};
public:
explicit Poll(const private_init&) {}
~Poll() noexcept override = default;
/**
* Create a poll handle using a file descriptor.
*
* @param loop Loop object where this handle runs.
* @param fd File descriptor.
*/
static std::shared_ptr<Poll> Create(Loop& loop, int fd);
/**
* Create a poll handle using a file descriptor.
*
* @param loop Loop object where this handle runs.
* @param fd File descriptor.
*/
static std::shared_ptr<Poll> Create(const std::shared_ptr<Loop>& loop,
int fd) {
return Create(*loop, fd);
}
/**
* Create a poll handle using a socket descriptor.
*
* @param loop Loop object where this handle runs.
* @param sock Socket descriptor.
*/
static std::shared_ptr<Poll> CreateSocket(Loop& loop, uv_os_sock_t sock);
/**
* Create a poll handle using a socket descriptor.
*
* @param loop Loop object where this handle runs.
* @param sock Socket descriptor.
*/
static std::shared_ptr<Poll> CreateSocket(const std::shared_ptr<Loop>& loop,
uv_os_sock_t sock) {
return CreateSocket(*loop, sock);
}
/**
* Reuse this handle. This closes the handle, and after the close completes,
* reinitializes it (identically to Create) and calls the provided callback.
* Unlike Close(), it does NOT emit the closed signal, however, IsClosing()
* will return true until the callback is called. This does nothing if
* IsClosing() is true (e.g. if Close() was called).
*
* @param fd File descriptor
* @param callback Callback
*/
void Reuse(int fd, std::function<void()> callback);
/**
* Reuse this handle. This closes the handle, and after the close completes,
* reinitializes it (identically to CreateSocket) and calls the provided
* callback. Unlike Close(), it does NOT emit the closed signal, however,
* IsClosing() will return true until the callback is called. This does
* nothing if IsClosing() is true (e.g. if Close() was called).
*
* @param sock Socket descriptor.
* @param callback Callback
*/
void ReuseSocket(uv_os_sock_t sock, std::function<void()> callback);
/**
* Start polling the file descriptor.
*
* @param events Bitmask of events (UV_READABLE, UV_WRITEABLE, UV_PRIORITIZED,
* and UV_DISCONNECT).
*/
void Start(int events);
/**
* Stop polling the file descriptor.
*/
void Stop() { Invoke(&uv_poll_stop, GetRaw()); }
/**
* Signal generated when a poll event occurs.
*/
sig::Signal<int> pollEvent;
private:
struct ReuseData {
std::function<void()> callback;
bool isSocket;
int fd;
uv_os_sock_t sock;
};
std::unique_ptr<ReuseData> m_reuseData;
};
} // namespace wpi::uv
#endif // WPINET_UV_POLL_H_

View File

@@ -0,0 +1,66 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#ifndef WPINET_UV_PREPARE_H_
#define WPINET_UV_PREPARE_H_
#include <uv.h>
#include <memory>
#include <wpi/Signal.h>
#include "wpinet/uv/Handle.h"
namespace wpi::uv {
class Loop;
/**
* Prepare handle.
* Prepare handles will generate a signal once per loop iteration, right
* before polling for I/O.
*/
class Prepare final : public HandleImpl<Prepare, uv_prepare_t> {
struct private_init {};
public:
explicit Prepare(const private_init&) {}
~Prepare() noexcept override = default;
/**
* Create a prepare handle.
*
* @param loop Loop object where this handle runs.
*/
static std::shared_ptr<Prepare> Create(Loop& loop);
/**
* Create a prepare handle.
*
* @param loop Loop object where this handle runs.
*/
static std::shared_ptr<Prepare> Create(const std::shared_ptr<Loop>& loop) {
return Create(*loop);
}
/**
* Start the handle.
*/
void Start();
/**
* Stop the handle. The signal will no longer be generated.
*/
void Stop() { Invoke(&uv_prepare_stop, GetRaw()); }
/**
* Signal generated once per loop iteration prior to polling for I/O.
*/
sig::Signal<> prepare;
};
} // namespace wpi::uv
#endif // WPINET_UV_PREPARE_H_

View File

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

View File

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

View File

@@ -0,0 +1,80 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#ifndef WPINET_UV_SIGNAL_H_
#define WPINET_UV_SIGNAL_H_
#include <uv.h>
#include <memory>
#include <wpi/Signal.h>
#include "wpinet/uv/Handle.h"
namespace wpi::uv {
class Loop;
/**
* Signal handle.
*/
class Signal final : public HandleImpl<Signal, uv_signal_t> {
struct private_init {};
public:
explicit Signal(const private_init&) {}
~Signal() noexcept override = default;
/**
* Create a signal handle.
*
* @param loop Loop object where this handle runs.
*/
static std::shared_ptr<Signal> Create(Loop& loop);
/**
* Create a signal handle.
*
* @param loop Loop object where this handle runs.
*/
static std::shared_ptr<Signal> Create(const std::shared_ptr<Loop>& loop) {
return Create(*loop);
}
/**
* Start watching for the given signal.
*
* @param signum Signal to watch for.
*/
void Start(int signum);
/**
* Start watching for the given signal. Same as Start() but the signal
* handler is reset the moment the signal is received.
*
* @param signum Signal to watch for.
*/
void StartOneshot(int signum);
/**
* Stop watching for the signal.
*/
void Stop() { Invoke(&uv_signal_stop, GetRaw()); }
/**
* Get the signal being monitored.
* @return Signal number.
*/
int GetSignal() const { return GetRaw()->signum; }
/**
* Signal generated when a signal occurs.
*/
sig::Signal<int> signal;
};
} // namespace wpi::uv
#endif // WPINET_UV_SIGNAL_H_

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,94 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#ifndef WPINET_UV_WORK_H_
#define WPINET_UV_WORK_H_
#include <uv.h>
#include <functional>
#include <memory>
#include <utility>
#include <wpi/Signal.h>
#include "wpinet/uv/Request.h"
namespace wpi::uv {
class Loop;
/**
* Work request.
* For use with `QueueWork()` function family.
*/
class WorkReq : public RequestImpl<WorkReq, uv_work_t> {
public:
WorkReq();
Loop& GetLoop() const { return *static_cast<Loop*>(GetRaw()->loop->data); }
/**
* Function(s) that will be run on the thread pool.
*/
sig::Signal<> work;
/**
* Function(s) that will be run on the loop thread after the work on the
* thread pool has been completed by the work callback.
*/
sig::Signal<> afterWork;
};
/**
* Initializes a work request which will run on the thread pool.
*
* @param loop Event loop
* @param req request
*/
void QueueWork(Loop& loop, const std::shared_ptr<WorkReq>& req);
/**
* Initializes a work request which will run on the thread pool.
*
* @param loop Event loop
* @param req request
*/
inline void QueueWork(const std::shared_ptr<Loop>& loop,
const std::shared_ptr<WorkReq>& req) {
QueueWork(*loop, req);
}
/**
* Initializes a work request which will run on the thread pool. The work
* callback will be run on a thread from the thread pool, and the afterWork
* callback will be called on the loop thread after the work completes. Any
* errors are forwarded to the loop.
*
* @param loop Event loop
* @param work Work callback (called from separate thread)
* @param afterWork After work callback (called on loop thread)
*/
void QueueWork(Loop& loop, std::function<void()> work,
std::function<void()> afterWork);
/**
* Initializes a work request which will run on the thread pool. The work
* callback will be run on a thread from the thread pool, and the afterWork
* callback will be called on the loop thread after the work completes. Any
* errors are forwarded to the loop.
*
* @param loop Event loop
* @param work Work callback (called from separate thread)
* @param afterWork After work callback (called on loop thread)
*/
inline void QueueWork(const std::shared_ptr<Loop>& loop,
std::function<void()> work,
std::function<void()> afterWork) {
QueueWork(*loop, std::move(work), std::move(afterWork));
}
} // namespace wpi::uv
#endif // WPINET_UV_WORK_H_

View File

@@ -0,0 +1,158 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#ifndef WPINET_UV_UTIL_H_
#define WPINET_UV_UTIL_H_
#include <uv.h>
#include <cstring>
#include <string_view>
#ifdef _WIN32
#pragma comment(lib, "Ws2_32.lib")
#endif
namespace wpi::uv {
namespace detail {
template <typename T>
concept StringAssignable = requires(T a, const char* p) {
{ a.assign(p, p) }; // NOLINT(readability/braces)
};
} // namespace detail
/**
* Convert a binary structure containing an IPv4 address to a string.
* @param addr Binary structure
* @param ip Output string (any type that has `assign(char*, char*)`)
* @param port Output port number
* @return Error (same as `uv_ip4_name()`).
*/
template <detail::StringAssignable T>
int AddrToName(const sockaddr_in& addr, T* ip, unsigned int* port) {
char name[128];
int err = uv_ip4_name(&addr, name, 128);
if (err == 0) {
ip->assign(name, name + std::strlen(name));
*port = ntohs(addr.sin_port);
} else {
ip->assign(name, name);
}
return err;
}
/**
* Convert a binary structure containing an IPv6 address to a string.
* @param addr Binary structure
* @param ip Output string (any type that has `assign(char*, char*)`)
* @param port Output port number
* @return Error (same as `uv_ip6_name()`).
*/
template <detail::StringAssignable T>
int AddrToName(const sockaddr_in6& addr, T* ip, unsigned int* port) {
char name[128];
int err = uv_ip6_name(&addr, name, 128);
if (err == 0) {
ip->assign(name, name + std::strlen(name));
*port = ntohs(addr.sin6_port);
} else {
ip->assign(name, name);
}
return err;
}
/**
* Convert a binary structure containing an IPv4 or IPv6 address to a string.
* @param addr Binary structure
* @param ip Output string (any type that has `assign(char*, char*)`)
* @param port Output port number
* @return Error (same as `uv_ip6_name()`).
*/
template <detail::StringAssignable T>
int AddrToName(const sockaddr_storage& addr, T* ip, unsigned int* port) {
if (addr.ss_family == AF_INET) {
return AddrToName(reinterpret_cast<const sockaddr_in&>(addr), ip, port);
}
if (addr.ss_family == AF_INET6) {
return AddrToName(reinterpret_cast<const sockaddr_in6&>(addr), ip, port);
}
char name[1] = {'\0'};
ip->assign(name, name);
return -1;
}
/**
* Convert a binary IPv4 address to a string.
* @param addr Binary address
* @param ip Output string (any type that has `assign(char*, char*)`)
* @return Error (same as `uv_inet_ntop()`).
*/
template <detail::StringAssignable T>
int AddrToName(const in_addr& addr, T* ip) {
char name[128];
int err = uv_inet_ntop(AF_INET, &addr, name, 128);
if (err == 0) {
ip->assign(name, name + std::strlen(name));
} else {
ip->assign(name, name);
}
return err;
}
/**
* Convert a binary IPv6 address to a string.
* @param addr Binary address
* @param ip Output string (any type that has `assign(char*, char*)`)
* @return Error (same as `uv_inet_ntop()`).
*/
template <detail::StringAssignable T>
int AddrToName(const in6_addr& addr, T* ip) {
char name[128];
int err = uv_inet_ntop(AF_INET6, &addr, name, 128);
if (err == 0) {
ip->assign(name, name + std::strlen(name));
} else {
ip->assign(name, name);
}
return err;
}
/**
* Convert a string containing an IPv4 address to a binary structure.
* @param ip IPv4 address string
* @param port Port number
* @param addr Output binary structure
* @return Error (same as `uv_ip4_addr()`).
*/
int NameToAddr(std::string_view ip, unsigned int port, sockaddr_in* addr);
/**
* Convert a string containing an IPv6 address to a binary structure.
* @param ip IPv6 address string
* @param port Port number
* @param addr Output binary structure
* @return Error (same as `uv_ip6_addr()`).
*/
int NameToAddr(std::string_view ip, unsigned int port, sockaddr_in6* addr);
/**
* Convert a string containing an IPv4 address to binary format.
* @param ip IPv4 address string
* @param addr Output binary
* @return Error (same as `uv_inet_pton()`).
*/
int NameToAddr(std::string_view ip, in_addr* addr);
/**
* Convert a string containing an IPv6 address to binary format.
* @param ip IPv6 address string
* @param addr Output binary
* @return Error (same as `uv_inet_pton()`).
*/
int NameToAddr(std::string_view ip, in6_addr* addr);
} // namespace wpi::uv
#endif // WPINET_UV_UTIL_H_