[ntcore] NetworkTables 4 (#3217)

This commit is contained in:
Peter Johnson
2022-10-08 10:01:31 -07:00
committed by GitHub
parent 90cfa00115
commit 77301b126c
380 changed files with 34573 additions and 22095 deletions

View File

@@ -0,0 +1,338 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#include "ConnectionList.h"
#include <atomic>
#include <optional>
#include <wpi/DataLog.h>
#include <wpi/DenseMap.h>
#include <wpi/SafeThread.h>
#include <wpi/Synchronization.h>
#include <wpi/UidVector.h>
#include <wpi/json_serializer.h>
#include <wpi/raw_ostream.h>
#include "HandleMap.h"
#include "ntcore_cpp.h"
using namespace nt;
namespace {
struct PollerData {
static constexpr auto kType = Handle::kConnectionListenerPoller;
explicit PollerData(NT_ConnectionListenerPoller handle) : handle{handle} {}
wpi::SignalObject<NT_ConnectionListenerPoller> handle;
std::vector<ConnectionNotification> queue;
};
struct ListenerData {
static constexpr auto kType = Handle::kConnectionListener;
ListenerData(NT_ConnectionListener handle, PollerData* poller)
: handle{handle}, poller{poller} {}
wpi::SignalObject<NT_ConnectionListener> handle;
PollerData* poller;
};
struct DataLoggerData {
static constexpr auto kType = Handle::kConnectionDataLogger;
DataLoggerData(NT_ConnectionDataLogger handle, wpi::log::DataLog& log,
std::string_view name, int64_t time)
: handle{handle},
entry{log, name, "{\"schema\":\"NTConnectionInfo\",\"source\":\"NT\"}",
"json", time} {}
NT_ConnectionDataLogger handle;
wpi::log::StringLogEntry entry;
};
class ListenerThread final : public wpi::SafeThreadEvent {
public:
explicit ListenerThread(NT_ConnectionListenerPoller poller)
: m_poller{poller} {}
void Main() final;
NT_ConnectionListenerPoller m_poller;
wpi::DenseMap<NT_ConnectionListener,
std::function<void(const ConnectionNotification& event)>>
m_callbacks;
};
class CLImpl {
public:
explicit CLImpl(int inst) : m_inst{inst} {}
int m_inst;
// shared with user (must be atomic or mutex-protected)
std::atomic_bool m_connected{false};
wpi::UidVector<std::optional<ConnectionInfo>, 8> m_connections;
HandleMap<PollerData, 8> m_pollers;
HandleMap<ListenerData, 8> m_listeners;
HandleMap<DataLoggerData, 8> m_dataloggers;
wpi::SafeThreadOwner<ListenerThread> m_listenerThread;
NT_ConnectionListener AddListener(
std::function<void(const ConnectionNotification& event)> callback,
bool immediateNotify);
PollerData* CreateListenerPoller() { return m_pollers.Add(m_inst); }
void DestroyListenerPoller(NT_ConnectionListenerPoller pollerHandle);
NT_ConnectionListener AddPolledListener(
NT_ConnectionListenerPoller pollerHandle, bool immediateNotify);
void RemoveListener(NT_ConnectionListener listenerHandle);
};
} // namespace
void ListenerThread::Main() {
while (m_active) {
WPI_Handle signaledBuf[2];
auto signaled =
wpi::WaitForObjects({m_poller, m_stopEvent.GetHandle()}, signaledBuf);
if (signaled.empty() || !m_active) {
return;
}
// call all the way back out to the C++ API to ensure valid handle
auto events = nt::ReadConnectionListenerQueue(m_poller);
if (events.empty()) {
continue;
}
std::unique_lock lock{m_mutex};
for (auto&& event : events) {
auto callbackIt = m_callbacks.find(event.listener);
if (callbackIt != m_callbacks.end()) {
auto callback = callbackIt->second;
lock.unlock();
callback(event);
lock.lock();
}
}
}
}
NT_ConnectionListener CLImpl::AddListener(
std::function<void(const ConnectionNotification& event)> callback,
bool immediateNotify) {
if (!m_listenerThread) {
m_listenerThread.Start(CreateListenerPoller()->handle);
}
if (auto thr = m_listenerThread.GetThread()) {
auto listener = AddPolledListener(thr->m_poller, immediateNotify);
if (listener) {
thr->m_callbacks.try_emplace(listener, std::move(callback));
}
return listener;
} else {
return {};
}
}
void CLImpl::DestroyListenerPoller(NT_ConnectionListenerPoller pollerHandle) {
if (auto poller = m_pollers.Remove(pollerHandle)) {
// ensure all listeners that use this poller are removed
wpi::SmallVector<NT_ConnectionListener, 16> toRemove;
for (auto&& listener : m_listeners) {
if (listener->poller == poller.get()) {
toRemove.emplace_back(listener->handle);
}
}
for (auto handle : toRemove) {
RemoveListener(handle);
}
}
}
NT_ConnectionListener CLImpl::AddPolledListener(
NT_ConnectionListenerPoller pollerHandle, bool immediateNotify) {
auto poller = m_pollers.Get(pollerHandle);
if (!poller) {
return {};
}
auto listener = m_listeners.Add(m_inst, poller);
if (immediateNotify && !m_connections.empty()) {
for (auto&& conn : m_connections) {
listener->poller->queue.emplace_back(listener->handle.GetHandle(), true,
*conn);
}
listener->poller->handle.Set();
listener->handle.Set();
}
return listener->handle;
}
void CLImpl::RemoveListener(NT_ConnectionListener listenerHandle) {
if (auto listener = m_listeners.Remove(listenerHandle)) {
if (auto thr = m_listenerThread.GetThread()) {
if (thr->m_poller == listener->poller->handle) {
thr->m_callbacks.erase(listenerHandle);
}
}
}
}
static std::string ConnInfoToJson(bool connected, const ConnectionInfo& info) {
std::string str;
wpi::raw_string_ostream os{str};
wpi::json::serializer s{os, ' ', 0};
os << "{\"connected\":" << (connected ? "true" : "false");
os << ",\"remote_id\":\"";
s.dump_escaped(info.remote_id, false);
os << "\",\"remote_ip\":\"";
s.dump_escaped(info.remote_ip, false);
os << "\",\"remote_port\":";
s.dump_integer(static_cast<uint64_t>(info.remote_port));
os << ",\"protocol_version\":";
s.dump_integer(static_cast<uint64_t>(info.protocol_version));
os << "}";
os.flush();
return str;
}
class ConnectionList::Impl : public CLImpl {
public:
explicit Impl(int inst) : CLImpl{inst} {}
};
ConnectionList::ConnectionList(int inst)
: m_impl{std::make_unique<Impl>(inst)} {}
ConnectionList::~ConnectionList() = default;
int ConnectionList::AddConnection(const ConnectionInfo& info) {
std::scoped_lock lock{m_mutex};
m_impl->m_connected = true;
for (auto&& listener : m_impl->m_listeners) {
listener->poller->queue.emplace_back(listener->handle.GetHandle(), true,
info);
listener->poller->handle.Set();
listener->handle.Set();
}
if (!m_impl->m_dataloggers.empty()) {
auto now = Now();
for (auto&& datalogger : m_impl->m_dataloggers) {
datalogger->entry.Append(ConnInfoToJson(true, info), now);
}
}
return m_impl->m_connections.emplace_back(info);
}
void ConnectionList::RemoveConnection(int handle) {
std::scoped_lock lock{m_mutex};
auto val = m_impl->m_connections.erase(handle);
if (m_impl->m_connections.empty()) {
m_impl->m_connected = false;
}
if (val) {
for (auto&& listener : m_impl->m_listeners) {
listener->poller->queue.emplace_back(listener->handle.GetHandle(), false,
*val);
listener->poller->handle.Set();
listener->handle.Set();
}
if (!m_impl->m_dataloggers.empty()) {
auto now = Now();
for (auto&& datalogger : m_impl->m_dataloggers) {
datalogger->entry.Append(ConnInfoToJson(false, *val), now);
}
}
}
}
void ConnectionList::ClearConnections() {
std::scoped_lock lock{m_mutex};
m_impl->m_connected = false;
for (auto&& conn : m_impl->m_connections) {
for (auto&& listener : m_impl->m_listeners) {
listener->poller->queue.emplace_back(listener->handle.GetHandle(), false,
*conn);
listener->poller->handle.Set();
listener->handle.Set();
}
}
m_impl->m_connections.clear();
}
std::vector<ConnectionInfo> ConnectionList::GetConnections() const {
std::scoped_lock lock{m_mutex};
std::vector<ConnectionInfo> info;
info.reserve(m_impl->m_connections.size());
for (auto&& conn : m_impl->m_connections) {
info.emplace_back(*conn);
}
return info;
}
bool ConnectionList::IsConnected() const {
return m_impl->m_connected;
}
NT_ConnectionListener ConnectionList::AddListener(
std::function<void(const ConnectionNotification& event)> callback,
bool immediateNotify) {
std::scoped_lock lock{m_mutex};
return m_impl->AddListener(std::move(callback), immediateNotify);
}
NT_ConnectionListenerPoller ConnectionList::CreateListenerPoller() {
std::scoped_lock lock{m_mutex};
return m_impl->CreateListenerPoller()->handle;
}
void ConnectionList::DestroyListenerPoller(
NT_ConnectionListenerPoller pollerHandle) {
std::scoped_lock lock{m_mutex};
m_impl->DestroyListenerPoller(pollerHandle);
}
NT_ConnectionListener ConnectionList::AddPolledListener(
NT_ConnectionListenerPoller pollerHandle, bool immediateNotify) {
std::scoped_lock lock{m_mutex};
return m_impl->AddPolledListener(pollerHandle, immediateNotify);
}
std::vector<ConnectionNotification> ConnectionList::ReadListenerQueue(
NT_ConnectionListenerPoller pollerHandle) {
std::scoped_lock lock{m_mutex};
if (auto poller = m_impl->m_pollers.Get(pollerHandle)) {
std::vector<ConnectionNotification> rv;
rv.swap(poller->queue);
return rv;
} else {
return {};
}
}
void ConnectionList::RemoveListener(NT_ConnectionListener listenerHandle) {
std::scoped_lock lock{m_mutex};
m_impl->RemoveListener(listenerHandle);
}
NT_ConnectionDataLogger ConnectionList::StartDataLog(wpi::log::DataLog& log,
std::string_view name) {
std::scoped_lock lock{m_mutex};
auto now = Now();
auto datalogger = m_impl->m_dataloggers.Add(m_impl->m_inst, log, name, now);
for (auto&& conn : m_impl->m_connections) {
datalogger->entry.Append(ConnInfoToJson(true, *conn), now);
}
return datalogger->handle;
}
void ConnectionList::StopDataLog(NT_ConnectionDataLogger logger) {
std::scoped_lock lock{m_mutex};
if (auto datalogger = m_impl->m_dataloggers.Remove(logger)) {
datalogger->entry.Finish(Now());
}
}

View File

@@ -0,0 +1,56 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source 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 <functional>
#include <memory>
#include <string_view>
#include <vector>
#include <wpi/mutex.h>
#include "IConnectionList.h"
#include "ntcore_cpp.h"
namespace nt {
class ConnectionList final : public IConnectionList {
public:
explicit ConnectionList(int inst);
~ConnectionList() final;
// IConnectionList interface
int AddConnection(const ConnectionInfo& info) final;
void RemoveConnection(int handle) final;
void ClearConnections() final;
// user-facing functions
std::vector<ConnectionInfo> GetConnections() const final;
bool IsConnected() const final;
NT_ConnectionListener AddListener(
std::function<void(const ConnectionNotification& event)> callback,
bool immediateNotify);
NT_ConnectionListenerPoller CreateListenerPoller();
void DestroyListenerPoller(NT_ConnectionListenerPoller pollerHandle);
NT_ConnectionListener AddPolledListener(
NT_ConnectionListenerPoller pollerHandle, bool immediateNotify);
std::vector<ConnectionNotification> ReadListenerQueue(
NT_ConnectionListenerPoller pollerHandle);
void RemoveListener(NT_ConnectionListener listenerHandle);
NT_ConnectionDataLogger StartDataLog(wpi::log::DataLog& log,
std::string_view name);
void StopDataLog(NT_ConnectionDataLogger logger);
private:
class Impl;
std::unique_ptr<Impl> m_impl;
mutable wpi::mutex m_mutex;
};
} // namespace nt

View File

@@ -1,32 +0,0 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#include "ConnectionNotifier.h"
using namespace nt;
ConnectionNotifier::ConnectionNotifier(int inst) : m_inst(inst) {}
void ConnectionNotifier::Start() {
DoStart(m_inst);
}
unsigned int ConnectionNotifier::Add(
std::function<void(const ConnectionNotification& event)> callback) {
return DoAdd(callback);
}
unsigned int ConnectionNotifier::AddPolled(unsigned int poller_uid) {
return DoAdd(poller_uid);
}
void ConnectionNotifier::Remove(unsigned int uid) {
CallbackManager::Remove(uid);
}
void ConnectionNotifier::NotifyConnection(bool connected,
const ConnectionInfo& conn_info,
unsigned int only_listener) {
Send(only_listener, 0, connected, conn_info);
}

View File

@@ -1,77 +0,0 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source 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 NTCORE_CONNECTIONNOTIFIER_H_
#define NTCORE_CONNECTIONNOTIFIER_H_
#include <utility>
#include <wpi/CallbackManager.h>
#include "Handle.h"
#include "IConnectionNotifier.h"
#include "ntcore_cpp.h"
namespace nt {
namespace impl {
class ConnectionNotifierThread
: public wpi::CallbackThread<ConnectionNotifierThread,
ConnectionNotification> {
public:
ConnectionNotifierThread(std::function<void()> on_start,
std::function<void()> on_exit, int inst)
: CallbackThread(std::move(on_start), std::move(on_exit)), m_inst(inst) {}
bool Matches(const ListenerData& /*listener*/,
const ConnectionNotification& /*data*/) {
return true;
}
void SetListener(ConnectionNotification* data, unsigned int listener_uid) {
data->listener =
Handle(m_inst, listener_uid, Handle::kConnectionListener).handle();
}
void DoCallback(
std::function<void(const ConnectionNotification& event)> callback,
const ConnectionNotification& data) {
callback(data);
}
int m_inst;
};
} // namespace impl
class ConnectionNotifier
: public IConnectionNotifier,
public wpi::CallbackManager<ConnectionNotifier,
impl::ConnectionNotifierThread> {
friend class ConnectionNotifierTest;
friend class wpi::CallbackManager<ConnectionNotifier,
impl::ConnectionNotifierThread>;
public:
explicit ConnectionNotifier(int inst);
void Start();
unsigned int Add(std::function<void(const ConnectionNotification& event)>
callback) override;
unsigned int AddPolled(unsigned int poller_uid) override;
void Remove(unsigned int uid) override;
void NotifyConnection(bool connected, const ConnectionInfo& conn_info,
unsigned int only_listener = UINT_MAX) override;
private:
int m_inst;
};
} // namespace nt
#endif // NTCORE_CONNECTIONNOTIFIER_H_

View File

@@ -1,765 +0,0 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#include "Dispatcher.h"
#include <algorithm>
#include <iterator>
#include <wpi/SmallVector.h>
#include <wpi/StringExtras.h>
#include <wpi/json_serializer.h>
#include <wpi/raw_ostream.h>
#include <wpi/timestamp.h>
#include <wpinet/TCPAcceptor.h>
#include <wpinet/TCPConnector.h>
#include "IConnectionNotifier.h"
#include "IStorage.h"
#include "Log.h"
#include "NetworkConnection.h"
using namespace nt;
static std::string ConnInfoToJson(bool connected, const ConnectionInfo& info) {
std::string str;
wpi::raw_string_ostream os{str};
wpi::json::serializer s{os, ' ', 0};
os << "{\"connected\":" << (connected ? "true" : "false");
os << ",\"remote_id\":\"";
s.dump_escaped(info.remote_id, false);
os << "\",\"remote_ip\":\"";
s.dump_escaped(info.remote_ip, false);
os << "\",\"remote_port\":";
s.dump_integer(static_cast<uint64_t>(info.remote_port));
os << ",\"protocol_version\":";
s.dump_integer(static_cast<uint64_t>(info.protocol_version));
os << "}";
os.flush();
return str;
}
void Dispatcher::StartServer(std::string_view persist_filename,
const char* listen_address, unsigned int port) {
std::string listen_address_copy(wpi::trim(listen_address));
DispatcherBase::StartServer(
persist_filename,
std::unique_ptr<wpi::NetworkAcceptor>(new wpi::TCPAcceptor(
static_cast<int>(port), listen_address_copy.c_str(), m_logger)));
}
void Dispatcher::SetServer(const char* server_name, unsigned int port) {
std::string server_name_copy(wpi::trim(server_name));
SetConnector([=]() -> std::unique_ptr<wpi::NetworkStream> {
return wpi::TCPConnector::connect(server_name_copy.c_str(),
static_cast<int>(port), m_logger, 1);
});
}
void Dispatcher::SetServer(
wpi::span<const std::pair<std::string_view, unsigned int>> servers) {
wpi::SmallVector<std::pair<std::string, int>, 16> servers_copy;
for (const auto& server : servers) {
servers_copy.emplace_back(std::string{wpi::trim(server.first)},
static_cast<int>(server.second));
}
SetConnector([=]() -> std::unique_ptr<wpi::NetworkStream> {
wpi::SmallVector<std::pair<const char*, int>, 16> servers_copy2;
for (const auto& server : servers_copy) {
servers_copy2.emplace_back(server.first.c_str(), server.second);
}
return wpi::TCPConnector::connect_parallel(servers_copy2, m_logger, 1);
});
}
void Dispatcher::SetServerTeam(unsigned int team, unsigned int port) {
std::pair<std::string_view, unsigned int> servers[5];
// 10.te.am.2
auto fixed = fmt::format("10.{}.{}.2", static_cast<int>(team / 100),
static_cast<int>(team % 100));
servers[0] = {fixed, port};
// 172.22.11.2
servers[1] = {"172.22.11.2", port};
// roboRIO-<team>-FRC.local
auto mdns = fmt::format("roboRIO-{}-FRC.local", team);
servers[2] = {mdns, port};
// roboRIO-<team>-FRC.lan
auto mdns_lan = fmt::format("roboRIO-{}-FRC.lan", team);
servers[3] = {mdns_lan, port};
// roboRIO-<team>-FRC.frc-field.local
auto field_local = fmt::format("roboRIO-{}-FRC.frc-field.local", team);
servers[4] = {field_local, port};
SetServer(servers);
}
void Dispatcher::SetServerOverride(const char* server_name, unsigned int port) {
std::string server_name_copy(wpi::trim(server_name));
SetConnectorOverride([=]() -> std::unique_ptr<wpi::NetworkStream> {
return wpi::TCPConnector::connect(server_name_copy.c_str(),
static_cast<int>(port), m_logger, 1);
});
}
void Dispatcher::ClearServerOverride() {
ClearConnectorOverride();
}
DispatcherBase::DispatcherBase(IStorage& storage, IConnectionNotifier& notifier,
wpi::Logger& logger)
: m_storage(storage), m_notifier(notifier), m_logger(logger) {
m_active = false;
m_update_rate = 100;
}
DispatcherBase::~DispatcherBase() {
Stop();
{
std::scoped_lock lock(m_user_mutex);
for (auto&& datalog : m_dataloggers) {
m_notifier.Remove(datalog.notifier);
}
}
}
unsigned int DispatcherBase::GetNetworkMode() const {
return m_networkMode;
}
void DispatcherBase::StartLocal() {
{
std::scoped_lock lock(m_user_mutex);
if (m_active) {
return;
}
m_active = true;
}
m_networkMode = NT_NET_MODE_LOCAL;
m_storage.SetDispatcher(this, false);
}
void DispatcherBase::StartServer(
std::string_view persist_filename,
std::unique_ptr<wpi::NetworkAcceptor> acceptor) {
{
std::scoped_lock lock(m_user_mutex);
if (m_active) {
return;
}
m_active = true;
}
m_networkMode = NT_NET_MODE_SERVER | NT_NET_MODE_STARTING;
m_persist_filename = persist_filename;
m_server_acceptor = std::move(acceptor);
// Load persistent file. Ignore errors, but pass along warnings.
if (!persist_filename.empty()) {
bool first = true;
m_storage.LoadPersistent(
persist_filename, [&](size_t line, const char* msg) {
if (first) {
first = false;
WARNING("When reading initial persistent values from '{}':",
persist_filename);
}
WARNING("{}:{}: {}", persist_filename, line, msg);
});
}
m_storage.SetDispatcher(this, true);
m_dispatch_thread = std::thread(&Dispatcher::DispatchThreadMain, this);
m_clientserver_thread = std::thread(&Dispatcher::ServerThreadMain, this);
}
void DispatcherBase::StartClient() {
{
std::scoped_lock lock(m_user_mutex);
if (m_active) {
return;
}
m_active = true;
}
m_networkMode = NT_NET_MODE_CLIENT | NT_NET_MODE_STARTING;
m_storage.SetDispatcher(this, false);
m_dispatch_thread = std::thread(&Dispatcher::DispatchThreadMain, this);
m_clientserver_thread = std::thread(&Dispatcher::ClientThreadMain, this);
}
void DispatcherBase::Stop() {
m_active = false;
// wake up dispatch thread with a flush
m_flush_cv.notify_one();
// wake up client thread with a reconnect
{
std::scoped_lock lock(m_user_mutex);
m_client_connector = nullptr;
}
ClientReconnect();
// wake up server thread by shutting down the socket
if (m_server_acceptor) {
m_server_acceptor->shutdown();
}
// join threads, with timeout
if (m_dispatch_thread.joinable()) {
m_dispatch_thread.join();
}
if (m_clientserver_thread.joinable()) {
m_clientserver_thread.join();
}
std::vector<std::shared_ptr<INetworkConnection>> conns;
{
std::scoped_lock lock(m_user_mutex);
conns.swap(m_connections);
}
// close all connections
conns.resize(0);
}
void DispatcherBase::SetUpdateRate(double interval) {
// don't allow update rates faster than 5 ms or slower than 1 second
if (interval < 0.005) {
interval = 0.005;
} else if (interval > 1.0) {
interval = 1.0;
}
m_update_rate = static_cast<unsigned int>(interval * 1000);
}
void DispatcherBase::SetIdentity(std::string_view name) {
std::scoped_lock lock(m_user_mutex);
m_identity = name;
}
void DispatcherBase::Flush() {
auto now = wpi::Now();
{
std::scoped_lock lock(m_flush_mutex);
// don't allow flushes more often than every 5 ms
if ((now - m_last_flush) < 5000) {
return;
}
m_last_flush = now;
m_do_flush = true;
}
m_flush_cv.notify_one();
}
std::vector<ConnectionInfo> DispatcherBase::GetConnections() const {
std::vector<ConnectionInfo> conns;
if (!m_active) {
return conns;
}
std::scoped_lock lock(m_user_mutex);
for (auto& conn : m_connections) {
if (conn->state() != NetworkConnection::kActive) {
continue;
}
conns.emplace_back(conn->info());
}
return conns;
}
bool DispatcherBase::IsConnected() const {
if (!m_active) {
return false;
}
if (m_networkMode == NT_NET_MODE_LOCAL) {
return true;
}
std::scoped_lock lock(m_user_mutex);
for (auto& conn : m_connections) {
if (conn->state() == NetworkConnection::kActive) {
return true;
}
}
return false;
}
unsigned int DispatcherBase::AddListener(
std::function<void(const ConnectionNotification& event)> callback,
bool immediate_notify) const {
std::scoped_lock lock(m_user_mutex);
unsigned int uid = m_notifier.Add(callback);
// perform immediate notifications
if (immediate_notify) {
for (auto& conn : m_connections) {
if (conn->state() != NetworkConnection::kActive) {
continue;
}
m_notifier.NotifyConnection(true, conn->info(), uid);
}
}
return uid;
}
unsigned int DispatcherBase::AddPolledListener(unsigned int poller_uid,
bool immediate_notify) const {
std::scoped_lock lock(m_user_mutex);
unsigned int uid = m_notifier.AddPolled(poller_uid);
// perform immediate notifications
if (immediate_notify) {
for (auto& conn : m_connections) {
if (conn->state() != NetworkConnection::kActive) {
continue;
}
m_notifier.NotifyConnection(true, conn->info(), uid);
}
}
return uid;
}
unsigned int DispatcherBase::StartDataLog(wpi::log::DataLog& log,
std::string_view name) {
std::scoped_lock lock(m_user_mutex);
auto now = nt::Now();
unsigned int uid = m_dataloggers.emplace_back(log, name, now);
m_dataloggers[uid].notifier =
m_notifier.Add([this, uid](const ConnectionNotification& n) {
std::scoped_lock lock(m_user_mutex);
if (uid < m_dataloggers.size() && m_dataloggers[uid].entry) {
m_dataloggers[uid].entry.Append(ConnInfoToJson(n.connected, n.conn),
nt::Now());
}
});
for (auto& conn : m_connections) {
if (conn->state() != NetworkConnection::kActive) {
continue;
}
m_dataloggers[uid].entry.Append(ConnInfoToJson(true, conn->info()), now);
}
return uid;
}
void DispatcherBase::StopDataLog(unsigned int logger) {
std::scoped_lock lock(m_user_mutex);
m_notifier.Remove(m_dataloggers.erase(logger).notifier);
}
void DispatcherBase::SetConnector(Connector connector) {
std::scoped_lock lock(m_user_mutex);
m_client_connector = std::move(connector);
}
void DispatcherBase::SetConnectorOverride(Connector connector) {
std::scoped_lock lock(m_user_mutex);
m_client_connector_override = std::move(connector);
}
void DispatcherBase::ClearConnectorOverride() {
std::scoped_lock lock(m_user_mutex);
m_client_connector_override = nullptr;
}
void DispatcherBase::DispatchThreadMain() {
auto timeout_time = std::chrono::steady_clock::now();
static const auto save_delta_time = std::chrono::seconds(1);
auto next_save_time = timeout_time + save_delta_time;
int count = 0;
while (m_active) {
// handle loop taking too long
auto start = std::chrono::steady_clock::now();
if (start > timeout_time) {
timeout_time = start;
}
// wait for periodic or when flushed
timeout_time += std::chrono::milliseconds(m_update_rate);
std::unique_lock<wpi::mutex> flush_lock(m_flush_mutex);
m_flush_cv.wait_until(flush_lock, timeout_time,
[&] { return !m_active || m_do_flush; });
m_do_flush = false;
flush_lock.unlock();
if (!m_active) {
break; // in case we were woken up to terminate
}
// perform periodic persistent save
if ((m_networkMode & NT_NET_MODE_SERVER) != 0 &&
!m_persist_filename.empty() && start > next_save_time) {
next_save_time += save_delta_time;
// handle loop taking too long
if (start > next_save_time) {
next_save_time = start + save_delta_time;
}
const char* err = m_storage.SavePersistent(m_persist_filename, true);
if (err) {
WARNING("periodic persistent save: {}", err);
}
}
{
std::scoped_lock user_lock(m_user_mutex);
bool reconnect = false;
if (++count > 10) {
DEBUG0("dispatch running {} connections", m_connections.size());
count = 0;
}
for (auto& conn : m_connections) {
// post outgoing messages if connection is active
// only send keep-alives on client
if (conn->state() == NetworkConnection::kActive) {
conn->PostOutgoing((m_networkMode & NT_NET_MODE_CLIENT) != 0);
}
// if client, reconnect if connection died
if ((m_networkMode & NT_NET_MODE_CLIENT) != 0 &&
conn->state() == NetworkConnection::kDead) {
reconnect = true;
}
}
// reconnect if we disconnected (and a reconnect is not in progress)
if (reconnect && !m_do_reconnect) {
m_do_reconnect = true;
m_reconnect_cv.notify_one();
}
}
}
}
void DispatcherBase::QueueOutgoing(std::shared_ptr<Message> msg,
INetworkConnection* only,
INetworkConnection* except) {
std::scoped_lock user_lock(m_user_mutex);
for (auto& conn : m_connections) {
if (conn.get() == except) {
continue;
}
if (only && conn.get() != only) {
continue;
}
auto state = conn->state();
if (state != NetworkConnection::kSynchronized &&
state != NetworkConnection::kActive) {
continue;
}
conn->QueueOutgoing(msg);
}
}
void DispatcherBase::ServerThreadMain() {
if (m_server_acceptor->start() != 0) {
m_active = false;
m_networkMode = NT_NET_MODE_SERVER | NT_NET_MODE_FAILURE;
return;
}
m_networkMode = NT_NET_MODE_SERVER;
while (m_active) {
auto stream = m_server_acceptor->accept();
if (!stream) {
m_active = false;
return;
}
if (!m_active) {
m_networkMode = NT_NET_MODE_NONE;
return;
}
DEBUG0("server: client connection from {} port {}", stream->getPeerIP(),
stream->getPeerPort());
// add to connections list
using namespace std::placeholders;
auto conn = std::make_shared<NetworkConnection>(
++m_connections_uid, std::move(stream), m_notifier, m_logger,
std::bind(&Dispatcher::ServerHandshake, this, _1, _2, _3), // NOLINT
std::bind(&IStorage::GetMessageEntryType, &m_storage, _1)); // NOLINT
conn->set_process_incoming(
std::bind(&IStorage::ProcessIncoming, &m_storage, _1, _2, // NOLINT
std::weak_ptr<NetworkConnection>(conn)));
{
std::scoped_lock lock(m_user_mutex);
// reuse dead connection slots
bool placed = false;
for (auto& c : m_connections) {
if (c->state() == NetworkConnection::kDead) {
c = conn;
placed = true;
break;
}
}
if (!placed) {
m_connections.emplace_back(conn);
}
conn->Start();
}
}
m_networkMode = NT_NET_MODE_NONE;
}
void DispatcherBase::ClientThreadMain() {
while (m_active) {
// sleep between retries
std::this_thread::sleep_for(std::chrono::milliseconds(250));
Connector connect;
// get next server to connect to
{
std::scoped_lock lock(m_user_mutex);
if (m_client_connector_override) {
connect = m_client_connector_override;
} else {
if (!m_client_connector) {
m_networkMode = NT_NET_MODE_CLIENT | NT_NET_MODE_FAILURE;
continue;
}
connect = m_client_connector;
}
}
// try to connect (with timeout)
DEBUG0("{}", "client trying to connect");
auto stream = connect();
if (!stream) {
m_networkMode = NT_NET_MODE_CLIENT | NT_NET_MODE_FAILURE;
continue; // keep retrying
}
DEBUG0("{}", "client connected");
m_networkMode = NT_NET_MODE_CLIENT;
std::unique_lock lock(m_user_mutex);
using namespace std::placeholders;
auto conn = std::make_shared<NetworkConnection>(
++m_connections_uid, std::move(stream), m_notifier, m_logger,
std::bind(&Dispatcher::ClientHandshake, this, _1, _2, _3), // NOLINT
std::bind(&IStorage::GetMessageEntryType, &m_storage, _1)); // NOLINT
conn->set_process_incoming(
std::bind(&IStorage::ProcessIncoming, &m_storage, _1, _2, // NOLINT
std::weak_ptr<NetworkConnection>(conn)));
m_connections.resize(0); // disconnect any current
m_connections.emplace_back(conn);
conn->set_proto_rev(m_reconnect_proto_rev);
conn->Start();
// reconnect the next time starting with latest protocol revision
m_reconnect_proto_rev = 0x0300;
// block until told to reconnect
m_do_reconnect = false;
m_reconnect_cv.wait(lock, [&] { return !m_active || m_do_reconnect; });
}
m_networkMode = NT_NET_MODE_NONE;
}
bool DispatcherBase::ClientHandshake(
NetworkConnection& conn, std::function<std::shared_ptr<Message>()> get_msg,
std::function<void(wpi::span<std::shared_ptr<Message>>)> send_msgs) {
// get identity
std::string self_id;
{
std::scoped_lock lock(m_user_mutex);
self_id = m_identity;
}
// send client hello
DEBUG0("{}", "client: sending hello");
auto msg = Message::ClientHello(self_id);
send_msgs(wpi::span(&msg, 1));
// wait for response
msg = get_msg();
if (!msg) {
// disconnected, retry
DEBUG0("{}", "client: server disconnected before first response");
return false;
}
if (msg->Is(Message::kProtoUnsup)) {
if (msg->id() == 0x0200) {
ClientReconnect(0x0200);
}
return false;
}
bool new_server = true;
if (conn.proto_rev() >= 0x0300) {
// should be server hello; if not, disconnect.
if (!msg->Is(Message::kServerHello)) {
return false;
}
conn.set_remote_id(msg->str());
if ((msg->flags() & 1) != 0) {
new_server = false;
}
// get the next message
msg = get_msg();
}
// receive initial assignments
std::vector<std::shared_ptr<Message>> incoming;
for (;;) {
if (!msg) {
// disconnected, retry
DEBUG0("{}", "client: server disconnected during initial entries");
return false;
}
DEBUG4("received init str={} id={} seq_num={}", msg->str(), msg->id(),
msg->seq_num_uid());
if (msg->Is(Message::kServerHelloDone)) {
break;
}
// shouldn't receive a keep alive, but handle gracefully
if (msg->Is(Message::kKeepAlive)) {
msg = get_msg();
continue;
}
if (!msg->Is(Message::kEntryAssign)) {
// unexpected message
DEBUG0(
"client: received message ({}) other than entry assignment during "
"initial handshake",
static_cast<int>(msg->type()));
return false;
}
incoming.emplace_back(std::move(msg));
// get the next message
msg = get_msg();
}
// generate outgoing assignments
NetworkConnection::Outgoing outgoing;
m_storage.ApplyInitialAssignments(conn, incoming, new_server, &outgoing);
if (conn.proto_rev() >= 0x0300) {
outgoing.emplace_back(Message::ClientHelloDone());
}
if (!outgoing.empty()) {
send_msgs(outgoing);
}
INFO("client: CONNECTED to server {} port {}", conn.stream().getPeerIP(),
conn.stream().getPeerPort());
return true;
}
bool DispatcherBase::ServerHandshake(
NetworkConnection& conn, std::function<std::shared_ptr<Message>()> get_msg,
std::function<void(wpi::span<std::shared_ptr<Message>>)> send_msgs) {
// Wait for the client to send us a hello.
auto msg = get_msg();
if (!msg) {
DEBUG0("{}", "server: client disconnected before sending hello");
return false;
}
if (!msg->Is(Message::kClientHello)) {
DEBUG0("{}", "server: client initial message was not client hello");
return false;
}
// Check that the client requested version is not too high.
unsigned int proto_rev = msg->id();
if (proto_rev > 0x0300) {
DEBUG0("{}", "server: client requested proto > 0x0300");
auto toSend = Message::ProtoUnsup();
send_msgs(wpi::span(&toSend, 1));
return false;
}
if (proto_rev >= 0x0300) {
conn.set_remote_id(msg->str());
}
// Set the proto version to the client requested version
DEBUG0("server: client protocol {}", proto_rev);
conn.set_proto_rev(proto_rev);
// Send initial set of assignments
NetworkConnection::Outgoing outgoing;
// Start with server hello. TODO: initial connection flag
if (proto_rev >= 0x0300) {
std::scoped_lock lock(m_user_mutex);
outgoing.emplace_back(Message::ServerHello(0u, m_identity));
}
// Get snapshot of initial assignments
m_storage.GetInitialAssignments(conn, &outgoing);
// Finish with server hello done
outgoing.emplace_back(Message::ServerHelloDone());
// Batch transmit
DEBUG0("{}", "server: sending initial assignments");
send_msgs(outgoing);
// In proto rev 3.0 and later, the handshake concludes with a client hello
// done message, so we can batch the assigns before marking the connection
// active. In pre-3.0, we need to just immediately mark it active and hand
// off control to the dispatcher to assign them as they arrive.
if (proto_rev >= 0x0300) {
// receive client initial assignments
std::vector<std::shared_ptr<Message>> incoming;
msg = get_msg();
for (;;) {
if (!msg) {
// disconnected, retry
DEBUG0("{}", "server: disconnected waiting for initial entries");
return false;
}
if (msg->Is(Message::kClientHelloDone)) {
break;
}
// shouldn't receive a keep alive, but handle gracefully
if (msg->Is(Message::kKeepAlive)) {
msg = get_msg();
continue;
}
if (!msg->Is(Message::kEntryAssign)) {
// unexpected message
DEBUG0(
"server: received message ({}) other than entry assignment during "
"initial handshake",
static_cast<int>(msg->type()));
return false;
}
incoming.push_back(msg);
// get the next message (blocks)
msg = get_msg();
}
for (auto& msg : incoming) {
m_storage.ProcessIncoming(msg, &conn, std::weak_ptr<NetworkConnection>());
}
}
INFO("server: client CONNECTED: {} port {}", conn.stream().getPeerIP(),
conn.stream().getPeerPort());
return true;
}
void DispatcherBase::ClientReconnect(unsigned int proto_rev) {
if ((m_networkMode & NT_NET_MODE_SERVER) != 0) {
return;
}
{
std::scoped_lock lock(m_user_mutex);
m_reconnect_proto_rev = proto_rev;
m_do_reconnect = true;
}
m_reconnect_cv.notify_one();
}

View File

@@ -1,168 +0,0 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source 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 NTCORE_DISPATCHER_H_
#define NTCORE_DISPATCHER_H_
#include <atomic>
#include <functional>
#include <memory>
#include <string>
#include <string_view>
#include <thread>
#include <utility>
#include <vector>
#include <wpi/DataLog.h>
#include <wpi/UidVector.h>
#include <wpi/condition_variable.h>
#include <wpi/mutex.h>
#include <wpi/span.h>
#include "IDispatcher.h"
#include "INetworkConnection.h"
namespace wpi {
class Logger;
class NetworkAcceptor;
class NetworkStream;
} // namespace wpi
namespace nt {
class IConnectionNotifier;
class IStorage;
class NetworkConnection;
class DispatcherBase : public IDispatcher {
friend class DispatcherTest;
public:
using Connector = std::function<std::unique_ptr<wpi::NetworkStream>()>;
DispatcherBase(IStorage& storage, IConnectionNotifier& notifier,
wpi::Logger& logger);
~DispatcherBase() override;
unsigned int GetNetworkMode() const;
void StartLocal();
void StartServer(std::string_view persist_filename,
std::unique_ptr<wpi::NetworkAcceptor> acceptor);
void StartClient();
void Stop();
void SetUpdateRate(double interval);
void SetIdentity(std::string_view name);
void Flush();
std::vector<ConnectionInfo> GetConnections() const;
bool IsConnected() const;
unsigned int AddListener(
std::function<void(const ConnectionNotification& event)> callback,
bool immediate_notify) const;
unsigned int AddPolledListener(unsigned int poller_uid,
bool immediate_notify) const;
unsigned int StartDataLog(wpi::log::DataLog& log, std::string_view name);
void StopDataLog(unsigned int logger);
void SetConnector(Connector connector);
void SetConnectorOverride(Connector connector);
void ClearConnectorOverride();
bool active() const { return m_active; }
DispatcherBase(const DispatcherBase&) = delete;
DispatcherBase& operator=(const DispatcherBase&) = delete;
private:
void DispatchThreadMain();
void ServerThreadMain();
void ClientThreadMain();
bool ClientHandshake(
NetworkConnection& conn,
std::function<std::shared_ptr<Message>()> get_msg,
std::function<void(wpi::span<std::shared_ptr<Message>>)> send_msgs);
bool ServerHandshake(
NetworkConnection& conn,
std::function<std::shared_ptr<Message>()> get_msg,
std::function<void(wpi::span<std::shared_ptr<Message>>)> send_msgs);
void ClientReconnect(unsigned int proto_rev = 0x0300);
void QueueOutgoing(std::shared_ptr<Message> msg, INetworkConnection* only,
INetworkConnection* except) override;
IStorage& m_storage;
IConnectionNotifier& m_notifier;
unsigned int m_networkMode = NT_NET_MODE_NONE;
std::string m_persist_filename;
std::thread m_dispatch_thread;
std::thread m_clientserver_thread;
std::unique_ptr<wpi::NetworkAcceptor> m_server_acceptor;
Connector m_client_connector_override;
Connector m_client_connector;
uint8_t m_connections_uid = 0;
// Mutex for user-accessible items
mutable wpi::mutex m_user_mutex;
std::vector<std::shared_ptr<INetworkConnection>> m_connections;
std::string m_identity;
std::atomic_bool m_active; // set to false to terminate threads
std::atomic_uint m_update_rate; // periodic dispatch update rate, in ms
// Condition variable for forced dispatch wakeup (flush)
wpi::mutex m_flush_mutex;
wpi::condition_variable m_flush_cv;
uint64_t m_last_flush = 0;
bool m_do_flush = false;
// Condition variable for client reconnect (uses user mutex)
wpi::condition_variable m_reconnect_cv;
unsigned int m_reconnect_proto_rev = 0x0300;
bool m_do_reconnect = true;
struct DataLogger {
DataLogger() = default;
DataLogger(wpi::log::DataLog& log, std::string_view name, int64_t time)
: entry{log, name,
"{\"schema\":\"NTConnectionInfo\",\"source\":\"NT\"}", "json",
time} {}
explicit operator bool() const { return static_cast<bool>(entry); }
wpi::log::StringLogEntry entry;
unsigned int notifier = 0;
};
wpi::UidVector<DataLogger, 4> m_dataloggers;
protected:
wpi::Logger& m_logger;
};
class Dispatcher : public DispatcherBase {
friend class DispatcherTest;
public:
Dispatcher(IStorage& storage, IConnectionNotifier& notifier,
wpi::Logger& logger)
: DispatcherBase(storage, notifier, logger) {}
void StartServer(std::string_view persist_filename,
const char* listen_address, unsigned int port);
void SetServer(const char* server_name, unsigned int port);
void SetServer(
wpi::span<const std::pair<std::string_view, unsigned int>> servers);
void SetServerTeam(unsigned int team, unsigned int port);
void SetServerOverride(const char* server_name, unsigned int port);
void ClearServerOverride();
};
} // namespace nt
#endif // NTCORE_DISPATCHER_H_

View File

@@ -1,177 +0,0 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#include "DsClient.h"
#include <wpi/SmallString.h>
#include <wpi/StringExtras.h>
#include <wpi/raw_ostream.h>
#include <wpinet/TCPConnector.h>
#include <wpinet/raw_socket_istream.h>
#include "Dispatcher.h"
#include "Log.h"
using namespace nt;
class DsClient::Thread : public wpi::SafeThread {
public:
Thread(Dispatcher& dispatcher, wpi::Logger& logger, unsigned int port)
: m_dispatcher(dispatcher), m_logger(logger), m_port(port) {}
void Main() override;
Dispatcher& m_dispatcher;
wpi::Logger& m_logger;
unsigned int m_port;
std::unique_ptr<wpi::NetworkStream> m_stream;
};
DsClient::DsClient(Dispatcher& dispatcher, wpi::Logger& logger)
: m_dispatcher(dispatcher), m_logger(logger) {}
void DsClient::Start(unsigned int port) {
auto thr = m_owner.GetThread();
if (!thr) {
m_owner.Start(m_dispatcher, m_logger, port);
} else {
thr->m_port = port;
}
}
void DsClient::Stop() {
{
// Close the stream so the read (if any) terminates.
auto thr = m_owner.GetThread();
if (thr) {
thr->m_active = false;
if (thr->m_stream) {
thr->m_stream->close();
}
}
}
m_owner.Stop();
}
void DsClient::Thread::Main() {
unsigned int oldip = 0;
wpi::Logger nolog; // to silence log messages from TCPConnector
while (m_active) {
// wait for periodic reconnect or termination
auto timeout_time =
std::chrono::steady_clock::now() + std::chrono::milliseconds(500);
unsigned int port;
{
std::unique_lock lock(m_mutex);
m_cond.wait_until(lock, timeout_time, [&] { return !m_active; });
port = m_port;
}
if (!m_active) {
goto done;
}
// Try to connect to DS on the local machine
m_stream = wpi::TCPConnector::connect("127.0.0.1", 1742, nolog, 1);
if (!m_active) {
goto done;
}
if (!m_stream) {
continue;
}
DEBUG3("{}", "connected to DS");
wpi::raw_socket_istream is(*m_stream);
while (m_active && !is.has_error()) {
// Read JSON "{...}". This is very limited, does not handle quoted "}" or
// nested {}, but is sufficient for this purpose.
wpi::SmallString<128> json;
char ch;
// Throw away characters until {
do {
is.read(ch);
if (is.has_error()) {
break;
}
if (!m_active) {
goto done;
}
} while (ch != '{');
json += '{';
if (is.has_error()) {
m_stream = nullptr;
break;
}
// Read characters until }
do {
is.read(ch);
if (is.has_error()) {
break;
}
if (!m_active) {
goto done;
}
json += ch;
} while (ch != '}');
if (is.has_error()) {
m_stream = nullptr;
break;
}
DEBUG3("json={}", json);
// Look for "robotIP":12345, and get 12345 portion
size_t pos = json.find("\"robotIP\"");
if (pos == std::string_view::npos) {
continue; // could not find?
}
pos += 9;
pos = json.find(':', pos);
if (pos == std::string_view::npos) {
continue; // could not find?
}
size_t endpos = json.find_first_not_of("0123456789", pos + 1);
DEBUG3("found robotIP={}", wpi::slice(json, pos + 1, endpos));
// Parse into number
unsigned int ip = 0;
if (auto v = wpi::parse_integer<unsigned int>(
wpi::slice(json, pos + 1, endpos), 10)) {
ip = v.value();
} else {
continue; // error
}
// If zero, clear the server override
if (ip == 0) {
m_dispatcher.ClearServerOverride();
oldip = 0;
continue;
}
// If unchanged, don't reconnect
if (ip == oldip) {
continue;
}
oldip = ip;
// Convert number into dotted quad
auto newip = fmt::format("{}.{}.{}.{}", (ip >> 24) & 0xff,
(ip >> 16) & 0xff, (ip >> 8) & 0xff, ip & 0xff);
INFO("client: DS overriding server IP to {}", newip);
m_dispatcher.SetServerOverride(newip.c_str(), port);
}
// We disconnected from the DS, clear the server override
m_dispatcher.ClearServerOverride();
oldip = 0;
}
done:
m_dispatcher.ClearServerOverride();
}

View File

@@ -1,33 +0,0 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#ifndef NTCORE_DSCLIENT_H_
#define NTCORE_DSCLIENT_H_
#include <wpi/SafeThread.h>
#include "Log.h"
namespace nt {
class Dispatcher;
class DsClient {
public:
DsClient(Dispatcher& dispatcher, wpi::Logger& logger);
~DsClient() = default;
void Start(unsigned int port);
void Stop();
private:
class Thread;
wpi::SafeThreadOwner<Thread> m_owner;
Dispatcher& m_dispatcher;
wpi::Logger& m_logger;
};
} // namespace nt
#endif // NTCORE_DSCLIENT_H_

View File

@@ -1,109 +0,0 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#include "EntryNotifier.h"
#include <wpi/StringExtras.h>
#include "Log.h"
using namespace nt;
EntryNotifier::EntryNotifier(int inst, wpi::Logger& logger)
: m_inst(inst), m_logger(logger) {
m_local_notifiers = false;
}
void EntryNotifier::Start() {
DoStart(m_inst);
}
bool EntryNotifier::local_notifiers() const {
return m_local_notifiers;
}
bool impl::EntryNotifierThread::Matches(const EntryListenerData& listener,
const EntryNotification& data) {
if (!data.value) {
return false;
}
// Flags must be within requested flag set for this listener.
// Because assign messages can result in both a value and flags update,
// we handle that case specially.
unsigned int listen_flags =
listener.flags & ~(NT_NOTIFY_IMMEDIATE | NT_NOTIFY_LOCAL);
unsigned int flags = data.flags & ~(NT_NOTIFY_IMMEDIATE | NT_NOTIFY_LOCAL);
unsigned int assign_both = NT_NOTIFY_UPDATE | NT_NOTIFY_FLAGS;
if ((flags & assign_both) == assign_both) {
if ((listen_flags & assign_both) == 0) {
return false;
}
listen_flags &= ~assign_both;
flags &= ~assign_both;
}
if ((flags & ~listen_flags) != 0) {
return false;
}
// must match local id or prefix
if (listener.entry != 0 && data.entry != listener.entry) {
return false;
}
if (listener.entry == 0 && !wpi::starts_with(data.name, listener.prefix)) {
return false;
}
return true;
}
unsigned int EntryNotifier::Add(
std::function<void(const EntryNotification& event)> callback,
std::string_view prefix, unsigned int flags) {
if ((flags & NT_NOTIFY_LOCAL) != 0) {
m_local_notifiers = true;
}
return DoAdd(callback, prefix, flags);
}
unsigned int EntryNotifier::Add(
std::function<void(const EntryNotification& event)> callback,
unsigned int local_id, unsigned int flags) {
if ((flags & NT_NOTIFY_LOCAL) != 0) {
m_local_notifiers = true;
}
return DoAdd(callback, Handle(m_inst, local_id, Handle::kEntry), flags);
}
unsigned int EntryNotifier::AddPolled(unsigned int poller_uid,
std::string_view prefix,
unsigned int flags) {
if ((flags & NT_NOTIFY_LOCAL) != 0) {
m_local_notifiers = true;
}
return DoAdd(poller_uid, prefix, flags);
}
unsigned int EntryNotifier::AddPolled(unsigned int poller_uid,
unsigned int local_id,
unsigned int flags) {
if ((flags & NT_NOTIFY_LOCAL) != 0) {
m_local_notifiers = true;
}
return DoAdd(poller_uid, Handle(m_inst, local_id, Handle::kEntry), flags);
}
void EntryNotifier::NotifyEntry(unsigned int local_id, std::string_view name,
std::shared_ptr<Value> value,
unsigned int flags,
unsigned int only_listener) {
// optimization: don't generate needless local queue entries if we have
// no local listeners (as this is a common case on the server side)
if ((flags & NT_NOTIFY_LOCAL) != 0 && !m_local_notifiers) {
return;
}
DEBUG0("notifying '{}' (local={}), flags={}", name, local_id, flags);
Send(only_listener, 0, Handle(m_inst, local_id, Handle::kEntry).handle(),
name, value, flags);
}

View File

@@ -1,112 +0,0 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source 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 NTCORE_ENTRYNOTIFIER_H_
#define NTCORE_ENTRYNOTIFIER_H_
#include <atomic>
#include <memory>
#include <string>
#include <string_view>
#include <utility>
#include <wpi/CallbackManager.h>
#include "Handle.h"
#include "IEntryNotifier.h"
#include "ntcore_cpp.h"
namespace wpi {
class Logger;
} // namespace wpi
namespace nt {
namespace impl {
struct EntryListenerData
: public wpi::CallbackListenerData<
std::function<void(const EntryNotification& event)>> {
EntryListenerData() = default;
EntryListenerData(
std::function<void(const EntryNotification& event)> callback_,
std::string_view prefix_, unsigned int flags_)
: CallbackListenerData(callback_), prefix(prefix_), flags(flags_) {}
EntryListenerData(
std::function<void(const EntryNotification& event)> callback_,
NT_Entry entry_, unsigned int flags_)
: CallbackListenerData(callback_), entry(entry_), flags(flags_) {}
EntryListenerData(unsigned int poller_uid_, std::string_view prefix_,
unsigned int flags_)
: CallbackListenerData(poller_uid_), prefix(prefix_), flags(flags_) {}
EntryListenerData(unsigned int poller_uid_, NT_Entry entry_,
unsigned int flags_)
: CallbackListenerData(poller_uid_), entry(entry_), flags(flags_) {}
std::string prefix;
NT_Entry entry = 0;
unsigned int flags;
};
class EntryNotifierThread
: public wpi::CallbackThread<EntryNotifierThread, EntryNotification,
EntryListenerData> {
public:
EntryNotifierThread(std::function<void()> on_start,
std::function<void()> on_exit, int inst)
: CallbackThread(std::move(on_start), std::move(on_exit)), m_inst(inst) {}
bool Matches(const EntryListenerData& listener,
const EntryNotification& data);
void SetListener(EntryNotification* data, unsigned int listener_uid) {
data->listener =
Handle(m_inst, listener_uid, Handle::kEntryListener).handle();
}
void DoCallback(std::function<void(const EntryNotification& event)> callback,
const EntryNotification& data) {
callback(data);
}
int m_inst;
};
} // namespace impl
class EntryNotifier
: public IEntryNotifier,
public wpi::CallbackManager<EntryNotifier, impl::EntryNotifierThread> {
friend class EntryNotifierTest;
friend class wpi::CallbackManager<EntryNotifier, impl::EntryNotifierThread>;
public:
explicit EntryNotifier(int inst, wpi::Logger& logger);
void Start();
bool local_notifiers() const override;
unsigned int Add(std::function<void(const EntryNotification& event)> callback,
std::string_view prefix, unsigned int flags) override;
unsigned int Add(std::function<void(const EntryNotification& event)> callback,
unsigned int local_id, unsigned int flags) override;
unsigned int AddPolled(unsigned int poller_uid, std::string_view prefix,
unsigned int flags) override;
unsigned int AddPolled(unsigned int poller_uid, unsigned int local_id,
unsigned int flags) override;
void NotifyEntry(unsigned int local_id, std::string_view name,
std::shared_ptr<Value> value, unsigned int flags,
unsigned int only_listener = UINT_MAX) override;
private:
int m_inst;
wpi::Logger& m_logger;
std::atomic_bool m_local_notifiers;
};
} // namespace nt
#endif // NTCORE_ENTRYNOTIFIER_H_

View File

@@ -2,8 +2,7 @@
// Open Source 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 NTCORE_HANDLE_H_
#define NTCORE_HANDLE_H_
#pragma once
#include <wpi/Synchronization.h>
@@ -27,11 +26,19 @@ class Handle {
kInstance,
kLogger,
kLoggerPoller,
kRpcCall,
kRpcCallPoller,
kDataLogger,
kConnectionDataLogger
kConnectionDataLogger,
kMultiSubscriber,
kTopic,
kTopicListener,
kTopicListenerPoller,
kSubscriber,
kPublisher,
kValueListener,
kValueListenerPoller,
kTypeMax
};
static_assert(kTypeMax <= wpi::kHandleTypeHALBase);
enum { kIndexMax = 0xfffff };
explicit Handle(NT_Handle handle) : m_handle(handle) {}
@@ -48,7 +55,9 @@ class Handle {
(index & 0xfffff);
}
int GetIndex() const { return static_cast<int>(m_handle) & 0xfffff; }
unsigned int GetIndex() const {
return static_cast<unsigned int>(m_handle) & 0xfffff;
}
Type GetType() const {
return static_cast<Type>((static_cast<int>(m_handle) >> 24) & 0x7f);
}
@@ -62,5 +71,3 @@ class Handle {
};
} // namespace nt
#endif // NTCORE_HANDLE_H_

View File

@@ -0,0 +1,54 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source 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 <utility>
#include <wpi/UidVector.h>
#include "Handle.h"
namespace nt {
// Utility wrapper class for our UidVectors
template <typename T, size_t Size>
class HandleMap : public wpi::UidVector<std::unique_ptr<T>, Size> {
public:
template <typename... Args>
T* Add(int inst, Args&&... args) {
auto i = this->emplace_back();
auto& it = (*this)[i];
it = std::make_unique<T>(Handle(inst, i, T::kType),
std::forward<Args>(args)...);
return it.get();
}
std::unique_ptr<T> Remove(NT_Handle handle) {
Handle h{handle};
if (!h.IsType(T::kType)) {
return {};
}
unsigned int i = h.GetIndex();
if (i >= this->size()) {
return {};
}
return this->erase(i);
}
T* Get(NT_Handle handle) {
Handle h{handle};
if (!h.IsType(T::kType)) {
return {};
}
unsigned int i = h.GetIndex();
if (i >= this->size()) {
return {};
}
return (*this)[i].get();
}
};
} // namespace nt

View File

@@ -0,0 +1,24 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source 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 <vector>
#include "ntcore_cpp.h"
namespace nt {
class IConnectionList {
public:
virtual ~IConnectionList() = default;
virtual int AddConnection(const ConnectionInfo& info) = 0;
virtual void RemoveConnection(int handle) = 0;
virtual void ClearConnections() = 0;
virtual std::vector<ConnectionInfo> GetConnections() const = 0;
virtual bool IsConnected() const = 0;
};
} // namespace nt

View File

@@ -1,30 +0,0 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#ifndef NTCORE_ICONNECTIONNOTIFIER_H_
#define NTCORE_ICONNECTIONNOTIFIER_H_
#include <climits>
#include "ntcore_cpp.h"
namespace nt {
class IConnectionNotifier {
public:
IConnectionNotifier() = default;
IConnectionNotifier(const IConnectionNotifier&) = delete;
IConnectionNotifier& operator=(const IConnectionNotifier&) = delete;
virtual ~IConnectionNotifier() = default;
virtual unsigned int Add(
std::function<void(const ConnectionNotification& event)> callback) = 0;
virtual unsigned int AddPolled(unsigned int poller_uid) = 0;
virtual void Remove(unsigned int uid) = 0;
virtual void NotifyConnection(bool connected, const ConnectionInfo& conn_info,
unsigned int only_listener = UINT_MAX) = 0;
};
} // namespace nt
#endif // NTCORE_ICONNECTIONNOTIFIER_H_

View File

@@ -1,31 +0,0 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#ifndef NTCORE_IDISPATCHER_H_
#define NTCORE_IDISPATCHER_H_
#include <memory>
#include "Message.h"
namespace nt {
class INetworkConnection;
// Interface for generation of outgoing messages to break a dependency loop
// between Storage and Dispatcher.
class IDispatcher {
public:
IDispatcher() = default;
IDispatcher(const IDispatcher&) = delete;
IDispatcher& operator=(const IDispatcher&) = delete;
virtual ~IDispatcher() = default;
virtual void QueueOutgoing(std::shared_ptr<Message> msg,
INetworkConnection* only,
INetworkConnection* except) = 0;
};
} // namespace nt
#endif // NTCORE_IDISPATCHER_H_

View File

@@ -1,43 +0,0 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source 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 NTCORE_IENTRYNOTIFIER_H_
#define NTCORE_IENTRYNOTIFIER_H_
#include <climits>
#include <memory>
#include <string_view>
#include "ntcore_cpp.h"
namespace nt {
class IEntryNotifier {
public:
IEntryNotifier() = default;
IEntryNotifier(const IEntryNotifier&) = delete;
IEntryNotifier& operator=(const IEntryNotifier&) = delete;
virtual ~IEntryNotifier() = default;
virtual bool local_notifiers() const = 0;
virtual unsigned int Add(
std::function<void(const EntryNotification& event)> callback,
std::string_view prefix, unsigned int flags) = 0;
virtual unsigned int Add(
std::function<void(const EntryNotification& event)> callback,
unsigned int local_id, unsigned int flags) = 0;
virtual unsigned int AddPolled(unsigned int poller_uid,
std::string_view prefix,
unsigned int flags) = 0;
virtual unsigned int AddPolled(unsigned int poller_uid, unsigned int local_id,
unsigned int flags) = 0;
virtual void NotifyEntry(unsigned int local_id, std::string_view name,
std::shared_ptr<Value> value, unsigned int flags,
unsigned int only_listener = UINT_MAX) = 0;
};
} // namespace nt
#endif // NTCORE_IENTRYNOTIFIER_H_

View File

@@ -0,0 +1,32 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source 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 <string>
#include <string_view>
#include <utility>
#include <vector>
#include <wpi/span.h>
#include "ntcore_cpp.h"
namespace nt {
class INetworkClient {
public:
virtual ~INetworkClient() = default;
virtual void SetServers(
wpi::span<const std::pair<std::string, unsigned int>> servers) = 0;
virtual void StartDSClient(unsigned int port) = 0;
virtual void StopDSClient() = 0;
virtual void FlushLocal() = 0;
virtual void Flush() = 0;
};
} // namespace nt

View File

@@ -1,38 +0,0 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source 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 NTCORE_INETWORKCONNECTION_H_
#define NTCORE_INETWORKCONNECTION_H_
#include <memory>
#include "Message.h"
#include "ntcore_cpp.h"
namespace nt {
class INetworkConnection {
public:
enum State { kCreated, kInit, kHandshake, kSynchronized, kActive, kDead };
INetworkConnection() = default;
INetworkConnection(const INetworkConnection&) = delete;
INetworkConnection& operator=(const INetworkConnection&) = delete;
virtual ~INetworkConnection() = default;
virtual ConnectionInfo info() const = 0;
virtual void QueueOutgoing(std::shared_ptr<Message> msg) = 0;
virtual void PostOutgoing(bool keep_alive) = 0;
virtual unsigned int proto_rev() const = 0;
virtual void set_proto_rev(unsigned int proto_rev) = 0;
virtual State state() const = 0;
virtual void set_state(State state) = 0;
};
} // namespace nt
#endif // NTCORE_INETWORKCONNECTION_H_

View File

@@ -1,36 +0,0 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source 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 NTCORE_IRPCSERVER_H_
#define NTCORE_IRPCSERVER_H_
#include <memory>
#include <string_view>
#include "Message.h"
#include "ntcore_cpp.h"
namespace nt {
class IRpcServer {
public:
using SendResponseFunc = std::function<void(std::string_view result)>;
IRpcServer() = default;
IRpcServer(const IRpcServer&) = delete;
IRpcServer& operator=(const IRpcServer&) = delete;
virtual ~IRpcServer() = default;
virtual void RemoveRpc(unsigned int rpc_uid) = 0;
virtual void ProcessRpc(unsigned int local_id, unsigned int call_uid,
std::string_view name, std::string_view params,
const ConnectionInfo& conn,
SendResponseFunc send_response,
unsigned int rpc_uid) = 0;
};
} // namespace nt
#endif // NTCORE_IRPCSERVER_H_

View File

@@ -1,62 +0,0 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#ifndef NTCORE_ISTORAGE_H_
#define NTCORE_ISTORAGE_H_
#include <functional>
#include <memory>
#include <string_view>
#include <vector>
#include <wpi/span.h>
#include "Message.h"
#include "ntcore_cpp.h"
namespace nt {
class IDispatcher;
class INetworkConnection;
class IStorage {
public:
IStorage() = default;
IStorage(const IStorage&) = delete;
IStorage& operator=(const IStorage&) = delete;
virtual ~IStorage() = default;
// Accessors required by Dispatcher. An interface is used for
// generation of outgoing messages to break a dependency loop between
// Storage and Dispatcher.
virtual void SetDispatcher(IDispatcher* dispatcher, bool server) = 0;
virtual void ClearDispatcher() = 0;
// Required for wire protocol 2.0 to get the entry type of an entry when
// receiving entry updates (because the length/type is not provided in the
// message itself). Not used in wire protocol 3.0.
virtual NT_Type GetMessageEntryType(unsigned int id) const = 0;
virtual void ProcessIncoming(std::shared_ptr<Message> msg,
INetworkConnection* conn,
std::weak_ptr<INetworkConnection> conn_weak) = 0;
virtual void GetInitialAssignments(
INetworkConnection& conn,
std::vector<std::shared_ptr<Message>>* msgs) = 0;
virtual void ApplyInitialAssignments(
INetworkConnection& conn, wpi::span<std::shared_ptr<Message>> msgs,
bool new_server, std::vector<std::shared_ptr<Message>>* out_msgs) = 0;
// Filename-based save/load functions. Used both by periodic saves and
// accessible directly via the user API.
virtual const char* SavePersistent(std::string_view filename,
bool periodic) const = 0;
virtual const char* LoadPersistent(
std::string_view filename,
std::function<void(size_t line, const char* msg)> warn) = 0;
};
} // namespace nt
#endif // NTCORE_ISTORAGE_H_

View File

@@ -16,19 +16,12 @@ InstanceImpl::InstanceImpl(int inst)
: logger_impl(inst),
logger(
std::bind(&LoggerImpl::Log, &logger_impl, _1, _2, _3, _4)), // NOLINT
connection_notifier(inst),
entry_notifier(inst, logger),
rpc_server(inst, logger),
storage(entry_notifier, rpc_server, logger),
dispatcher(storage, connection_notifier, logger),
ds_client(dispatcher, logger) {
connectionList(inst),
localStorage(inst, logger),
m_inst{inst} {
logger.set_min_level(logger_impl.GetMinLevel());
}
InstanceImpl::~InstanceImpl() {
logger.SetLogger(nullptr);
}
InstanceImpl* InstanceImpl::GetDefault() {
return Get(GetDefaultIndex());
}
@@ -83,7 +76,90 @@ void InstanceImpl::Destroy(int inst) {
return;
}
InstanceImpl* ptr = nullptr;
s_instances[inst].exchange(ptr);
delete ptr;
delete s_instances[inst].exchange(nullptr);
}
void InstanceImpl::StartLocal() {
std::scoped_lock lock{m_mutex};
if (networkMode != NT_NET_MODE_NONE) {
return;
}
networkMode = NT_NET_MODE_LOCAL;
}
void InstanceImpl::StopLocal() {
std::scoped_lock lock{m_mutex};
if ((networkMode & NT_NET_MODE_LOCAL) == 0) {
return;
}
networkMode = NT_NET_MODE_NONE;
}
void InstanceImpl::StartServer(std::string_view persistFilename,
std::string_view listenAddress,
unsigned int port3, unsigned int port4) {
std::scoped_lock lock{m_mutex};
if (networkMode != NT_NET_MODE_NONE) {
return;
}
m_networkServer = std::make_shared<NetworkServer>(
persistFilename, listenAddress, port3, port4, localStorage,
connectionList, logger, [this] {
std::scoped_lock lock{m_mutex};
networkMode &= ~NT_NET_MODE_STARTING;
});
networkMode = NT_NET_MODE_SERVER | NT_NET_MODE_STARTING;
}
void InstanceImpl::StopServer() {
std::scoped_lock lock{m_mutex};
if ((networkMode & NT_NET_MODE_SERVER) == 0) {
return;
}
m_networkServer.reset();
networkMode = NT_NET_MODE_NONE;
}
void InstanceImpl::StartClient3() {
std::scoped_lock lock{m_mutex};
if (networkMode != NT_NET_MODE_NONE) {
return;
}
m_networkClient = std::make_shared<NetworkClient3>(
m_inst, m_identity, localStorage, connectionList, logger);
networkMode = NT_NET_MODE_CLIENT3;
}
void InstanceImpl::StartClient4() {
std::scoped_lock lock{m_mutex};
if (networkMode != NT_NET_MODE_NONE) {
return;
}
m_networkClient = std::make_shared<NetworkClient>(
m_inst, m_identity, localStorage, connectionList, logger);
networkMode = NT_NET_MODE_CLIENT4;
}
void InstanceImpl::StopClient() {
std::scoped_lock lock{m_mutex};
if ((networkMode & (NT_NET_MODE_CLIENT3 | NT_NET_MODE_CLIENT4)) == 0) {
return;
}
m_networkClient.reset();
networkMode = NT_NET_MODE_NONE;
}
void InstanceImpl::SetIdentity(std::string_view identity) {
std::scoped_lock lock{m_mutex};
m_identity = identity;
}
std::shared_ptr<NetworkServer> InstanceImpl::GetServer() {
std::scoped_lock lock{m_mutex};
return m_networkServer;
}
std::shared_ptr<INetworkClient> InstanceImpl::GetClient() {
std::scoped_lock lock{m_mutex};
return m_networkClient;
}

View File

@@ -2,45 +2,62 @@
// Open Source 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 NTCORE_INSTANCEIMPL_H_
#define NTCORE_INSTANCEIMPL_H_
#pragma once
#include <atomic>
#include <memory>
#include <string>
#include <string_view>
#include <vector>
#include <wpi/mutex.h>
#include "ConnectionNotifier.h"
#include "Dispatcher.h"
#include "DsClient.h"
#include "EntryNotifier.h"
#include "ConnectionList.h"
#include "Handle.h"
#include "LocalStorage.h"
#include "Log.h"
#include "LoggerImpl.h"
#include "RpcServer.h"
#include "Storage.h"
#include "NetworkClient.h"
#include "NetworkServer.h"
namespace nt {
class InstanceImpl {
public:
explicit InstanceImpl(int inst);
~InstanceImpl();
// Instance repository
static InstanceImpl* GetDefault();
static InstanceImpl* Get(int inst);
static InstanceImpl* GetHandle(NT_Handle handle) {
return Get(Handle{handle}.GetInst());
}
static InstanceImpl* GetTyped(NT_Handle handle, Handle::Type type) {
return Get(Handle{handle}.GetTypedInst(type));
}
static int GetDefaultIndex();
static int Alloc();
static void Destroy(int inst);
void StartLocal();
void StopLocal();
void StartServer(std::string_view persistFilename,
std::string_view listenAddress, unsigned int port3,
unsigned int port4);
void StopServer();
void StartClient3();
void StartClient4();
void StopClient();
void SetIdentity(std::string_view identity);
std::shared_ptr<NetworkServer> GetServer();
std::shared_ptr<INetworkClient> GetClient();
LoggerImpl logger_impl;
wpi::Logger logger;
ConnectionNotifier connection_notifier;
EntryNotifier entry_notifier;
RpcServer rpc_server;
Storage storage;
Dispatcher dispatcher;
DsClient ds_client;
ConnectionList connectionList;
LocalStorage localStorage;
std::atomic<int> networkMode{NT_NET_MODE_NONE};
private:
static int AllocImpl();
@@ -49,8 +66,12 @@ class InstanceImpl {
static constexpr int kNumInstances = 16;
static std::atomic<InstanceImpl*> s_instances[kNumInstances];
static wpi::mutex s_mutex;
wpi::mutex m_mutex;
std::string m_identity;
std::shared_ptr<NetworkServer> m_networkServer;
std::shared_ptr<INetworkClient> m_networkClient;
int m_inst;
};
} // namespace nt
#endif // NTCORE_INSTANCEIMPL_H_

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,250 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#pragma once
#include <stdint.h>
#include <functional>
#include <memory>
#include <string>
#include <string_view>
#include <utility>
#include <vector>
#include <wpi/mutex.h>
#include <wpi/span.h>
#include "net/NetworkInterface.h"
#include "ntcore_cpp.h"
namespace wpi {
class Logger;
} // namespace wpi
namespace nt {
class LocalStorage final : public net::ILocalStorage {
public:
LocalStorage(int inst, wpi::Logger& logger);
LocalStorage(const LocalStorage&) = delete;
LocalStorage& operator=(const LocalStorage&) = delete;
~LocalStorage() final;
// network interface functions
NT_Topic NetworkAnnounce(std::string_view name, std::string_view typeStr,
const wpi::json& properties,
NT_Publisher pubHandle) final;
void NetworkUnannounce(std::string_view name) final;
void NetworkPropertiesUpdate(std::string_view name, const wpi::json& update,
bool ack) final;
void NetworkSetValue(NT_Topic topicHandle, const Value& value) final;
void StartNetwork(net::NetworkStartupInterface& startup) final;
void SetNetwork(net::NetworkInterface* network) final;
void ClearNetwork() final;
// User functions. These are the actual implementations of the corresponding
// user API functions in ntcore_cpp.
std::vector<NT_Topic> GetTopics(std::string_view prefix, unsigned int types);
std::vector<NT_Topic> GetTopics(std::string_view prefix,
wpi::span<const std::string_view> types);
std::vector<TopicInfo> GetTopicInfo(std::string_view prefix,
unsigned int types);
std::vector<TopicInfo> GetTopicInfo(std::string_view prefix,
wpi::span<const std::string_view> types);
NT_Topic GetTopic(std::string_view name);
std::string GetTopicName(NT_Topic topic);
NT_Type GetTopicType(NT_Topic topic);
std::string GetTopicTypeString(NT_Topic topic);
void SetTopicPersistent(NT_Topic topic, bool value);
bool GetTopicPersistent(NT_Topic topic);
void SetTopicRetained(NT_Topic topic, bool value);
bool GetTopicRetained(NT_Topic topic);
bool GetTopicExists(NT_Handle handle);
wpi::json GetTopicProperty(NT_Topic topic, std::string_view name);
void SetTopicProperty(NT_Topic topic, std::string_view name,
const wpi::json& value);
void DeleteTopicProperty(NT_Topic topic, std::string_view name);
wpi::json GetTopicProperties(NT_Topic topic);
void SetTopicProperties(NT_Topic topic, const wpi::json& update);
TopicInfo GetTopicInfo(NT_Topic topic);
NT_Subscriber Subscribe(NT_Topic topic, NT_Type type,
std::string_view typeStr,
wpi::span<const PubSubOption> options);
void Unsubscribe(NT_Subscriber sub);
NT_MultiSubscriber SubscribeMultiple(
wpi::span<const std::string_view> prefixes,
wpi::span<const PubSubOption> options);
void UnsubscribeMultiple(NT_MultiSubscriber subHandle);
NT_Publisher Publish(NT_Topic topic, NT_Type type, std::string_view typeStr,
const wpi::json& properties,
wpi::span<const PubSubOption> options);
void Unpublish(NT_Handle pubentry);
NT_Entry GetEntry(NT_Topic topic, NT_Type type, std::string_view typeStr,
wpi::span<const PubSubOption> options);
void ReleaseEntry(NT_Entry entry);
void Release(NT_Handle pubsubentry);
NT_Topic GetTopicFromHandle(NT_Handle pubsubentry);
bool SetEntryValue(NT_Handle pubentry, const Value& value);
bool SetDefaultEntryValue(NT_Handle pubsubentry, const Value& value);
TimestampedBoolean GetAtomicBoolean(NT_Handle subentry, bool defaultValue);
TimestampedInteger GetAtomicInteger(NT_Handle subentry, int64_t defaultValue);
TimestampedFloat GetAtomicFloat(NT_Handle subentry, float defaultValue);
TimestampedDouble GetAtomicDouble(NT_Handle subentry, double defaultValue);
TimestampedString GetAtomicString(NT_Handle subentry,
std::string_view defaultValue);
TimestampedRaw GetAtomicRaw(NT_Handle subentry,
wpi::span<const uint8_t> defaultValue);
TimestampedBooleanArray GetAtomicBooleanArray(
NT_Handle subentry, wpi::span<const int> defaultValue);
TimestampedIntegerArray GetAtomicIntegerArray(
NT_Handle subentry, wpi::span<const int64_t> defaultValue);
TimestampedFloatArray GetAtomicFloatArray(
NT_Handle subentry, wpi::span<const float> defaultValue);
TimestampedDoubleArray GetAtomicDoubleArray(
NT_Handle subentry, wpi::span<const double> defaultValue);
TimestampedStringArray GetAtomicStringArray(
NT_Handle subentry, wpi::span<const std::string> defaultValue);
TimestampedStringView GetAtomicString(NT_Handle subentry,
wpi::SmallVectorImpl<char>& buf,
std::string_view defaultValue);
TimestampedRawView GetAtomicRaw(NT_Handle subentry,
wpi::SmallVectorImpl<uint8_t>& buf,
wpi::span<const uint8_t> defaultValue);
TimestampedBooleanArrayView GetAtomicBooleanArray(
NT_Handle subentry, wpi::SmallVectorImpl<int>& buf,
wpi::span<const int> defaultValue);
TimestampedIntegerArrayView GetAtomicIntegerArray(
NT_Handle subentry, wpi::SmallVectorImpl<int64_t>& buf,
wpi::span<const int64_t> defaultValue);
TimestampedFloatArrayView GetAtomicFloatArray(
NT_Handle subentry, wpi::SmallVectorImpl<float>& buf,
wpi::span<const float> defaultValue);
TimestampedDoubleArrayView GetAtomicDoubleArray(
NT_Handle subentry, wpi::SmallVectorImpl<double>& buf,
wpi::span<const double> defaultValue);
std::vector<Value> ReadQueueValue(NT_Handle subentry);
std::vector<TimestampedBoolean> ReadQueueBoolean(NT_Handle subentry);
std::vector<TimestampedInteger> ReadQueueInteger(NT_Handle subentry);
std::vector<TimestampedFloat> ReadQueueFloat(NT_Handle subentry);
std::vector<TimestampedDouble> ReadQueueDouble(NT_Handle subentry);
std::vector<TimestampedString> ReadQueueString(NT_Handle subentry);
std::vector<TimestampedRaw> ReadQueueRaw(NT_Handle subentry);
std::vector<TimestampedBooleanArray> ReadQueueBooleanArray(
NT_Handle subentry);
std::vector<TimestampedIntegerArray> ReadQueueIntegerArray(
NT_Handle subentry);
std::vector<TimestampedFloatArray> ReadQueueFloatArray(NT_Handle subentry);
std::vector<TimestampedDoubleArray> ReadQueueDoubleArray(NT_Handle subentry);
std::vector<TimestampedStringArray> ReadQueueStringArray(NT_Handle subentry);
//
// Backwards compatible user functions
//
Value GetEntryValue(NT_Handle subentry);
void SetEntryFlags(NT_Entry entry, unsigned int flags);
unsigned int GetEntryFlags(NT_Entry entry);
// Index-only
NT_Entry GetEntry(std::string_view name);
std::string GetEntryName(NT_Entry entry);
NT_Type GetEntryType(NT_Entry entry);
int64_t GetEntryLastChange(NT_Entry entry);
//
// Topic listener functions
//
NT_TopicListener AddTopicListener(
wpi::span<const std::string_view> prefixes, unsigned int mask,
std::function<void(const TopicNotification&)> callback);
NT_TopicListener AddTopicListener(
NT_Handle handle, unsigned int mask,
std::function<void(const TopicNotification&)> callback);
NT_TopicListenerPoller CreateTopicListenerPoller();
void DestroyTopicListenerPoller(NT_TopicListenerPoller poller);
NT_TopicListener AddPolledTopicListener(
NT_TopicListenerPoller poller, wpi::span<const std::string_view> prefixes,
unsigned int mask);
NT_TopicListener AddPolledTopicListener(NT_TopicListenerPoller poller,
NT_Handle handle, unsigned int mask);
std::vector<TopicNotification> ReadTopicListenerQueue(
NT_TopicListenerPoller poller);
void RemoveTopicListener(NT_TopicListener listener);
//
// Value listener functions
//
NT_ValueListener AddValueListener(
NT_Handle subentry, unsigned int mask,
std::function<void(const ValueNotification&)> callback);
NT_ValueListenerPoller CreateValueListenerPoller();
void DestroyValueListenerPoller(NT_ValueListenerPoller poller);
NT_ValueListener AddPolledValueListener(NT_ValueListenerPoller poller,
NT_Handle subentry,
unsigned int mask);
std::vector<ValueNotification> ReadValueListenerQueue(
NT_ValueListenerPoller poller);
void RemoveValueListener(NT_ValueListener listener);
//
// Data log functions
//
NT_DataLogger StartDataLog(wpi::log::DataLog& log, std::string_view prefix,
std::string_view logPrefix);
void StopDataLog(NT_DataLogger logger);
private:
class Impl;
std::unique_ptr<Impl> m_impl;
wpi::mutex m_mutex;
};
} // namespace nt

View File

@@ -2,8 +2,7 @@
// Open Source 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 NTCORE_LOG_H_
#define NTCORE_LOG_H_
#pragma once
#include <wpi/Logger.h>
@@ -19,5 +18,3 @@
#define DEBUG2(format, ...) WPI_DEBUG2(m_logger, format, __VA_ARGS__)
#define DEBUG3(format, ...) WPI_DEBUG3(m_logger, format, __VA_ARGS__)
#define DEBUG4(format, ...) WPI_DEBUG4(m_logger, format, __VA_ARGS__)
#endif // NTCORE_LOG_H_

View File

@@ -5,6 +5,7 @@
#include "LoggerImpl.h"
#include <fmt/format.h>
#include <wpi/DenseMap.h>
#include <wpi/fs.h>
using namespace nt;
@@ -29,34 +30,108 @@ static void DefaultLogger(unsigned int level, const char* file,
fmt::print(stderr, "NT: {}: {} ({}:{})\n", levelmsg, msg, file, line);
}
LoggerImpl::LoggerImpl(int inst) : m_inst(inst) {}
class LoggerImpl::Thread final : public wpi::SafeThreadEvent {
public:
explicit Thread(NT_LoggerPoller poller) : m_poller{poller} {}
void LoggerImpl::Start() {
DoStart(m_inst);
void Main() final;
NT_LoggerPoller m_poller;
wpi::DenseMap<NT_Logger, std::function<void(const LogMessage& msg)>>
m_callbacks;
};
void LoggerImpl::Thread::Main() {
while (m_active) {
WPI_Handle signaledBuf[2];
auto signaled =
wpi::WaitForObjects({m_poller, m_stopEvent.GetHandle()}, signaledBuf);
if (signaled.empty() || !m_active) {
return;
}
// call all the way back out to the C++ API to ensure valid handle
auto events = nt::ReadLoggerQueue(m_poller);
if (events.empty()) {
continue;
}
std::unique_lock lock{m_mutex};
for (auto&& event : events) {
auto callbackIt = m_callbacks.find(event.logger);
if (callbackIt != m_callbacks.end()) {
auto callback = callbackIt->second;
lock.unlock();
callback(event);
lock.lock();
}
}
}
}
unsigned int LoggerImpl::Add(
std::function<void(const LogMessage& msg)> callback, unsigned int min_level,
unsigned int max_level) {
return DoAdd(callback, min_level, max_level);
LoggerImpl::LoggerImpl(int inst) : m_inst{inst} {}
LoggerImpl::~LoggerImpl() = default;
NT_Logger LoggerImpl::Add(std::function<void(const LogMessage& msg)> callback,
unsigned int minLevel, unsigned int maxLevel) {
if (!m_thread) {
m_thread.Start(CreatePoller());
}
if (auto thr = m_thread.GetThread()) {
auto listener = AddPolled(thr->m_poller, minLevel, maxLevel);
if (listener) {
thr->m_callbacks.try_emplace(listener, std::move(callback));
}
return listener;
} else {
return {};
}
}
unsigned int LoggerImpl::AddPolled(unsigned int poller_uid,
unsigned int min_level,
unsigned int max_level) {
return DoAdd(poller_uid, min_level, max_level);
NT_LoggerPoller LoggerImpl::CreatePoller() {
std::scoped_lock lock{m_mutex};
return m_pollers.Add(m_inst)->handle;
}
void LoggerImpl::DestroyPoller(NT_LoggerPoller pollerHandle) {
std::scoped_lock lock{m_mutex};
m_pollers.Remove(pollerHandle);
}
NT_Logger LoggerImpl::AddPolled(NT_LoggerPoller pollerHandle,
unsigned int minLevel, unsigned int maxLevel) {
std::scoped_lock lock{m_mutex};
if (auto poller = m_pollers.Get(pollerHandle)) {
return m_listeners.Add(m_inst, poller, minLevel, maxLevel)->handle;
} else {
return {};
}
}
std::vector<LogMessage> LoggerImpl::ReadQueue(NT_LoggerPoller pollerHandle) {
std::scoped_lock lock{m_mutex};
if (auto poller = m_pollers.Get(pollerHandle)) {
std::vector<LogMessage> rv;
rv.swap(poller->queue);
return rv;
} else {
return {};
}
}
void LoggerImpl::Remove(NT_Logger listenerHandle) {
std::scoped_lock lock{m_mutex};
m_listeners.Remove(listenerHandle);
if (auto thr = m_thread.GetThread()) {
thr->m_callbacks.erase(listenerHandle);
}
}
unsigned int LoggerImpl::GetMinLevel() {
auto thr = GetThread();
if (!thr) {
return NT_LOG_INFO;
}
// return 0;
unsigned int level = NT_LOG_INFO;
for (size_t i = 0; i < thr->m_listeners.size(); ++i) {
const auto& listener = thr->m_listeners[i];
if (listener && listener.min_level < level) {
level = listener.min_level;
for (auto&& listener : m_listeners) {
if (listener && listener->minLevel < level) {
level = listener->minLevel;
}
}
return level;
@@ -66,10 +141,18 @@ void LoggerImpl::Log(unsigned int level, const char* file, unsigned int line,
const char* msg) {
auto filename = fs::path{file}.filename();
{
auto thr = GetThread();
if (!thr || thr->m_listeners.empty()) {
std::scoped_lock lock{m_mutex};
if (m_listeners.empty()) {
DefaultLogger(level, filename.string().c_str(), line, msg);
} else {
for (auto&& listener : m_listeners) {
if (level >= listener->minLevel && level <= listener->maxLevel) {
listener->poller->queue.emplace_back(listener->handle.GetHandle(),
level, file, line, msg);
listener->poller->handle.Set();
listener->handle.Set();
}
}
}
}
Send(UINT_MAX, 0, level, filename.string(), line, msg);
}

View File

@@ -2,76 +2,36 @@
// Open Source 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 NTCORE_LOGGERIMPL_H_
#define NTCORE_LOGGERIMPL_H_
#pragma once
#include <utility>
#include <vector>
#include <wpi/CallbackManager.h>
#include <wpi/SafeThread.h>
#include <wpi/Synchronization.h>
#include <wpi/mutex.h>
#include "Handle.h"
#include "HandleMap.h"
#include "ntcore_c.h"
#include "ntcore_cpp.h"
namespace nt {
namespace impl {
struct LoggerListenerData : public wpi::CallbackListenerData<
std::function<void(const LogMessage& msg)>> {
LoggerListenerData() = default;
LoggerListenerData(std::function<void(const LogMessage& msg)> callback_,
unsigned int min_level_, unsigned int max_level_)
: CallbackListenerData(callback_),
min_level(min_level_),
max_level(max_level_) {}
LoggerListenerData(unsigned int poller_uid_, unsigned int min_level_,
unsigned int max_level_)
: CallbackListenerData(poller_uid_),
min_level(min_level_),
max_level(max_level_) {}
unsigned int min_level;
unsigned int max_level;
};
class LoggerThread
: public wpi::CallbackThread<LoggerThread, LogMessage, LoggerListenerData> {
public:
LoggerThread(std::function<void()> on_start, std::function<void()> on_exit,
int inst)
: CallbackThread(std::move(on_start), std::move(on_exit)), m_inst(inst) {}
bool Matches(const LoggerListenerData& listener, const LogMessage& data) {
return data.level >= listener.min_level && data.level <= listener.max_level;
}
void SetListener(LogMessage* data, unsigned int listener_uid) {
data->logger = Handle(m_inst, listener_uid, Handle::kLogger).handle();
}
void DoCallback(std::function<void(const LogMessage& msg)> callback,
const LogMessage& data) {
callback(data);
}
int m_inst;
};
} // namespace impl
class LoggerImpl : public wpi::CallbackManager<LoggerImpl, impl::LoggerThread> {
friend class LoggerTest;
friend class wpi::CallbackManager<LoggerImpl, impl::LoggerThread>;
class LoggerImpl {
public:
explicit LoggerImpl(int inst);
~LoggerImpl();
void Start();
NT_Logger Add(std::function<void(const LogMessage& msg)> callback,
unsigned int minLevel, unsigned int maxLevel);
unsigned int Add(std::function<void(const LogMessage& msg)> callback,
unsigned int min_level, unsigned int max_level);
unsigned int AddPolled(unsigned int poller_uid, unsigned int min_level,
unsigned int max_level);
NT_LoggerPoller CreatePoller();
void DestroyPoller(NT_LoggerPoller pollerHandle);
NT_Logger AddPolled(NT_LoggerPoller pollerHandle, unsigned int minLevel,
unsigned int maxLevel);
std::vector<LogMessage> ReadQueue(NT_LoggerPoller pollerHandle);
void Remove(NT_Logger listenerHandle);
unsigned int GetMinLevel();
@@ -80,8 +40,37 @@ class LoggerImpl : public wpi::CallbackManager<LoggerImpl, impl::LoggerThread> {
private:
int m_inst;
mutable wpi::mutex m_mutex;
struct PollerData {
static constexpr auto kType = Handle::kLoggerPoller;
explicit PollerData(NT_LoggerPoller handle) : handle{handle} {}
wpi::SignalObject<NT_LoggerPoller> handle;
std::vector<LogMessage> queue;
};
HandleMap<PollerData, 8> m_pollers;
struct ListenerData {
static constexpr auto kType = Handle::kLogger;
ListenerData(NT_Logger handle, PollerData* poller, unsigned int minLevel,
unsigned int maxLevel)
: handle{handle},
poller{poller},
minLevel{minLevel},
maxLevel{maxLevel} {}
wpi::SignalObject<NT_Logger> handle;
PollerData* poller;
unsigned int minLevel;
unsigned int maxLevel;
};
HandleMap<ListenerData, 8> m_listeners;
class Thread;
wpi::SafeThreadOwner<Thread> m_thread;
};
} // namespace nt
#endif // NTCORE_LOGGERIMPL_H_

View File

@@ -1,375 +0,0 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#include "Message.h"
#include <stdint.h>
#include "Log.h"
#include "WireDecoder.h"
#include "WireEncoder.h"
#define kClearAllMagic 0xD06CB27Aul
using namespace nt;
std::shared_ptr<Message> Message::Read(WireDecoder& decoder,
GetEntryTypeFunc get_entry_type) {
unsigned int msg_type = 0;
if (!decoder.Read8(&msg_type)) {
return nullptr;
}
auto msg =
std::make_shared<Message>(static_cast<MsgType>(msg_type), private_init());
switch (msg_type) {
case kKeepAlive:
break;
case kClientHello: {
unsigned int proto_rev;
if (!decoder.Read16(&proto_rev)) {
return nullptr;
}
msg->m_id = proto_rev;
// This intentionally uses the provided proto_rev instead of
// decoder.proto_rev().
if (proto_rev >= 0x0300u) {
if (!decoder.ReadString(&msg->m_str)) {
return nullptr;
}
}
break;
}
case kProtoUnsup: {
if (!decoder.Read16(&msg->m_id)) {
return nullptr; // proto rev
}
break;
}
case kServerHelloDone:
break;
case kServerHello:
if (decoder.proto_rev() < 0x0300u) {
decoder.set_error("received SERVER_HELLO in protocol < 3.0");
return nullptr;
}
if (!decoder.Read8(&msg->m_flags)) {
return nullptr;
}
if (!decoder.ReadString(&msg->m_str)) {
return nullptr;
}
break;
case kClientHelloDone:
if (decoder.proto_rev() < 0x0300u) {
decoder.set_error("received CLIENT_HELLO_DONE in protocol < 3.0");
return nullptr;
}
break;
case kEntryAssign: {
if (!decoder.ReadString(&msg->m_str)) {
return nullptr; // name
}
NT_Type type;
if (!decoder.ReadType(&type)) {
return nullptr; // entry type
}
if (!decoder.Read16(&msg->m_id)) {
return nullptr; // id
}
if (!decoder.Read16(&msg->m_seq_num_uid)) {
return nullptr; // seq num
}
if (decoder.proto_rev() >= 0x0300u) {
if (!decoder.Read8(&msg->m_flags)) {
return nullptr; // flags
}
}
msg->m_value = decoder.ReadValue(type);
if (!msg->m_value) {
return nullptr;
}
break;
}
case kEntryUpdate: {
if (!decoder.Read16(&msg->m_id)) {
return nullptr; // id
}
if (!decoder.Read16(&msg->m_seq_num_uid)) {
return nullptr; // seq num
}
NT_Type type;
if (decoder.proto_rev() >= 0x0300u) {
if (!decoder.ReadType(&type)) {
return nullptr;
}
} else {
type = get_entry_type(msg->m_id);
}
WPI_DEBUG4(decoder.logger(), "update message data type: {}",
static_cast<int>(type));
msg->m_value = decoder.ReadValue(type);
if (!msg->m_value) {
return nullptr;
}
break;
}
case kFlagsUpdate: {
if (decoder.proto_rev() < 0x0300u) {
decoder.set_error("received FLAGS_UPDATE in protocol < 3.0");
return nullptr;
}
if (!decoder.Read16(&msg->m_id)) {
return nullptr;
}
if (!decoder.Read8(&msg->m_flags)) {
return nullptr;
}
break;
}
case kEntryDelete: {
if (decoder.proto_rev() < 0x0300u) {
decoder.set_error("received ENTRY_DELETE in protocol < 3.0");
return nullptr;
}
if (!decoder.Read16(&msg->m_id)) {
return nullptr;
}
break;
}
case kClearEntries: {
if (decoder.proto_rev() < 0x0300u) {
decoder.set_error("received CLEAR_ENTRIES in protocol < 3.0");
return nullptr;
}
uint32_t magic;
if (!decoder.Read32(&magic)) {
return nullptr;
}
if (magic != kClearAllMagic) {
decoder.set_error(
"received incorrect CLEAR_ENTRIES magic value, ignoring");
return nullptr;
}
break;
}
case kExecuteRpc: {
if (decoder.proto_rev() < 0x0300u) {
decoder.set_error("received EXECUTE_RPC in protocol < 3.0");
return nullptr;
}
if (!decoder.Read16(&msg->m_id)) {
return nullptr;
}
if (!decoder.Read16(&msg->m_seq_num_uid)) {
return nullptr; // uid
}
uint64_t size;
if (!decoder.ReadUleb128(&size)) {
return nullptr;
}
const char* params;
if (!decoder.Read(&params, size)) {
return nullptr;
}
msg->m_str.assign(params, size);
break;
}
case kRpcResponse: {
if (decoder.proto_rev() < 0x0300u) {
decoder.set_error("received RPC_RESPONSE in protocol < 3.0");
return nullptr;
}
if (!decoder.Read16(&msg->m_id)) {
return nullptr;
}
if (!decoder.Read16(&msg->m_seq_num_uid)) {
return nullptr; // uid
}
uint64_t size;
if (!decoder.ReadUleb128(&size)) {
return nullptr;
}
const char* results;
if (!decoder.Read(&results, size)) {
return nullptr;
}
msg->m_str.assign(results, size);
break;
}
default:
decoder.set_error("unrecognized message type");
WPI_INFO(decoder.logger(), "unrecognized message type: {}", msg_type);
return nullptr;
}
return msg;
}
std::shared_ptr<Message> Message::ClientHello(std::string_view self_id) {
auto msg = std::make_shared<Message>(kClientHello, private_init());
msg->m_str = self_id;
return msg;
}
std::shared_ptr<Message> Message::ServerHello(unsigned int flags,
std::string_view self_id) {
auto msg = std::make_shared<Message>(kServerHello, private_init());
msg->m_str = self_id;
msg->m_flags = flags;
return msg;
}
std::shared_ptr<Message> Message::EntryAssign(std::string_view name,
unsigned int id,
unsigned int seq_num,
std::shared_ptr<Value> value,
unsigned int flags) {
auto msg = std::make_shared<Message>(kEntryAssign, private_init());
msg->m_str = name;
msg->m_value = value;
msg->m_id = id;
msg->m_flags = flags;
msg->m_seq_num_uid = seq_num;
return msg;
}
std::shared_ptr<Message> Message::EntryUpdate(unsigned int id,
unsigned int seq_num,
std::shared_ptr<Value> value) {
auto msg = std::make_shared<Message>(kEntryUpdate, private_init());
msg->m_value = value;
msg->m_id = id;
msg->m_seq_num_uid = seq_num;
return msg;
}
std::shared_ptr<Message> Message::FlagsUpdate(unsigned int id,
unsigned int flags) {
auto msg = std::make_shared<Message>(kFlagsUpdate, private_init());
msg->m_id = id;
msg->m_flags = flags;
return msg;
}
std::shared_ptr<Message> Message::EntryDelete(unsigned int id) {
auto msg = std::make_shared<Message>(kEntryDelete, private_init());
msg->m_id = id;
return msg;
}
std::shared_ptr<Message> Message::ExecuteRpc(unsigned int id, unsigned int uid,
std::string_view params) {
auto msg = std::make_shared<Message>(kExecuteRpc, private_init());
msg->m_str = params;
msg->m_id = id;
msg->m_seq_num_uid = uid;
return msg;
}
std::shared_ptr<Message> Message::RpcResponse(unsigned int id, unsigned int uid,
std::string_view result) {
auto msg = std::make_shared<Message>(kRpcResponse, private_init());
msg->m_str = result;
msg->m_id = id;
msg->m_seq_num_uid = uid;
return msg;
}
void Message::Write(WireEncoder& encoder) const {
switch (m_type) {
case kKeepAlive:
encoder.Write8(kKeepAlive);
break;
case kClientHello:
encoder.Write8(kClientHello);
encoder.Write16(encoder.proto_rev());
if (encoder.proto_rev() < 0x0300u) {
return;
}
encoder.WriteString(m_str);
break;
case kProtoUnsup:
encoder.Write8(kProtoUnsup);
encoder.Write16(encoder.proto_rev());
break;
case kServerHelloDone:
encoder.Write8(kServerHelloDone);
break;
case kServerHello:
if (encoder.proto_rev() < 0x0300u) {
return; // new message in version 3.0
}
encoder.Write8(kServerHello);
encoder.Write8(m_flags);
encoder.WriteString(m_str);
break;
case kClientHelloDone:
if (encoder.proto_rev() < 0x0300u) {
return; // new message in version 3.0
}
encoder.Write8(kClientHelloDone);
break;
case kEntryAssign:
encoder.Write8(kEntryAssign);
encoder.WriteString(m_str);
encoder.WriteType(m_value->type());
encoder.Write16(m_id);
encoder.Write16(m_seq_num_uid);
if (encoder.proto_rev() >= 0x0300u) {
encoder.Write8(m_flags);
}
encoder.WriteValue(*m_value);
break;
case kEntryUpdate:
encoder.Write8(kEntryUpdate);
encoder.Write16(m_id);
encoder.Write16(m_seq_num_uid);
if (encoder.proto_rev() >= 0x0300u) {
encoder.WriteType(m_value->type());
}
encoder.WriteValue(*m_value);
break;
case kFlagsUpdate:
if (encoder.proto_rev() < 0x0300u) {
return; // new message in version 3.0
}
encoder.Write8(kFlagsUpdate);
encoder.Write16(m_id);
encoder.Write8(m_flags);
break;
case kEntryDelete:
if (encoder.proto_rev() < 0x0300u) {
return; // new message in version 3.0
}
encoder.Write8(kEntryDelete);
encoder.Write16(m_id);
break;
case kClearEntries:
if (encoder.proto_rev() < 0x0300u) {
return; // new message in version 3.0
}
encoder.Write8(kClearEntries);
encoder.Write32(kClearAllMagic);
break;
case kExecuteRpc:
if (encoder.proto_rev() < 0x0300u) {
return; // new message in version 3.0
}
encoder.Write8(kExecuteRpc);
encoder.Write16(m_id);
encoder.Write16(m_seq_num_uid);
encoder.WriteString(m_str);
break;
case kRpcResponse:
if (encoder.proto_rev() < 0x0300u) {
return; // new message in version 3.0
}
encoder.Write8(kRpcResponse);
encoder.Write16(m_id);
encoder.Write16(m_seq_num_uid);
encoder.WriteString(m_str);
break;
default:
break;
}
}

View File

@@ -1,114 +0,0 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source 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 NTCORE_MESSAGE_H_
#define NTCORE_MESSAGE_H_
#include <functional>
#include <memory>
#include <string>
#include <string_view>
#include "networktables/NetworkTableValue.h"
namespace nt {
class WireDecoder;
class WireEncoder;
class Message {
struct private_init {};
public:
enum MsgType {
kUnknown = -1,
kKeepAlive = 0x00,
kClientHello = 0x01,
kProtoUnsup = 0x02,
kServerHelloDone = 0x03,
kServerHello = 0x04,
kClientHelloDone = 0x05,
kEntryAssign = 0x10,
kEntryUpdate = 0x11,
kFlagsUpdate = 0x12,
kEntryDelete = 0x13,
kClearEntries = 0x14,
kExecuteRpc = 0x20,
kRpcResponse = 0x21
};
using GetEntryTypeFunc = std::function<NT_Type(unsigned int id)>;
Message() = default;
Message(MsgType type, const private_init&) : m_type(type) {}
MsgType type() const { return m_type; }
bool Is(MsgType type) const { return type == m_type; }
// Message data accessors. Callers are responsible for knowing what data is
// actually provided for a particular message.
std::string_view str() const { return m_str; }
std::shared_ptr<Value> value() const { return m_value; }
unsigned int id() const { return m_id; }
unsigned int flags() const { return m_flags; }
unsigned int seq_num_uid() const { return m_seq_num_uid; }
// Read and write from wire representation
void Write(WireEncoder& encoder) const;
static std::shared_ptr<Message> Read(WireDecoder& decoder,
GetEntryTypeFunc get_entry_type);
// Create messages without data
static std::shared_ptr<Message> KeepAlive() {
return std::make_shared<Message>(kKeepAlive, private_init());
}
static std::shared_ptr<Message> ProtoUnsup() {
return std::make_shared<Message>(kProtoUnsup, private_init());
}
static std::shared_ptr<Message> ServerHelloDone() {
return std::make_shared<Message>(kServerHelloDone, private_init());
}
static std::shared_ptr<Message> ClientHelloDone() {
return std::make_shared<Message>(kClientHelloDone, private_init());
}
static std::shared_ptr<Message> ClearEntries() {
return std::make_shared<Message>(kClearEntries, private_init());
}
// Create messages with data
static std::shared_ptr<Message> ClientHello(std::string_view self_id);
static std::shared_ptr<Message> ServerHello(unsigned int flags,
std::string_view self_id);
static std::shared_ptr<Message> EntryAssign(std::string_view name,
unsigned int id,
unsigned int seq_num,
std::shared_ptr<Value> value,
unsigned int flags);
static std::shared_ptr<Message> EntryUpdate(unsigned int id,
unsigned int seq_num,
std::shared_ptr<Value> value);
static std::shared_ptr<Message> FlagsUpdate(unsigned int id,
unsigned int flags);
static std::shared_ptr<Message> EntryDelete(unsigned int id);
static std::shared_ptr<Message> ExecuteRpc(unsigned int id, unsigned int uid,
std::string_view params);
static std::shared_ptr<Message> RpcResponse(unsigned int id, unsigned int uid,
std::string_view result);
Message(const Message&) = delete;
Message& operator=(const Message&) = delete;
private:
MsgType m_type{kUnknown};
// Message data. Use varies by message type.
std::string m_str;
std::shared_ptr<Value> m_value;
unsigned int m_id{0}; // also used for proto_rev
unsigned int m_flags{0};
unsigned int m_seq_num_uid{0};
};
} // namespace nt
#endif // NTCORE_MESSAGE_H_

View File

@@ -0,0 +1,530 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#include "NetworkClient.h"
#include <stdint.h>
#include <atomic>
#include <memory>
#include <vector>
#include <fmt/format.h>
#include <wpi/StringExtras.h>
#include <wpinet/DsClient.h>
#include <wpinet/EventLoopRunner.h>
#include <wpinet/ParallelTcpConnector.h>
#include <wpinet/WebSocket.h>
#include <wpinet/uv/Async.h>
#include <wpinet/uv/Loop.h>
#include <wpinet/uv/Tcp.h>
#include <wpinet/uv/Timer.h>
#include <wpinet/uv/util.h>
#include "IConnectionList.h"
#include "Log.h"
#include "net/ClientImpl.h"
#include "net/Message.h"
#include "net/NetworkLoopQueue.h"
#include "net/WebSocketConnection.h"
#include "net3/ClientImpl3.h"
#include "net3/UvStreamConnection3.h"
using namespace nt;
namespace uv = wpi::uv;
static constexpr uv::Timer::Time kReconnectRate{1000};
static constexpr uv::Timer::Time kWebsocketHandshakeTimeout{500};
// use a larger max message size for websockets
static constexpr size_t kMaxMessageSize = 2 * 1024 * 1024;
namespace {
class NCImpl {
public:
NCImpl(int inst, std::string_view id, net::ILocalStorage& localStorage,
IConnectionList& connList, wpi::Logger& logger);
virtual ~NCImpl() = default;
// user-facing functions
void SetServers(wpi::span<const std::pair<std::string, unsigned int>> servers,
unsigned int defaultPort);
void StartDSClient(unsigned int port);
void StopDSClient();
virtual void TcpConnected(uv::Tcp& tcp) = 0;
virtual void Disconnect(std::string_view reason);
// invariants
int m_inst;
net::ILocalStorage& m_localStorage;
IConnectionList& m_connList;
wpi::Logger& m_logger;
std::string m_id;
// used only from loop
std::shared_ptr<wpi::ParallelTcpConnector> m_parallelConnect;
std::shared_ptr<uv::Timer> m_readLocalTimer;
std::shared_ptr<uv::Timer> m_sendValuesTimer;
std::shared_ptr<uv::Async<>> m_flushLocal;
std::shared_ptr<uv::Async<>> m_flush;
std::vector<net::ClientMessage> m_localMsgs;
std::vector<std::pair<std::string, unsigned int>> m_servers;
std::pair<std::string, unsigned int> m_dsClientServer{"", 0};
std::shared_ptr<wpi::DsClient> m_dsClient;
// shared with user
std::atomic<uv::Async<>*> m_flushLocalAtomic{nullptr};
std::atomic<uv::Async<>*> m_flushAtomic{nullptr};
net::NetworkLoopQueue m_localQueue;
int m_connHandle = 0;
wpi::EventLoopRunner m_loopRunner;
uv::Loop& m_loop;
};
class NCImpl3 : public NCImpl {
public:
NCImpl3(int inst, std::string_view id, net::ILocalStorage& localStorage,
IConnectionList& connList, wpi::Logger& logger);
~NCImpl3() override;
void HandleLocal();
void TcpConnected(uv::Tcp& tcp) final;
void Disconnect(std::string_view reason) override;
std::shared_ptr<net3::UvStreamConnection3> m_wire;
std::shared_ptr<net3::ClientImpl3> m_clientImpl;
};
class NCImpl4 : public NCImpl {
public:
NCImpl4(int inst, std::string_view id, net::ILocalStorage& localStorage,
IConnectionList& connList, wpi::Logger& logger);
~NCImpl4() override;
void HandleLocal();
void TcpConnected(uv::Tcp& tcp) final;
void WsConnected(wpi::WebSocket& ws, uv::Tcp& tcp);
void Disconnect(std::string_view reason) override;
std::unique_ptr<net::WebSocketConnection> m_wire;
std::unique_ptr<net::ClientImpl> m_clientImpl;
};
} // namespace
NCImpl::NCImpl(int inst, std::string_view id, net::ILocalStorage& localStorage,
IConnectionList& connList, wpi::Logger& logger)
: m_inst{inst},
m_localStorage{localStorage},
m_connList{connList},
m_logger{logger},
m_id{id},
m_localQueue{logger},
m_loop{*m_loopRunner.GetLoop()} {
m_localMsgs.reserve(net::NetworkLoopQueue::kInitialQueueSize);
INFO("{}", "starting network client");
}
void NCImpl::SetServers(
wpi::span<const std::pair<std::string, unsigned int>> servers,
unsigned int defaultPort) {
std::vector<std::pair<std::string, unsigned int>> serversCopy;
serversCopy.reserve(servers.size());
for (auto&& server : servers) {
serversCopy.emplace_back(wpi::trim(server.first),
server.second == 0 ? defaultPort : server.second);
}
m_loopRunner.ExecAsync(
[this, servers = std::move(serversCopy)](uv::Loop&) mutable {
m_servers = std::move(servers);
if (m_dsClientServer.first.empty()) {
m_parallelConnect->SetServers(m_servers);
}
});
}
void NCImpl::StartDSClient(unsigned int port) {
m_loopRunner.ExecAsync([=](uv::Loop& loop) {
if (m_dsClient) {
return;
}
m_dsClientServer.second = port == 0 ? NT_DEFAULT_PORT4 : port;
m_dsClient = wpi::DsClient::Create(m_loop, m_logger);
m_dsClient->setIp.connect([this](std::string_view ip) {
m_dsClientServer.first = ip;
m_parallelConnect->SetServers({{m_dsClientServer}});
});
m_dsClient->clearIp.connect([this] {
m_dsClientServer.first.clear();
m_parallelConnect->SetServers(m_servers);
});
});
}
void NCImpl::StopDSClient() {
m_loopRunner.ExecAsync([this](uv::Loop& loop) {
if (m_dsClient) {
m_dsClient->Close();
m_dsClient.reset();
}
});
}
void NCImpl::Disconnect(std::string_view reason) {
if (m_readLocalTimer) {
m_readLocalTimer->Stop();
}
m_sendValuesTimer->Stop();
m_localStorage.ClearNetwork();
m_localQueue.ClearQueue();
m_connList.RemoveConnection(m_connHandle);
m_connHandle = 0;
// start trying to connect again
m_parallelConnect->Disconnected();
}
NCImpl3::NCImpl3(int inst, std::string_view id,
net::ILocalStorage& localStorage, IConnectionList& connList,
wpi::Logger& logger)
: NCImpl{inst, id, localStorage, connList, logger} {
m_loopRunner.ExecAsync([this](uv::Loop& loop) {
m_parallelConnect = wpi::ParallelTcpConnector::Create(
loop, kReconnectRate, m_logger,
[this](uv::Tcp& tcp) { TcpConnected(tcp); });
m_sendValuesTimer = uv::Timer::Create(loop);
m_sendValuesTimer->timeout.connect([this] {
if (m_clientImpl) {
HandleLocal();
m_clientImpl->SendPeriodic(m_loop.Now().count());
}
});
// set up flush async
m_flush = uv::Async<>::Create(m_loop);
m_flush->wakeup.connect([this] {
HandleLocal();
m_clientImpl->SendPeriodic(m_loop.Now().count());
});
m_flushAtomic = m_flush.get();
m_flushLocal = uv::Async<>::Create(m_loop);
m_flushLocal->wakeup.connect([this] { HandleLocal(); });
m_flushLocalAtomic = m_flushLocal.get();
});
}
NCImpl3::~NCImpl3() {
// must explicitly destroy these on loop
m_loopRunner.ExecSync([&](auto&) {
m_clientImpl.reset();
m_wire.reset();
});
// shut down loop here to avoid race
m_loopRunner.Stop();
}
void NCImpl3::HandleLocal() {
m_localQueue.ReadQueue(&m_localMsgs);
m_clientImpl->HandleLocal(m_localMsgs);
}
void NCImpl3::TcpConnected(uv::Tcp& tcp) {
tcp.SetNoDelay(true);
// create as shared_ptr and capture in lambda because there may be multiple
// simultaneous attempts
auto wire = std::make_shared<net3::UvStreamConnection3>(tcp);
auto clientImpl = std::make_shared<net3::ClientImpl3>(
m_loop.Now().count(), m_inst, *wire, m_logger, [this](uint32_t repeatMs) {
DEBUG4("Setting periodic timer to {}", repeatMs);
m_sendValuesTimer->Start(uv::Timer::Time{repeatMs},
uv::Timer::Time{repeatMs});
});
clientImpl->Start(
m_id, [this, wire,
clientWeak = std::weak_ptr<net3::ClientImpl3>{clientImpl}, &tcp] {
auto clientImpl = clientWeak.lock();
if (!clientImpl) {
return;
}
if (m_connList.IsConnected()) {
tcp.Close(); // no longer needed
return;
}
m_parallelConnect->Succeeded(tcp);
m_wire = std::move(wire);
m_clientImpl = std::move(clientImpl);
ConnectionInfo connInfo;
uv::AddrToName(tcp.GetPeer(), &connInfo.remote_ip,
&connInfo.remote_port);
connInfo.protocol_version = 0x0300;
INFO("CONNECTED NT3 to {} port {}", connInfo.remote_ip,
connInfo.remote_port);
m_connHandle = m_connList.AddConnection(connInfo);
tcp.error.connect([this, &tcp](uv::Error err) {
DEBUG3("NT3 TCP error {}", err.str());
if (!tcp.IsLoopClosing()) {
Disconnect(err.str());
}
});
tcp.end.connect([this, &tcp] {
DEBUG3("{}", "NT3 TCP read ended");
if (!tcp.IsLoopClosing()) {
Disconnect("remote end closed connection");
}
});
tcp.closed.connect([this, &tcp] {
DEBUG3("{}", "NT3 TCP connection closed");
if (!tcp.IsLoopClosing()) {
Disconnect(m_wire->GetDisconnectReason());
}
});
{
net3::ClientStartup3 startup{*m_clientImpl};
m_localStorage.StartNetwork(startup);
}
m_localStorage.SetNetwork(&m_localQueue);
m_clientImpl->SetLocal(&m_localStorage);
});
tcp.SetData(clientImpl);
tcp.data.connect(
[clientImpl = clientImpl.get()](uv::Buffer& buf, size_t len) {
clientImpl->ProcessIncoming(
{reinterpret_cast<const uint8_t*>(buf.base), len});
});
tcp.StartRead();
}
void NCImpl3::Disconnect(std::string_view reason) {
INFO("DISCONNECTED NT3 connection: {}", reason);
m_clientImpl.reset();
m_wire.reset();
NCImpl::Disconnect(reason);
}
NCImpl4::NCImpl4(int inst, std::string_view id,
net::ILocalStorage& localStorage, IConnectionList& connList,
wpi::Logger& logger)
: NCImpl{inst, id, localStorage, connList, logger} {
m_loopRunner.ExecAsync([this](uv::Loop& loop) {
m_parallelConnect = wpi::ParallelTcpConnector::Create(
loop, kReconnectRate, m_logger,
[this](uv::Tcp& tcp) { TcpConnected(tcp); });
m_readLocalTimer = uv::Timer::Create(loop);
m_readLocalTimer->timeout.connect([this] {
if (m_clientImpl) {
HandleLocal();
m_clientImpl->SendControl(m_loop.Now().count());
}
});
m_readLocalTimer->Start(uv::Timer::Time{100}, uv::Timer::Time{100});
m_sendValuesTimer = uv::Timer::Create(loop);
m_sendValuesTimer->timeout.connect([this] {
if (m_clientImpl) {
HandleLocal();
m_clientImpl->SendValues(m_loop.Now().count());
}
});
// set up flush async
m_flush = uv::Async<>::Create(m_loop);
m_flush->wakeup.connect([this] {
HandleLocal();
m_clientImpl->SendValues(m_loop.Now().count());
});
m_flushAtomic = m_flush.get();
m_flushLocal = uv::Async<>::Create(m_loop);
m_flushLocal->wakeup.connect([this] { HandleLocal(); });
m_flushLocalAtomic = m_flushLocal.get();
});
}
NCImpl4::~NCImpl4() {
// must explicitly destroy these on loop
m_loopRunner.ExecSync([&](auto&) {
m_clientImpl.reset();
m_wire.reset();
});
// shut down loop here to avoid race
m_loopRunner.Stop();
}
void NCImpl4::HandleLocal() {
m_localQueue.ReadQueue(&m_localMsgs);
m_clientImpl->HandleLocal(std::move(m_localMsgs));
}
void NCImpl4::TcpConnected(uv::Tcp& tcp) {
tcp.SetNoDelay(true);
// Start the WS client
if (m_logger.min_level() >= wpi::WPI_LOG_DEBUG4) {
std::string ip;
unsigned int port = 0;
uv::AddrToName(tcp.GetPeer(), &ip, &port);
DEBUG4("Starting WebSocket client on {} port {}", ip, port);
}
wpi::WebSocket::ClientOptions options;
options.handshakeTimeout = kWebsocketHandshakeTimeout;
auto ws =
wpi::WebSocket::CreateClient(tcp, fmt::format("/nt/{}", m_id), "",
{{"networktables.first.wpi.edu"}}, options);
ws->SetMaxMessageSize(kMaxMessageSize);
ws->open.connect([this, &tcp, ws = ws.get()](std::string_view) {
if (m_connList.IsConnected()) {
ws->Terminate(1006, "no longer needed");
return;
}
WsConnected(*ws, tcp);
});
}
void NCImpl4::WsConnected(wpi::WebSocket& ws, uv::Tcp& tcp) {
m_parallelConnect->Succeeded(tcp);
ConnectionInfo connInfo;
uv::AddrToName(tcp.GetPeer(), &connInfo.remote_ip, &connInfo.remote_port);
connInfo.protocol_version = 0x0400;
INFO("CONNECTED NT4 to {} port {}", connInfo.remote_ip, connInfo.remote_port);
m_connHandle = m_connList.AddConnection(connInfo);
m_wire = std::make_unique<net::WebSocketConnection>(ws);
m_clientImpl = std::make_unique<net::ClientImpl>(
m_loop.Now().count(), m_inst, *m_wire, m_logger,
[this](uint32_t repeatMs) {
DEBUG4("Setting periodic timer to {}", repeatMs);
m_sendValuesTimer->Start(uv::Timer::Time{repeatMs},
uv::Timer::Time{repeatMs});
});
{
net::ClientStartup startup{*m_clientImpl};
m_localStorage.StartNetwork(startup);
}
m_localStorage.SetNetwork(&m_localQueue);
m_clientImpl->SetLocal(&m_localStorage);
ws.closed.connect([this, &ws](uint16_t, std::string_view reason) {
if (!ws.GetStream().IsLoopClosing()) {
Disconnect(reason);
}
});
ws.text.connect([this](std::string_view data, bool) {
m_clientImpl->ProcessIncomingText(data);
});
ws.binary.connect([this](wpi::span<const uint8_t> data, bool) {
m_clientImpl->ProcessIncomingBinary(data);
});
}
void NCImpl4::Disconnect(std::string_view reason) {
INFO("DISCONNECTED NT4 connection: {}", reason);
m_clientImpl.reset();
m_wire.reset();
NCImpl::Disconnect(reason);
}
class NetworkClient::Impl final : public NCImpl4 {
public:
Impl(int inst, std::string_view id, net::ILocalStorage& localStorage,
IConnectionList& connList, wpi::Logger& logger)
: NCImpl4{inst, id, localStorage, connList, logger} {}
};
NetworkClient::NetworkClient(int inst, std::string_view id,
net::ILocalStorage& localStorage,
IConnectionList& connList, wpi::Logger& logger)
: m_impl{std::make_unique<Impl>(inst, id, localStorage, connList, logger)} {
}
NetworkClient::~NetworkClient() {
m_impl->m_localStorage.ClearNetwork();
m_impl->m_connList.ClearConnections();
}
void NetworkClient::SetServers(
wpi::span<const std::pair<std::string, unsigned int>> servers) {
m_impl->SetServers(servers, NT_DEFAULT_PORT4);
}
void NetworkClient::StartDSClient(unsigned int port) {
m_impl->StartDSClient(port);
}
void NetworkClient::StopDSClient() {
m_impl->StopDSClient();
}
void NetworkClient::FlushLocal() {
m_impl->m_loopRunner.ExecAsync([this](uv::Loop&) { m_impl->HandleLocal(); });
}
void NetworkClient::Flush() {
m_impl->m_loopRunner.ExecAsync([this](uv::Loop&) {
m_impl->HandleLocal();
m_impl->m_clientImpl->SendValues(m_impl->m_loop.Now().count());
});
}
class NetworkClient3::Impl final : public NCImpl3 {
public:
Impl(int inst, std::string_view id, net::ILocalStorage& localStorage,
IConnectionList& connList, wpi::Logger& logger)
: NCImpl3{inst, id, localStorage, connList, logger} {}
};
NetworkClient3::NetworkClient3(int inst, std::string_view id,
net::ILocalStorage& localStorage,
IConnectionList& connList, wpi::Logger& logger)
: m_impl{std::make_unique<Impl>(inst, id, localStorage, connList, logger)} {
}
NetworkClient3::~NetworkClient3() {
m_impl->m_localStorage.ClearNetwork();
m_impl->m_connList.ClearConnections();
}
void NetworkClient3::SetServers(
wpi::span<const std::pair<std::string, unsigned int>> servers) {
m_impl->SetServers(servers, NT_DEFAULT_PORT3);
}
void NetworkClient3::StartDSClient(unsigned int port) {
m_impl->StartDSClient(port);
}
void NetworkClient3::StopDSClient() {
m_impl->StopDSClient();
}
void NetworkClient3::FlushLocal() {
if (auto async = m_impl->m_flushLocalAtomic.load(std::memory_order_relaxed)) {
async->UnsafeSend();
}
}
void NetworkClient3::Flush() {
if (auto async = m_impl->m_flushAtomic.load(std::memory_order_relaxed)) {
async->UnsafeSend();
}
}

View File

@@ -0,0 +1,68 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source 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 <utility>
#include "INetworkClient.h"
#include "ntcore_cpp.h"
namespace wpi {
class Logger;
} // namespace wpi
namespace nt::net {
class ILocalStorage;
} // namespace nt::net
namespace nt {
class IConnectionList;
class NetworkClient final : public INetworkClient {
public:
NetworkClient(int inst, std::string_view id, net::ILocalStorage& localStorage,
IConnectionList& connList, wpi::Logger& logger);
~NetworkClient() final;
void SetServers(
wpi::span<const std::pair<std::string, unsigned int>> servers) final;
void StartDSClient(unsigned int port) final;
void StopDSClient() final;
void FlushLocal() final;
void Flush() final;
private:
class Impl;
std::unique_ptr<Impl> m_impl;
};
class NetworkClient3 final : public INetworkClient {
public:
NetworkClient3(int inst, std::string_view id,
net::ILocalStorage& localStorage, IConnectionList& connList,
wpi::Logger& logger);
~NetworkClient3() final;
void SetServers(
wpi::span<const std::pair<std::string, unsigned int>> servers) final;
void StartDSClient(unsigned int port) final;
void StopDSClient() final;
void FlushLocal() final;
void Flush() final;
private:
class Impl;
std::unique_ptr<Impl> m_impl;
};
} // namespace nt

View File

@@ -1,380 +0,0 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#include "NetworkConnection.h"
#include <utility>
#include <wpi/timestamp.h>
#include <wpinet/NetworkStream.h>
#include <wpinet/raw_socket_istream.h>
#include "IConnectionNotifier.h"
#include "Log.h"
#include "WireDecoder.h"
#include "WireEncoder.h"
using namespace nt;
NetworkConnection::NetworkConnection(unsigned int uid,
std::unique_ptr<wpi::NetworkStream> stream,
IConnectionNotifier& notifier,
wpi::Logger& logger,
HandshakeFunc handshake,
Message::GetEntryTypeFunc get_entry_type)
: m_uid(uid),
m_stream(std::move(stream)),
m_notifier(notifier),
m_logger(logger),
m_handshake(std::move(handshake)),
m_get_entry_type(std::move(get_entry_type)),
m_state(kCreated) {
m_active = false;
m_proto_rev = 0x0300;
m_last_update = 0;
// turn off Nagle algorithm; we bundle packets for transmission
m_stream->setNoDelay();
}
NetworkConnection::~NetworkConnection() {
Stop();
}
void NetworkConnection::Start() {
if (m_active) {
return;
}
m_active = true;
set_state(kInit);
// clear queue
while (!m_outgoing.empty()) {
m_outgoing.pop();
}
// reset shutdown flags
{
std::scoped_lock lock(m_shutdown_mutex);
m_read_shutdown = false;
m_write_shutdown = false;
}
// start threads
m_write_thread = std::thread(&NetworkConnection::WriteThreadMain, this);
m_read_thread = std::thread(&NetworkConnection::ReadThreadMain, this);
}
void NetworkConnection::Stop() {
DEBUG2("NetworkConnection stopping ({})", fmt::ptr(this));
set_state(kDead);
m_active = false;
// closing the stream so the read thread terminates
if (m_stream) {
m_stream->close();
}
// send an empty outgoing message set so the write thread terminates
m_outgoing.push(Outgoing());
// wait for threads to terminate, with timeout
if (m_write_thread.joinable()) {
std::unique_lock lock(m_shutdown_mutex);
auto timeout_time =
std::chrono::steady_clock::now() + std::chrono::milliseconds(200);
if (m_write_shutdown_cv.wait_until(lock, timeout_time,
[&] { return m_write_shutdown; })) {
m_write_thread.join();
} else {
m_write_thread.detach(); // timed out, detach it
}
}
if (m_read_thread.joinable()) {
std::unique_lock lock(m_shutdown_mutex);
auto timeout_time =
std::chrono::steady_clock::now() + std::chrono::milliseconds(200);
if (m_read_shutdown_cv.wait_until(lock, timeout_time,
[&] { return m_read_shutdown; })) {
m_read_thread.join();
} else {
m_read_thread.detach(); // timed out, detach it
}
}
// clear queue
while (!m_outgoing.empty()) {
m_outgoing.pop();
}
}
ConnectionInfo NetworkConnection::info() const {
return ConnectionInfo{remote_id(), std::string{m_stream->getPeerIP()},
static_cast<unsigned int>(m_stream->getPeerPort()),
m_last_update, m_proto_rev};
}
unsigned int NetworkConnection::proto_rev() const {
return m_proto_rev;
}
void NetworkConnection::set_proto_rev(unsigned int proto_rev) {
m_proto_rev = proto_rev;
}
NetworkConnection::State NetworkConnection::state() const {
std::scoped_lock lock(m_state_mutex);
return m_state;
}
void NetworkConnection::set_state(State state) {
std::scoped_lock lock(m_state_mutex);
// Don't update state any more once we've died
if (m_state == kDead) {
return;
}
// One-shot notify state changes
if (m_state != kActive && state == kActive) {
m_notifier.NotifyConnection(true, info());
}
if (m_state != kDead && state == kDead) {
m_notifier.NotifyConnection(false, info());
}
m_state = state;
}
std::string NetworkConnection::remote_id() const {
std::scoped_lock lock(m_remote_id_mutex);
return m_remote_id;
}
void NetworkConnection::set_remote_id(std::string_view remote_id) {
std::scoped_lock lock(m_remote_id_mutex);
m_remote_id = remote_id;
}
void NetworkConnection::ReadThreadMain() {
wpi::raw_socket_istream is(*m_stream);
WireDecoder decoder(is, m_proto_rev, m_logger);
set_state(kHandshake);
if (!m_handshake(
*this,
[&] {
decoder.set_proto_rev(m_proto_rev);
auto msg = Message::Read(decoder, m_get_entry_type);
if (!msg && decoder.error()) {
DEBUG0("error reading in handshake: {}", decoder.error());
}
return msg;
},
[&](auto msgs) {
m_outgoing.emplace(std::vector<std::shared_ptr<Message>>(
msgs.begin(), msgs.end()));
})) {
set_state(kDead);
m_active = false;
goto done;
}
set_state(kActive);
while (m_active) {
if (!m_stream) {
break;
}
decoder.set_proto_rev(m_proto_rev);
decoder.Reset();
auto msg = Message::Read(decoder, m_get_entry_type);
if (!msg) {
if (decoder.error()) {
INFO("read error: {}", decoder.error());
}
// terminate connection on bad message
if (m_stream) {
m_stream->close();
}
break;
}
DEBUG3("received type={} with str={} id={} seq_num={}",
static_cast<int>(msg->type()), msg->str(), msg->id(),
msg->seq_num_uid());
m_last_update = Now();
m_process_incoming(std::move(msg), this);
}
DEBUG2("read thread died ({})", fmt::ptr(this));
set_state(kDead);
m_active = false;
m_outgoing.push(Outgoing()); // also kill write thread
done:
// use condition variable to signal thread shutdown
{
std::scoped_lock lock(m_shutdown_mutex);
m_read_shutdown = true;
m_read_shutdown_cv.notify_one();
}
}
void NetworkConnection::WriteThreadMain() {
WireEncoder encoder(m_proto_rev);
while (m_active) {
auto msgs = m_outgoing.pop();
DEBUG4("{}", "write thread woke up");
if (msgs.empty()) {
continue;
}
encoder.set_proto_rev(m_proto_rev);
encoder.Reset();
DEBUG3("sending {} messages", msgs.size());
for (auto& msg : msgs) {
if (msg) {
DEBUG3("sending type={} with str={} id={} seq_num={}",
static_cast<int>(msg->type()), msg->str(), msg->id(),
msg->seq_num_uid());
msg->Write(encoder);
}
}
wpi::NetworkStream::Error err;
if (!m_stream) {
break;
}
if (encoder.size() == 0) {
continue;
}
if (m_stream->send(encoder.data(), encoder.size(), &err) == 0) {
break;
}
DEBUG4("sent {} bytes", encoder.size());
}
DEBUG2("write thread died ({})", fmt::ptr(this));
set_state(kDead);
m_active = false;
if (m_stream) {
m_stream->close(); // also kill read thread
}
// use condition variable to signal thread shutdown
{
std::scoped_lock lock(m_shutdown_mutex);
m_write_shutdown = true;
m_write_shutdown_cv.notify_one();
}
}
void NetworkConnection::QueueOutgoing(std::shared_ptr<Message> msg) {
std::scoped_lock lock(m_pending_mutex);
// Merge with previous. One case we don't combine: delete/assign loop.
switch (msg->type()) {
case Message::kEntryAssign:
case Message::kEntryUpdate: {
// don't do this for unassigned id's
unsigned int id = msg->id();
if (id == 0xffff) {
m_pending_outgoing.push_back(msg);
break;
}
if (id < m_pending_update.size() && m_pending_update[id].first != 0) {
// overwrite the previous one for this id
auto& oldmsg = m_pending_outgoing[m_pending_update[id].first - 1];
if (oldmsg && oldmsg->Is(Message::kEntryAssign) &&
msg->Is(Message::kEntryUpdate)) {
// need to update assignment with new seq_num and value
oldmsg = Message::EntryAssign(oldmsg->str(), id, msg->seq_num_uid(),
msg->value(), oldmsg->flags());
} else {
oldmsg = msg; // easy update
}
} else {
// new, but remember it
size_t pos = m_pending_outgoing.size();
m_pending_outgoing.push_back(msg);
if (id >= m_pending_update.size()) {
m_pending_update.resize(id + 1);
}
m_pending_update[id].first = pos + 1;
}
break;
}
case Message::kEntryDelete: {
// don't do this for unassigned id's
unsigned int id = msg->id();
if (id == 0xffff) {
m_pending_outgoing.push_back(msg);
break;
}
// clear previous updates
if (id < m_pending_update.size()) {
if (m_pending_update[id].first != 0) {
m_pending_outgoing[m_pending_update[id].first - 1].reset();
m_pending_update[id].first = 0;
}
if (m_pending_update[id].second != 0) {
m_pending_outgoing[m_pending_update[id].second - 1].reset();
m_pending_update[id].second = 0;
}
}
// add deletion
m_pending_outgoing.push_back(msg);
break;
}
case Message::kFlagsUpdate: {
// don't do this for unassigned id's
unsigned int id = msg->id();
if (id == 0xffff) {
m_pending_outgoing.push_back(msg);
break;
}
if (id < m_pending_update.size() && m_pending_update[id].second != 0) {
// overwrite the previous one for this id
m_pending_outgoing[m_pending_update[id].second - 1] = msg;
} else {
// new, but remember it
size_t pos = m_pending_outgoing.size();
m_pending_outgoing.push_back(msg);
if (id >= m_pending_update.size()) {
m_pending_update.resize(id + 1);
}
m_pending_update[id].second = pos + 1;
}
break;
}
case Message::kClearEntries: {
// knock out all previous assigns/updates!
for (auto& i : m_pending_outgoing) {
if (!i) {
continue;
}
auto t = i->type();
if (t == Message::kEntryAssign || t == Message::kEntryUpdate ||
t == Message::kFlagsUpdate || t == Message::kEntryDelete ||
t == Message::kClearEntries) {
i.reset();
}
}
m_pending_update.resize(0);
m_pending_outgoing.push_back(msg);
break;
}
default:
m_pending_outgoing.push_back(msg);
break;
}
}
void NetworkConnection::PostOutgoing(bool keep_alive) {
std::scoped_lock lock(m_pending_mutex);
auto now = std::chrono::steady_clock::now();
if (m_pending_outgoing.empty()) {
if (!keep_alive) {
return;
}
// send keep-alives once a second (if no other messages have been sent)
if ((now - m_last_post) < std::chrono::seconds(1)) {
return;
}
m_outgoing.emplace(Outgoing{Message::KeepAlive()});
} else {
m_outgoing.emplace(std::move(m_pending_outgoing));
m_pending_outgoing.resize(0);
m_pending_update.resize(0);
}
m_last_post = now;
} // NOLINT

View File

@@ -1,124 +0,0 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source 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 NTCORE_NETWORKCONNECTION_H_
#define NTCORE_NETWORKCONNECTION_H_
#include <stdint.h>
#include <atomic>
#include <chrono>
#include <memory>
#include <string>
#include <string_view>
#include <thread>
#include <utility>
#include <vector>
#include <wpi/ConcurrentQueue.h>
#include <wpi/condition_variable.h>
#include <wpi/mutex.h>
#include <wpi/span.h>
#include "INetworkConnection.h"
#include "Message.h"
#include "ntcore_cpp.h"
namespace wpi {
class Logger;
class NetworkStream;
} // namespace wpi
namespace nt {
class IConnectionNotifier;
class NetworkConnection : public INetworkConnection {
public:
using HandshakeFunc = std::function<bool(
NetworkConnection& conn,
std::function<std::shared_ptr<Message>()> get_msg,
std::function<void(wpi::span<std::shared_ptr<Message>>)> send_msgs)>;
using ProcessIncomingFunc =
std::function<void(std::shared_ptr<Message>, NetworkConnection*)>;
using Outgoing = std::vector<std::shared_ptr<Message>>;
using OutgoingQueue = wpi::ConcurrentQueue<Outgoing>;
NetworkConnection(unsigned int uid,
std::unique_ptr<wpi::NetworkStream> stream,
IConnectionNotifier& notifier, wpi::Logger& logger,
HandshakeFunc handshake,
Message::GetEntryTypeFunc get_entry_type);
~NetworkConnection() override;
// Set the input processor function. This must be called before Start().
void set_process_incoming(ProcessIncomingFunc func) {
m_process_incoming = func;
}
void Start();
void Stop();
ConnectionInfo info() const final;
bool active() const { return m_active; }
wpi::NetworkStream& stream() { return *m_stream; }
void QueueOutgoing(std::shared_ptr<Message> msg) final;
void PostOutgoing(bool keep_alive) override;
unsigned int uid() const { return m_uid; }
unsigned int proto_rev() const final;
void set_proto_rev(unsigned int proto_rev) final;
State state() const final;
void set_state(State state) final;
std::string remote_id() const;
void set_remote_id(std::string_view remote_id);
uint64_t last_update() const { return m_last_update; }
NetworkConnection(const NetworkConnection&) = delete;
NetworkConnection& operator=(const NetworkConnection&) = delete;
private:
void ReadThreadMain();
void WriteThreadMain();
unsigned int m_uid;
std::unique_ptr<wpi::NetworkStream> m_stream;
IConnectionNotifier& m_notifier;
wpi::Logger& m_logger;
OutgoingQueue m_outgoing;
HandshakeFunc m_handshake;
Message::GetEntryTypeFunc m_get_entry_type;
ProcessIncomingFunc m_process_incoming;
std::thread m_read_thread;
std::thread m_write_thread;
std::atomic_bool m_active;
std::atomic_uint m_proto_rev;
mutable wpi::mutex m_state_mutex;
State m_state;
mutable wpi::mutex m_remote_id_mutex;
std::string m_remote_id;
std::atomic_ullong m_last_update;
std::chrono::steady_clock::time_point m_last_post;
wpi::mutex m_pending_mutex;
Outgoing m_pending_outgoing;
std::vector<std::pair<size_t, size_t>> m_pending_update;
// Condition variables for shutdown
wpi::mutex m_shutdown_mutex;
wpi::condition_variable m_read_shutdown_cv;
wpi::condition_variable m_write_shutdown_cv;
bool m_read_shutdown = false;
bool m_write_shutdown = false;
};
} // namespace nt
#endif // NTCORE_NETWORKCONNECTION_H_

View File

@@ -0,0 +1,556 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#include "NetworkServer.h"
#include <stdint.h>
#include <atomic>
#include <system_error>
#include <vector>
#include <wpi/StringExtras.h>
#include <wpi/fs.h>
#include <wpi/mutex.h>
#include <wpi/raw_istream.h>
#include <wpi/raw_ostream.h>
#include <wpinet/EventLoopRunner.h>
#include <wpinet/HttpWebSocketServerConnection.h>
#include <wpinet/UrlParser.h>
#include <wpinet/uv/Async.h>
#include <wpinet/uv/Tcp.h>
#include <wpinet/uv/Work.h>
#include <wpinet/uv/util.h>
#include "IConnectionList.h"
#include "InstanceImpl.h"
#include "Log.h"
#include "net/Message.h"
#include "net/NetworkLoopQueue.h"
#include "net/ServerImpl.h"
#include "net/WebSocketConnection.h"
#include "net3/UvStreamConnection3.h"
using namespace nt;
namespace uv = wpi::uv;
// use a larger max message size for websockets
static constexpr size_t kMaxMessageSize = 2 * 1024 * 1024;
namespace {
class NSImpl;
class ServerConnection {
public:
ServerConnection(NSImpl& server, std::string_view addr, unsigned int port,
wpi::Logger& logger)
: m_server{server},
m_connInfo{fmt::format("{}:{}", addr, port)},
m_logger{logger} {
m_info.remote_ip = addr;
m_info.remote_port = port;
}
int GetClientId() const { return m_clientId; }
protected:
void SetupPeriodicTimer();
void UpdatePeriodicTimer(uint32_t repeatMs);
void ConnectionClosed();
NSImpl& m_server;
ConnectionInfo m_info;
std::string m_connInfo;
wpi::Logger& m_logger;
int m_clientId;
private:
std::shared_ptr<uv::Timer> m_sendValuesTimer;
};
class ServerConnection4 final
: public ServerConnection,
public wpi::HttpWebSocketServerConnection<ServerConnection4> {
public:
ServerConnection4(std::shared_ptr<uv::Stream> stream, NSImpl& server,
std::string_view addr, unsigned int port,
wpi::Logger& logger)
: ServerConnection{server, addr, port, logger},
HttpWebSocketServerConnection(stream, {"networktables.first.wpi.edu"}) {
m_info.protocol_version = 0x0400;
}
private:
void ProcessRequest() final;
void ProcessWsUpgrade() final;
std::unique_ptr<net::WebSocketConnection> m_wire;
};
class ServerConnection3 : public ServerConnection {
public:
ServerConnection3(std::shared_ptr<uv::Stream> stream, NSImpl& server,
std::string_view addr, unsigned int port,
wpi::Logger& logger);
private:
std::unique_ptr<net3::UvStreamConnection3> m_wire;
};
class NSImpl {
public:
NSImpl(std::string_view persistFilename, std::string_view listenAddress,
unsigned int port3, unsigned int port4,
net::ILocalStorage& localStorage, IConnectionList& connList,
wpi::Logger& logger, std::function<void()> initDone);
void HandleLocal();
void LoadPersistent();
void Init();
void AddConnection(ServerConnection* conn, const ConnectionInfo& info);
void RemoveConnection(ServerConnection* conn);
net::ILocalStorage& m_localStorage;
IConnectionList& m_connList;
wpi::Logger& m_logger;
std::function<void()> m_initDone;
std::string m_persistentData;
std::string m_persistentFilename;
std::string m_listenAddress;
unsigned int m_port3;
unsigned int m_port4;
// used only from loop
std::shared_ptr<uv::Timer> m_readLocalTimer;
std::shared_ptr<uv::Timer> m_savePersistentTimer;
std::shared_ptr<uv::Async<>> m_flushLocal;
std::shared_ptr<uv::Async<>> m_flush;
std::vector<net::ClientMessage> m_localMsgs;
net::ServerImpl m_serverImpl;
// shared with user (must be atomic or mutex-protected)
std::atomic<uv::Async<>*> m_flushLocalAtomic{nullptr};
std::atomic<uv::Async<>*> m_flushAtomic{nullptr};
mutable wpi::mutex m_mutex;
struct Connection {
ServerConnection* conn;
int connHandle;
};
std::vector<Connection> m_connections;
net::NetworkLoopQueue m_localQueue;
wpi::EventLoopRunner m_loopRunner;
wpi::uv::Loop& m_loop;
};
} // namespace
void ServerConnection::SetupPeriodicTimer() {
m_sendValuesTimer = uv::Timer::Create(m_server.m_loop);
m_sendValuesTimer->timeout.connect([this] {
m_server.HandleLocal();
m_server.m_serverImpl.SendValues(m_clientId, m_server.m_loop.Now().count());
});
}
void ServerConnection::UpdatePeriodicTimer(uint32_t repeatMs) {
if (repeatMs == UINT32_MAX) {
m_sendValuesTimer->Stop();
} else {
m_sendValuesTimer->Start(uv::Timer::Time{repeatMs},
uv::Timer::Time{repeatMs});
}
}
void ServerConnection::ConnectionClosed() {
// don't call back into m_server if it's being destroyed
if (!m_sendValuesTimer->IsLoopClosing()) {
m_server.m_serverImpl.RemoveClient(m_clientId);
m_server.RemoveConnection(this);
}
m_sendValuesTimer->Close();
}
void ServerConnection4::ProcessRequest() {
DEBUG1("HTTP request: '{}'", m_request.GetUrl());
wpi::UrlParser url{m_request.GetUrl(),
m_request.GetMethod() == wpi::HTTP_CONNECT};
if (!url.IsValid()) {
// failed to parse URL
SendError(400);
return;
}
std::string_view path;
if (url.HasPath()) {
path = url.GetPath();
}
DEBUG4("path: \"{}\"", path);
std::string_view query;
if (url.HasQuery()) {
query = url.GetQuery();
}
DEBUG4("query: \"{}\"\n", query);
const bool isGET = m_request.GetMethod() == wpi::HTTP_GET;
if (isGET && path == "/") {
// build HTML root page
SendResponse(200, "OK", "text/html",
"<html><head><title>NetworkTables</title></head>"
"<body><p>WebSockets must be used to access NetworkTables."
"</body></html>");
} else if (isGET && path == "/nt/persistent.json") {
SendResponse(200, "OK", "application/json",
m_server.m_serverImpl.DumpPersistent());
} else {
SendError(404, "Resource not found");
}
}
void ServerConnection4::ProcessWsUpgrade() {
// get name from URL
wpi::UrlParser url{m_request.GetUrl(), false};
std::string_view path;
if (url.HasPath()) {
path = url.GetPath();
}
DEBUG4("path: '{}'", path);
std::string_view name;
if (wpi::starts_with(path, "/nt/")) {
name = wpi::drop_front(path, 4);
}
if (name.empty()) {
INFO("invalid path '{}' (from {}), closing", path, m_connInfo);
m_websocket->Fail(404, fmt::format("invalid path '{}'", path));
return;
}
m_websocket->SetMaxMessageSize(kMaxMessageSize);
m_websocket->open.connect([this, name = std::string{name}](std::string_view) {
m_wire = std::make_unique<net::WebSocketConnection>(*m_websocket);
// TODO: set local flag appropriately
m_clientId = m_server.m_serverImpl.AddClient(
name, m_connInfo, false, *m_wire,
[this](uint32_t repeatMs) { UpdatePeriodicTimer(repeatMs); });
if (m_clientId < 0) {
INFO("duplicate connection name '{}' (from {}), closing", name,
m_connInfo);
m_websocket->Fail(409, fmt::format("duplicate name '{}'", name));
return;
}
m_info.remote_id = name;
m_server.AddConnection(this, m_info);
m_websocket->closed.connect([this](uint16_t, std::string_view reason) {
INFO("NT4 connection '{}' closed (from {})", m_info.remote_id,
m_connInfo);
ConnectionClosed();
});
m_websocket->text.connect([this](std::string_view data, bool) {
m_server.m_serverImpl.ProcessIncomingText(m_clientId, data);
});
m_websocket->binary.connect([this](wpi::span<const uint8_t> data, bool) {
m_server.m_serverImpl.ProcessIncomingBinary(m_clientId, data);
});
SetupPeriodicTimer();
});
}
ServerConnection3::ServerConnection3(std::shared_ptr<uv::Stream> stream,
NSImpl& server, std::string_view addr,
unsigned int port, wpi::Logger& logger)
: ServerConnection{server, addr, port, logger},
m_wire{std::make_unique<net3::UvStreamConnection3>(*stream)} {
m_info.remote_ip = addr;
m_info.remote_port = port;
// TODO: set local flag appropriately
m_clientId = m_server.m_serverImpl.AddClient3(
m_connInfo, false, *m_wire,
[this](std::string_view name, uint16_t proto) {
m_info.remote_id = name;
m_info.protocol_version = proto;
m_server.AddConnection(this, m_info);
},
[this](uint32_t repeatMs) { UpdatePeriodicTimer(repeatMs); });
stream->error.connect([this](uv::Error err) {
if (!m_wire->GetDisconnectReason().empty()) {
return;
}
m_wire->Disconnect(fmt::format("stream error: {}", err.name()));
m_wire->GetStream().Shutdown([this] { m_wire->GetStream().Close(); });
});
stream->end.connect([this] {
if (!m_wire->GetDisconnectReason().empty()) {
return;
}
m_wire->Disconnect("remote end closed connection");
m_wire->GetStream().Shutdown([this] { m_wire->GetStream().Close(); });
});
stream->closed.connect([this] {
INFO("NT3 connection '{}' closed (from {}): {}", m_info.remote_id,
m_connInfo, m_wire->GetDisconnectReason());
ConnectionClosed();
});
stream->data.connect([this](uv::Buffer& buf, size_t size) {
m_server.m_serverImpl.ProcessIncomingBinary(
m_clientId, {reinterpret_cast<const uint8_t*>(buf.base), size});
});
stream->StartRead();
SetupPeriodicTimer();
}
NSImpl::NSImpl(std::string_view persistentFilename,
std::string_view listenAddress, unsigned int port3,
unsigned int port4, net::ILocalStorage& localStorage,
IConnectionList& connList, wpi::Logger& logger,
std::function<void()> initDone)
: m_localStorage{localStorage},
m_connList{connList},
m_logger{logger},
m_initDone{std::move(initDone)},
m_persistentFilename{persistentFilename},
m_listenAddress{wpi::trim(listenAddress)},
m_port3{port3},
m_port4{port4},
m_serverImpl{logger},
m_localQueue{logger},
m_loop(*m_loopRunner.GetLoop()) {
m_localMsgs.reserve(net::NetworkLoopQueue::kInitialQueueSize);
m_loopRunner.ExecAsync([=](uv::Loop& loop) {
// connect local storage to server
{
net::ServerStartup startup{m_serverImpl};
m_localStorage.StartNetwork(startup);
}
m_localStorage.SetNetwork(&m_localQueue);
m_serverImpl.SetLocal(&m_localStorage);
// load persistent file first, then initialize
uv::QueueWork(
m_loop, [this] { LoadPersistent(); }, [this] { Init(); });
});
}
void NSImpl::HandleLocal() {
m_localQueue.ReadQueue(&m_localMsgs);
m_serverImpl.HandleLocal(m_localMsgs);
}
void NSImpl::LoadPersistent() {
std::error_code ec;
auto size = fs::file_size(m_persistentFilename, ec);
wpi::raw_fd_istream is{m_persistentFilename, ec};
if (ec.value() != 0) {
INFO("could not open persistent file '{}': {}", m_persistentFilename,
ec.message());
return;
}
is.readinto(m_persistentData, size);
DEBUG4("read data: {}", m_persistentData);
if (is.has_error()) {
WARNING("{}", "error reading persistent file");
return;
}
}
static void SavePersistent(std::string_view filename, std::string_view data) {
// write to temporary file
auto tmp = fmt::format("{}.tmp", filename);
std::error_code ec;
wpi::raw_fd_ostream os{tmp, ec, fs::F_Text};
os << data;
os.close();
if (os.has_error()) {
fs::remove(tmp);
return;
}
// move to real file
auto bak = fmt::format("{}.bck", filename);
fs::remove(bak, ec);
fs::rename(filename, bak, ec);
fs::rename(tmp, filename, ec);
if (ec.value() != 0) {
// attempt to restore backup
fs::rename(bak, filename, ec);
}
}
void NSImpl::Init() {
auto errs = m_serverImpl.LoadPersistent(m_persistentData);
if (!errs.empty()) {
WARNING("error reading persistent file: {}", errs);
}
// set up timers
m_readLocalTimer = uv::Timer::Create(m_loop);
m_readLocalTimer->timeout.connect([this] {
HandleLocal();
m_serverImpl.SendControl(m_loop.Now().count());
});
m_readLocalTimer->Start(uv::Timer::Time{100}, uv::Timer::Time{100});
m_savePersistentTimer = uv::Timer::Create(m_loop);
m_savePersistentTimer->timeout.connect([this] {
if (m_serverImpl.PersistentChanged()) {
uv::QueueWork(
m_loop,
[fn = m_persistentFilename, data = m_serverImpl.DumpPersistent()] {
SavePersistent(fn, data);
},
nullptr);
}
});
m_savePersistentTimer->Start(uv::Timer::Time{1000}, uv::Timer::Time{1000});
// set up flush async
m_flush = uv::Async<>::Create(m_loop);
m_flush->wakeup.connect([this] {
HandleLocal();
for (auto&& conn : m_connections) {
m_serverImpl.SendValues(conn.conn->GetClientId(), m_loop.Now().count());
}
});
m_flushAtomic = m_flush.get();
m_flushLocal = uv::Async<>::Create(m_loop);
m_flushLocal->wakeup.connect([this] { HandleLocal(); });
m_flushLocalAtomic = m_flushLocal.get();
INFO("Listening on NT3 port {}, NT4 port {}", m_port3, m_port4);
if (m_port3 != 0) {
auto tcp3 = uv::Tcp::Create(m_loop);
tcp3->error.connect([logger = &m_logger](uv::Error err) {
WPI_INFO(*logger, "NT3 server socket error: {}", err.str());
});
tcp3->Bind(m_listenAddress, m_port3);
// when we get a NT3 connection, accept it and start reading
tcp3->connection.connect([this, srv = tcp3.get()] {
auto tcp = srv->Accept();
if (!tcp) {
return;
}
tcp->error.connect([logger = &m_logger](uv::Error err) {
WPI_INFO(*logger, "NT3 socket error: {}", err.str());
});
tcp->SetNoDelay(true);
std::string peerAddr;
unsigned int peerPort = 0;
if (uv::AddrToName(tcp->GetPeer(), &peerAddr, &peerPort) == 0) {
INFO("Got a NT3 connection from {} port {}", peerAddr, peerPort);
} else {
INFO("{}", "Got a NT3 connection from unknown");
}
auto conn = std::make_shared<ServerConnection3>(tcp, *this, peerAddr,
peerPort, m_logger);
tcp->SetData(conn);
});
tcp3->Listen();
}
if (m_port4 != 0) {
auto tcp4 = uv::Tcp::Create(m_loop);
tcp4->error.connect([logger = &m_logger](uv::Error err) {
WPI_INFO(*logger, "NT4 server socket error: {}", err.str());
});
tcp4->Bind(m_listenAddress, m_port4);
// when we get a NT4 connection, accept it and start reading
tcp4->connection.connect([this, srv = tcp4.get()] {
auto tcp = srv->Accept();
if (!tcp) {
return;
}
tcp->error.connect([logger = &m_logger](uv::Error err) {
WPI_INFO(*logger, "NT4 socket error: {}", err.str());
});
tcp->SetNoDelay(true);
std::string peerAddr;
unsigned int peerPort = 0;
if (uv::AddrToName(tcp->GetPeer(), &peerAddr, &peerPort) == 0) {
INFO("Got a NT4 connection from {} port {}", peerAddr, peerPort);
} else {
INFO("{}", "Got a NT4 connection from unknown");
}
auto conn = std::make_shared<ServerConnection4>(tcp, *this, peerAddr,
peerPort, m_logger);
tcp->SetData(conn);
});
tcp4->Listen();
}
if (m_initDone) {
DEBUG4("{}", "NetworkServer initDone()");
m_initDone();
m_initDone = nullptr;
}
}
void NSImpl::AddConnection(ServerConnection* conn, const ConnectionInfo& info) {
std::scoped_lock lock{m_mutex};
m_connections.emplace_back(Connection{conn, m_connList.AddConnection(info)});
m_serverImpl.ConnectionsChanged(m_connList.GetConnections());
}
void NSImpl::RemoveConnection(ServerConnection* conn) {
std::scoped_lock lock{m_mutex};
auto it = std::find_if(m_connections.begin(), m_connections.end(),
[=](auto&& c) { return c.conn == conn; });
if (it != m_connections.end()) {
m_connList.RemoveConnection(it->connHandle);
m_connections.erase(it);
m_serverImpl.ConnectionsChanged(m_connList.GetConnections());
}
}
class NetworkServer::Impl final : public NSImpl {
public:
Impl(std::string_view persistFilename, std::string_view listenAddress,
unsigned int port3, unsigned int port4, net::ILocalStorage& localStorage,
IConnectionList& connList, wpi::Logger& logger,
std::function<void()> initDone)
: NSImpl{persistFilename, listenAddress, port3, port4,
localStorage, connList, logger, std::move(initDone)} {}
};
NetworkServer::NetworkServer(std::string_view persistFilename,
std::string_view listenAddress, unsigned int port3,
unsigned int port4,
net::ILocalStorage& localStorage,
IConnectionList& connList, wpi::Logger& logger,
std::function<void()> initDone)
: m_impl{std::make_unique<Impl>(persistFilename, listenAddress, port3,
port4, localStorage, connList, logger,
std::move(initDone))} {}
NetworkServer::~NetworkServer() {
m_impl->m_localStorage.ClearNetwork();
m_impl->m_connList.ClearConnections();
}
void NetworkServer::FlushLocal() {
if (auto async = m_impl->m_flushLocalAtomic.load(std::memory_order_relaxed)) {
async->UnsafeSend();
}
}
void NetworkServer::Flush() {
if (auto async = m_impl->m_flushAtomic.load(std::memory_order_relaxed)) {
async->UnsafeSend();
}
}

View File

@@ -0,0 +1,42 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source 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 <functional>
#include <memory>
#include <string_view>
#include "ntcore_cpp.h"
namespace wpi {
class Logger;
} // namespace wpi
namespace nt::net {
class ILocalStorage;
} // namespace nt::net
namespace nt {
class IConnectionList;
class NetworkServer {
public:
NetworkServer(std::string_view persistentFilename,
std::string_view listenAddress, unsigned int port3,
unsigned int port4, net::ILocalStorage& localStorage,
IConnectionList& connList, wpi::Logger& logger,
std::function<void()> initDone);
~NetworkServer();
void FlushLocal();
void Flush();
private:
class Impl;
std::unique_ptr<Impl> m_impl;
};
} // namespace nt

View File

@@ -0,0 +1,36 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#include "PubSubOptions.h"
#include "ntcore_cpp.h"
using namespace nt;
nt::PubSubOptions::PubSubOptions(wpi::span<const PubSubOption> options) {
for (auto&& option : options) {
switch (option.type) {
case NT_PUBSUB_PERIODIC:
periodic = option.value;
break;
case NT_PUBSUB_SENDALL:
sendAll = option.value != 0;
if (sendAll) {
pollStorageSize = 20;
}
break;
case NT_PUBSUB_TOPICSONLY:
topicsOnly = option.value != 0;
break;
case NT_PUBSUB_KEEPDUPLICATES:
keepDuplicates = option.value != 0;
break;
case NT_PUBSUB_POLLSTORAGE:
pollStorageSize = static_cast<size_t>(option.value);
break;
default:
break;
}
}
}

View File

@@ -0,0 +1,27 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source 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/span.h>
#include "ntcore_cpp.h"
namespace nt {
// options built from array of PubSubOption
class PubSubOptions {
public:
PubSubOptions() = default;
explicit PubSubOptions(wpi::span<const PubSubOption> options);
double periodic = 0.1;
size_t pollStorageSize = 1;
bool sendAll = false;
bool topicsOnly = false;
bool prefixMatch = false;
bool keepDuplicates = false;
};
} // namespace nt

View File

@@ -1,51 +0,0 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#include "RpcServer.h"
using namespace nt;
RpcServer::RpcServer(int inst, wpi::Logger& logger)
: m_inst(inst), m_logger(logger) {}
void RpcServer::Start() {
DoStart(m_inst, m_logger);
}
unsigned int RpcServer::Add(
std::function<void(const RpcAnswer& answer)> callback) {
return DoAdd(callback);
}
unsigned int RpcServer::AddPolled(unsigned int poller_uid) {
return DoAdd(poller_uid);
}
void RpcServer::RemoveRpc(unsigned int rpc_uid) {
Remove(rpc_uid);
}
void RpcServer::ProcessRpc(unsigned int local_id, unsigned int call_uid,
std::string_view name, std::string_view params,
const ConnectionInfo& conn,
SendResponseFunc send_response,
unsigned int rpc_uid) {
Send(rpc_uid, Handle(m_inst, local_id, Handle::kEntry).handle(),
Handle(m_inst, call_uid, Handle::kRpcCall).handle(), name, params, conn,
send_response);
}
bool RpcServer::PostRpcResponse(unsigned int local_id, unsigned int call_uid,
std::string_view result) {
auto thr = GetThread();
auto i = thr->m_response_map.find(impl::RpcIdPair{local_id, call_uid});
if (i == thr->m_response_map.end()) {
WARNING("{}",
"posting RPC response to nonexistent call (or duplicate response)");
return false;
}
(i->getSecond())(result);
thr->m_response_map.erase(i);
return true;
}

View File

@@ -1,114 +0,0 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source 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 NTCORE_RPCSERVER_H_
#define NTCORE_RPCSERVER_H_
#include <utility>
#include <wpi/CallbackManager.h>
#include <wpi/DenseMap.h>
#include <wpi/mutex.h>
#include "Handle.h"
#include "IRpcServer.h"
#include "Log.h"
namespace nt {
namespace impl {
using RpcIdPair = std::pair<unsigned int, unsigned int>;
struct RpcNotifierData : public RpcAnswer {
RpcNotifierData(NT_Entry entry_, NT_RpcCall call_, std::string_view name_,
std::string_view params_, const ConnectionInfo& conn_,
IRpcServer::SendResponseFunc send_response_)
: RpcAnswer{entry_, call_, name_, params_, conn_},
send_response{std::move(send_response_)} {}
IRpcServer::SendResponseFunc send_response;
};
using RpcListenerData =
wpi::CallbackListenerData<std::function<void(const RpcAnswer& answer)>>;
class RpcServerThread
: public wpi::CallbackThread<RpcServerThread, RpcAnswer, RpcListenerData,
RpcNotifierData> {
public:
RpcServerThread(std::function<void()> on_start, std::function<void()> on_exit,
int inst, wpi::Logger& logger)
: CallbackThread(std::move(on_start), std::move(on_exit)),
m_inst(inst),
m_logger(logger) {}
bool Matches(const RpcListenerData& /*listener*/,
const RpcNotifierData& data) {
return !data.name.empty() && data.send_response;
}
void SetListener(RpcNotifierData* data, unsigned int /*listener_uid*/) {
unsigned int local_id = Handle{data->entry}.GetIndex();
unsigned int call_uid = Handle{data->call}.GetIndex();
RpcIdPair lookup_uid{local_id, call_uid};
m_response_map.insert(std::make_pair(lookup_uid, data->send_response));
}
void DoCallback(std::function<void(const RpcAnswer& call)> callback,
const RpcNotifierData& data) {
DEBUG4("rpc calling {}", data.name);
unsigned int local_id = Handle{data.entry}.GetIndex();
unsigned int call_uid = Handle{data.call}.GetIndex();
RpcIdPair lookup_uid{local_id, call_uid};
callback(data);
{
std::scoped_lock lock(m_mutex);
auto i = m_response_map.find(lookup_uid);
if (i != m_response_map.end()) {
// post an empty response and erase it
(i->getSecond())("");
m_response_map.erase(i);
}
}
}
int m_inst;
wpi::Logger& m_logger;
wpi::DenseMap<RpcIdPair, IRpcServer::SendResponseFunc> m_response_map;
};
} // namespace impl
class RpcServer
: public IRpcServer,
public wpi::CallbackManager<RpcServer, impl::RpcServerThread> {
friend class RpcServerTest;
friend class wpi::CallbackManager<RpcServer, impl::RpcServerThread>;
public:
RpcServer(int inst, wpi::Logger& logger);
void Start();
unsigned int Add(std::function<void(const RpcAnswer& answer)> callback);
unsigned int AddPolled(unsigned int poller_uid);
void RemoveRpc(unsigned int rpc_uid) override;
void ProcessRpc(unsigned int local_id, unsigned int call_uid,
std::string_view name, std::string_view params,
const ConnectionInfo& conn, SendResponseFunc send_response,
unsigned int rpc_uid) override;
bool PostRpcResponse(unsigned int local_id, unsigned int call_uid,
std::string_view result);
private:
int m_inst;
wpi::Logger& m_logger;
};
} // namespace nt
#endif // NTCORE_RPCSERVER_H_

File diff suppressed because it is too large Load Diff

View File

@@ -1,304 +0,0 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source 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 NTCORE_STORAGE_H_
#define NTCORE_STORAGE_H_
#include <stdint.h>
#include <atomic>
#include <cstddef>
#include <functional>
#include <memory>
#include <string>
#include <string_view>
#include <utility>
#include <vector>
#include <wpi/DenseMap.h>
#include <wpi/SmallSet.h>
#include <wpi/SmallVector.h>
#include <wpi/StringMap.h>
#include <wpi/UidVector.h>
#include <wpi/condition_variable.h>
#include <wpi/mutex.h>
#include <wpi/span.h>
#include "IStorage.h"
#include "Message.h"
#include "SequenceNumber.h"
#include "ntcore_cpp.h"
namespace wpi {
class Logger;
class raw_istream;
class raw_ostream;
} // namespace wpi
namespace nt {
class IEntryNotifier;
class INetworkConnection;
class IRpcServer;
class IStorageTest;
class Storage : public IStorage {
friend class StorageTest;
public:
Storage(IEntryNotifier& notifier, IRpcServer& rpcserver, wpi::Logger& logger);
Storage(const Storage&) = delete;
Storage& operator=(const Storage&) = delete;
~Storage() override;
// Accessors required by Dispatcher. An interface is used for
// generation of outgoing messages to break a dependency loop between
// Storage and Dispatcher.
void SetDispatcher(IDispatcher* dispatcher, bool server) override;
void ClearDispatcher() override;
// Required for wire protocol 2.0 to get the entry type of an entry when
// receiving entry updates (because the length/type is not provided in the
// message itself). Not used in wire protocol 3.0.
NT_Type GetMessageEntryType(unsigned int id) const override;
void ProcessIncoming(std::shared_ptr<Message> msg, INetworkConnection* conn,
std::weak_ptr<INetworkConnection> conn_weak) override;
void GetInitialAssignments(
INetworkConnection& conn,
std::vector<std::shared_ptr<Message>>* msgs) override;
void ApplyInitialAssignments(
INetworkConnection& conn, wpi::span<std::shared_ptr<Message>> msgs,
bool new_server,
std::vector<std::shared_ptr<Message>>* out_msgs) override;
// User functions. These are the actual implementations of the corresponding
// user API functions in ntcore_cpp.
std::shared_ptr<Value> GetEntryValue(std::string_view name) const;
std::shared_ptr<Value> GetEntryValue(unsigned int local_id) const;
bool SetDefaultEntryValue(std::string_view name,
std::shared_ptr<Value> value);
bool SetDefaultEntryValue(unsigned int local_id,
std::shared_ptr<Value> value);
bool SetEntryValue(std::string_view name, std::shared_ptr<Value> value);
bool SetEntryValue(unsigned int local_id, std::shared_ptr<Value> value);
void SetEntryTypeValue(std::string_view name, std::shared_ptr<Value> value);
void SetEntryTypeValue(unsigned int local_id, std::shared_ptr<Value> value);
void SetEntryFlags(std::string_view name, unsigned int flags);
void SetEntryFlags(unsigned int local_id, unsigned int flags);
unsigned int GetEntryFlags(std::string_view name) const;
unsigned int GetEntryFlags(unsigned int local_id) const;
void DeleteEntry(std::string_view name);
void DeleteEntry(unsigned int local_id);
void DeleteAllEntries();
std::vector<EntryInfo> GetEntryInfo(int inst, std::string_view prefix,
unsigned int types);
unsigned int AddListener(
std::string_view prefix,
std::function<void(const EntryNotification& event)> callback,
unsigned int flags) const;
unsigned int AddListener(
unsigned int local_id,
std::function<void(const EntryNotification& event)> callback,
unsigned int flags) const;
unsigned int AddPolledListener(unsigned int poller_uid,
std::string_view prefix,
unsigned int flags) const;
unsigned int AddPolledListener(unsigned int poller_uid, unsigned int local_id,
unsigned int flags) const;
unsigned int StartDataLog(wpi::log::DataLog& log, std::string_view prefix,
std::string_view log_prefix);
void StopDataLog(unsigned int uid);
// Index-only
unsigned int GetEntry(std::string_view name);
std::vector<unsigned int> GetEntries(std::string_view prefix,
unsigned int types);
EntryInfo GetEntryInfo(int inst, unsigned int local_id) const;
std::string GetEntryName(unsigned int local_id) const;
NT_Type GetEntryType(unsigned int local_id) const;
uint64_t GetEntryLastChange(unsigned int local_id) const;
// Filename-based save/load functions. Used both by periodic saves and
// accessible directly via the user API.
const char* SavePersistent(std::string_view filename,
bool periodic) const override;
const char* LoadPersistent(
std::string_view filename,
std::function<void(size_t line, const char* msg)> warn) override;
const char* SaveEntries(std::string_view filename,
std::string_view prefix) const;
const char* LoadEntries(
std::string_view filename, std::string_view prefix,
std::function<void(size_t line, const char* msg)> warn);
// Stream-based save/load functions (exposed for testing purposes). These
// implement the guts of the filename-based functions.
void SavePersistent(wpi::raw_ostream& os, bool periodic) const;
bool LoadEntries(wpi::raw_istream& is, std::string_view prefix,
bool persistent,
std::function<void(size_t line, const char* msg)> warn);
void SaveEntries(wpi::raw_ostream& os, std::string_view prefix) const;
// RPC configuration needs to come through here as RPC definitions are
// actually special Storage value types.
void CreateRpc(unsigned int local_id, std::string_view def,
unsigned int rpc_uid);
unsigned int CallRpc(unsigned int local_id, std::string_view params);
bool GetRpcResult(unsigned int local_id, unsigned int call_uid,
std::string* result);
bool GetRpcResult(unsigned int local_id, unsigned int call_uid,
std::string* result, double timeout, bool* timed_out);
void CancelRpcResult(unsigned int local_id, unsigned int call_uid);
private:
struct DataLoggerEntry {
DataLoggerEntry(wpi::log::DataLog* log, int entry, unsigned int logger_uid)
: log{log}, entry{entry}, logger_uid{logger_uid} {}
wpi::log::DataLog* log;
int entry;
unsigned int logger_uid;
};
struct DataLogger {
DataLogger() = default;
DataLogger(wpi::log::DataLog& log, std::string_view prefix,
std::string_view log_prefix)
: log{&log}, prefix{prefix}, log_prefix{log_prefix} {}
explicit operator bool() const { return log != nullptr; }
wpi::log::DataLog* log = nullptr;
std::string prefix;
std::string log_prefix;
unsigned int uid;
};
// Data for each table entry.
struct Entry {
explicit Entry(std::string_view name_) : name(name_) {}
bool IsPersistent() const { return (flags & NT_PERSISTENT) != 0; }
// We redundantly store the name so that it's available when accessing the
// raw Entry* via the ID map.
std::string name;
// The current value and flags.
std::shared_ptr<Value> value;
unsigned int flags{0};
// Unique ID for this entry as used in network messages. The value is
// assigned by the server, so on the client this is 0xffff until an
// entry assignment is received back from the server.
unsigned int id{0xffff};
// Local ID.
unsigned int local_id{UINT_MAX};
// Sequence number for update resolution.
SequenceNumber seq_num;
// If value has been written locally. Used during initial handshake
// on client to determine whether or not to accept remote changes.
bool local_write{false};
// RPC handle.
unsigned int rpc_uid{UINT_MAX};
// Last UID used when calling this RPC (primarily for client use). This
// is incremented for each call.
unsigned int rpc_call_uid{0};
// log entries
wpi::SmallVector<DataLoggerEntry, 0> datalogs;
NT_Type datalog_type{NT_UNASSIGNED};
};
using EntriesMap = wpi::StringMap<Entry*>;
using IdMap = std::vector<Entry*>;
using LocalMap = std::vector<std::unique_ptr<Entry>>;
using RpcIdPair = std::pair<unsigned int, unsigned int>;
using RpcResultMap = wpi::DenseMap<RpcIdPair, std::string>;
using RpcBlockingCallSet = wpi::SmallSet<RpcIdPair, 12>;
mutable wpi::mutex m_mutex;
EntriesMap m_entries;
IdMap m_idmap;
LocalMap m_localmap;
RpcResultMap m_rpc_results;
RpcBlockingCallSet m_rpc_blocking_calls;
wpi::UidVector<DataLogger, 4> m_dataloggers;
// If any persistent values have changed
mutable bool m_persistent_dirty = false;
// condition variable and termination flag for blocking on a RPC result
std::atomic_bool m_terminating;
wpi::condition_variable m_rpc_results_cond;
// configured by dispatcher at startup
IDispatcher* m_dispatcher = nullptr;
bool m_server = true;
IEntryNotifier& m_notifier;
IRpcServer& m_rpc_server;
wpi::Logger& m_logger;
void ProcessIncomingEntryAssign(std::shared_ptr<Message> msg,
INetworkConnection* conn);
void ProcessIncomingEntryUpdate(std::shared_ptr<Message> msg,
INetworkConnection* conn);
void ProcessIncomingFlagsUpdate(std::shared_ptr<Message> msg,
INetworkConnection* conn);
void ProcessIncomingEntryDelete(std::shared_ptr<Message> msg,
INetworkConnection* conn);
void ProcessIncomingClearEntries(std::shared_ptr<Message> msg,
INetworkConnection* conn);
void ProcessIncomingExecuteRpc(std::shared_ptr<Message> msg,
INetworkConnection* conn,
std::weak_ptr<INetworkConnection> conn_weak);
void ProcessIncomingRpcResponse(std::shared_ptr<Message> msg,
INetworkConnection* conn);
bool GetPersistentEntries(
bool periodic,
std::vector<std::pair<std::string, std::shared_ptr<Value>>>* entries)
const;
bool GetEntries(std::string_view prefix,
std::vector<std::pair<std::string, std::shared_ptr<Value>>>*
entries) const;
void SetEntryValueImpl(Entry* entry, std::shared_ptr<Value> value,
std::unique_lock<wpi::mutex>& lock, bool local);
void SetEntryFlagsImpl(Entry* entry, unsigned int flags,
std::unique_lock<wpi::mutex>& lock, bool local);
void DeleteEntryImpl(Entry* entry, std::unique_lock<wpi::mutex>& lock,
bool local);
void Notify(Entry* entry, unsigned int flags, bool local,
std::shared_ptr<Value> value = {});
// Must be called with m_mutex held
template <typename F>
void DeleteAllEntriesImpl(bool local, F should_delete);
void DeleteAllEntriesImpl(bool local);
Entry* GetOrNew(std::string_view name);
};
} // namespace nt
#endif // NTCORE_STORAGE_H_

View File

@@ -1,484 +0,0 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#include <cctype>
#include <string>
#include <utility>
#include <wpi/Base64.h>
#include <wpi/SmallString.h>
#include <wpi/StringExtras.h>
#include <wpi/raw_istream.h>
#include "IDispatcher.h"
#include "IEntryNotifier.h"
#include "Storage.h"
using namespace nt;
namespace {
class LoadPersistentImpl {
public:
using Entry = std::pair<std::string, std::shared_ptr<Value>>;
using WarnFunc = std::function<void(size_t, const char*)>;
LoadPersistentImpl(wpi::raw_istream& is, WarnFunc warn)
: m_is(is), m_warn(std::move(warn)) {}
bool Load(std::string_view prefix, std::vector<Entry>* entries);
private:
bool ReadLine();
bool ReadHeader();
NT_Type ReadType();
std::string_view ReadName(wpi::SmallVectorImpl<char>& buf);
std::shared_ptr<Value> ReadValue(NT_Type type);
std::shared_ptr<Value> ReadBooleanValue();
std::shared_ptr<Value> ReadDoubleValue();
std::shared_ptr<Value> ReadStringValue();
std::shared_ptr<Value> ReadRawValue();
std::shared_ptr<Value> ReadBooleanArrayValue();
std::shared_ptr<Value> ReadDoubleArrayValue();
std::shared_ptr<Value> ReadStringArrayValue();
void Warn(const char* msg) {
if (m_warn) {
m_warn(m_line_num, msg);
}
}
wpi::raw_istream& m_is;
WarnFunc m_warn;
std::string_view m_line;
wpi::SmallString<128> m_line_buf;
size_t m_line_num = 0;
std::vector<int> m_buf_boolean_array;
std::vector<double> m_buf_double_array;
std::vector<std::string> m_buf_string_array;
};
} // namespace
/* Extracts an escaped string token. Does not unescape the string.
* If a string cannot be matched, an empty string is returned.
* If the string is unterminated, an empty tail string is returned.
* The returned token includes the starting and trailing quotes (unless the
* string is unterminated).
* Returns a pair containing the extracted token (if any) and the remaining
* tail string.
*/
static std::pair<std::string_view, std::string_view> ReadStringToken(
std::string_view source) {
// Match opening quote
if (source.empty() || source.front() != '"') {
return {{}, source};
}
// Scan for ending double quote, checking for escaped as we go.
size_t size = source.size();
size_t pos;
for (pos = 1; pos < size; ++pos) {
if (source[pos] == '"' && source[pos - 1] != '\\') {
++pos; // we want to include the trailing quote in the result
break;
}
}
return {wpi::slice(source, 0, pos), wpi::substr(source, pos)};
}
static int fromxdigit(char ch) {
if (ch >= 'a' && ch <= 'f') {
return (ch - 'a' + 10);
} else if (ch >= 'A' && ch <= 'F') {
return (ch - 'A' + 10);
} else {
return ch - '0';
}
}
static std::string_view UnescapeString(std::string_view source,
wpi::SmallVectorImpl<char>& buf) {
assert(source.size() >= 2 && source.front() == '"' && source.back() == '"');
buf.clear();
buf.reserve(source.size() - 2);
for (auto s = source.begin() + 1, end = source.end() - 1; s != end; ++s) {
if (*s != '\\') {
buf.push_back(*s);
continue;
}
switch (*++s) {
case 't':
buf.push_back('\t');
break;
case 'n':
buf.push_back('\n');
break;
case 'x': {
if (!isxdigit(*(s + 1))) {
buf.push_back('x'); // treat it like a unknown escape
break;
}
int ch = fromxdigit(*++s);
if (std::isxdigit(*(s + 1))) {
ch <<= 4;
ch |= fromxdigit(*++s);
}
buf.push_back(static_cast<char>(ch));
break;
}
default:
buf.push_back(*s);
break;
}
}
return {buf.data(), buf.size()};
}
bool LoadPersistentImpl::Load(std::string_view prefix,
std::vector<Entry>* entries) {
if (!ReadHeader()) {
return false; // header
}
while (ReadLine()) {
// type
NT_Type type = ReadType();
if (type == NT_UNASSIGNED) {
Warn("unrecognized type");
continue;
}
// name
wpi::SmallString<128> buf;
std::string_view name = ReadName(buf);
if (name.empty() || !wpi::starts_with(name, prefix)) {
continue;
}
// =
m_line = wpi::ltrim(m_line, " \t");
if (m_line.empty() || m_line.front() != '=') {
Warn("expected = after name");
continue;
}
m_line.remove_prefix(1);
m_line = wpi::ltrim(m_line, " \t");
// value
auto value = ReadValue(type);
// move to entries
if (value) {
entries->emplace_back(name, std::move(value));
}
}
return true;
}
bool LoadPersistentImpl::ReadLine() {
// ignore blank lines and lines that start with ; or # (comments)
while (!m_is.has_error()) {
++m_line_num;
m_line = wpi::trim(m_is.getline(m_line_buf, INT_MAX));
if (!m_line.empty() && m_line.front() != ';' && m_line.front() != '#') {
return true;
}
}
return false;
}
bool LoadPersistentImpl::ReadHeader() {
// header
if (!ReadLine() || m_line != "[NetworkTables Storage 3.0]") {
Warn("header line mismatch, ignoring rest of file");
return false;
}
return true;
}
NT_Type LoadPersistentImpl::ReadType() {
std::string_view tok;
std::tie(tok, m_line) = wpi::split(m_line, ' ');
if (tok == "boolean") {
return NT_BOOLEAN;
} else if (tok == "double") {
return NT_DOUBLE;
} else if (tok == "string") {
return NT_STRING;
} else if (tok == "raw") {
return NT_RAW;
} else if (tok == "array") {
std::string_view array_tok;
std::tie(array_tok, m_line) = wpi::split(m_line, ' ');
if (array_tok == "boolean") {
return NT_BOOLEAN_ARRAY;
} else if (array_tok == "double") {
return NT_DOUBLE_ARRAY;
} else if (array_tok == "string") {
return NT_STRING_ARRAY;
}
}
return NT_UNASSIGNED;
}
std::string_view LoadPersistentImpl::ReadName(wpi::SmallVectorImpl<char>& buf) {
std::string_view tok;
std::tie(tok, m_line) = ReadStringToken(m_line);
if (tok.empty()) {
Warn("missing name");
return {};
}
if (tok.back() != '"') {
Warn("unterminated name string");
return {};
}
return UnescapeString(tok, buf);
}
std::shared_ptr<Value> LoadPersistentImpl::ReadValue(NT_Type type) {
switch (type) {
case NT_BOOLEAN:
return ReadBooleanValue();
case NT_DOUBLE:
return ReadDoubleValue();
case NT_STRING:
return ReadStringValue();
case NT_RAW:
return ReadRawValue();
case NT_BOOLEAN_ARRAY:
return ReadBooleanArrayValue();
case NT_DOUBLE_ARRAY:
return ReadDoubleArrayValue();
case NT_STRING_ARRAY:
return ReadStringArrayValue();
default:
return nullptr;
}
}
std::shared_ptr<Value> LoadPersistentImpl::ReadBooleanValue() {
// only true or false is accepted
if (m_line == "true") {
return Value::MakeBoolean(true);
}
if (m_line == "false") {
return Value::MakeBoolean(false);
}
Warn("unrecognized boolean value, not 'true' or 'false'");
return nullptr;
}
std::shared_ptr<Value> LoadPersistentImpl::ReadDoubleValue() {
// need to convert to null-terminated string for std::strtod()
wpi::SmallString<64> buf{m_line};
char* end;
double v = std::strtod(buf.c_str(), &end);
if (*end != '\0') {
Warn("invalid double value");
return nullptr;
}
return Value::MakeDouble(v);
}
std::shared_ptr<Value> LoadPersistentImpl::ReadStringValue() {
std::string_view tok;
std::tie(tok, m_line) = ReadStringToken(m_line);
if (tok.empty()) {
Warn("missing string value");
return nullptr;
}
if (tok.back() != '"') {
Warn("unterminated string value");
return nullptr;
}
wpi::SmallString<128> buf;
return Value::MakeString(UnescapeString(tok, buf));
}
std::shared_ptr<Value> LoadPersistentImpl::ReadRawValue() {
wpi::SmallString<128> buf;
size_t nr;
return Value::MakeRaw(wpi::Base64Decode(m_line, &nr, buf));
}
std::shared_ptr<Value> LoadPersistentImpl::ReadBooleanArrayValue() {
m_buf_boolean_array.clear();
while (!m_line.empty()) {
std::string_view tok;
std::tie(tok, m_line) = wpi::split(m_line, ',');
tok = wpi::trim(tok, " \t");
if (tok == "true") {
m_buf_boolean_array.push_back(1);
} else if (tok == "false") {
m_buf_boolean_array.push_back(0);
} else {
Warn("unrecognized boolean value, not 'true' or 'false'");
return nullptr;
}
}
return Value::MakeBooleanArray(std::move(m_buf_boolean_array));
}
std::shared_ptr<Value> LoadPersistentImpl::ReadDoubleArrayValue() {
m_buf_double_array.clear();
while (!m_line.empty()) {
std::string_view tok;
std::tie(tok, m_line) = wpi::split(m_line, ',');
tok = wpi::trim(tok, " \t");
// need to convert to null-terminated string for std::strtod()
wpi::SmallString<64> buf{tok};
char* end;
double v = std::strtod(buf.c_str(), &end);
if (*end != '\0') {
Warn("invalid double value");
return nullptr;
}
m_buf_double_array.push_back(v);
}
return Value::MakeDoubleArray(std::move(m_buf_double_array));
}
std::shared_ptr<Value> LoadPersistentImpl::ReadStringArrayValue() {
m_buf_string_array.clear();
while (!m_line.empty()) {
std::string_view tok;
std::tie(tok, m_line) = ReadStringToken(m_line);
if (tok.empty()) {
Warn("missing string value");
return nullptr;
}
if (tok.back() != '"') {
Warn("unterminated string value");
return nullptr;
}
wpi::SmallString<128> buf;
m_buf_string_array.emplace_back(UnescapeString(tok, buf));
m_line = wpi::ltrim(m_line, " \t");
if (m_line.empty()) {
break;
}
if (m_line.front() != ',') {
Warn("expected comma between strings");
return nullptr;
}
m_line.remove_prefix(1);
m_line = wpi::ltrim(m_line, " \t");
}
return Value::MakeStringArray(std::move(m_buf_string_array));
}
bool Storage::LoadEntries(
wpi::raw_istream& is, std::string_view prefix, bool persistent,
std::function<void(size_t line, const char* msg)> warn) {
// entries to add
std::vector<LoadPersistentImpl::Entry> entries;
// load file
if (!LoadPersistentImpl(is, warn).Load(prefix, &entries)) {
return false;
}
// copy values into storage as quickly as possible so lock isn't held
std::vector<std::shared_ptr<Message>> msgs;
std::unique_lock lock(m_mutex);
for (auto& i : entries) {
Entry* entry = GetOrNew(i.first);
auto old_value = entry->value;
entry->value = i.second;
bool was_persist = entry->IsPersistent();
if (!was_persist && persistent) {
entry->flags |= NT_PERSISTENT;
}
// if we're the server, assign an id if it doesn't have one
if (m_server && entry->id == 0xffff) {
unsigned int id = m_idmap.size();
entry->id = id;
m_idmap.push_back(entry);
}
// notify (for local listeners)
if (m_notifier.local_notifiers()) {
if (!old_value) {
m_notifier.NotifyEntry(entry->local_id, i.first, i.second,
NT_NOTIFY_NEW | NT_NOTIFY_LOCAL);
} else if (*old_value != *i.second) {
unsigned int notify_flags = NT_NOTIFY_UPDATE | NT_NOTIFY_LOCAL;
if (!was_persist && persistent) {
notify_flags |= NT_NOTIFY_FLAGS;
}
m_notifier.NotifyEntry(entry->local_id, i.first, i.second,
notify_flags);
} else if (!was_persist && persistent) {
m_notifier.NotifyEntry(entry->local_id, i.first, i.second,
NT_NOTIFY_FLAGS | NT_NOTIFY_LOCAL);
}
}
if (!m_dispatcher) {
continue; // shortcut
}
++entry->seq_num;
// put on update queue
if (!old_value || old_value->type() != i.second->type()) {
msgs.emplace_back(Message::EntryAssign(
i.first, entry->id, entry->seq_num.value(), i.second, entry->flags));
} else if (entry->id != 0xffff) {
// don't send an update if we don't have an assigned id yet
if (*old_value != *i.second) {
msgs.emplace_back(
Message::EntryUpdate(entry->id, entry->seq_num.value(), i.second));
}
if (!was_persist) {
msgs.emplace_back(Message::FlagsUpdate(entry->id, entry->flags));
}
}
}
if (m_dispatcher) {
auto dispatcher = m_dispatcher;
lock.unlock();
for (auto& msg : msgs) {
dispatcher->QueueOutgoing(std::move(msg), nullptr, nullptr);
}
}
return true;
}
const char* Storage::LoadPersistent(
std::string_view filename,
std::function<void(size_t line, const char* msg)> warn) {
std::error_code ec;
wpi::raw_fd_istream is(filename, ec);
if (ec.value() != 0) {
return "could not open file";
}
if (!LoadEntries(is, "", true, warn)) {
return "error reading file";
}
return nullptr;
}
const char* Storage::LoadEntries(
std::string_view filename, std::string_view prefix,
std::function<void(size_t line, const char* msg)> warn) {
std::error_code ec;
wpi::raw_fd_istream is(filename, ec);
if (ec.value() != 0) {
return "could not open file";
}
if (!LoadEntries(is, prefix, false, warn)) {
return "error reading file";
}
return nullptr;
}

View File

@@ -1,283 +0,0 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#include <cctype>
#include <string>
#include <fmt/format.h>
#include <wpi/Base64.h>
#include <wpi/SmallString.h>
#include <wpi/StringExtras.h>
#include <wpi/fs.h>
#include <wpi/raw_ostream.h>
#include "Log.h"
#include "Storage.h"
using namespace nt;
namespace {
class SavePersistentImpl {
public:
using Entry = std::pair<std::string, std::shared_ptr<Value>>;
explicit SavePersistentImpl(wpi::raw_ostream& os) : m_os(os) {}
void Save(wpi::span<const Entry> entries);
private:
void WriteString(std::string_view str);
void WriteHeader();
void WriteEntries(wpi::span<const Entry> entries);
void WriteEntry(std::string_view name, const Value& value);
bool WriteType(NT_Type type);
void WriteValue(const Value& value);
wpi::raw_ostream& m_os;
};
} // namespace
/* Escapes and writes a string, including start and end double quotes */
void SavePersistentImpl::WriteString(std::string_view str) {
m_os << '"';
for (auto c : str) {
switch (c) {
case '\\':
m_os << "\\\\";
break;
case '\t':
m_os << "\\t";
break;
case '\n':
m_os << "\\n";
break;
case '"':
m_os << "\\\"";
break;
default:
if (std::isprint(c) && c != '=') {
m_os << c;
break;
}
// Write out the escaped representation.
m_os << "\\x";
m_os << wpi::hexdigit((c >> 4) & 0xF);
m_os << wpi::hexdigit((c >> 0) & 0xF);
}
}
m_os << '"';
}
void SavePersistentImpl::Save(wpi::span<const Entry> entries) {
WriteHeader();
WriteEntries(entries);
}
void SavePersistentImpl::WriteHeader() {
m_os << "[NetworkTables Storage 3.0]\n";
}
void SavePersistentImpl::WriteEntries(wpi::span<const Entry> entries) {
for (auto& i : entries) {
if (!i.second) {
continue;
}
WriteEntry(i.first, *i.second);
}
}
void SavePersistentImpl::WriteEntry(std::string_view name, const Value& value) {
if (!WriteType(value.type())) {
return; // type
}
WriteString(name); // name
m_os << '='; // '='
WriteValue(value); // value
m_os << '\n'; // eol
}
bool SavePersistentImpl::WriteType(NT_Type type) {
switch (type) {
case NT_BOOLEAN:
m_os << "boolean ";
break;
case NT_DOUBLE:
m_os << "double ";
break;
case NT_STRING:
m_os << "string ";
break;
case NT_RAW:
m_os << "raw ";
break;
case NT_BOOLEAN_ARRAY:
m_os << "array boolean ";
break;
case NT_DOUBLE_ARRAY:
m_os << "array double ";
break;
case NT_STRING_ARRAY:
m_os << "array string ";
break;
default:
return false;
}
return true;
}
void SavePersistentImpl::WriteValue(const Value& value) {
switch (value.type()) {
case NT_BOOLEAN:
m_os << (value.GetBoolean() ? "true" : "false");
break;
case NT_DOUBLE:
m_os << fmt::format("{:g}", value.GetDouble());
break;
case NT_STRING:
WriteString(value.GetString());
break;
case NT_RAW: {
wpi::Base64Encode(m_os, value.GetRaw());
break;
}
case NT_BOOLEAN_ARRAY: {
bool first = true;
for (auto elem : value.GetBooleanArray()) {
if (!first) {
m_os << ',';
}
first = false;
m_os << (elem ? "true" : "false");
}
break;
}
case NT_DOUBLE_ARRAY: {
bool first = true;
for (auto elem : value.GetDoubleArray()) {
if (!first) {
m_os << ',';
}
first = false;
m_os << fmt::format("{:g}", elem);
}
break;
}
case NT_STRING_ARRAY: {
bool first = true;
for (auto& elem : value.GetStringArray()) {
if (!first) {
m_os << ',';
}
first = false;
WriteString(elem);
}
break;
}
default:
break;
}
}
void Storage::SavePersistent(wpi::raw_ostream& os, bool periodic) const {
std::vector<SavePersistentImpl::Entry> entries;
if (!GetPersistentEntries(periodic, &entries)) {
return;
}
SavePersistentImpl(os).Save(entries);
}
const char* Storage::SavePersistent(std::string_view filename,
bool periodic) const {
std::string fn{filename};
auto tmp = fmt::format("{}.tmp", filename);
auto bak = fmt::format("{}.bak", filename);
// Get entries before creating file
std::vector<SavePersistentImpl::Entry> entries;
if (!GetPersistentEntries(periodic, &entries)) {
return nullptr;
}
const char* err = nullptr;
// start by writing to temporary file
std::error_code ec;
wpi::raw_fd_ostream os(tmp, ec, fs::F_Text);
if (ec.value() != 0) {
err = "could not open file";
goto done;
}
DEBUG0("saving persistent file '{}'", filename);
SavePersistentImpl(os).Save(entries);
os.close();
if (os.has_error()) {
std::remove(tmp.c_str());
err = "error saving file";
goto done;
}
// Safely move to real file. We ignore any failures related to the backup.
std::remove(bak.c_str());
std::rename(fn.c_str(), bak.c_str());
if (std::rename(tmp.c_str(), fn.c_str()) != 0) {
std::rename(bak.c_str(), fn.c_str()); // attempt to restore backup
err = "could not rename temp file to real file";
goto done;
}
done:
// try again if there was an error
if (err && periodic) {
m_persistent_dirty = true;
}
return err;
}
void Storage::SaveEntries(wpi::raw_ostream& os, std::string_view prefix) const {
std::vector<SavePersistentImpl::Entry> entries;
if (!GetEntries(prefix, &entries)) {
return;
}
SavePersistentImpl(os).Save(entries);
}
const char* Storage::SaveEntries(std::string_view filename,
std::string_view prefix) const {
std::string fn{filename};
auto tmp = fmt::format("{}.tmp", filename);
auto bak = fmt::format("{}.bak", filename);
// Get entries before creating file
std::vector<SavePersistentImpl::Entry> entries;
if (!GetEntries(prefix, &entries)) {
return nullptr;
}
// start by writing to temporary file
std::error_code ec;
wpi::raw_fd_ostream os(tmp, ec, fs::F_Text);
if (ec.value() != 0) {
return "could not open file";
}
DEBUG0("saving file '{}'", filename);
SavePersistentImpl(os).Save(entries);
os.close();
if (os.has_error()) {
std::remove(tmp.c_str());
return "error saving file";
}
// Safely move to real file. We ignore any failures related to the backup.
std::remove(bak.c_str());
std::rename(fn.c_str(), bak.c_str());
if (std::rename(tmp.c_str(), fn.c_str()) != 0) {
std::rename(bak.c_str(), fn.c_str()); // attempt to restore backup
return "could not rename temp file to real file";
}
return nullptr;
}

View File

@@ -0,0 +1,83 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#include "Types_internal.h"
std::string_view nt::TypeToString(NT_Type type) {
switch (type) {
case NT_BOOLEAN:
return "boolean";
case NT_DOUBLE:
return "double";
case NT_STRING:
return "string";
case NT_BOOLEAN_ARRAY:
return "boolean[]";
case NT_DOUBLE_ARRAY:
return "double[]";
case NT_STRING_ARRAY:
return "string[]";
case NT_RPC:
return "rpc";
case NT_INTEGER:
return "int";
case NT_FLOAT:
return "float";
case NT_INTEGER_ARRAY:
return "int[]";
case NT_FLOAT_ARRAY:
return "float";
default:
return "raw";
}
}
NT_Type nt::StringToType(std::string_view typeStr) {
if (typeStr == "boolean") {
return NT_BOOLEAN;
} else if (typeStr == "double") {
return NT_DOUBLE;
} else if (typeStr == "string" || typeStr == "json") {
return NT_STRING;
} else if (typeStr == "boolean[]") {
return NT_BOOLEAN_ARRAY;
} else if (typeStr == "double[]") {
return NT_DOUBLE_ARRAY;
} else if (typeStr == "string[]") {
return NT_STRING_ARRAY;
} else if (typeStr == "rpc") {
return NT_RPC;
} else if (typeStr == "int") {
return NT_INTEGER;
} else if (typeStr == "float") {
return NT_FLOAT;
} else if (typeStr == "int[]") {
return NT_INTEGER_ARRAY;
} else if (typeStr == "float[]") {
return NT_FLOAT_ARRAY;
} else {
return NT_RAW;
}
}
NT_Type nt::StringToType3(std::string_view typeStr) {
if (typeStr == "boolean") {
return NT_BOOLEAN;
} else if (typeStr == "double" || typeStr == "int" || typeStr == "float") {
return NT_DOUBLE;
} else if (typeStr == "string" || typeStr == "json") {
return NT_STRING;
} else if (typeStr == "boolean[]") {
return NT_BOOLEAN_ARRAY;
} else if (typeStr == "double[]" || typeStr == "int[]" ||
typeStr == "float[]") {
return NT_DOUBLE_ARRAY;
} else if (typeStr == "string[]") {
return NT_STRING_ARRAY;
} else if (typeStr == "rpc") {
return NT_RPC;
} else {
return NT_RAW;
}
}

View File

@@ -0,0 +1,17 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source 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 <string_view>
#include "ntcore_c.h"
namespace nt {
std::string_view TypeToString(NT_Type type);
NT_Type StringToType(std::string_view typeStr);
NT_Type StringToType3(std::string_view typeStr);
} // namespace nt

View File

@@ -11,23 +11,56 @@
#include "Value_internal.h"
#include "networktables/NetworkTableValue.h"
#include "ntcore_cpp.h"
using namespace nt;
Value::Value() {
m_val.type = NT_UNASSIGNED;
m_val.last_change = wpi::Now();
namespace {
struct StringArrayStorage {
explicit StringArrayStorage(wpi::span<const std::string> value)
: strings{value.begin(), value.end()} {
InitNtStrings();
}
explicit StringArrayStorage(std::vector<std::string>&& value)
: strings{std::move(value)} {
InitNtStrings();
}
void InitNtStrings();
std::vector<std::string> strings;
std::vector<NT_String> ntStrings;
};
} // namespace
void StringArrayStorage::InitNtStrings() {
// point NT_String's to the contents in the vector.
ntStrings.reserve(strings.size());
for (const auto& str : strings) {
ntStrings.emplace_back(
NT_String{const_cast<char*>(str.c_str()), str.size()});
}
}
Value::Value(NT_Type type, uint64_t time, const private_init&) {
Value::Value() {
m_val.type = NT_UNASSIGNED;
m_val.last_change = 0;
m_val.server_time = 0;
}
Value::Value(NT_Type type, int64_t time, const private_init&)
: Value{type, time == 0 ? nt::Now() : time, 1, private_init{}} {}
Value::Value(NT_Type type, int64_t time, int64_t serverTime,
const private_init&) {
m_val.type = type;
if (time == 0) {
m_val.last_change = wpi::Now();
} else {
m_val.last_change = time;
}
m_val.last_change = time;
m_val.server_time = serverTime;
if (m_val.type == NT_BOOLEAN_ARRAY) {
m_val.data.arr_boolean.arr = nullptr;
} else if (m_val.type == NT_INTEGER_ARRAY) {
m_val.data.arr_int.arr = nullptr;
} else if (m_val.type == NT_FLOAT_ARRAY) {
m_val.data.arr_float.arr = nullptr;
} else if (m_val.type == NT_DOUBLE_ARRAY) {
m_val.data.arr_double.arr = nullptr;
} else if (m_val.type == NT_STRING_ARRAY) {
@@ -35,93 +68,85 @@ Value::Value(NT_Type type, uint64_t time, const private_init&) {
}
}
Value::~Value() {
if (m_val.type == NT_BOOLEAN_ARRAY) {
delete[] m_val.data.arr_boolean.arr;
} else if (m_val.type == NT_DOUBLE_ARRAY) {
delete[] m_val.data.arr_double.arr;
} else if (m_val.type == NT_STRING_ARRAY) {
delete[] m_val.data.arr_string.arr;
}
}
std::shared_ptr<Value> Value::MakeBooleanArray(wpi::span<const bool> value,
uint64_t time) {
auto val = std::make_shared<Value>(NT_BOOLEAN_ARRAY, time, private_init());
val->m_val.data.arr_boolean.arr = new int[value.size()];
val->m_val.data.arr_boolean.size = value.size();
std::copy(value.begin(), value.end(), val->m_val.data.arr_boolean.arr);
Value Value::MakeBooleanArray(wpi::span<const bool> value, int64_t time) {
Value val{NT_BOOLEAN_ARRAY, time, private_init{}};
auto data = std::make_shared<std::vector<int>>(value.begin(), value.end());
// data->reserve(value.size());
// std::copy(value.begin(), value.end(), *data);
val.m_val.data.arr_boolean.arr = data->data();
val.m_val.data.arr_boolean.size = data->size();
val.m_storage = std::move(data);
return val;
}
std::shared_ptr<Value> Value::MakeBooleanArray(wpi::span<const int> value,
uint64_t time) {
auto val = std::make_shared<Value>(NT_BOOLEAN_ARRAY, time, private_init());
val->m_val.data.arr_boolean.arr = new int[value.size()];
val->m_val.data.arr_boolean.size = value.size();
std::copy(value.begin(), value.end(), val->m_val.data.arr_boolean.arr);
Value Value::MakeBooleanArray(wpi::span<const int> value, int64_t time) {
Value val{NT_BOOLEAN_ARRAY, time, private_init{}};
auto data = std::make_shared<std::vector<int>>(value.begin(), value.end());
val.m_val.data.arr_boolean.arr = data->data();
val.m_val.data.arr_boolean.size = data->size();
val.m_storage = std::move(data);
return val;
}
std::shared_ptr<Value> Value::MakeDoubleArray(wpi::span<const double> value,
uint64_t time) {
auto val = std::make_shared<Value>(NT_DOUBLE_ARRAY, time, private_init());
val->m_val.data.arr_double.arr = new double[value.size()];
val->m_val.data.arr_double.size = value.size();
std::copy(value.begin(), value.end(), val->m_val.data.arr_double.arr);
Value Value::MakeIntegerArray(wpi::span<const int64_t> value, int64_t time) {
Value val{NT_INTEGER_ARRAY, time, private_init{}};
auto data =
std::make_shared<std::vector<int64_t>>(value.begin(), value.end());
val.m_val.data.arr_int.arr = data->data();
val.m_val.data.arr_int.size = data->size();
val.m_storage = std::move(data);
return val;
}
std::shared_ptr<Value> Value::MakeStringArray(
wpi::span<const std::string> value, uint64_t time) {
auto val = std::make_shared<Value>(NT_STRING_ARRAY, time, private_init());
val->m_string_array.assign(value.begin(), value.end());
// point NT_Value to the contents in the vector.
val->m_val.data.arr_string.arr = new NT_String[value.size()];
val->m_val.data.arr_string.size = val->m_string_array.size();
for (size_t i = 0; i < value.size(); ++i) {
val->m_val.data.arr_string.arr[i].str = const_cast<char*>(value[i].c_str());
val->m_val.data.arr_string.arr[i].len = value[i].size();
}
Value Value::MakeFloatArray(wpi::span<const float> value, int64_t time) {
Value val{NT_FLOAT_ARRAY, time, private_init{}};
auto data = std::make_shared<std::vector<float>>(value.begin(), value.end());
val.m_val.data.arr_float.arr = data->data();
val.m_val.data.arr_float.size = data->size();
val.m_storage = std::move(data);
return val;
}
std::shared_ptr<Value> Value::MakeStringArray(std::vector<std::string>&& value,
uint64_t time) {
auto val = std::make_shared<Value>(NT_STRING_ARRAY, time, private_init());
val->m_string_array = std::move(value);
value.clear();
// point NT_Value to the contents in the vector.
val->m_val.data.arr_string.arr = new NT_String[val->m_string_array.size()];
val->m_val.data.arr_string.size = val->m_string_array.size();
for (size_t i = 0; i < val->m_string_array.size(); ++i) {
val->m_val.data.arr_string.arr[i].str =
const_cast<char*>(val->m_string_array[i].c_str());
val->m_val.data.arr_string.arr[i].len = val->m_string_array[i].size();
}
Value Value::MakeDoubleArray(wpi::span<const double> value, int64_t time) {
Value val{NT_DOUBLE_ARRAY, time, private_init{}};
auto data = std::make_shared<std::vector<double>>(value.begin(), value.end());
val.m_val.data.arr_double.arr = data->data();
val.m_val.data.arr_double.size = data->size();
val.m_storage = std::move(data);
return val;
}
Value Value::MakeStringArray(wpi::span<const std::string> value, int64_t time) {
Value val{NT_STRING_ARRAY, time, private_init{}};
auto data = std::make_shared<StringArrayStorage>(value);
val.m_val.data.arr_string.arr = data->ntStrings.data();
val.m_val.data.arr_string.size = data->ntStrings.size();
val.m_storage = std::move(data);
return val;
}
Value Value::MakeStringArray(std::vector<std::string>&& value, int64_t time) {
Value val{NT_STRING_ARRAY, time, private_init{}};
auto data = std::make_shared<StringArrayStorage>(std::move(value));
val.m_val.data.arr_string.arr = data->ntStrings.data();
val.m_val.data.arr_string.size = data->ntStrings.size();
val.m_storage = std::move(data);
return val;
}
void nt::ConvertToC(const Value& in, NT_Value* out) {
out->type = NT_UNASSIGNED;
*out = in.value();
switch (in.type()) {
case NT_UNASSIGNED:
return;
case NT_BOOLEAN:
out->data.v_boolean = in.GetBoolean() ? 1 : 0;
break;
case NT_DOUBLE:
out->data.v_double = in.GetDouble();
break;
case NT_STRING:
ConvertToC(in.GetString(), &out->data.v_string);
break;
case NT_RAW:
ConvertToC(in.GetRaw(), &out->data.v_raw);
break;
case NT_RPC:
ConvertToC(in.GetRpc(), &out->data.v_raw);
case NT_RAW: {
auto v = in.GetRaw();
out->data.v_raw.data = static_cast<uint8_t*>(wpi::safe_malloc(v.size()));
out->data.v_raw.size = v.size();
std::memcpy(out->data.v_raw.data, v.data(), v.size());
break;
}
case NT_BOOLEAN_ARRAY: {
auto v = in.GetBooleanArray();
out->data.arr_boolean.arr =
@@ -130,6 +155,22 @@ void nt::ConvertToC(const Value& in, NT_Value* out) {
std::copy(v.begin(), v.end(), out->data.arr_boolean.arr);
break;
}
case NT_INTEGER_ARRAY: {
auto v = in.GetIntegerArray();
out->data.arr_int.arr =
static_cast<int64_t*>(wpi::safe_malloc(v.size() * sizeof(int64_t)));
out->data.arr_int.size = v.size();
std::copy(v.begin(), v.end(), out->data.arr_int.arr);
break;
}
case NT_FLOAT_ARRAY: {
auto v = in.GetFloatArray();
out->data.arr_float.arr =
static_cast<float*>(wpi::safe_malloc(v.size() * sizeof(float)));
out->data.arr_float.size = v.size();
std::copy(v.begin(), v.end(), out->data.arr_float.arr);
break;
}
case NT_DOUBLE_ARRAY: {
auto v = in.GetDoubleArray();
out->data.arr_double.arr =
@@ -143,16 +184,21 @@ void nt::ConvertToC(const Value& in, NT_Value* out) {
out->data.arr_string.arr = static_cast<NT_String*>(
wpi::safe_malloc(v.size() * sizeof(NT_String)));
for (size_t i = 0; i < v.size(); ++i) {
ConvertToC(v[i], &out->data.arr_string.arr[i]);
ConvertToC(std::string_view{v[i]}, &out->data.arr_string.arr[i]);
}
out->data.arr_string.size = v.size();
break;
}
default:
// assert(false && "unknown value type");
return;
break;
}
out->type = in.type();
}
size_t nt::ConvertToC(std::string_view in, char** out) {
*out = static_cast<char*>(wpi::safe_malloc(in.size() + 1));
std::memmove(*out, in.data(), in.size()); // NOLINT
(*out)[in.size()] = '\0';
return in.size();
}
void nt::ConvertToC(std::string_view in, NT_String* out) {
@@ -162,37 +208,48 @@ void nt::ConvertToC(std::string_view in, NT_String* out) {
out->str[in.size()] = '\0';
}
std::shared_ptr<Value> nt::ConvertFromC(const NT_Value& value) {
Value nt::ConvertFromC(const NT_Value& value) {
switch (value.type) {
case NT_UNASSIGNED:
return nullptr;
case NT_BOOLEAN:
return Value::MakeBoolean(value.data.v_boolean != 0);
return Value::MakeBoolean(value.data.v_boolean != 0, value.last_change);
case NT_INTEGER:
return Value::MakeInteger(value.data.v_int, value.last_change);
case NT_FLOAT:
return Value::MakeFloat(value.data.v_float, value.last_change);
case NT_DOUBLE:
return Value::MakeDouble(value.data.v_double);
return Value::MakeDouble(value.data.v_double, value.last_change);
case NT_STRING:
return Value::MakeString(ConvertFromC(value.data.v_string));
return Value::MakeString(ConvertFromC(value.data.v_string),
value.last_change);
case NT_RAW:
return Value::MakeRaw(ConvertFromC(value.data.v_raw));
case NT_RPC:
return Value::MakeRpc(ConvertFromC(value.data.v_raw));
return Value::MakeRaw({value.data.v_raw.data, value.data.v_raw.size},
value.last_change);
case NT_BOOLEAN_ARRAY:
return Value::MakeBooleanArray(
wpi::span(value.data.arr_boolean.arr, value.data.arr_boolean.size));
wpi::span(value.data.arr_boolean.arr, value.data.arr_boolean.size),
value.last_change);
case NT_INTEGER_ARRAY:
return Value::MakeIntegerArray(
wpi::span(value.data.arr_int.arr, value.data.arr_int.size),
value.last_change);
case NT_FLOAT_ARRAY:
return Value::MakeFloatArray(
wpi::span(value.data.arr_float.arr, value.data.arr_float.size),
value.last_change);
case NT_DOUBLE_ARRAY:
return Value::MakeDoubleArray(
wpi::span(value.data.arr_double.arr, value.data.arr_double.size));
wpi::span(value.data.arr_double.arr, value.data.arr_double.size),
value.last_change);
case NT_STRING_ARRAY: {
std::vector<std::string> v;
v.reserve(value.data.arr_string.size);
for (size_t i = 0; i < value.data.arr_string.size; ++i) {
v.emplace_back(ConvertFromC(value.data.arr_string.arr[i]));
}
return Value::MakeStringArray(std::move(v));
return Value::MakeStringArray(std::move(v), value.last_change);
}
default:
// assert(false && "unknown value type");
return nullptr;
return {};
}
}
@@ -205,12 +262,20 @@ bool nt::operator==(const Value& lhs, const Value& rhs) {
return true; // XXX: is this better being false instead?
case NT_BOOLEAN:
return lhs.m_val.data.v_boolean == rhs.m_val.data.v_boolean;
case NT_INTEGER:
return lhs.m_val.data.v_int == rhs.m_val.data.v_int;
case NT_FLOAT:
return lhs.m_val.data.v_float == rhs.m_val.data.v_float;
case NT_DOUBLE:
return lhs.m_val.data.v_double == rhs.m_val.data.v_double;
case NT_STRING:
return lhs.GetString() == rhs.GetString();
case NT_RAW:
case NT_RPC:
return lhs.m_string == rhs.m_string;
if (lhs.m_val.data.v_raw.size != rhs.m_val.data.v_raw.size) {
return false;
}
return std::memcmp(lhs.m_val.data.v_raw.data, rhs.m_val.data.v_raw.data,
lhs.m_val.data.v_raw.size) == 0;
case NT_BOOLEAN_ARRAY:
if (lhs.m_val.data.arr_boolean.size != rhs.m_val.data.arr_boolean.size) {
return false;
@@ -219,6 +284,21 @@ bool nt::operator==(const Value& lhs, const Value& rhs) {
rhs.m_val.data.arr_boolean.arr,
lhs.m_val.data.arr_boolean.size *
sizeof(lhs.m_val.data.arr_boolean.arr[0])) == 0;
case NT_INTEGER_ARRAY:
if (lhs.m_val.data.arr_int.size != rhs.m_val.data.arr_int.size) {
return false;
}
return std::memcmp(lhs.m_val.data.arr_int.arr, rhs.m_val.data.arr_int.arr,
lhs.m_val.data.arr_int.size *
sizeof(lhs.m_val.data.arr_int.arr[0])) == 0;
case NT_FLOAT_ARRAY:
if (lhs.m_val.data.arr_float.size != rhs.m_val.data.arr_float.size) {
return false;
}
return std::memcmp(lhs.m_val.data.arr_float.arr,
rhs.m_val.data.arr_float.arr,
lhs.m_val.data.arr_float.size *
sizeof(lhs.m_val.data.arr_float.arr[0])) == 0;
case NT_DOUBLE_ARRAY:
if (lhs.m_val.data.arr_double.size != rhs.m_val.data.arr_double.size) {
return false;
@@ -228,7 +308,8 @@ bool nt::operator==(const Value& lhs, const Value& rhs) {
lhs.m_val.data.arr_double.size *
sizeof(lhs.m_val.data.arr_double.arr[0])) == 0;
case NT_STRING_ARRAY:
return lhs.m_string_array == rhs.m_string_array;
return static_cast<StringArrayStorage*>(lhs.m_storage.get())->strings ==
static_cast<StringArrayStorage*>(rhs.m_storage.get())->strings;
default:
// assert(false && "unknown value type");
return false;

View File

@@ -2,12 +2,15 @@
// Open Source 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 NTCORE_VALUE_INTERNAL_H_
#define NTCORE_VALUE_INTERNAL_H_
#pragma once
#include <cstring>
#include <memory>
#include <string>
#include <string_view>
#include <vector>
#include <wpi/MemAlloc.h>
#include "ntcore_c.h"
@@ -15,13 +18,42 @@ namespace nt {
class Value;
template <typename T>
inline void ConvertToC(const T& in, T* out) {
*out = in;
}
void ConvertToC(const Value& in, NT_Value* out);
std::shared_ptr<Value> ConvertFromC(const NT_Value& value);
Value ConvertFromC(const NT_Value& value);
size_t ConvertToC(std::string_view in, char** out);
void ConvertToC(std::string_view in, NT_String* out);
inline std::string_view ConvertFromC(const NT_String& str) {
return {str.str, str.len};
}
} // namespace nt
template <typename O, typename I>
O* ConvertToC(const std::vector<I>& in, size_t* out_len) {
if (!out_len) {
return nullptr;
}
*out_len = in.size();
if (in.empty()) {
return nullptr;
}
O* out = static_cast<O*>(wpi::safe_malloc(sizeof(O) * in.size()));
for (size_t i = 0; i < in.size(); ++i) {
ConvertToC(in[i], &out[i]);
}
return out;
}
#endif // NTCORE_VALUE_INTERNAL_H_
template <typename O, typename I>
O* ConvertToC(const std::basic_string<I>& in, size_t* out_len) {
char* out = static_cast<char*>(wpi::safe_malloc(in.size() + 1));
std::memmove(out, in.data(), in.size()); // NOLINT
out[in.size()] = '\0';
*out_len = in.size();
return out;
}
} // namespace nt

View File

@@ -1,247 +0,0 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#include "WireDecoder.h"
#include <stdint.h>
#include <cassert>
#include <cstdlib>
#include <cstring>
#include <wpi/MathExtras.h>
#include <wpi/MemAlloc.h>
#include <wpi/leb128.h>
using namespace nt;
static double ReadDouble(const char*& buf) {
// Fast but non-portable!
uint64_t val = (*reinterpret_cast<const unsigned char*>(buf)) & 0xff;
++buf;
val <<= 8;
val |= (*reinterpret_cast<const unsigned char*>(buf)) & 0xff;
++buf;
val <<= 8;
val |= (*reinterpret_cast<const unsigned char*>(buf)) & 0xff;
++buf;
val <<= 8;
val |= (*reinterpret_cast<const unsigned char*>(buf)) & 0xff;
++buf;
val <<= 8;
val |= (*reinterpret_cast<const unsigned char*>(buf)) & 0xff;
++buf;
val <<= 8;
val |= (*reinterpret_cast<const unsigned char*>(buf)) & 0xff;
++buf;
val <<= 8;
val |= (*reinterpret_cast<const unsigned char*>(buf)) & 0xff;
++buf;
val <<= 8;
val |= (*reinterpret_cast<const unsigned char*>(buf)) & 0xff;
++buf;
return wpi::BitsToDouble(val);
}
WireDecoder::WireDecoder(wpi::raw_istream& is, unsigned int proto_rev,
wpi::Logger& logger)
: m_is(is), m_logger(logger) {
// Start with a 1K temporary buffer. Use malloc instead of new so we can
// realloc.
m_allocated = 1024;
m_buf = static_cast<char*>(wpi::safe_malloc(m_allocated));
m_proto_rev = proto_rev;
m_error = nullptr;
}
WireDecoder::~WireDecoder() {
std::free(m_buf);
}
bool WireDecoder::ReadDouble(double* val) {
const char* buf;
if (!Read(&buf, 8)) {
return false;
}
*val = ::ReadDouble(buf);
return true;
}
void WireDecoder::Realloc(size_t len) {
// Double current buffer size until we have enough space.
if (m_allocated >= len) {
return;
}
size_t newlen = m_allocated * 2;
while (newlen < len) {
newlen *= 2;
}
m_buf = static_cast<char*>(wpi::safe_realloc(m_buf, newlen));
m_allocated = newlen;
}
bool WireDecoder::ReadType(NT_Type* type) {
unsigned int itype;
if (!Read8(&itype)) {
return false;
}
// Convert from byte value to enum
switch (itype) {
case 0x00:
*type = NT_BOOLEAN;
break;
case 0x01:
*type = NT_DOUBLE;
break;
case 0x02:
*type = NT_STRING;
break;
case 0x03:
*type = NT_RAW;
break;
case 0x10:
*type = NT_BOOLEAN_ARRAY;
break;
case 0x11:
*type = NT_DOUBLE_ARRAY;
break;
case 0x12:
*type = NT_STRING_ARRAY;
break;
case 0x20:
*type = NT_RPC;
break;
default:
*type = NT_UNASSIGNED;
m_error = "unrecognized value type";
return false;
}
return true;
}
std::shared_ptr<Value> WireDecoder::ReadValue(NT_Type type) {
switch (type) {
case NT_BOOLEAN: {
unsigned int v;
if (!Read8(&v)) {
return nullptr;
}
return Value::MakeBoolean(v != 0);
}
case NT_DOUBLE: {
double v;
if (!ReadDouble(&v)) {
return nullptr;
}
return Value::MakeDouble(v);
}
case NT_STRING: {
std::string v;
if (!ReadString(&v)) {
return nullptr;
}
return Value::MakeString(std::move(v));
}
case NT_RAW: {
if (m_proto_rev < 0x0300u) {
m_error = "received raw value in protocol < 3.0";
return nullptr;
}
std::string v;
if (!ReadString(&v)) {
return nullptr;
}
return Value::MakeRaw(std::move(v));
}
case NT_RPC: {
if (m_proto_rev < 0x0300u) {
m_error = "received RPC value in protocol < 3.0";
return nullptr;
}
std::string v;
if (!ReadString(&v)) {
return nullptr;
}
return Value::MakeRpc(std::move(v));
}
case NT_BOOLEAN_ARRAY: {
// size
unsigned int size;
if (!Read8(&size)) {
return nullptr;
}
// array values
const char* buf;
if (!Read(&buf, size)) {
return nullptr;
}
std::vector<int> v(size);
for (unsigned int i = 0; i < size; ++i) {
v[i] = buf[i] ? 1 : 0;
}
return Value::MakeBooleanArray(std::move(v));
}
case NT_DOUBLE_ARRAY: {
// size
unsigned int size;
if (!Read8(&size)) {
return nullptr;
}
// array values
const char* buf;
if (!Read(&buf, size * 8)) {
return nullptr;
}
std::vector<double> v(size);
for (unsigned int i = 0; i < size; ++i) {
v[i] = ::ReadDouble(buf);
}
return Value::MakeDoubleArray(std::move(v));
}
case NT_STRING_ARRAY: {
// size
unsigned int size;
if (!Read8(&size)) {
return nullptr;
}
// array values
std::vector<std::string> v(size);
for (unsigned int i = 0; i < size; ++i) {
if (!ReadString(&v[i])) {
return nullptr;
}
}
return Value::MakeStringArray(std::move(v));
}
default:
m_error = "invalid type when trying to read value";
return nullptr;
}
}
bool WireDecoder::ReadString(std::string* str) {
size_t len;
if (m_proto_rev < 0x0300u) {
unsigned int v;
if (!Read16(&v)) {
return false;
}
len = v;
} else {
uint64_t v;
if (!ReadUleb128(&v)) {
return false;
}
len = v;
}
const char* buf;
if (!Read(&buf, len)) {
return false;
}
str->assign(buf, len);
return true;
}

View File

@@ -1,165 +0,0 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source 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 NTCORE_WIREDECODER_H_
#define NTCORE_WIREDECODER_H_
#include <stdint.h>
#include <cstddef>
#include <memory>
#include <string>
#include <wpi/leb128.h>
#include <wpi/raw_istream.h>
#include "Log.h"
#include "networktables/NetworkTableValue.h"
namespace nt {
/* Decodes network data into native representation.
* This class is designed to read from a raw_istream, which provides a blocking
* read interface. There are no provisions in this class for resuming a read
* that was interrupted partway. Read functions return false if
* raw_istream.read() returned false (indicating the end of the input data
* stream).
*/
class WireDecoder {
public:
WireDecoder(wpi::raw_istream& is, unsigned int proto_rev,
wpi::Logger& logger);
~WireDecoder();
void set_proto_rev(unsigned int proto_rev) { m_proto_rev = proto_rev; }
/* Get the active protocol revision. */
unsigned int proto_rev() const { return m_proto_rev; }
/* Get the logger. */
wpi::Logger& logger() const { return m_logger; }
/* Clears error indicator. */
void Reset() { m_error = nullptr; }
/* Returns error indicator (a string describing the error). Returns nullptr
* if no error has occurred.
*/
const char* error() const { return m_error; }
void set_error(const char* error) { m_error = error; }
/* Reads the specified number of bytes.
* @param buf pointer to read data (output parameter)
* @param len number of bytes to read
* Caution: the buffer is only temporarily valid.
*/
bool Read(const char** buf, size_t len) {
if (len > m_allocated) {
Realloc(len);
}
*buf = m_buf;
m_is.read(m_buf, len);
#if 0
if (m_logger.min_level() <= NT_LOG_DEBUG4 && m_logger.HasLogger()) {
std::ostringstream oss;
oss << "read " << len << " bytes:" << std::hex;
if (!rv) {
oss << "error";
} else {
for (size_t i = 0; i < len; ++i)
oss << ' ' << static_cast<unsigned int>((*buf)[i]);
}
m_logger.Log(NT_LOG_DEBUG4, __FILE__, __LINE__, oss.str().c_str());
}
#endif
return !m_is.has_error();
}
/* Reads a single byte. */
bool Read8(unsigned int* val) {
const char* buf;
if (!Read(&buf, 1)) {
return false;
}
*val = (*reinterpret_cast<const unsigned char*>(buf)) & 0xff;
return true;
}
/* Reads a 16-bit word. */
bool Read16(unsigned int* val) {
const char* buf;
if (!Read(&buf, 2)) {
return false;
}
unsigned int v = (*reinterpret_cast<const unsigned char*>(buf)) & 0xff;
++buf;
v <<= 8;
v |= (*reinterpret_cast<const unsigned char*>(buf)) & 0xff;
*val = v;
return true;
}
/* Reads a 32-bit word. */
bool Read32(uint32_t* val) {
const char* buf;
if (!Read(&buf, 4)) {
return false;
}
unsigned int v = (*reinterpret_cast<const unsigned char*>(buf)) & 0xff;
++buf;
v <<= 8;
v |= (*reinterpret_cast<const unsigned char*>(buf)) & 0xff;
++buf;
v <<= 8;
v |= (*reinterpret_cast<const unsigned char*>(buf)) & 0xff;
++buf;
v <<= 8;
v |= (*reinterpret_cast<const unsigned char*>(buf)) & 0xff;
*val = v;
return true;
}
/* Reads a double. */
bool ReadDouble(double* val);
/* Reads an ULEB128-encoded unsigned integer. */
bool ReadUleb128(uint64_t* val) {
return wpi::ReadUleb128(m_is, val);
}
bool ReadType(NT_Type* type);
bool ReadString(std::string* str);
std::shared_ptr<Value> ReadValue(NT_Type type);
WireDecoder(const WireDecoder&) = delete;
WireDecoder& operator=(const WireDecoder&) = delete;
protected:
/* The protocol revision. E.g. 0x0200 for version 2.0. */
unsigned int m_proto_rev;
/* Error indicator. */
const char* m_error;
private:
/* Reallocate temporary buffer to specified length. */
void Realloc(size_t len);
/* input stream */
wpi::raw_istream& m_is;
/* logger */
wpi::Logger& m_logger;
/* temporary buffer */
char* m_buf;
/* allocated size of temporary buffer */
size_t m_allocated;
};
} // namespace nt
#endif // NTCORE_WIREDECODER_H_

View File

@@ -1,226 +0,0 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#include "WireEncoder.h"
#include <stdint.h>
#include <cassert>
#include <cstdlib>
#include <cstring>
#include <wpi/MathExtras.h>
#include <wpi/leb128.h>
using namespace nt;
WireEncoder::WireEncoder(unsigned int proto_rev) {
m_proto_rev = proto_rev;
m_error = nullptr;
}
void WireEncoder::WriteDouble(double val) {
// The highest performance way to do this, albeit non-portable.
uint64_t v = wpi::DoubleToBits(val);
m_data.append(
{static_cast<char>((v >> 56) & 0xff), static_cast<char>((v >> 48) & 0xff),
static_cast<char>((v >> 40) & 0xff), static_cast<char>((v >> 32) & 0xff),
static_cast<char>((v >> 24) & 0xff), static_cast<char>((v >> 16) & 0xff),
static_cast<char>((v >> 8) & 0xff), static_cast<char>(v & 0xff)});
}
void WireEncoder::WriteUleb128(uint32_t val) {
wpi::WriteUleb128(m_data, val);
}
void WireEncoder::WriteType(NT_Type type) {
char ch;
// Convert from enum to actual byte value.
switch (type) {
case NT_BOOLEAN:
ch = 0x00;
break;
case NT_DOUBLE:
ch = 0x01;
break;
case NT_STRING:
ch = 0x02;
break;
case NT_RAW:
if (m_proto_rev < 0x0300u) {
m_error = "raw type not supported in protocol < 3.0";
return;
}
ch = 0x03;
break;
case NT_BOOLEAN_ARRAY:
ch = 0x10;
break;
case NT_DOUBLE_ARRAY:
ch = 0x11;
break;
case NT_STRING_ARRAY:
ch = 0x12;
break;
case NT_RPC:
if (m_proto_rev < 0x0300u) {
m_error = "RPC type not supported in protocol < 3.0";
return;
}
ch = 0x20;
break;
default:
m_error = "unrecognized type";
return;
}
m_data.push_back(ch);
}
size_t WireEncoder::GetValueSize(const Value& value) const {
switch (value.type()) {
case NT_BOOLEAN:
return 1;
case NT_DOUBLE:
return 8;
case NT_STRING:
return GetStringSize(value.GetString());
case NT_RAW:
if (m_proto_rev < 0x0300u) {
return 0;
}
return GetStringSize(value.GetRaw());
case NT_RPC:
if (m_proto_rev < 0x0300u) {
return 0;
}
return GetStringSize(value.GetRpc());
case NT_BOOLEAN_ARRAY: {
// 1-byte size, 1 byte per element
size_t size = value.GetBooleanArray().size();
if (size > 0xff) {
size = 0xff; // size is only 1 byte, truncate
}
return 1 + size;
}
case NT_DOUBLE_ARRAY: {
// 1-byte size, 8 bytes per element
size_t size = value.GetDoubleArray().size();
if (size > 0xff) {
size = 0xff; // size is only 1 byte, truncate
}
return 1 + size * 8;
}
case NT_STRING_ARRAY: {
auto v = value.GetStringArray();
size_t size = v.size();
if (size > 0xff) {
size = 0xff; // size is only 1 byte, truncate
}
size_t len = 1; // 1-byte size
for (size_t i = 0; i < size; ++i) {
len += GetStringSize(v[i]);
}
return len;
}
default:
return 0;
}
}
void WireEncoder::WriteValue(const Value& value) {
switch (value.type()) {
case NT_BOOLEAN:
Write8(value.GetBoolean() ? 1 : 0);
break;
case NT_DOUBLE:
WriteDouble(value.GetDouble());
break;
case NT_STRING:
WriteString(value.GetString());
break;
case NT_RAW:
if (m_proto_rev < 0x0300u) {
m_error = "raw values not supported in protocol < 3.0";
return;
}
WriteString(value.GetRaw());
break;
case NT_RPC:
if (m_proto_rev < 0x0300u) {
m_error = "RPC values not supported in protocol < 3.0";
return;
}
WriteString(value.GetRpc());
break;
case NT_BOOLEAN_ARRAY: {
auto v = value.GetBooleanArray();
size_t size = v.size();
if (size > 0xff) {
size = 0xff; // size is only 1 byte, truncate
}
Write8(size);
for (size_t i = 0; i < size; ++i) {
Write8(v[i] ? 1 : 0);
}
break;
}
case NT_DOUBLE_ARRAY: {
auto v = value.GetDoubleArray();
size_t size = v.size();
if (size > 0xff) {
size = 0xff; // size is only 1 byte, truncate
}
Write8(size);
for (size_t i = 0; i < size; ++i) {
WriteDouble(v[i]);
}
break;
}
case NT_STRING_ARRAY: {
auto v = value.GetStringArray();
size_t size = v.size();
if (size > 0xff) {
size = 0xff; // size is only 1 byte, truncate
}
Write8(size);
for (size_t i = 0; i < size; ++i) {
WriteString(v[i]);
}
break;
}
default:
m_error = "unrecognized type when writing value";
return;
}
}
size_t WireEncoder::GetStringSize(std::string_view str) const {
if (m_proto_rev < 0x0300u) {
size_t len = str.size();
if (len > 0xffff) {
len = 0xffff; // Limited to 64K length; truncate
}
return 2 + len;
}
return wpi::SizeUleb128(str.size()) + str.size();
}
void WireEncoder::WriteString(std::string_view str) {
// length
size_t len = str.size();
if (m_proto_rev < 0x0300u) {
if (len > 0xffff) {
len = 0xffff; // Limited to 64K length; truncate
}
Write16(len);
} else {
WriteUleb128(len);
}
// contents
m_data.append(str.data(), str.data() + len);
}

View File

@@ -1,108 +0,0 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source 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 NTCORE_WIREENCODER_H_
#define NTCORE_WIREENCODER_H_
#include <stdint.h>
#include <cassert>
#include <cstddef>
#include <string_view>
#include <wpi/SmallVector.h>
#include "networktables/NetworkTableValue.h"
namespace nt {
/* Encodes native data for network transmission.
* This class maintains an internal memory buffer for written data so that
* it can be efficiently bursted to the network after a number of writes
* have been performed. For this reason, all operations are non-blocking.
*/
class WireEncoder {
public:
explicit WireEncoder(unsigned int proto_rev);
/* Change the protocol revision (mostly affects value encoding). */
void set_proto_rev(unsigned int proto_rev) { m_proto_rev = proto_rev; }
/* Get the active protocol revision. */
unsigned int proto_rev() const { return m_proto_rev; }
/* Clears buffer and error indicator. */
void Reset() {
m_data.clear();
m_error = nullptr;
}
/* Returns error indicator (a string describing the error). Returns nullptr
* if no error has occurred.
*/
const char* error() const { return m_error; }
/* Returns pointer to start of memory buffer with written data. */
const char* data() const { return m_data.data(); }
/* Returns number of bytes written to memory buffer. */
size_t size() const { return m_data.size(); }
std::string_view ToStringView() const {
return {m_data.data(), m_data.size()};
}
/* Writes a single byte. */
void Write8(unsigned int val) {
m_data.push_back(static_cast<char>(val & 0xff));
}
/* Writes a 16-bit word. */
void Write16(unsigned int val) {
m_data.append(
{static_cast<char>((val >> 8) & 0xff), static_cast<char>(val & 0xff)});
}
/* Writes a 32-bit word. */
void Write32(uint32_t val) {
m_data.append({static_cast<char>((val >> 24) & 0xff),
static_cast<char>((val >> 16) & 0xff),
static_cast<char>((val >> 8) & 0xff),
static_cast<char>(val & 0xff)});
}
/* Writes a double. */
void WriteDouble(double val);
/* Writes an ULEB128-encoded unsigned integer. */
void WriteUleb128(uint32_t val);
void WriteType(NT_Type type);
void WriteValue(const Value& value);
void WriteString(std::string_view str);
/* Utility function to get the written size of a value (without actually
* writing it).
*/
size_t GetValueSize(const Value& value) const;
/* Utility function to get the written size of a string (without actually
* writing it).
*/
size_t GetStringSize(std::string_view str) const;
protected:
/* The protocol revision. E.g. 0x0200 for version 2.0. */
unsigned int m_proto_rev;
/* Error indicator. */
const char* m_error;
private:
wpi::SmallVector<char, 256> m_data;
};
} // namespace nt
#endif // NTCORE_WIREENCODER_H_

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,487 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#include "ClientImpl.h"
#include <numeric>
#include <optional>
#include <string>
#include <variant>
#include <fmt/format.h>
#include <wpi/DenseMap.h>
#include <wpi/Logger.h>
#include <wpi/raw_ostream.h>
#include <wpi/timestamp.h>
#include "Handle.h"
#include "Log.h"
#include "Message.h"
#include "NetworkInterface.h"
#include "PubSubOptions.h"
#include "WireConnection.h"
#include "WireDecoder.h"
#include "WireEncoder.h"
#include "networktables/NetworkTableValue.h"
using namespace nt;
using namespace nt::net;
static constexpr uint32_t kMinPeriodMs = 5;
// maximum number of times the wire can be not ready to send another
// transmission before we close the connection
static constexpr int kWireMaxNotReady = 10;
namespace {
struct PublisherData {
NT_Publisher handle;
PubSubOptions options;
// in options as double, but copy here as integer; rounded to the nearest
// 10 ms
uint32_t periodMs;
uint64_t nextSendMs{0};
Value lastValue; // only used for duplicate value checking
std::vector<Value> outValues; // outgoing values
};
class CImpl : public ServerMessageHandler {
public:
CImpl(uint64_t curTimeMs, int inst, WireConnection& wire, wpi::Logger& logger,
std::function<void(uint32_t repeatMs)> setPeriodic);
void ProcessIncomingBinary(wpi::span<const uint8_t> data);
void HandleLocal(std::vector<ClientMessage>&& msgs);
void SendOutgoing(wpi::span<const ClientMessage> msgs);
bool SendControl(uint64_t curTimeMs);
void SendValues(uint64_t curTimeMs);
bool CheckNetworkReady();
// ServerMessageHandler interface
void ServerAnnounce(std::string_view name, int64_t id,
std::string_view typeStr, const wpi::json& properties,
std::optional<int64_t> pubuid) final;
void ServerUnannounce(std::string_view name, int64_t id) final;
void ServerPropertiesUpdate(std::string_view name, const wpi::json& update,
bool ack) final;
void Publish(NT_Publisher pubHandle, NT_Topic topicHandle,
std::string_view name, std::string_view typeStr,
const wpi::json& properties, const PubSubOptions& options);
bool Unpublish(NT_Publisher pubHandle, NT_Topic topicHandle);
void SetValue(NT_Publisher pubHandle, const Value& value);
int m_inst;
WireConnection& m_wire;
wpi::Logger& m_logger;
LocalInterface* m_local{nullptr};
std::function<void(uint32_t repeatMs)> m_setPeriodic;
// indexed by publisher index
std::vector<std::unique_ptr<PublisherData>> m_publishers;
// indexed by server-provided topic id
wpi::DenseMap<int64_t, NT_Topic> m_topicMap;
// timestamp handling
static constexpr uint32_t kPingIntervalMs = 3000;
uint64_t m_nextPingTimeMs{0};
uint32_t m_rtt2Us{UINT32_MAX};
bool m_haveTimeOffset{false};
int64_t m_serverTimeOffsetUs{0};
// periodic sweep handling
uint32_t m_periodMs{kPingIntervalMs + 10};
uint64_t m_lastSendMs{0};
int m_notReadyCount{0};
// outgoing queue
std::vector<ClientMessage> m_outgoing;
};
} // namespace
CImpl::CImpl(uint64_t curTimeMs, int inst, WireConnection& wire,
wpi::Logger& logger,
std::function<void(uint32_t repeatMs)> setPeriodic)
: m_inst{inst},
m_wire{wire},
m_logger{logger},
m_setPeriodic{std::move(setPeriodic)},
m_nextPingTimeMs{curTimeMs + kPingIntervalMs} {
// immediately send RTT ping
auto out = m_wire.SendBinary();
auto now = wpi::Now();
DEBUG4("Sending initial RTT ping {}", now);
WireEncodeBinary(out.Add(), -1, 0, Value::MakeInteger(now));
m_wire.Flush();
m_setPeriodic(m_periodMs);
}
void CImpl::ProcessIncomingBinary(wpi::span<const uint8_t> data) {
for (;;) {
if (data.empty()) {
break;
}
// decode message
int64_t id;
Value value;
std::string error;
if (!WireDecodeBinary(&data, &id, &value, &error, -m_serverTimeOffsetUs)) {
ERROR("binary decode error: {}", error);
break; // FIXME
}
DEBUG4("BinaryMessage({})", id);
// handle RTT ping response
if (id == -1) {
if (!value.IsInteger()) {
WARNING("RTT ping response with non-integer type {}",
static_cast<int>(value.type()));
continue;
}
DEBUG4("RTT ping response time {} value {}", value.time(),
value.GetInteger());
int64_t now = wpi::Now();
int64_t rtt2 = (now - value.GetInteger()) / 2;
if (rtt2 < m_rtt2Us) {
m_rtt2Us = rtt2;
m_serverTimeOffsetUs = value.server_time() + rtt2 - now;
DEBUG3("Time offset: {}", m_serverTimeOffsetUs);
m_haveTimeOffset = true;
}
continue;
}
// otherwise it's a value message, get the local topic handle for it
auto topicIt = m_topicMap.find(id);
if (topicIt == m_topicMap.end()) {
WARNING("received unknown id {}", id);
continue;
}
// pass along to local handler
if (m_local) {
m_local->NetworkSetValue(topicIt->second, value);
}
}
}
void CImpl::HandleLocal(std::vector<ClientMessage>&& msgs) {
DEBUG4("{}", "HandleLocal()");
for (auto&& elem : msgs) {
// common case is value
if (auto msg = std::get_if<ClientValueMsg>(&elem.contents)) {
SetValue(msg->pubHandle, msg->value);
// setvalue puts on individual publish outgoing queues
} else if (auto msg = std::get_if<PublishMsg>(&elem.contents)) {
Publish(msg->pubHandle, msg->topicHandle, msg->name, msg->typeStr,
msg->properties, msg->options);
m_outgoing.emplace_back(std::move(elem));
} else if (auto msg = std::get_if<UnpublishMsg>(&elem.contents)) {
if (Unpublish(msg->pubHandle, msg->topicHandle)) {
m_outgoing.emplace_back(std::move(elem));
}
} else {
m_outgoing.emplace_back(std::move(elem));
}
}
}
bool CImpl::SendControl(uint64_t curTimeMs) {
DEBUG4("SendControl({})", curTimeMs);
// rate limit sends
if (curTimeMs < (m_lastSendMs + kMinPeriodMs)) {
return false;
}
// start a timestamp RTT ping if it's time to do one
if (curTimeMs >= m_nextPingTimeMs) {
if (!CheckNetworkReady()) {
return false;
}
auto now = wpi::Now();
DEBUG4("Sending RTT ping {}", now);
WireEncodeBinary(m_wire.SendBinary().Add(), -1, 0, Value::MakeInteger(now));
// drift isn't critical here, so just go from current time
m_nextPingTimeMs = curTimeMs + kPingIntervalMs;
}
if (!m_outgoing.empty()) {
if (!CheckNetworkReady()) {
return false;
}
auto writer = m_wire.SendText();
for (auto&& msg : m_outgoing) {
auto& stream = writer.Add();
if (!WireEncodeText(stream, msg)) {
// shouldn't happen, but just in case...
stream << "{}";
}
}
m_outgoing.resize(0);
}
m_lastSendMs = curTimeMs;
return true;
}
void CImpl::SendValues(uint64_t curTimeMs) {
DEBUG4("SendPeriodic({})", curTimeMs);
// can't send value updates until we have a RTT
if (!m_haveTimeOffset) {
return;
}
// ensure all control messages are sent ahead of value updates
if (!SendControl(curTimeMs)) {
return;
}
// send any pending updates due to be sent
bool checkedNetwork = false;
auto writer = m_wire.SendBinary();
for (auto&& pub : m_publishers) {
if (pub && !pub->outValues.empty() && curTimeMs >= pub->nextSendMs) {
for (auto&& val : pub->outValues) {
if (!checkedNetwork) {
if (!CheckNetworkReady()) {
return;
}
checkedNetwork = true;
}
DEBUG4("Sending {} value time={} server_time={} st_off={}", pub->handle,
val.time(), val.server_time(), m_serverTimeOffsetUs);
int64_t time = val.time();
if (time != 0) {
time += m_serverTimeOffsetUs;
}
WireEncodeBinary(writer.Add(), Handle{pub->handle}.GetIndex(), time,
val);
}
pub->outValues.resize(0);
pub->nextSendMs = curTimeMs + pub->periodMs;
}
}
}
bool CImpl::CheckNetworkReady() {
if (!m_wire.Ready()) {
++m_notReadyCount;
if (m_notReadyCount > kWireMaxNotReady) {
m_wire.Disconnect("transmit stalled");
}
return false;
}
m_notReadyCount = 0;
return true;
}
void CImpl::Publish(NT_Publisher pubHandle, NT_Topic topicHandle,
std::string_view name, std::string_view typeStr,
const wpi::json& properties, const PubSubOptions& options) {
unsigned int index = Handle{pubHandle}.GetIndex();
if (index >= m_publishers.size()) {
m_publishers.resize(index + 1);
}
auto& publisher = m_publishers[index];
if (!publisher) {
publisher = std::make_unique<PublisherData>();
}
publisher->handle = pubHandle;
publisher->options = options;
publisher->periodMs = std::lround(options.periodic * 100) * 10;
if (publisher->periodMs < kMinPeriodMs) {
publisher->periodMs = kMinPeriodMs;
}
// update period
m_periodMs = std::gcd(m_periodMs, publisher->periodMs);
if (m_periodMs < kMinPeriodMs) {
m_periodMs = kMinPeriodMs;
}
m_setPeriodic(m_periodMs);
}
bool CImpl::Unpublish(NT_Publisher pubHandle, NT_Topic topicHandle) {
unsigned int index = Handle{pubHandle}.GetIndex();
if (index >= m_publishers.size()) {
return false;
}
bool doSend = true;
if (m_publishers[index]) {
// Look through outgoing queue to see if the publish hasn't been sent yet;
// if it hasn't, delete it and don't send the server a message.
// The outgoing queue doesn't contain values; those are deleted with the
// publisher object.
auto it = std::find_if(
m_outgoing.begin(), m_outgoing.end(), [&](const auto& elem) {
if (auto msg = std::get_if<PublishMsg>(&elem.contents)) {
return msg->pubHandle == pubHandle;
}
return false;
});
if (it != m_outgoing.end()) {
m_outgoing.erase(it);
doSend = false;
}
}
m_publishers[index].reset();
// loop over all publishers to update period
m_periodMs = kPingIntervalMs + 10;
for (auto&& pub : m_publishers) {
if (pub) {
m_periodMs = std::gcd(m_periodMs, pub->periodMs);
}
}
if (m_periodMs < kMinPeriodMs) {
m_periodMs = kMinPeriodMs;
}
m_setPeriodic(m_periodMs);
return doSend;
}
void CImpl::SetValue(NT_Publisher pubHandle, const Value& value) {
DEBUG4("SetValue({}, time={}, server_time={}, st_off={})", pubHandle,
value.time(), value.server_time(), m_serverTimeOffsetUs);
unsigned int index = Handle{pubHandle}.GetIndex();
if (index >= m_publishers.size() || !m_publishers[index]) {
return;
}
auto& publisher = *m_publishers[index];
if (!publisher.options.keepDuplicates) {
if (value == publisher.lastValue) {
return;
}
publisher.lastValue = value;
}
if (publisher.outValues.empty() || publisher.options.sendAll) {
publisher.outValues.emplace_back(value);
} else {
publisher.outValues.back() = value;
}
}
void CImpl::ServerAnnounce(std::string_view name, int64_t id,
std::string_view typeStr,
const wpi::json& properties,
std::optional<int64_t> pubuid) {
DEBUG4("ServerAnnounce({}, {}, {})", name, id, typeStr);
assert(m_local);
NT_Publisher pubHandle{0};
if (pubuid) {
pubHandle = Handle(m_inst, pubuid.value(), Handle::kPublisher);
}
m_topicMap[id] =
m_local->NetworkAnnounce(name, typeStr, properties, pubHandle);
}
void CImpl::ServerUnannounce(std::string_view name, int64_t id) {
DEBUG4("ServerUnannounce({}, {})", name, id);
assert(m_local);
m_local->NetworkUnannounce(name);
m_topicMap.erase(id);
}
void CImpl::ServerPropertiesUpdate(std::string_view name,
const wpi::json& update, bool ack) {
DEBUG4("ServerProperties({}, {}, {})", name, update.dump(), ack);
assert(m_local);
m_local->NetworkPropertiesUpdate(name, update, ack);
}
class ClientImpl::Impl final : public CImpl {
public:
Impl(uint64_t curTimeMs, int inst, WireConnection& wire, wpi::Logger& logger,
std::function<void(uint32_t repeatMs)> setPeriodic)
: CImpl{curTimeMs, inst, wire, logger, std::move(setPeriodic)} {}
};
ClientImpl::ClientImpl(uint64_t curTimeMs, int inst, WireConnection& wire,
wpi::Logger& logger,
std::function<void(uint32_t repeatMs)> setPeriodic)
: m_impl{std::make_unique<Impl>(curTimeMs, inst, wire, logger,
std::move(setPeriodic))} {}
ClientImpl::~ClientImpl() = default;
void ClientImpl::ProcessIncomingText(std::string_view data) {
if (!m_impl->m_local) {
return;
}
WireDecodeText(data, *m_impl, m_impl->m_logger);
}
void ClientImpl::ProcessIncomingBinary(wpi::span<const uint8_t> data) {
m_impl->ProcessIncomingBinary(data);
}
void ClientImpl::HandleLocal(std::vector<ClientMessage>&& msgs) {
m_impl->HandleLocal(std::move(msgs));
}
void ClientImpl::SendControl(uint64_t curTimeMs) {
m_impl->SendControl(curTimeMs);
m_impl->m_wire.Flush();
}
void ClientImpl::SendValues(uint64_t curTimeMs) {
m_impl->SendValues(curTimeMs);
m_impl->m_wire.Flush();
}
void ClientImpl::SetLocal(LocalInterface* local) {
m_impl->m_local = local;
}
ClientStartup::ClientStartup(ClientImpl& client)
: m_client{client},
m_binaryWriter{client.m_impl->m_wire.SendBinary()},
m_textWriter{client.m_impl->m_wire.SendText()} {}
ClientStartup::~ClientStartup() {
m_client.m_impl->m_wire.Flush();
}
void ClientStartup::Publish(NT_Publisher pubHandle, NT_Topic topicHandle,
std::string_view name, std::string_view typeStr,
const wpi::json& properties,
const PubSubOptions& options) {
WPI_DEBUG4(m_client.m_impl->m_logger, "StartupPublish({}, {}, {}, {})",
pubHandle, topicHandle, name, typeStr);
m_client.m_impl->Publish(pubHandle, topicHandle, name, typeStr, properties,
options);
WireEncodePublish(m_textWriter.Add(), Handle{pubHandle}.GetIndex(), name,
typeStr, properties);
}
void ClientStartup::Subscribe(NT_Subscriber subHandle,
wpi::span<const std::string> prefixes,
const PubSubOptions& options) {
WPI_DEBUG4(m_client.m_impl->m_logger, "StartupSubscribe({})", subHandle);
WireEncodeSubscribe(m_textWriter.Add(), Handle{subHandle}.GetIndex(),
prefixes, options);
}
void ClientStartup::SetValue(NT_Publisher pubHandle, const Value& value) {
WPI_DEBUG4(m_client.m_impl->m_logger, "StartupSetValue({})", pubHandle);
// Similar to Client::SetValue(), except always set lastValue and send
unsigned int index = Handle{pubHandle}.GetIndex();
assert(index < m_client.m_impl->m_publishers.size() &&
m_client.m_impl->m_publishers[index]);
auto& publisher = *m_client.m_impl->m_publishers[index];
publisher.lastValue = value;
// only send time 0 values until we have a RTT
if (value.server_time() == 0) {
WireEncodeBinary(m_binaryWriter.Add(), index, 0, value);
} else {
publisher.outValues.emplace_back(value);
}
}

View File

@@ -0,0 +1,79 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#pragma once
#include <stdint.h>
#include <functional>
#include <memory>
#include <string>
#include <string_view>
#include <vector>
#include <wpi/span.h>
#include "NetworkInterface.h"
#include "WireConnection.h"
namespace wpi {
class Logger;
} // namespace wpi
namespace nt {
class PubSubOptions;
class Value;
} // namespace nt
namespace nt::net {
struct ClientMessage;
class ClientStartup;
class WireConnection;
class ClientImpl {
friend class ClientStartup;
public:
ClientImpl(uint64_t curTimeMs, int inst, WireConnection& wire,
wpi::Logger& logger,
std::function<void(uint32_t repeatMs)> setPeriodic);
~ClientImpl();
void ProcessIncomingText(std::string_view data);
void ProcessIncomingBinary(wpi::span<const uint8_t> data);
void HandleLocal(std::vector<ClientMessage>&& msgs);
void SendControl(uint64_t curTimeMs);
void SendValues(uint64_t curTimeMs);
void SetLocal(LocalInterface* local);
private:
class Impl;
std::unique_ptr<Impl> m_impl;
};
class ClientStartup final : public NetworkStartupInterface {
public:
explicit ClientStartup(ClientImpl& client);
~ClientStartup() final;
ClientStartup(const ClientStartup&) = delete;
ClientStartup& operator=(const ClientStartup&) = delete;
// NetworkStartupInterface interface
void Publish(NT_Publisher pubHandle, NT_Topic topicHandle,
std::string_view name, std::string_view typeStr,
const wpi::json& properties, const PubSubOptions& options) final;
void Subscribe(NT_Subscriber subHandle, wpi::span<const std::string> prefixes,
const PubSubOptions& options) final;
void SetValue(NT_Publisher pubHandle, const Value& value) final;
private:
ClientImpl& m_client;
BinaryWriter m_binaryWriter;
TextWriter m_textWriter;
};
} // namespace nt::net

View File

@@ -0,0 +1,100 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source 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 <optional>
#include <string>
#include <variant>
#include <vector>
#include <wpi/json.h>
#include "PubSubOptions.h"
#include "networktables/NetworkTableValue.h"
#include "ntcore_c.h"
namespace nt::net {
struct PublishMsg {
static constexpr std::string_view kMethodStr = "publish";
NT_Publisher pubHandle{0};
NT_Topic topicHandle{0}; // will be 0 when coming from network
std::string name;
std::string typeStr;
wpi::json properties;
PubSubOptions options; // will be empty when coming from network
};
struct UnpublishMsg {
static constexpr std::string_view kMethodStr = "unpublish";
NT_Publisher pubHandle{0};
NT_Topic topicHandle{0}; // will be 0 when coming from network
};
struct SetPropertiesMsg {
static constexpr std::string_view kMethodStr = "setproperties";
NT_Topic topicHandle{0}; // will be 0 when coming from network
std::string name;
wpi::json update;
};
struct SubscribeMsg {
static constexpr std::string_view kMethodStr = "subscribe";
NT_Subscriber subHandle{0};
std::vector<std::string> topicNames;
PubSubOptions options;
};
struct UnsubscribeMsg {
static constexpr std::string_view kMethodStr = "unsubscribe";
NT_Subscriber subHandle{0};
};
struct ClientValueMsg {
NT_Publisher pubHandle{0};
Value value;
};
struct ClientMessage {
using Contents =
std::variant<std::monostate, PublishMsg, UnpublishMsg, SetPropertiesMsg,
SubscribeMsg, UnsubscribeMsg, ClientValueMsg>;
Contents contents;
};
struct AnnounceMsg {
static constexpr std::string_view kMethodStr = "announce";
std::string name;
int64_t id{0};
std::string typeStr;
std::optional<int64_t> pubuid;
wpi::json properties;
};
struct UnannounceMsg {
static constexpr std::string_view kMethodStr = "unannounce";
std::string name;
int64_t id{0};
};
struct PropertiesUpdateMsg {
static constexpr std::string_view kMethodStr = "properties";
std::string name;
wpi::json update;
bool ack;
};
struct ServerValueMsg {
NT_Topic topic{0};
Value value;
};
struct ServerMessage {
using Contents = std::variant<std::monostate, AnnounceMsg, UnannounceMsg,
PropertiesUpdateMsg, ServerValueMsg>;
Contents contents;
};
} // namespace nt::net

View File

@@ -0,0 +1,68 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source 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 <string>
#include <string_view>
#include <wpi/span.h>
#include "ntcore_cpp.h"
namespace wpi {
class json;
} // namespace wpi
namespace nt {
class PubSubOptions;
class Value;
} // namespace nt
namespace nt::net {
class LocalInterface {
public:
virtual ~LocalInterface() = default;
virtual NT_Topic NetworkAnnounce(std::string_view name,
std::string_view typeStr,
const wpi::json& properties,
NT_Publisher pubHandle) = 0;
virtual void NetworkUnannounce(std::string_view name) = 0;
virtual void NetworkPropertiesUpdate(std::string_view name,
const wpi::json& update, bool ack) = 0;
virtual void NetworkSetValue(NT_Topic topicHandle, const Value& value) = 0;
};
class NetworkStartupInterface {
public:
virtual ~NetworkStartupInterface() = default;
virtual void Publish(NT_Publisher pubHandle, NT_Topic topicHandle,
std::string_view name, std::string_view typeStr,
const wpi::json& properties,
const PubSubOptions& options) = 0;
virtual void Subscribe(NT_Subscriber subHandle,
wpi::span<const std::string> topicNames,
const PubSubOptions& options) = 0;
virtual void SetValue(NT_Publisher pubHandle, const Value& value) = 0;
};
class NetworkInterface : public NetworkStartupInterface {
public:
virtual void Unpublish(NT_Publisher pubHandle, NT_Topic topicHandle) = 0;
virtual void SetProperties(NT_Topic topicHandle, std::string_view name,
const wpi::json& update) = 0;
virtual void Unsubscribe(NT_Subscriber subHandle) = 0;
};
class ILocalStorage : public LocalInterface {
public:
virtual void StartNetwork(NetworkStartupInterface& startup) = 0;
virtual void SetNetwork(NetworkInterface* network) = 0;
virtual void ClearNetwork() = 0;
};
} // namespace nt::net

View File

@@ -0,0 +1,54 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#include "NetworkLoopQueue.h"
#include <wpi/Logger.h>
using namespace nt::net;
static constexpr size_t kMaxSize = 2 * 1024 * 1024;
void NetworkLoopQueue::SetValue(NT_Publisher pubHandle, const Value& value) {
std::scoped_lock lock{m_mutex};
switch (value.type()) {
case NT_STRING:
m_size += value.GetString().size(); // imperfect but good enough
break;
case NT_RAW:
m_size += value.GetRaw().size_bytes();
break;
case NT_BOOLEAN_ARRAY:
m_size += value.GetBooleanArray().size_bytes();
break;
case NT_INTEGER_ARRAY:
m_size += value.GetIntegerArray().size_bytes();
break;
case NT_FLOAT_ARRAY:
m_size += value.GetFloatArray().size_bytes();
break;
case NT_DOUBLE_ARRAY:
m_size += value.GetDoubleArray().size_bytes();
break;
case NT_STRING_ARRAY: {
auto arr = value.GetStringArray();
m_size += arr.size_bytes();
for (auto&& s : arr) {
m_size += s.capacity();
}
break;
}
default:
break;
}
m_size += sizeof(ClientMessage);
if (m_size > kMaxSize) {
if (!m_sizeErrored) {
WPI_ERROR(m_logger, "{}", "NT: dropping value set due to memory limits");
m_sizeErrored = true;
}
return; // avoid potential out of memory
}
m_queue.emplace_back(ClientMessage{ClientValueMsg{pubHandle, value}});
}

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 <string>
#include <vector>
#include <wpi/mutex.h>
#include "Message.h"
#include "NetworkInterface.h"
namespace wpi {
class Logger;
} // namespace wpi
namespace nt::net {
class NetworkLoopQueue : public NetworkInterface {
public:
static constexpr size_t kInitialQueueSize = 2000;
explicit NetworkLoopQueue(wpi::Logger& logger) : m_logger{logger} {
m_queue.reserve(kInitialQueueSize);
}
void ReadQueue(std::vector<ClientMessage>* out);
void ClearQueue();
// NetworkInterface - calls to these append to the queue
void Publish(NT_Publisher pubHandle, NT_Topic topicHandle,
std::string_view name, std::string_view typeStr,
const wpi::json& properties, const PubSubOptions& options) final;
void Unpublish(NT_Publisher pubHandle, NT_Topic topicHandle) final;
void SetProperties(NT_Topic topicHandle, std::string_view name,
const wpi::json& update) final;
void Subscribe(NT_Subscriber subHandle,
wpi::span<const std::string> topicNames,
const PubSubOptions& options) final;
void Unsubscribe(NT_Subscriber subHandle) final;
void SetValue(NT_Publisher pubHandle, const Value& value) final;
private:
wpi::mutex m_mutex;
std::vector<ClientMessage> m_queue;
wpi::Logger& m_logger;
size_t m_size{0};
bool m_sizeErrored{false};
};
} // namespace nt::net
#include "NetworkLoopQueue.inc"

View File

@@ -0,0 +1,70 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source 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 <string>
#include <vector>
#include "NetworkLoopQueue.h"
#include "ntcore_c.h"
namespace nt::net {
inline void NetworkLoopQueue::ReadQueue(std::vector<ClientMessage>* out) {
std::scoped_lock lock{m_mutex};
out->swap(m_queue);
m_queue.resize(0);
m_queue.reserve(out->capacity()); // keep the same running capacity
m_size = 0;
m_sizeErrored = false;
}
inline void NetworkLoopQueue::ClearQueue() {
std::scoped_lock lock{m_mutex};
m_queue.resize(0);
m_size = 0;
m_sizeErrored = false;
}
inline void NetworkLoopQueue::Publish(NT_Publisher pubHandle,
NT_Topic topicHandle,
std::string_view name,
std::string_view typeStr,
const wpi::json& properties,
const PubSubOptions& options) {
std::scoped_lock lock{m_mutex};
m_queue.emplace_back(
ClientMessage{PublishMsg{pubHandle, topicHandle, std::string{name},
std::string{typeStr}, properties, options}});
}
inline void NetworkLoopQueue::Unpublish(NT_Publisher pubHandle,
NT_Topic topicHandle) {
std::scoped_lock lock{m_mutex};
m_queue.emplace_back(ClientMessage{UnpublishMsg{pubHandle, topicHandle}});
}
inline void NetworkLoopQueue::SetProperties(NT_Topic topicHandle,
std::string_view name,
const wpi::json& update) {
std::scoped_lock lock{m_mutex};
m_queue.emplace_back(
ClientMessage{SetPropertiesMsg{topicHandle, std::string{name}, update}});
}
inline void NetworkLoopQueue::Subscribe(NT_Subscriber subHandle,
wpi::span<const std::string> topicNames,
const PubSubOptions& options) {
std::scoped_lock lock{m_mutex};
m_queue.emplace_back(ClientMessage{SubscribeMsg{
subHandle, {topicNames.begin(), topicNames.end()}, options}});
}
inline void NetworkLoopQueue::Unsubscribe(NT_Subscriber subHandle) {
std::scoped_lock lock{m_mutex};
m_queue.emplace_back(ClientMessage{UnsubscribeMsg{subHandle}});
}
} // namespace nt::net

File diff suppressed because it is too large Load Diff

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.
#pragma once
#include <stdint.h>
#include <functional>
#include <memory>
#include <string>
#include <string_view>
#include <vector>
#include <wpi/span.h>
#include "NetworkInterface.h"
#include "net3/WireConnection3.h"
namespace wpi {
class Logger;
} // namespace wpi
namespace nt::net3 {
class WireConnection3;
} // namespace nt::net3
namespace nt::net {
struct ClientMessage;
class LocalInterface;
class ServerStartup;
class WireConnection;
class ServerImpl final {
friend class ServerStartup;
public:
using SetPeriodicFunc = std::function<void(uint32_t repeatMs)>;
using Connected3Func =
std::function<void(std::string_view name, uint16_t proto)>;
explicit ServerImpl(wpi::Logger& logger);
~ServerImpl();
void SendControl(uint64_t curTimeMs);
void SendValues(int clientId, uint64_t curTimeMs);
void HandleLocal(wpi::span<const ClientMessage> msgs);
void SetLocal(LocalInterface* local);
void ProcessIncomingText(int clientId, std::string_view data);
void ProcessIncomingBinary(int clientId, wpi::span<const uint8_t> data);
// Returns -1 if cannot add client (e.g. due to duplicate name).
// Caller must ensure WireConnection lifetime lasts until RemoveClient() call.
int AddClient(std::string_view name, std::string_view connInfo, bool local,
WireConnection& wire, SetPeriodicFunc setPeriodic);
int AddClient3(std::string_view connInfo, bool local,
net3::WireConnection3& wire, Connected3Func connected,
SetPeriodicFunc setPeriodic);
void RemoveClient(int clientId);
void ConnectionsChanged(const std::vector<ConnectionInfo>& conns);
// if any persistent values changed since the last call to this function
bool PersistentChanged();
std::string DumpPersistent();
// returns newline-separated errors
std::string LoadPersistent(std::string_view in);
private:
class Impl;
std::unique_ptr<Impl> m_impl;
};
class ServerStartup final : public NetworkStartupInterface {
public:
explicit ServerStartup(ServerImpl& server) : m_server{server} {}
// NetworkStartupInterface interface
void Publish(NT_Publisher pubHandle, NT_Topic topicHandle,
std::string_view name, std::string_view typeStr,
const wpi::json& properties, const PubSubOptions& options) final;
void Subscribe(NT_Subscriber subHandle,
wpi::span<const std::string> topicNames,
const PubSubOptions& options) final;
void SetValue(NT_Publisher pubHandle, const Value& value) final;
private:
ServerImpl& m_server;
};
} // namespace nt::net

View File

@@ -0,0 +1,122 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#include "WebSocketConnection.h"
#include <wpi/SpanExtras.h>
#include <wpinet/WebSocket.h>
using namespace nt;
using namespace nt::net;
static constexpr size_t kAllocSize = 4096;
static constexpr size_t kTextFrameRolloverSize = 4096;
static constexpr size_t kBinaryFrameRolloverSize = 8192;
WebSocketConnection::WebSocketConnection(wpi::WebSocket& ws)
: m_ws{ws},
m_text_os{m_text_buffers, [this] { return AllocBuf(); }},
m_binary_os{m_binary_buffers, [this] { return AllocBuf(); }} {}
WebSocketConnection::~WebSocketConnection() {
for (auto&& buf : m_buf_pool) {
buf.Deallocate();
}
}
void WebSocketConnection::Flush() {
FinishSendText();
FinishSendBinary();
if (m_frames.empty()) {
return;
}
// convert internal frames into WS frames
m_ws_frames.clear();
m_ws_frames.reserve(m_frames.size());
for (auto&& frame : m_frames) {
m_ws_frames.emplace_back(frame.opcode,
wpi::span{frame.bufs->begin() + frame.start,
frame.bufs->begin() + frame.end});
}
++m_sendsActive;
m_ws.SendFrames(m_ws_frames, [this](auto bufs, auto) {
m_buf_pool.insert(m_buf_pool.end(), bufs.begin(), bufs.end());
if (m_sendsActive > 0) {
--m_sendsActive;
}
});
m_frames.clear();
m_text_buffers.clear();
m_binary_buffers.clear();
m_text_pos = 0;
m_binary_pos = 0;
}
void WebSocketConnection::Disconnect(std::string_view reason) {
m_ws.Close(1005, reason);
}
void WebSocketConnection::StartSendText() {
// limit amount per single frame
size_t total = 0;
for (size_t i = m_text_pos; i < m_text_buffers.size(); ++i) {
total += m_text_buffers[i].len;
}
if (total >= kTextFrameRolloverSize) {
FinishSendText();
}
if (m_in_text) {
m_text_os << ',';
} else {
m_text_os << '[';
m_in_text = true;
}
}
void WebSocketConnection::FinishSendText() {
if (m_in_text) {
m_text_os << ']';
m_in_text = false;
}
if (m_text_pos >= m_text_buffers.size()) {
return;
}
m_frames.emplace_back(wpi::WebSocket::Frame::kText, &m_text_buffers,
m_text_pos, m_text_buffers.size());
m_text_pos = m_text_buffers.size();
m_text_os.reset();
}
void WebSocketConnection::StartSendBinary() {
// limit amount per single frame
size_t total = 0;
for (size_t i = m_binary_pos; i < m_binary_buffers.size(); ++i) {
total += m_binary_buffers[i].len;
}
if (total >= kBinaryFrameRolloverSize) {
FinishSendBinary();
}
}
void WebSocketConnection::FinishSendBinary() {
if (m_binary_pos >= m_binary_buffers.size()) {
return;
}
m_frames.emplace_back(wpi::WebSocket::Frame::kBinary, &m_binary_buffers,
m_binary_pos, m_binary_buffers.size());
m_binary_pos = m_binary_buffers.size();
m_binary_os.reset();
}
wpi::uv::Buffer WebSocketConnection::AllocBuf() {
if (!m_buf_pool.empty()) {
auto buf = m_buf_pool.back();
m_buf_pool.pop_back();
return buf;
}
return wpi::uv::Buffer::Allocate(kAllocSize);
}

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.
#pragma once
#include <vector>
#include <wpi/SmallVector.h>
#include <wpinet/WebSocket.h>
#include <wpinet/raw_uv_ostream.h>
#include <wpinet/uv/Buffer.h>
#include "WireConnection.h"
namespace nt::net {
class WebSocketConnection final : public WireConnection {
public:
explicit WebSocketConnection(wpi::WebSocket& ws);
~WebSocketConnection() override;
WebSocketConnection(const WebSocketConnection&) = delete;
WebSocketConnection& operator=(const WebSocketConnection&) = delete;
bool Ready() const final { return m_sendsActive == 0; }
TextWriter SendText() final { return {m_text_os, *this}; }
BinaryWriter SendBinary() final { return {m_binary_os, *this}; }
void Flush() final;
void Disconnect(std::string_view reason) final;
private:
void StartSendText() final;
void FinishSendText() final;
void StartSendBinary() final;
void FinishSendBinary() final;
wpi::uv::Buffer AllocBuf();
wpi::WebSocket& m_ws;
// Can't use WS frames directly as span could have dangling pointers
struct Frame {
Frame(uint8_t opcode, wpi::SmallVectorImpl<wpi::uv::Buffer>* bufs,
size_t start, size_t end)
: opcode{opcode}, bufs{bufs}, start{start}, end{end} {}
uint8_t opcode;
wpi::SmallVectorImpl<wpi::uv::Buffer>* bufs;
size_t start;
size_t end;
};
std::vector<Frame> m_frames;
std::vector<wpi::WebSocket::Frame> m_ws_frames; // to reduce allocs
wpi::SmallVector<wpi::uv::Buffer, 4> m_text_buffers;
wpi::SmallVector<wpi::uv::Buffer, 4> m_binary_buffers;
std::vector<wpi::uv::Buffer> m_buf_pool;
wpi::raw_uv_ostream m_text_os;
wpi::raw_uv_ostream m_binary_os;
size_t m_text_pos = 0;
size_t m_binary_pos = 0;
bool m_in_text = false;
int m_sendsActive = 0;
};
} // namespace nt::net

View File

@@ -0,0 +1,110 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source 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 <string_view>
#include <wpi/raw_ostream.h>
namespace nt::net {
class BinaryWriter;
class TextWriter;
class WireConnection {
friend class TextWriter;
friend class BinaryWriter;
public:
virtual ~WireConnection() = default;
virtual bool Ready() const = 0;
virtual TextWriter SendText() = 0;
virtual BinaryWriter SendBinary() = 0;
virtual void Flush() = 0;
virtual void Disconnect(std::string_view reason) = 0;
protected:
virtual void StartSendText() = 0;
virtual void FinishSendText() = 0;
virtual void StartSendBinary() = 0;
virtual void FinishSendBinary() = 0;
};
class TextWriter {
public:
TextWriter(wpi::raw_ostream& os, WireConnection& wire)
: m_os{&os}, m_wire{&wire} {}
TextWriter(const TextWriter&) = delete;
TextWriter(TextWriter&& rhs) : m_os{rhs.m_os}, m_wire{rhs.m_wire} {
rhs.m_os = nullptr;
rhs.m_wire = nullptr;
}
TextWriter& operator=(const TextWriter&) = delete;
TextWriter& operator=(TextWriter&& rhs) {
m_os = rhs.m_os;
m_wire = rhs.m_wire;
rhs.m_os = nullptr;
rhs.m_wire = nullptr;
return *this;
}
~TextWriter() {
if (m_os) {
m_wire->FinishSendText();
}
}
wpi::raw_ostream& Add() {
m_wire->StartSendText();
return *m_os;
}
WireConnection& wire() { return *m_wire; }
private:
wpi::raw_ostream* m_os;
WireConnection* m_wire;
};
class BinaryWriter {
public:
BinaryWriter(wpi::raw_ostream& os, WireConnection& wire)
: m_os{&os}, m_wire{&wire} {}
BinaryWriter(const BinaryWriter&) = delete;
BinaryWriter(BinaryWriter&& rhs) : m_os{rhs.m_os}, m_wire{rhs.m_wire} {
rhs.m_os = nullptr;
rhs.m_wire = nullptr;
}
BinaryWriter& operator=(const BinaryWriter&) = delete;
BinaryWriter& operator=(BinaryWriter&& rhs) {
m_os = rhs.m_os;
m_wire = rhs.m_wire;
rhs.m_os = nullptr;
rhs.m_wire = nullptr;
return *this;
}
~BinaryWriter() {
if (m_wire) {
m_wire->FinishSendBinary();
}
}
wpi::raw_ostream& Add() {
m_wire->StartSendBinary();
return *m_os;
}
WireConnection& wire() { return *m_wire; }
private:
wpi::raw_ostream* m_os;
WireConnection* m_wire;
};
} // namespace nt::net

View File

@@ -0,0 +1,564 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#include "WireDecoder.h"
#include <algorithm>
#include <fmt/format.h>
#include <wpi/Logger.h>
#include <wpi/SpanExtras.h>
#include <wpi/json.h>
#include <wpi/mpack.h>
#include "Message.h"
using namespace nt;
using namespace nt::net;
using namespace mpack;
static bool GetNumber(wpi::json& val, double* num) {
if (auto v = val.get_ptr<const int64_t*>()) {
*num = *v;
} else if (auto v = val.get_ptr<const uint64_t*>()) {
*num = *v;
} else if (auto v = val.get_ptr<const double*>()) {
*num = *v;
} else {
return false;
}
return true;
}
static bool GetNumber(wpi::json& val, int64_t* num) {
if (auto v = val.get_ptr<const int64_t*>()) {
*num = *v;
} else if (auto v = val.get_ptr<const uint64_t*>()) {
*num = *v;
} else {
return false;
}
return true;
}
static std::string* ObjGetString(wpi::json::object_t& obj, std::string_view key,
std::string* error) {
auto it = obj.find(key);
if (it == obj.end()) {
*error = fmt::format("no {} key", key);
return nullptr;
}
auto val = it->second.get_ptr<std::string*>();
if (!val) {
*error = fmt::format("{} must be a string", key);
}
return val;
}
static bool ObjGetNumber(wpi::json::object_t& obj, std::string_view key,
std::string* error, int64_t* num) {
auto it = obj.find(key);
if (it == obj.end()) {
*error = fmt::format("no {} key", key);
return false;
}
if (!GetNumber(it->second, num)) {
*error = fmt::format("{} must be a number", key);
return false;
}
return true;
}
static bool ObjGetStringArray(wpi::json::object_t& obj, std::string_view key,
std::string* error,
std::vector<std::string>* out) {
// prefixes
auto it = obj.find(key);
if (it == obj.end()) {
*error = fmt::format("no {} key", key);
return false;
}
auto jarr = it->second.get_ptr<wpi::json::array_t*>();
if (!jarr) {
*error = fmt::format("{} must be an array", key);
return false;
}
out->resize(0);
out->reserve(jarr->size());
for (auto&& jval : *jarr) {
auto str = jval.get_ptr<std::string*>();
if (!str) {
*error = fmt::format("{}/{} must be a string", key, out->size());
return false;
}
out->emplace_back(std::move(*str));
}
return true;
}
// avoid a fmtlib "unused type alias 'char_type'" warning false positive
#ifdef __clang__
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunused-local-typedef"
#endif
template <typename T>
static void WireDecodeTextImpl(std::string_view in, T& out,
wpi::Logger& logger) {
static_assert(std::is_same_v<T, ClientMessageHandler> ||
std::is_same_v<T, ServerMessageHandler>,
"T must be ClientMessageHandler or ServerMessageHandler");
wpi::json j;
try {
j = wpi::json::parse(in);
} catch (wpi::json::parse_error& err) {
WPI_WARNING(logger, "could not decode JSON message: {}", err.what());
return;
}
if (!j.is_array()) {
WPI_WARNING(logger, "{}", "expected JSON array at top level");
return;
}
int i = -1;
for (auto&& jmsg : j) {
++i;
std::string error;
{
auto obj = jmsg.get_ptr<wpi::json::object_t*>();
if (!obj) {
error = "expected message to be an object";
goto err;
}
auto method = ObjGetString(*obj, "method", &error);
if (!method) {
goto err;
}
auto paramsIt = obj->find("params");
if (paramsIt == obj->end()) {
error = "no params key";
goto err;
}
auto params = paramsIt->second.get_ptr<wpi::json::object_t*>();
if (!params) {
error = "params must be an object";
goto err;
}
if constexpr (std::is_same_v<T, ClientMessageHandler>) {
if (*method == PublishMsg::kMethodStr) {
// name
auto name = ObjGetString(*params, "name", &error);
if (!name) {
goto err;
}
// type
auto typeStr = ObjGetString(*params, "type", &error);
if (!typeStr) {
goto err;
}
// pubuid
int64_t pubuid;
if (!ObjGetNumber(*params, "pubuid", &error, &pubuid)) {
goto err;
}
// properties; allow missing (treated as empty)
wpi::json* properties = nullptr;
auto propertiesIt = params->find("properties");
if (propertiesIt != params->end()) {
properties = &propertiesIt->second;
if (!properties->is_object()) {
error = "properties must be an object";
goto err;
}
}
wpi::json propertiesEmpty;
if (!properties) {
propertiesEmpty = wpi::json::object();
properties = &propertiesEmpty;
}
// complete
out.ClientPublish(pubuid, *name, *typeStr, *properties);
} else if (*method == UnpublishMsg::kMethodStr) {
// pubuid
int64_t pubuid;
if (!ObjGetNumber(*params, "pubuid", &error, &pubuid)) {
goto err;
}
// complete
out.ClientUnpublish(pubuid);
} else if (*method == SetPropertiesMsg::kMethodStr) {
// name
auto name = ObjGetString(*params, "name", &error);
if (!name) {
goto err;
}
// update
auto updateIt = params->find("update");
if (updateIt == params->end()) {
error = "no update key";
goto err;
}
auto update = &updateIt->second;
if (!update->is_object()) {
error = "update must be an object";
goto err;
}
// complete
out.ClientSetProperties(*name, *update);
} else if (*method == SubscribeMsg::kMethodStr) {
// subuid
int64_t subuid;
if (!ObjGetNumber(*params, "subuid", &error, &subuid)) {
goto err;
}
// options
PubSubOptions options;
auto optionsIt = params->find("options");
if (optionsIt != params->end()) {
auto joptions = optionsIt->second.get_ptr<wpi::json::object_t*>();
if (!joptions) {
error = "options must be an object";
goto err;
}
// periodic
auto periodicIt = joptions->find("periodic");
if (periodicIt != joptions->end()) {
if (!GetNumber(periodicIt->second, &options.periodic)) {
error = "periodic value must be a number";
goto err;
}
}
// send all changes
auto sendAllIt = joptions->find("all");
if (sendAllIt != joptions->end()) {
auto sendAll = sendAllIt->second.get_ptr<bool*>();
if (!sendAll) {
error = "all value must be a boolean";
goto err;
}
options.sendAll = *sendAll;
}
// topics only
auto topicsOnlyIt = joptions->find("topicsonly");
if (topicsOnlyIt != joptions->end()) {
auto topicsOnly = topicsOnlyIt->second.get_ptr<bool*>();
if (!topicsOnly) {
error = "topicsonly value must be a boolean";
goto err;
}
options.topicsOnly = *topicsOnly;
}
// prefix match
auto prefixMatchIt = joptions->find("prefix");
if (prefixMatchIt != joptions->end()) {
auto prefixMatch = prefixMatchIt->second.get_ptr<bool*>();
if (!prefixMatch) {
error = "prefix value must be a boolean";
goto err;
}
options.prefixMatch = *prefixMatch;
}
}
// topic names
std::vector<std::string> topicNames;
if (!ObjGetStringArray(*params, "topics", &error, &topicNames)) {
goto err;
}
// complete
out.ClientSubscribe(subuid, topicNames, options);
} else if (*method == UnsubscribeMsg::kMethodStr) {
// subuid
int64_t subuid;
if (!ObjGetNumber(*params, "subuid", &error, &subuid)) {
goto err;
}
// complete
out.ClientUnsubscribe(subuid);
} else {
error = fmt::format("unrecognized method '{}'", *method);
goto err;
}
} else if constexpr (std::is_same_v<T, ServerMessageHandler>) {
if (*method == AnnounceMsg::kMethodStr) {
// name
auto name = ObjGetString(*params, "name", &error);
if (!name) {
goto err;
}
// id
int64_t id;
if (!ObjGetNumber(*params, "id", &error, &id)) {
goto err;
}
// type
auto typeStr = ObjGetString(*params, "type", &error);
if (!typeStr) {
goto err;
}
// pubuid
std::optional<int64_t> pubuid;
auto pubuidIt = params->find("pubuid");
if (pubuidIt != params->end()) {
int64_t val;
if (!GetNumber(pubuidIt->second, &val)) {
error = "pubuid value must be a number";
goto err;
}
pubuid = val;
}
// properties
auto propertiesIt = params->find("properties");
if (propertiesIt == params->end()) {
error = "no properties key";
goto err;
}
auto properties = &propertiesIt->second;
if (!properties->is_object()) {
WPI_WARNING(logger, "{}: properties is not an object", *name);
*properties = wpi::json::object();
}
// complete
out.ServerAnnounce(*name, id, *typeStr, *properties, pubuid);
} else if (*method == UnannounceMsg::kMethodStr) {
// name
auto name = ObjGetString(*params, "name", &error);
if (!name) {
goto err;
}
// id
int64_t id;
if (!ObjGetNumber(*params, "id", &error, &id)) {
goto err;
}
// complete
out.ServerUnannounce(*name, id);
} else if (*method == PropertiesUpdateMsg::kMethodStr) {
// name
auto name = ObjGetString(*params, "name", &error);
if (!name) {
goto err;
}
// update
auto updateIt = params->find("update");
if (updateIt == params->end()) {
error = "no update key";
goto err;
}
auto update = &updateIt->second;
if (!update->is_object()) {
error = "update must be an object";
goto err;
}
bool ack = false;
auto ackIt = params->find("ack");
if (ackIt != params->end()) {
auto val = ackIt->second.get_ptr<bool*>();
if (!val) {
error = "ack must be a boolean";
goto err;
}
ack = *val;
}
// complete
out.ServerPropertiesUpdate(*name, *update, ack);
} else {
error = fmt::format("unrecognized method '{}'", *method);
goto err;
}
}
continue;
}
err:
WPI_WARNING(logger, "{}: {}", i, error);
}
}
#ifdef __clang__
#pragma clang diagnostic pop
#endif
void nt::net::WireDecodeText(std::string_view in, ClientMessageHandler& out,
wpi::Logger& logger) {
::WireDecodeTextImpl(in, out, logger);
}
void nt::net::WireDecodeText(std::string_view in, ServerMessageHandler& out,
wpi::Logger& logger) {
::WireDecodeTextImpl(in, out, logger);
}
bool nt::net::WireDecodeBinary(wpi::span<const uint8_t>* in, int64_t* outId,
Value* outValue, std::string* error,
int64_t localTimeOffset) {
mpack_reader_t reader;
mpack_reader_init_data(&reader, reinterpret_cast<const char*>(in->data()),
in->size());
mpack_expect_array_match(&reader, 4);
*outId = mpack_expect_i64(&reader);
auto time = mpack_expect_i64(&reader);
int type = mpack_expect_int(&reader);
switch (type) {
case 0: // boolean
*outValue = Value::MakeBoolean(mpack_expect_bool(&reader), 1);
break;
case 2: // integer
*outValue = Value::MakeInteger(mpack_expect_i64(&reader), 1);
break;
case 3: // float
*outValue = Value::MakeFloat(mpack_expect_float(&reader), 1);
break;
case 1: // double
*outValue = Value::MakeDouble(mpack_expect_double(&reader), 1);
break;
case 4: { // string
auto length = mpack_expect_str(&reader);
auto data = mpack_read_bytes_inplace(&reader, length);
if (mpack_reader_error(&reader) == mpack_ok) {
*outValue = Value::MakeString({data, length}, 1);
}
mpack_done_str(&reader);
break;
}
case 5: { // raw
auto length = mpack_expect_bin(&reader);
auto data = mpack_read_bytes_inplace(&reader, length);
if (mpack_reader_error(&reader) == mpack_ok) {
*outValue =
Value::MakeRaw({reinterpret_cast<const uint8_t*>(data), length}, 1);
}
mpack_done_bin(&reader);
break;
}
case 16: { // boolean array
auto length = mpack_expect_array(&reader);
std::vector<int> arr;
arr.reserve((std::min)(length, 1000u));
for (uint32_t i = 0; i < length; ++i) {
arr.emplace_back(mpack_expect_bool(&reader));
if (mpack_reader_error(&reader) != mpack_ok) {
break;
}
}
if (mpack_reader_error(&reader) == mpack_ok) {
*outValue = Value::MakeBooleanArray(std::move(arr), 1);
}
mpack_done_array(&reader);
break;
}
case 18: { // integer array
auto length = mpack_expect_array(&reader);
std::vector<int64_t> arr;
arr.reserve((std::min)(length, 1000u));
for (uint32_t i = 0; i < length; ++i) {
arr.emplace_back(mpack_expect_i64(&reader));
if (mpack_reader_error(&reader) != mpack_ok) {
break;
}
}
if (mpack_reader_error(&reader) == mpack_ok) {
*outValue = Value::MakeIntegerArray(std::move(arr), 1);
}
mpack_done_array(&reader);
break;
}
case 19: { // float array
auto length = mpack_expect_array(&reader);
std::vector<float> arr;
arr.reserve((std::min)(length, 1000u));
for (uint32_t i = 0; i < length; ++i) {
arr.emplace_back(mpack_expect_float(&reader));
if (mpack_reader_error(&reader) != mpack_ok) {
break;
}
}
if (mpack_reader_error(&reader) == mpack_ok) {
*outValue = Value::MakeFloatArray(std::move(arr), 1);
}
mpack_done_array(&reader);
break;
}
case 17: { // double array
auto length = mpack_expect_array(&reader);
std::vector<double> arr;
arr.reserve((std::min)(length, 1000u));
for (uint32_t i = 0; i < length; ++i) {
arr.emplace_back(mpack_expect_double(&reader));
if (mpack_reader_error(&reader) != mpack_ok) {
break;
}
}
if (mpack_reader_error(&reader) == mpack_ok) {
*outValue = Value::MakeDoubleArray(std::move(arr), 1);
}
mpack_done_array(&reader);
break;
}
case 20: { // string array
auto length = mpack_expect_array(&reader);
std::vector<std::string> arr;
arr.reserve((std::min)(length, 1000u));
for (uint32_t i = 0; i < length; ++i) {
auto length = mpack_expect_str(&reader);
auto data = mpack_read_bytes_inplace(&reader, length);
if (mpack_reader_error(&reader) == mpack_ok) {
arr.emplace_back(std::string{data, length});
} else {
break;
}
mpack_done_str(&reader);
}
if (mpack_reader_error(&reader) == mpack_ok) {
*outValue = Value::MakeStringArray(std::move(arr), 1);
}
mpack_done_array(&reader);
break;
}
default:
*error = fmt::format("unrecognized type {}", type);
return false;
}
mpack_done_array(&reader);
auto err = mpack_reader_destroy(&reader);
if (err != mpack_ok) {
*error = mpack_error_to_string(err);
return false;
}
// set time
outValue->SetServerTime(time);
outValue->SetTime(time == 0 ? 0 : time + localTimeOffset);
// update input range
*in = wpi::drop_front(*in,
in->size() - mpack_reader_remaining(&reader, nullptr));
return true;
}

View File

@@ -0,0 +1,65 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source 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 <optional>
#include <string>
#include <string_view>
#include <wpi/span.h>
namespace wpi {
class Logger;
class json;
} // namespace wpi
namespace nt {
class PubSubOptions;
class Value;
} // namespace nt
namespace nt::net {
class ClientMessageHandler {
public:
virtual ~ClientMessageHandler() = default;
virtual void ClientPublish(int64_t pubuid, std::string_view name,
std::string_view typeStr,
const wpi::json& properties) = 0;
virtual void ClientUnpublish(int64_t pubuid) = 0;
virtual void ClientSetProperties(std::string_view name,
const wpi::json& update) = 0;
virtual void ClientSubscribe(int64_t subuid,
wpi::span<const std::string> topicNames,
const PubSubOptions& options) = 0;
virtual void ClientUnsubscribe(int64_t subuid) = 0;
};
class ServerMessageHandler {
public:
virtual ~ServerMessageHandler() = default;
virtual void ServerAnnounce(std::string_view name, int64_t id,
std::string_view typeStr,
const wpi::json& properties,
std::optional<int64_t> pubuid) = 0;
virtual void ServerUnannounce(std::string_view name, int64_t id) = 0;
virtual void ServerPropertiesUpdate(std::string_view name,
const wpi::json& update, bool ack) = 0;
};
void WireDecodeText(std::string_view in, ClientMessageHandler& out,
wpi::Logger& logger);
void WireDecodeText(std::string_view in, ServerMessageHandler& out,
wpi::Logger& logger);
// returns true if successfully decoded a message
bool WireDecodeBinary(wpi::span<const uint8_t>* in, int64_t* outId,
Value* outValue, std::string* error,
int64_t localTimeOffset);
} // namespace nt::net

View File

@@ -0,0 +1,316 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#include "WireEncoder.h"
#include <optional>
#include <wpi/json_serializer.h>
#include <wpi/mpack.h>
#include <wpi/raw_ostream.h>
#include "Handle.h"
#include "Message.h"
#include "PubSubOptions.h"
#include "networktables/NetworkTableValue.h"
using namespace nt;
using namespace nt::net;
using namespace mpack;
void nt::net::WireEncodePublish(wpi::raw_ostream& os, int64_t pubuid,
std::string_view name, std::string_view typeStr,
const wpi::json& properties) {
wpi::json::serializer s{os, ' ', 0};
os << "{\"method\":\"" << PublishMsg::kMethodStr << "\",\"params\":{";
os << "\"name\":\"";
s.dump_escaped(name, false);
os << "\",\"properties\":";
s.dump(properties, false, false, 0, 0);
os << ",\"pubuid\":";
s.dump_integer(pubuid);
os << ",\"type\":\"";
s.dump_escaped(typeStr, false);
os << "\"}}";
}
void nt::net::WireEncodeUnpublish(wpi::raw_ostream& os, int64_t pubuid) {
wpi::json::serializer s{os, ' ', 0};
os << "{\"method\":\"" << UnpublishMsg::kMethodStr << "\",\"params\":{";
os << "\"pubuid\":";
s.dump_integer(pubuid);
os << "}}";
}
void nt::net::WireEncodeSetProperties(wpi::raw_ostream& os,
std::string_view name,
const wpi::json& update) {
wpi::json::serializer s{os, ' ', 0};
os << "{\"method\":\"" << SetPropertiesMsg::kMethodStr << "\",\"params\":{";
os << "\"name\":\"";
s.dump_escaped(name, false);
os << "\",\"update\":";
s.dump(update, false, false, 0, 0);
os << "}}";
}
template <typename T>
static void EncodePrefixes(wpi::raw_ostream& os, wpi::span<const T> topicNames,
wpi::json::serializer& s) {
os << '[';
bool first = true;
for (auto&& name : topicNames) {
if (first) {
first = false;
} else {
os << ',';
}
os << '"';
s.dump_escaped(name, false);
os << '"';
}
os << ']';
}
template <typename T>
static void WireEncodeSubscribeImpl(wpi::raw_ostream& os, int64_t subuid,
wpi::span<const T> topicNames,
const PubSubOptions& options) {
wpi::json::serializer s{os, ' ', 0};
os << "{\"method\":\"" << SubscribeMsg::kMethodStr << "\",\"params\":{";
os << "\"options\":{";
bool first = true;
if (options.sendAll) {
os << "\"all\":true";
first = false;
}
if (options.topicsOnly) {
if (!first) {
os << ',';
}
os << "\"topicsonly\":true";
first = false;
}
if (options.prefixMatch) {
if (!first) {
os << ',';
}
os << "\"prefix\":true";
first = false;
}
if (options.periodic != 0.1) {
if (!first) {
os << ',';
}
os << "\"periodic\":";
s.dump_float(options.periodic);
}
os << "},\"topics\":";
EncodePrefixes(os, topicNames, s);
os << ",\"subuid\":";
s.dump_integer(subuid);
os << "}}";
}
void nt::net::WireEncodeSubscribe(wpi::raw_ostream& os, int64_t subuid,
wpi::span<const std::string_view> topicNames,
const PubSubOptions& options) {
WireEncodeSubscribeImpl(os, subuid, topicNames, options);
}
void nt::net::WireEncodeSubscribe(wpi::raw_ostream& os, int64_t subuid,
wpi::span<const std::string> topicNames,
const PubSubOptions& options) {
WireEncodeSubscribeImpl(os, subuid, topicNames, options);
}
void nt::net::WireEncodeUnsubscribe(wpi::raw_ostream& os, int64_t subHandle) {
wpi::json::serializer s{os, ' ', 0};
os << "{\"method\":\"" << UnsubscribeMsg::kMethodStr << "\",\"params\":{";
os << "\"subuid\":";
s.dump_integer(subHandle);
os << "}}";
}
bool nt::net::WireEncodeText(wpi::raw_ostream& os, const ClientMessage& msg) {
if (auto m = std::get_if<PublishMsg>(&msg.contents)) {
WireEncodePublish(os, Handle{m->pubHandle}.GetIndex(), m->name, m->typeStr,
m->properties);
} else if (auto m = std::get_if<UnpublishMsg>(&msg.contents)) {
WireEncodeUnpublish(os, Handle{m->pubHandle}.GetIndex());
} else if (auto m = std::get_if<SetPropertiesMsg>(&msg.contents)) {
WireEncodeSetProperties(os, m->name, m->update);
} else if (auto m = std::get_if<SubscribeMsg>(&msg.contents)) {
WireEncodeSubscribe(os, Handle{m->subHandle}.GetIndex(), m->topicNames,
m->options);
} else if (auto m = std::get_if<UnsubscribeMsg>(&msg.contents)) {
WireEncodeUnsubscribe(os, Handle{m->subHandle}.GetIndex());
} else {
return false;
}
return true;
}
void nt::net::WireEncodeAnnounce(wpi::raw_ostream& os, std::string_view name,
int64_t id, std::string_view typeStr,
const wpi::json& properties,
std::optional<int64_t> pubHandle) {
wpi::json::serializer s{os, ' ', 0};
os << "{\"method\":\"" << AnnounceMsg::kMethodStr << "\",\"params\":{";
os << "\"id\":";
s.dump_integer(id);
os << ",\"name\":\"";
s.dump_escaped(name, false);
os << "\",\"properties\":";
s.dump(properties, false, false, 0, 0);
if (pubHandle) {
os << ",\"pubuid\":";
s.dump_integer(*pubHandle);
}
os << ",\"type\":\"";
s.dump_escaped(typeStr, false);
os << "\"}}";
}
void nt::net::WireEncodeUnannounce(wpi::raw_ostream& os, std::string_view name,
int64_t id) {
wpi::json::serializer s{os, ' ', 0};
os << "{\"method\":\"" << UnannounceMsg::kMethodStr << "\",\"params\":{";
os << "\"id\":";
s.dump_integer(id);
os << ",\"name\":\"";
s.dump_escaped(name, false);
os << "\"}}";
}
void nt::net::WireEncodePropertiesUpdate(wpi::raw_ostream& os,
std::string_view name,
const wpi::json& update, bool ack) {
wpi::json::serializer s{os, ' ', 0};
os << "{\"method\":\"" << PropertiesUpdateMsg::kMethodStr
<< "\",\"params\":{";
os << "\"name\":\"";
s.dump_escaped(name, false);
os << "\",\"update\":";
s.dump(update, false, false, 0, 0);
if (ack) {
os << ",\"ack\":true";
}
os << "}}";
}
bool nt::net::WireEncodeText(wpi::raw_ostream& os, const ServerMessage& msg) {
if (auto m = std::get_if<AnnounceMsg>(&msg.contents)) {
WireEncodeAnnounce(os, m->name, m->id, m->typeStr, m->properties,
m->pubuid);
} else if (auto m = std::get_if<UnannounceMsg>(&msg.contents)) {
WireEncodeUnannounce(os, m->name, m->id);
} else if (auto m = std::get_if<PropertiesUpdateMsg>(&msg.contents)) {
WireEncodePropertiesUpdate(os, m->name, m->update, m->ack);
} else {
return false;
}
return true;
}
bool nt::net::WireEncodeBinary(wpi::raw_ostream& os, int64_t id, int64_t time,
const Value& value) {
char buf[128];
mpack_writer_t writer;
mpack_writer_init(&writer, buf, sizeof(buf));
mpack_writer_set_context(&writer, &os);
mpack_writer_set_flush(
&writer, [](mpack_writer_t* writer, const char* buffer, size_t count) {
static_cast<wpi::raw_ostream*>(writer->context)->write(buffer, count);
});
mpack_start_array(&writer, 4);
mpack_write_int(&writer, id);
mpack_write_int(&writer, time);
switch (value.type()) {
case NT_BOOLEAN:
mpack_write_u8(&writer, 0);
mpack_write_bool(&writer, value.GetBoolean());
break;
case NT_INTEGER:
mpack_write_u8(&writer, 2);
mpack_write_int(&writer, value.GetInteger());
break;
case NT_FLOAT:
mpack_write_u8(&writer, 3);
mpack_write_float(&writer, value.GetFloat());
break;
case NT_DOUBLE:
mpack_write_u8(&writer, 1);
mpack_write_double(&writer, value.GetDouble());
break;
case NT_STRING: {
auto v = value.GetString();
mpack_write_u8(&writer, 4);
mpack_write_str(&writer, v.data(), v.size());
break;
}
case NT_RPC:
case NT_RAW: {
auto v = value.GetRaw();
mpack_write_u8(&writer, 5);
mpack_write_bin(&writer, reinterpret_cast<const char*>(v.data()),
v.size());
break;
}
case NT_BOOLEAN_ARRAY: {
auto v = value.GetBooleanArray();
mpack_write_u8(&writer, 16);
mpack_start_array(&writer, v.size());
for (auto val : v) {
mpack_write_bool(&writer, val);
}
mpack_finish_array(&writer);
break;
}
case NT_INTEGER_ARRAY: {
auto v = value.GetIntegerArray();
mpack_write_u8(&writer, 18);
mpack_start_array(&writer, v.size());
for (auto val : v) {
mpack_write_int(&writer, val);
}
mpack_finish_array(&writer);
break;
}
case NT_FLOAT_ARRAY: {
auto v = value.GetFloatArray();
mpack_write_u8(&writer, 19);
mpack_start_array(&writer, v.size());
for (auto val : v) {
mpack_write_float(&writer, val);
}
mpack_finish_array(&writer);
break;
}
case NT_DOUBLE_ARRAY: {
auto v = value.GetDoubleArray();
mpack_write_u8(&writer, 17);
mpack_start_array(&writer, v.size());
for (auto val : v) {
mpack_write_double(&writer, val);
}
mpack_finish_array(&writer);
break;
}
case NT_STRING_ARRAY: {
auto v = value.GetStringArray();
mpack_write_u8(&writer, 20);
mpack_start_array(&writer, v.size());
for (auto&& val : v) {
mpack_write_str(&writer, val.data(), val.size());
}
mpack_finish_array(&writer);
break;
}
default:
return false;
}
mpack_finish_array(&writer);
return mpack_writer_destroy(&writer) == mpack_ok;
}

View File

@@ -0,0 +1,62 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source 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 <optional>
#include <string>
#include <string_view>
#include <wpi/span.h>
namespace wpi {
class json;
class raw_ostream;
} // namespace wpi
namespace nt {
class PubSubOptions;
class Value;
} // namespace nt
namespace nt::net {
struct ClientMessage;
struct ServerMessage;
// encoders for client text messages (avoids need to construct a Message struct)
void WireEncodePublish(wpi::raw_ostream& os, int64_t pubuid,
std::string_view name, std::string_view typeStr,
const wpi::json& properties);
void WireEncodeUnpublish(wpi::raw_ostream& os, int64_t pubuid);
void WireEncodeSetProperties(wpi::raw_ostream& os, std::string_view name,
const wpi::json& update);
void WireEncodeSubscribe(wpi::raw_ostream& os, int64_t subuid,
wpi::span<const std::string_view> topicNames,
const PubSubOptions& options);
void WireEncodeSubscribe(wpi::raw_ostream& os, int64_t subuid,
wpi::span<const std::string> topicNames,
const PubSubOptions& options);
void WireEncodeUnsubscribe(wpi::raw_ostream& os, int64_t subuid);
// encoders for server text messages (avoids need to construct a Message struct)
void WireEncodeAnnounce(wpi::raw_ostream& os, std::string_view name, int64_t id,
std::string_view typeStr, const wpi::json& properties,
std::optional<int64_t> pubuid);
void WireEncodeUnannounce(wpi::raw_ostream& os, std::string_view name,
int64_t id);
void WireEncodePropertiesUpdate(wpi::raw_ostream& os, std::string_view name,
const wpi::json& update, bool ack);
// Encode a single message; note text messages must be put into a
// JSON array "[msg1, msg2]" for transmission.
// Returns true if message was written
bool WireEncodeText(wpi::raw_ostream& os, const ClientMessage& msg);
bool WireEncodeText(wpi::raw_ostream& os, const ServerMessage& msg);
// encoder for binary messages
bool WireEncodeBinary(wpi::raw_ostream& os, int64_t id, int64_t time,
const Value& value);
} // namespace nt::net

View File

@@ -0,0 +1,672 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#include "ClientImpl3.h"
#include <algorithm>
#include <numeric>
#include <string>
#include <vector>
#include <fmt/format.h>
#include <wpi/DenseMap.h>
#include <wpi/StringMap.h>
#include <wpi/json.h>
#include "Handle.h"
#include "Log.h"
#include "Types_internal.h"
#include "net/Message.h"
#include "net/NetworkInterface.h"
#include "net3/Message3.h"
#include "net3/SequenceNumber.h"
#include "net3/WireConnection3.h"
#include "net3/WireDecoder3.h"
#include "net3/WireEncoder3.h"
#include "networktables/NetworkTableValue.h"
using namespace nt;
using namespace nt::net3;
static constexpr uint32_t kMinPeriodMs = 5;
// maximum number of times the wire can be not ready to send another
// transmission before we close the connection
static constexpr int kWireMaxNotReady = 10;
namespace {
struct Entry;
struct PublisherData {
explicit PublisherData(Entry* entry) : entry{entry} {}
Entry* entry;
NT_Publisher handle;
PubSubOptions options;
// in options as double, but copy here as integer; rounded to the nearest
// 10 ms
uint32_t periodMs;
uint64_t nextSendMs{0};
std::vector<Value> outValues; // outgoing values
};
// data for each entry
struct Entry {
explicit Entry(std::string_view name_) : name(name_) {}
bool IsPersistent() const { return (flags & NT_PERSISTENT) != 0; }
wpi::json SetFlags(unsigned int flags_);
std::string name;
std::string typeStr;
NT_Type type{NT_UNASSIGNED};
wpi::json properties = wpi::json::object();
// The current value and flags
Value value;
unsigned int flags{0};
// Unique ID used in network messages; this is 0xffff until assigned
// by the server.
unsigned int id{0xffff};
// Sequence number for update resolution
SequenceNumber seqNum;
// Local topic handle
NT_Topic topic{0};
// Local publishers
std::vector<PublisherData*> publishers;
};
class CImpl : public MessageHandler3 {
public:
CImpl(uint64_t curTimeMs, int inst, WireConnection3& wire,
wpi::Logger& logger,
std::function<void(uint32_t repeatMs)> setPeriodic);
void ProcessIncoming(wpi::span<const uint8_t> data);
void HandleLocal(wpi::span<const net::ClientMessage> msgs);
void SendPeriodic(uint64_t curTimeMs, bool initial);
void SendValue(Writer& out, Entry* entry, const Value& value);
bool CheckNetworkReady();
// Outgoing handlers
void Publish(NT_Publisher pubHandle, NT_Topic topicHandle,
std::string_view name, std::string_view typeStr,
const wpi::json& properties, const PubSubOptions& options);
void Unpublish(NT_Publisher pubHandle, NT_Topic topicHandle);
void SetProperties(NT_Topic topicHandle, std::string_view name,
const wpi::json& update);
void SetValue(NT_Publisher pubHandle, const Value& value);
// MessageHandler interface
void KeepAlive() final;
void ServerHelloDone() final;
void ClientHelloDone() final;
void ClearEntries() final;
void ProtoUnsup(unsigned int proto_rev) final;
void ClientHello(std::string_view self_id, unsigned int proto_rev) final;
void ServerHello(unsigned int flags, std::string_view self_id) final;
void EntryAssign(std::string_view name, unsigned int id, unsigned int seq_num,
const Value& value, unsigned int flags) final;
void EntryUpdate(unsigned int id, unsigned int seq_num,
const Value& value) final;
void FlagsUpdate(unsigned int id, unsigned int flags) final;
void EntryDelete(unsigned int id) final;
void ExecuteRpc(unsigned int id, unsigned int uid,
wpi::span<const uint8_t> params) final {}
void RpcResponse(unsigned int id, unsigned int uid,
wpi::span<const uint8_t> result) final {}
enum State {
kStateInitial,
kStateHelloSent,
kStateInitialAssignments,
kStateRunning
};
int m_inst;
WireConnection3& m_wire;
wpi::Logger& m_logger;
net::LocalInterface* m_local{nullptr};
std::function<void(uint32_t repeatMs)> m_setPeriodic;
uint64_t m_initTimeMs;
// periodic sweep handling
static constexpr uint32_t kKeepAliveIntervalMs = 1000;
uint32_t m_periodMs{kKeepAliveIntervalMs + 10};
uint64_t m_lastSendMs{0};
uint64_t m_nextKeepAliveTimeMs;
int m_notReadyCount{0};
// indexed by publisher index
std::vector<std::unique_ptr<PublisherData>> m_publishers;
State m_state{kStateInitial};
WireDecoder3 m_decoder;
std::string m_remoteId;
std::function<void()> m_handshakeSucceeded;
std::vector<std::pair<unsigned int, unsigned int>> m_outgoingFlags;
using NameMap = wpi::StringMap<std::unique_ptr<Entry>>;
using IdMap = std::vector<Entry*>;
NameMap m_nameMap;
IdMap m_idMap;
Entry* GetOrNewEntry(std::string_view name) {
auto& entry = m_nameMap[name];
if (!entry) {
entry = std::make_unique<Entry>(name);
}
return entry.get();
}
Entry* LookupId(unsigned int id) {
return id < m_idMap.size() ? m_idMap[id] : nullptr;
}
};
} // namespace
wpi::json Entry::SetFlags(unsigned int flags_) {
bool wasPersistent = IsPersistent();
flags = flags_;
bool isPersistent = IsPersistent();
if (isPersistent && !wasPersistent) {
properties["persistent"] = true;
return {{"persistent", true}};
} else if (!isPersistent && wasPersistent) {
properties.erase("persistent");
return {{"persistent", wpi::json()}};
} else {
return wpi::json::object();
}
}
CImpl::CImpl(uint64_t curTimeMs, int inst, WireConnection3& wire,
wpi::Logger& logger,
std::function<void(uint32_t repeatMs)> setPeriodic)
: m_inst{inst},
m_wire{wire},
m_logger{logger},
m_setPeriodic{std::move(setPeriodic)},
m_initTimeMs{curTimeMs},
m_nextKeepAliveTimeMs{curTimeMs + kKeepAliveIntervalMs},
m_decoder{*this} {}
void CImpl::ProcessIncoming(wpi::span<const uint8_t> data) {
DEBUG4("received {} bytes", data.size());
if (!m_decoder.Execute(&data)) {
m_wire.Disconnect(m_decoder.GetError());
}
}
void CImpl::HandleLocal(wpi::span<const net::ClientMessage> msgs) {
for (const auto& elem : msgs) { // NOLINT
// common case is value
if (auto msg = std::get_if<net::ClientValueMsg>(&elem.contents)) {
SetValue(msg->pubHandle, msg->value);
} else if (auto msg = std::get_if<net::PublishMsg>(&elem.contents)) {
Publish(msg->pubHandle, msg->topicHandle, msg->name, msg->typeStr,
msg->properties, msg->options);
} else if (auto msg = std::get_if<net::UnpublishMsg>(&elem.contents)) {
Unpublish(msg->pubHandle, msg->topicHandle);
} else if (auto msg = std::get_if<net::SetPropertiesMsg>(&elem.contents)) {
SetProperties(msg->topicHandle, msg->name, msg->update);
}
}
}
void CImpl::SendPeriodic(uint64_t curTimeMs, bool initial) {
DEBUG4("SendPeriodic({})", curTimeMs);
// rate limit sends
if (curTimeMs < (m_lastSendMs + kMinPeriodMs)) {
return;
}
auto out = m_wire.Send();
// send keep-alives
if (curTimeMs >= m_nextKeepAliveTimeMs) {
if (!CheckNetworkReady()) {
return;
}
DEBUG4("{}", "Sending keep alive");
WireEncodeKeepAlive(out.stream());
// drift isn't critical here, so just go from current time
m_nextKeepAliveTimeMs = curTimeMs + kKeepAliveIntervalMs;
}
// send any stored-up flags updates
if (!m_outgoingFlags.empty()) {
if (!CheckNetworkReady()) {
return;
}
for (auto&& p : m_outgoingFlags) {
WireEncodeFlagsUpdate(out.stream(), p.first, p.second);
}
m_outgoingFlags.resize(0);
}
// send any pending updates due to be sent
bool checkedNetwork = false;
for (auto&& pub : m_publishers) {
if (pub && !pub->outValues.empty() && curTimeMs >= pub->nextSendMs) {
if (!checkedNetwork) {
if (!CheckNetworkReady()) {
return;
}
checkedNetwork = true;
}
for (auto&& val : pub->outValues) {
SendValue(out, pub->entry, val);
}
pub->outValues.resize(0);
pub->nextSendMs = curTimeMs + pub->periodMs;
}
}
if (initial) {
DEBUG4("{}", "Sending ClientHelloDone");
WireEncodeClientHelloDone(out.stream());
}
m_wire.Flush();
m_lastSendMs = curTimeMs;
}
void CImpl::SendValue(Writer& out, Entry* entry, const Value& value) {
DEBUG4("sending value for '{}', seqnum {}", entry->name,
entry->seqNum.value());
// bump sequence number
++entry->seqNum;
// only send assigns during initial handshake
if (entry->id == 0xffff || m_state == kStateInitialAssignments) {
// send assign
WireEncodeEntryAssign(out.stream(), entry->name, entry->id,
entry->seqNum.value(), value, entry->flags);
} else {
// send update
WireEncodeEntryUpdate(out.stream(), entry->id, entry->seqNum.value(),
value);
}
}
bool CImpl::CheckNetworkReady() {
if (!m_wire.Ready()) {
++m_notReadyCount;
if (m_notReadyCount > kWireMaxNotReady) {
m_wire.Disconnect("transmit stalled");
}
return false;
}
m_notReadyCount = 0;
return true;
}
void CImpl::Publish(NT_Publisher pubHandle, NT_Topic topicHandle,
std::string_view name, std::string_view typeStr,
const wpi::json& properties, const PubSubOptions& options) {
DEBUG4("Publish('{}', '{}')", name, typeStr);
unsigned int index = Handle{pubHandle}.GetIndex();
if (index >= m_publishers.size()) {
m_publishers.resize(index + 1);
}
auto& publisher = m_publishers[index];
if (!publisher) {
publisher = std::make_unique<PublisherData>(GetOrNewEntry(name));
publisher->entry->typeStr = typeStr;
publisher->entry->type = StringToType3(typeStr);
publisher->entry->publishers.emplace_back(publisher.get());
}
publisher->handle = pubHandle;
publisher->options = options;
publisher->periodMs = std::lround(options.periodic * 100) * 10;
if (publisher->periodMs < 10) {
publisher->periodMs = 10;
}
// update period
m_periodMs = std::gcd(m_periodMs, publisher->periodMs);
m_setPeriodic(m_periodMs);
}
void CImpl::Unpublish(NT_Publisher pubHandle, NT_Topic topicHandle) {
DEBUG4("Unpublish({}, {})", pubHandle, topicHandle);
unsigned int index = Handle{pubHandle}.GetIndex();
if (index >= m_publishers.size()) {
return;
}
auto& publisher = m_publishers[index];
publisher->entry->publishers.erase(
std::remove(publisher->entry->publishers.begin(),
publisher->entry->publishers.end(), publisher.get()),
publisher->entry->publishers.end());
publisher.reset();
// loop over all publishers to update period
m_periodMs = kKeepAliveIntervalMs + 10;
for (auto&& pub : m_publishers) {
if (pub) {
m_periodMs = std::gcd(m_periodMs, pub->periodMs);
}
}
m_setPeriodic(m_periodMs);
}
void CImpl::SetProperties(NT_Topic topicHandle, std::string_view name,
const wpi::json& update) {
DEBUG4("SetProperties({}, {}, {})", topicHandle, name, update.dump());
auto entry = GetOrNewEntry(name);
bool updated = false;
for (auto&& elem : update.items()) {
entry->properties[elem.key()] = elem.value();
if (elem.key() == "persistent") {
if (auto val = elem.value().get_ptr<const bool*>()) {
if (*val) {
entry->flags |= NT_PERSISTENT;
} else {
entry->flags &= ~NT_PERSISTENT;
}
updated = true;
}
}
}
if (updated && entry->id == 0xffff) {
m_outgoingFlags.emplace_back(entry->id, entry->flags);
}
}
void CImpl::SetValue(NT_Publisher pubHandle, const Value& value) {
DEBUG4("SetValue({})", pubHandle);
unsigned int index = Handle{pubHandle}.GetIndex();
assert(index < m_publishers.size() && m_publishers[index]);
auto& publisher = *m_publishers[index];
if (value == publisher.entry->value) {
return;
}
publisher.entry->value = value;
if (publisher.outValues.empty() || publisher.options.sendAll) {
publisher.outValues.emplace_back(value);
} else {
publisher.outValues.back() = value;
}
}
void CImpl::KeepAlive() {
DEBUG4("{}", "KeepAlive()");
if (m_state != kStateRunning && m_state != kStateInitialAssignments) {
m_decoder.SetError("received unexpected KeepAlive message");
return;
}
// ignore
}
void CImpl::ServerHelloDone() {
DEBUG4("{}", "ServerHelloDone()");
if (m_state != kStateInitialAssignments) {
m_decoder.SetError("received unexpected ServerHelloDone message");
return;
}
// send initial assignments
SendPeriodic(m_initTimeMs, true);
m_state = kStateRunning;
m_setPeriodic(m_periodMs);
}
void CImpl::ClientHelloDone() {
DEBUG4("{}", "ClientHelloDone()");
m_decoder.SetError("received unexpected ClientHelloDone message");
}
void CImpl::ProtoUnsup(unsigned int proto_rev) {
DEBUG4("ProtoUnsup({})", proto_rev);
m_decoder.SetError(fmt::format("received ProtoUnsup(version={})", proto_rev));
}
void CImpl::ClientHello(std::string_view self_id, unsigned int proto_rev) {
DEBUG4("ClientHello({}, {})", self_id, proto_rev);
m_decoder.SetError("received unexpected ClientHello message");
}
void CImpl::ServerHello(unsigned int flags, std::string_view self_id) {
DEBUG4("ServerHello({}, {})", flags, self_id);
if (m_state != kStateHelloSent) {
m_decoder.SetError("received unexpected ServerHello message");
return;
}
m_state = kStateInitialAssignments;
m_remoteId = self_id;
m_handshakeSucceeded();
m_handshakeSucceeded = nullptr; // no longer required
}
void CImpl::EntryAssign(std::string_view name, unsigned int id,
unsigned int seq_num, const Value& value,
unsigned int flags) {
DEBUG4("EntryAssign({}, {}, {}, value, {})", name, id, seq_num, flags);
if (m_state != kStateInitialAssignments && m_state != kStateRunning) {
m_decoder.SetError("received unexpected EntryAssign message");
return;
}
auto entry = GetOrNewEntry(name);
bool flagsChanged = entry->flags != flags;
bool typeChanged;
bool valueChanged;
// don't update value if we locally published a "strong" value
if (m_state == kStateInitialAssignments && entry->value &&
entry->value.server_time() != 0) {
typeChanged = false;
valueChanged = false;
} else {
typeChanged = entry->type != value.type();
valueChanged = entry->value != value;
if (m_state == kStateInitialAssignments) {
// remove outgoing during initial assignments so we don't get out of sync
for (auto publisher : entry->publishers) {
publisher->outValues.clear();
}
}
}
entry->id = id;
entry->seqNum = SequenceNumber{seq_num};
entry->SetFlags(flags);
if (typeChanged) {
entry->type = value.type();
entry->typeStr = TypeToString(value.type());
}
if (valueChanged) {
entry->value = value;
}
// add to id map
if (id >= m_idMap.size()) {
m_idMap.resize(id + 1);
}
m_idMap[id] = entry;
if (m_local) {
// XXX: need to handle type change specially? (e.g. with unannounce)
if (entry->topic == 0 || flagsChanged || typeChanged) {
DEBUG4("NetworkAnnounce({}, {})", name, entry->typeStr);
entry->topic =
m_local->NetworkAnnounce(name, entry->typeStr, entry->properties, 0);
}
if (valueChanged) {
m_local->NetworkSetValue(entry->topic, entry->value);
}
}
}
void CImpl::EntryUpdate(unsigned int id, unsigned int seq_num,
const Value& value) {
DEBUG4("EntryUpdate({}, {}, value)", id, seq_num);
if (m_state != kStateRunning) {
m_decoder.SetError("received EntryUpdate message before ServerHelloDone");
return;
}
if (auto entry = LookupId(id)) {
entry->value = value;
if (m_local && entry->topic != 0) {
m_local->NetworkSetValue(entry->topic, entry->value);
}
}
}
void CImpl::FlagsUpdate(unsigned int id, unsigned int flags) {
DEBUG4("FlagsUpdate({}, {})", id, flags);
if (m_state != kStateRunning) {
m_decoder.SetError("received FlagsUpdate message before ServerHelloDone");
return;
}
if (auto entry = LookupId(id)) {
wpi::json update = entry->SetFlags(flags);
if (!update.empty() && m_local) {
m_local->NetworkPropertiesUpdate(entry->name, update, false);
}
}
// erase any outgoing flags updates
m_outgoingFlags.erase(
std::remove_if(m_outgoingFlags.begin(), m_outgoingFlags.end(),
[&](const auto& p) { return p.first == id; }),
m_outgoingFlags.end());
}
void CImpl::EntryDelete(unsigned int id) {
DEBUG4("EntryDelete({})", id);
if (m_state != kStateRunning) {
m_decoder.SetError("received EntryDelete message before ServerHelloDone");
return;
}
if (auto entry = LookupId(id)) {
m_idMap[id] = nullptr;
// set id to 0xffff so any future local setvalue will result in assign
entry->id = 0xffff;
entry->value = Value{};
// if we have no local publishers, unannounce
if (entry->publishers.empty() && m_local) {
m_local->NetworkUnannounce(entry->name);
}
}
// erase any outgoing flags updates
m_outgoingFlags.erase(
std::remove_if(m_outgoingFlags.begin(), m_outgoingFlags.end(),
[&](const auto& p) { return p.first == id; }),
m_outgoingFlags.end());
}
void CImpl::ClearEntries() {
DEBUG4("{}", "ClearEntries()");
if (m_state != kStateRunning) {
m_decoder.SetError("received ClearEntries message before ServerHelloDone");
return;
}
for (auto& entry : m_idMap) {
if (entry && entry->id != 0xffff && !entry->IsPersistent()) {
entry->id = 0xffff;
entry->value = Value{};
// if we have no local publishers, unannounce
if (entry->publishers.empty() && m_local) {
m_local->NetworkUnannounce(entry->name);
}
entry = nullptr; // clear id mapping
}
}
// erase all outgoing flags updates
m_outgoingFlags.resize(0);
}
class ClientImpl3::Impl final : public CImpl {
public:
Impl(uint64_t curTimeMs, int inst, WireConnection3& wire, wpi::Logger& logger,
std::function<void(uint32_t repeatMs)> setPeriodic)
: CImpl{curTimeMs, inst, wire, logger, std::move(setPeriodic)} {}
};
ClientImpl3::ClientImpl3(uint64_t curTimeMs, int inst, WireConnection3& wire,
wpi::Logger& logger,
std::function<void(uint32_t repeatMs)> setPeriodic)
: m_impl{std::make_unique<Impl>(curTimeMs, inst, wire, logger,
std::move(setPeriodic))} {}
ClientImpl3::~ClientImpl3() {
WPI_DEBUG4(m_impl->m_logger, "{}", "NT3 ClientImpl destroyed");
}
void ClientImpl3::Start(std::string_view selfId,
std::function<void()> succeeded) {
if (m_impl->m_state != CImpl::kStateInitial) {
return;
}
m_impl->m_handshakeSucceeded = std::move(succeeded);
auto writer = m_impl->m_wire.Send();
WireEncodeClientHello(writer.stream(), selfId, 0x0300);
m_impl->m_wire.Flush();
m_impl->m_state = CImpl::kStateHelloSent;
}
void ClientImpl3::ProcessIncoming(wpi::span<const uint8_t> data) {
m_impl->ProcessIncoming(data);
}
void ClientImpl3::HandleLocal(wpi::span<const net::ClientMessage> msgs) {
m_impl->HandleLocal(msgs);
}
void ClientImpl3::SendPeriodic(uint64_t curTimeMs) {
m_impl->SendPeriodic(curTimeMs, false);
}
void ClientImpl3::SetLocal(net::LocalInterface* local) {
m_impl->m_local = local;
}
ClientStartup3::ClientStartup3(ClientImpl3& client) : m_client{client} {}
ClientStartup3::~ClientStartup3() = default;
void ClientStartup3::Publish(NT_Publisher pubHandle, NT_Topic topicHandle,
std::string_view name, std::string_view typeStr,
const wpi::json& properties,
const PubSubOptions& options) {
WPI_DEBUG4(m_client.m_impl->m_logger, "StartupPublish({}, {}, {}, {})",
pubHandle, topicHandle, name, typeStr);
m_client.m_impl->Publish(pubHandle, topicHandle, name, typeStr, properties,
options);
}
void ClientStartup3::Subscribe(NT_Subscriber subHandle,
wpi::span<const std::string> prefixes,
const PubSubOptions& options) {
// NT3 ignores subscribes, so no action required
}
void ClientStartup3::SetValue(NT_Publisher pubHandle, const Value& value) {
WPI_DEBUG4(m_client.m_impl->m_logger, "StartupSetValue({})", pubHandle);
// Similar to Client::SetValue(), except always set value and queue
unsigned int index = Handle{pubHandle}.GetIndex();
assert(index < m_client.m_impl->m_publishers.size() &&
m_client.m_impl->m_publishers[index]);
auto& publisher = *m_client.m_impl->m_publishers[index];
publisher.entry->value = value;
publisher.outValues.emplace_back(value);
}

View File

@@ -0,0 +1,73 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#pragma once
#include <stdint.h>
#include <functional>
#include <memory>
#include <string>
#include <string_view>
#include <wpi/span.h>
#include "net/NetworkInterface.h"
namespace wpi {
class Logger;
} // namespace wpi
namespace nt::net {
struct ClientMessage;
class LocalInterface;
} // namespace nt::net
namespace nt::net3 {
class ClientStartup3;
class WireConnection3;
class ClientImpl3 {
friend class ClientStartup3;
public:
explicit ClientImpl3(uint64_t curTimeMs, int inst, WireConnection3& wire,
wpi::Logger& logger,
std::function<void(uint32_t repeatMs)> setPeriodic);
~ClientImpl3();
void Start(std::string_view selfId, std::function<void()> succeeded);
void ProcessIncoming(wpi::span<const uint8_t> data);
void HandleLocal(wpi::span<const net::ClientMessage> msgs);
void SendPeriodic(uint64_t curTimeMs);
void SetLocal(net::LocalInterface* local);
private:
class Impl;
std::unique_ptr<Impl> m_impl;
};
class ClientStartup3 final : public net::NetworkStartupInterface {
public:
explicit ClientStartup3(ClientImpl3& client);
~ClientStartup3() final;
ClientStartup3(const ClientStartup3&) = delete;
ClientStartup3& operator=(const ClientStartup3&) = delete;
// NetworkStartupInterface interface
void Publish(NT_Publisher pubHandle, NT_Topic topicHandle,
std::string_view name, std::string_view typeStr,
const wpi::json& properties, const PubSubOptions& options) final;
void Subscribe(NT_Subscriber subHandle, wpi::span<const std::string> prefixes,
const PubSubOptions& options) final;
void SetValue(NT_Publisher pubHandle, const Value& value) final;
private:
ClientImpl3& m_client;
};
} // namespace nt::net3

View File

@@ -0,0 +1,155 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source 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 <string>
#include "networktables/NetworkTableValue.h"
#include "ntcore_c.h"
namespace nt::net3 {
class WireDecoder3;
class Message3 {
struct private_init {};
friend class WireDecoder3;
public:
enum MsgType {
kUnknown = -1,
kKeepAlive = 0x00,
kClientHello = 0x01,
kProtoUnsup = 0x02,
kServerHelloDone = 0x03,
kServerHello = 0x04,
kClientHelloDone = 0x05,
kEntryAssign = 0x10,
kEntryUpdate = 0x11,
kFlagsUpdate = 0x12,
kEntryDelete = 0x13,
kClearEntries = 0x14,
kExecuteRpc = 0x20,
kRpcResponse = 0x21
};
enum DataType {
kBoolean = 0x00,
kDouble = 0x01,
kString = 0x02,
kRaw = 0x03,
kBooleanArray = 0x10,
kDoubleArray = 0x11,
kStringArray = 0x12,
kRpcDef = 0x20
};
static constexpr uint32_t kClearAllMagic = 0xD06CB27Aul;
Message3() = default;
Message3(MsgType type, const private_init&) : m_type(type) {}
MsgType type() const { return m_type; }
bool Is(MsgType type) const { return type == m_type; }
// Message data accessors. Callers are responsible for knowing what data is
// actually provided for a particular message.
std::string_view str() const { return m_str; }
wpi::span<const uint8_t> bytes() const {
return {reinterpret_cast<const uint8_t*>(m_str.data()), m_str.size()};
}
const Value& value() const { return m_value; }
unsigned int id() const { return m_id; }
unsigned int flags() const { return m_flags; }
unsigned int seq_num_uid() const { return m_seq_num_uid; }
void SetValue(const Value& value) { m_value = value; }
// Create messages without data
static Message3 KeepAlive() { return {kKeepAlive, {}}; }
static Message3 ServerHelloDone() { return {kServerHelloDone, {}}; }
static Message3 ClientHelloDone() { return {kClientHelloDone, {}}; }
static Message3 ClearEntries() { return {kClearEntries, {}}; }
// Create messages with data
static Message3 ProtoUnsup(unsigned int proto_rev = 0x0300u) {
Message3 msg{kProtoUnsup, {}};
msg.m_id = proto_rev;
return msg;
}
static Message3 ClientHello(std::string_view self_id,
unsigned int proto_rev = 0x0300u) {
Message3 msg{kClientHello, {}};
msg.m_str = self_id;
msg.m_id = proto_rev;
return msg;
}
static Message3 ServerHello(unsigned int flags, std::string_view self_id) {
Message3 msg{kServerHello, {}};
msg.m_str = self_id;
msg.m_flags = flags;
return msg;
}
static Message3 EntryAssign(std::string_view name, unsigned int id,
unsigned int seq_num, const Value& value,
unsigned int flags) {
Message3 msg{kEntryAssign, {}};
msg.m_str = name;
msg.m_value = value;
msg.m_id = id;
msg.m_flags = flags;
msg.m_seq_num_uid = seq_num;
return msg;
}
static Message3 EntryUpdate(unsigned int id, unsigned int seq_num,
const Value& value) {
Message3 msg{kEntryUpdate, {}};
msg.m_value = value;
msg.m_id = id;
msg.m_seq_num_uid = seq_num;
return msg;
}
static Message3 FlagsUpdate(unsigned int id, unsigned int flags) {
Message3 msg{kFlagsUpdate, {}};
msg.m_id = id;
msg.m_flags = flags;
return msg;
}
static Message3 EntryDelete(unsigned int id) {
Message3 msg{kEntryDelete, {}};
msg.m_id = id;
return msg;
}
static Message3 ExecuteRpc(unsigned int id, unsigned int uid,
wpi::span<const uint8_t> params) {
Message3 msg{kExecuteRpc, {}};
msg.m_str.assign(reinterpret_cast<const char*>(params.data()),
params.size());
msg.m_id = id;
msg.m_seq_num_uid = uid;
return msg;
}
static Message3 RpcResponse(unsigned int id, unsigned int uid,
wpi::span<const uint8_t> result) {
Message3 msg{kRpcResponse, {}};
msg.m_str.assign(reinterpret_cast<const char*>(result.data()),
result.size());
msg.m_id = id;
msg.m_seq_num_uid = uid;
return msg;
}
private:
MsgType m_type{kUnknown};
// Message data. Use varies by message type.
std::string m_str;
Value m_value;
unsigned int m_id{0}; // also used for proto_rev
unsigned int m_flags{0};
unsigned int m_seq_num_uid{0};
};
} // namespace nt::net3

View File

@@ -4,7 +4,7 @@
#include "SequenceNumber.h"
namespace nt {
namespace nt::net3 {
bool operator<(const SequenceNumber& lhs, const SequenceNumber& rhs) {
if (lhs.m_value < rhs.m_value) {
@@ -26,4 +26,4 @@ bool operator>(const SequenceNumber& lhs, const SequenceNumber& rhs) {
}
}
} // namespace nt
} // namespace nt::net3

View File

@@ -2,10 +2,9 @@
// Open Source 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 NTCORE_SEQUENCENUMBER_H_
#define NTCORE_SEQUENCENUMBER_H_
#pragma once
namespace nt {
namespace nt::net3 {
/* A sequence number per RFC 1982 */
class SequenceNumber {
@@ -57,6 +56,4 @@ inline bool operator!=(const SequenceNumber& lhs, const SequenceNumber& rhs) {
return lhs.m_value != rhs.m_value;
}
} // namespace nt
#endif // NTCORE_SEQUENCENUMBER_H_
} // namespace nt::net3

View File

@@ -0,0 +1,50 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#include "UvStreamConnection3.h"
#include <wpinet/uv/Stream.h>
using namespace nt;
using namespace nt::net3;
UvStreamConnection3::UvStreamConnection3(wpi::uv::Stream& stream)
: m_stream{stream}, m_os{m_buffers, [this] { return AllocBuf(); }} {}
UvStreamConnection3::~UvStreamConnection3() {
for (auto&& buf : m_buf_pool) {
buf.Deallocate();
}
}
void UvStreamConnection3::Flush() {
if (m_buffers.empty()) {
return;
}
++m_sendsActive;
m_stream.Write(m_buffers, [this](auto bufs, auto) {
m_buf_pool.insert(m_buf_pool.end(), bufs.begin(), bufs.end());
if (m_sendsActive > 0) {
--m_sendsActive;
}
});
m_buffers.clear();
m_os.reset();
}
void UvStreamConnection3::Disconnect(std::string_view reason) {
m_reason = reason;
m_stream.Close();
}
void UvStreamConnection3::FinishSend() {}
wpi::uv::Buffer UvStreamConnection3::AllocBuf() {
if (!m_buf_pool.empty()) {
auto buf = m_buf_pool.back();
m_buf_pool.pop_back();
return buf;
}
return wpi::uv::Buffer::Allocate(kAllocSize);
}

View File

@@ -0,0 +1,57 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source 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 <string>
#include <string_view>
#include <vector>
#include <wpi/SmallVector.h>
#include <wpinet/raw_uv_ostream.h>
#include <wpinet/uv/Buffer.h>
#include "net3/WireConnection3.h"
namespace wpi::uv {
class Stream;
} // namespace wpi::uv
namespace nt::net3 {
class UvStreamConnection3 final : public WireConnection3 {
static constexpr size_t kAllocSize = 4096;
public:
explicit UvStreamConnection3(wpi::uv::Stream& stream);
~UvStreamConnection3() override;
UvStreamConnection3(const UvStreamConnection3&) = delete;
UvStreamConnection3& operator=(const UvStreamConnection3&) = delete;
bool Ready() const final { return m_sendsActive == 0; }
Writer Send() final { return {m_os, *this}; }
void Flush() final;
void Disconnect(std::string_view reason) final;
std::string_view GetDisconnectReason() const { return m_reason; }
wpi::uv::Stream& GetStream() { return m_stream; }
private:
void FinishSend() final;
wpi::uv::Buffer AllocBuf();
wpi::uv::Stream& m_stream;
wpi::SmallVector<wpi::uv::Buffer, 4> m_buffers;
std::vector<wpi::uv::Buffer> m_buf_pool;
wpi::raw_uv_ostream m_os;
std::string m_reason;
int m_sendsActive = 0;
};
} // namespace nt::net3

View File

@@ -0,0 +1,65 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source 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 <string_view>
namespace wpi {
class raw_ostream;
} // namespace wpi
namespace nt::net3 {
class Writer;
class WireConnection3 {
friend class Writer;
public:
virtual ~WireConnection3() = default;
virtual bool Ready() const = 0;
virtual Writer Send() = 0;
virtual void Flush() = 0;
virtual void Disconnect(std::string_view reason) = 0;
protected:
virtual void FinishSend() = 0;
};
class Writer {
public:
Writer(wpi::raw_ostream& os, WireConnection3& wire)
: m_os{&os}, m_wire{&wire} {}
Writer(const Writer&) = delete;
Writer(Writer&& rhs) : m_os{rhs.m_os}, m_wire{rhs.m_wire} {
rhs.m_os = nullptr;
rhs.m_wire = nullptr;
}
~Writer() {
if (m_wire) {
m_wire->FinishSend();
}
}
Writer& operator=(const Writer&) = delete;
Writer& operator=(Writer&& rhs) {
m_os = rhs.m_os;
m_wire = rhs.m_wire;
rhs.m_os = nullptr;
rhs.m_wire = nullptr;
return *this;
}
wpi::raw_ostream& stream() { return *m_os; }
private:
wpi::raw_ostream* m_os;
WireConnection3* m_wire;
};
} // namespace nt::net3

View File

@@ -0,0 +1,600 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#include "WireDecoder3.h"
#include <algorithm>
#include <optional>
#include <string>
#include <vector>
#include <fmt/format.h>
#include <wpi/MathExtras.h>
#include <wpi/SpanExtras.h>
#include <wpi/leb128.h>
#include "Message3.h"
using namespace nt;
using namespace nt::net3;
namespace {
class SimpleValueReader {
public:
std::optional<uint16_t> Read16(wpi::span<const uint8_t>* in);
std::optional<uint32_t> Read32(wpi::span<const uint8_t>* in);
std::optional<uint64_t> Read64(wpi::span<const uint8_t>* in);
std::optional<double> ReadDouble(wpi::span<const uint8_t>* in);
private:
uint64_t m_value = 0;
int m_count = 0;
};
struct StringReader {
void SetLen(uint64_t len_) {
len = len_;
buf.clear();
}
std::optional<uint64_t> len;
std::string buf;
};
struct RawReader {
void SetLen(uint64_t len_) {
len = len_;
buf.clear();
}
std::optional<uint64_t> len;
std::vector<uint8_t> buf;
};
struct ValueReader {
ValueReader() = default;
explicit ValueReader(NT_Type type_) : type{type_} {}
void SetSize(uint32_t size_) {
haveSize = true;
size = size_;
ints.clear();
doubles.clear();
strings.clear();
}
NT_Type type = NT_UNASSIGNED;
bool haveSize = false;
uint32_t size = 0;
std::vector<int> ints;
std::vector<double> doubles;
std::vector<std::string> strings;
};
struct WDImpl {
explicit WDImpl(MessageHandler3& out) : m_out{out} {}
MessageHandler3& m_out;
// primary (message) decode state
enum {
kStart,
kClientHello_1ProtoRev,
kClientHello_2Id,
kProtoUnsup_1ProtoRev,
kServerHello_1Flags,
kServerHello_2Id,
kEntryAssign_1Name,
kEntryAssign_2Type,
kEntryAssign_3Id,
kEntryAssign_4SeqNum,
kEntryAssign_5Flags,
kEntryAssign_6Value,
kEntryUpdate_1Id,
kEntryUpdate_2SeqNum,
kEntryUpdate_3Type,
kEntryUpdate_4Value,
kFlagsUpdate_1Id,
kFlagsUpdate_2Flags,
kEntryDelete_1Id,
kClearEntries_1Magic,
kExecuteRpc_1Id,
kExecuteRpc_2Uid,
kExecuteRpc_3Params,
kRpcResponse_1Id,
kRpcResponse_2Uid,
kRpcResponse_3Result,
kError
} m_state = kStart;
// detail decoders
wpi::Uleb128Reader m_ulebReader;
SimpleValueReader m_simpleReader;
StringReader m_stringReader;
RawReader m_rawReader;
ValueReader m_valueReader;
std::string m_error;
std::string m_str;
unsigned int m_id{0}; // also used for proto_rev
unsigned int m_flags{0};
unsigned int m_seq_num_uid{0};
void Execute(wpi::span<const uint8_t>* in);
std::nullopt_t EmitError(std::string_view msg) {
m_state = kError;
m_error = msg;
return std::nullopt;
}
std::optional<std::string> ReadString(wpi::span<const uint8_t>* in);
std::optional<std::vector<uint8_t>> ReadRaw(wpi::span<const uint8_t>* in);
std::optional<NT_Type> ReadType(wpi::span<const uint8_t>* in);
std::optional<Value> ReadValue(wpi::span<const uint8_t>* in);
};
} // namespace
static uint8_t Read8(wpi::span<const uint8_t>* in) {
uint8_t val = in->front();
*in = wpi::drop_front(*in);
return val;
}
std::optional<uint16_t> SimpleValueReader::Read16(
wpi::span<const uint8_t>* in) {
while (!in->empty()) {
m_value <<= 8;
m_value |= in->front() & 0xff;
*in = wpi::drop_front(*in);
if (++m_count >= 2) {
uint16_t val = static_cast<uint16_t>(m_value);
m_count = 0;
m_value = 0;
return val;
}
}
return std::nullopt;
}
std::optional<uint32_t> SimpleValueReader::Read32(
wpi::span<const uint8_t>* in) {
while (!in->empty()) {
m_value <<= 8;
m_value |= in->front() & 0xff;
*in = wpi::drop_front(*in);
if (++m_count >= 4) {
uint32_t val = static_cast<uint32_t>(m_value);
m_count = 0;
m_value = 0;
return val;
}
}
return std::nullopt;
}
std::optional<uint64_t> SimpleValueReader::Read64(
wpi::span<const uint8_t>* in) {
while (!in->empty()) {
m_value <<= 8;
m_value |= in->front() & 0xff;
*in = wpi::drop_front(*in);
if (++m_count >= 8) {
uint64_t val = m_value;
m_count = 0;
m_value = 0;
return val;
}
}
return std::nullopt;
}
std::optional<double> SimpleValueReader::ReadDouble(
wpi::span<const uint8_t>* in) {
if (auto val = Read64(in)) {
return wpi::BitsToDouble(val.value());
} else {
return std::nullopt;
}
}
void WDImpl::Execute(wpi::span<const uint8_t>* in) {
while (!in->empty()) {
switch (m_state) {
case kStart: {
uint8_t msgType = Read8(in);
switch (msgType) {
case Message3::kKeepAlive:
m_out.KeepAlive();
break;
case Message3::kClientHello:
m_state = kClientHello_1ProtoRev;
break;
case Message3::kProtoUnsup:
m_state = kProtoUnsup_1ProtoRev;
break;
case Message3::kServerHello:
m_state = kServerHello_1Flags;
break;
case Message3::kServerHelloDone:
m_out.ServerHelloDone();
break;
case Message3::kClientHelloDone:
m_out.ClientHelloDone();
break;
case Message3::kEntryAssign:
m_state = kEntryAssign_1Name;
break;
case Message3::kEntryUpdate:
m_state = kEntryUpdate_1Id;
break;
case Message3::kFlagsUpdate:
m_state = kFlagsUpdate_1Id;
break;
case Message3::kEntryDelete:
m_state = kEntryDelete_1Id;
break;
case Message3::kClearEntries:
m_state = kClearEntries_1Magic;
break;
case Message3::kExecuteRpc:
m_state = kExecuteRpc_1Id;
break;
case Message3::kRpcResponse:
m_state = kRpcResponse_1Id;
break;
default:
EmitError(fmt::format("unrecognized message type: {}",
static_cast<uint32_t>(msgType)));
return;
}
break;
}
case kClientHello_1ProtoRev:
if (auto val = m_simpleReader.Read16(in)) {
if (val < 0x0300u) {
m_state = kStart;
m_out.ClientHello("", val.value());
} else {
m_state = kClientHello_2Id;
m_id = val.value();
}
}
break;
case kClientHello_2Id:
if (auto val = ReadString(in)) {
m_state = kStart;
m_out.ClientHello(val.value(), m_id);
}
break;
case kProtoUnsup_1ProtoRev:
if (auto val = m_simpleReader.Read16(in)) {
m_state = kStart;
m_out.ProtoUnsup(val.value());
}
break;
case kServerHello_1Flags: {
m_state = kServerHello_2Id;
m_flags = Read8(in);
break;
}
case kServerHello_2Id:
if (auto val = ReadString(in)) {
m_state = kStart;
m_out.ServerHello(m_flags, val.value());
}
break;
case kEntryAssign_1Name:
if (auto val = ReadString(in)) {
m_state = kEntryAssign_2Type;
m_str = std::move(val.value());
}
break;
case kEntryAssign_2Type:
if (auto val = ReadType(in)) {
m_state = kEntryAssign_3Id;
m_valueReader = ValueReader{val.value()};
}
break;
case kEntryAssign_3Id:
if (auto val = m_simpleReader.Read16(in)) {
m_state = kEntryAssign_4SeqNum;
m_id = val.value();
}
break;
case kEntryAssign_4SeqNum:
if (auto val = m_simpleReader.Read16(in)) {
m_state = kEntryAssign_5Flags;
m_seq_num_uid = val.value();
}
break;
case kEntryAssign_5Flags: {
m_state = kEntryAssign_6Value;
m_flags = Read8(in);
break;
}
case kEntryAssign_6Value:
if (auto val = ReadValue(in)) {
m_state = kStart;
m_out.EntryAssign(m_str, m_id, m_seq_num_uid, val.value(), m_flags);
}
break;
case kEntryUpdate_1Id:
if (auto val = m_simpleReader.Read16(in)) {
m_state = kEntryUpdate_2SeqNum;
m_id = val.value();
}
break;
case kEntryUpdate_2SeqNum:
if (auto val = m_simpleReader.Read16(in)) {
m_state = kEntryUpdate_3Type;
m_seq_num_uid = val.value();
}
break;
case kEntryUpdate_3Type:
if (auto val = ReadType(in)) {
m_state = kEntryUpdate_4Value;
m_valueReader = ValueReader{val.value()};
}
break;
case kEntryUpdate_4Value:
if (auto val = ReadValue(in)) {
m_state = kStart;
m_out.EntryUpdate(m_id, m_seq_num_uid, val.value());
}
break;
case kFlagsUpdate_1Id:
if (auto val = m_simpleReader.Read16(in)) {
m_state = kFlagsUpdate_2Flags;
m_id = val.value();
}
break;
case kFlagsUpdate_2Flags: {
m_state = kStart;
m_out.FlagsUpdate(m_id, Read8(in));
break;
}
case kEntryDelete_1Id:
if (auto val = m_simpleReader.Read16(in)) {
m_state = kStart;
m_out.EntryDelete(val.value());
}
break;
case kClearEntries_1Magic:
if (auto val = m_simpleReader.Read32(in)) {
m_state = kStart;
if (val.value() == Message3::kClearAllMagic) {
m_out.ClearEntries();
} else {
EmitError("received incorrect CLEAR_ENTRIES magic value");
}
break;
}
break;
case kExecuteRpc_1Id:
if (auto val = m_simpleReader.Read16(in)) {
m_state = kExecuteRpc_2Uid;
m_id = val.value();
}
break;
case kExecuteRpc_2Uid:
if (auto val = m_simpleReader.Read16(in)) {
m_state = kExecuteRpc_3Params;
m_seq_num_uid = val.value();
}
break;
case kExecuteRpc_3Params:
if (auto val = ReadRaw(in)) {
m_state = kStart;
m_out.ExecuteRpc(m_id, m_seq_num_uid, val.value());
}
break;
case kRpcResponse_1Id:
if (auto val = m_simpleReader.Read16(in)) {
m_state = kRpcResponse_2Uid;
m_id = val.value();
}
break;
case kRpcResponse_2Uid:
if (auto val = m_simpleReader.Read16(in)) {
m_state = kRpcResponse_3Result;
m_seq_num_uid = val.value();
}
break;
case kRpcResponse_3Result:
if (auto val = ReadRaw(in)) {
m_state = kStart;
m_out.RpcResponse(m_id, m_seq_num_uid, val.value());
}
break;
case kError:
return;
}
}
}
std::optional<std::string> WDImpl::ReadString(wpi::span<const uint8_t>* in) {
// string length
if (!m_stringReader.len) {
if (auto val = m_ulebReader.ReadOne(in)) {
m_stringReader.SetLen(val.value());
m_stringReader.buf.clear();
} else {
return std::nullopt;
}
}
// string data; nolint to avoid clang-tidy false positive
size_t toCopy =
(std::min)(in->size(),
static_cast<size_t>(m_stringReader.len.value() -
m_stringReader.buf.size())); // NOLINT
m_stringReader.buf.append(reinterpret_cast<const char*>(in->data()), toCopy);
*in = wpi::drop_front(*in, toCopy);
if (m_stringReader.buf.size() >= m_stringReader.len) {
m_stringReader.len.reset();
return std::move(m_stringReader.buf);
}
return std::nullopt;
}
std::optional<std::vector<uint8_t>> WDImpl::ReadRaw(
wpi::span<const uint8_t>* in) {
// string length
if (!m_rawReader.len) {
if (auto val = m_ulebReader.ReadOne(in)) {
m_rawReader.SetLen(val.value());
m_rawReader.buf.clear();
} else {
return std::nullopt;
}
}
// string data
size_t toCopy = (std::min)(
static_cast<size_t>(in->size()),
static_cast<size_t>(m_rawReader.len.value() - m_rawReader.buf.size()));
m_rawReader.buf.insert(m_rawReader.buf.end(), in->begin(),
in->begin() + toCopy);
*in = wpi::drop_front(*in, toCopy);
if (m_rawReader.buf.size() >= m_rawReader.len) {
m_rawReader.len.reset();
return std::move(m_rawReader.buf);
}
return std::nullopt;
}
std::optional<NT_Type> WDImpl::ReadType(wpi::span<const uint8_t>* in) {
// Convert from byte value to enum
switch (Read8(in)) {
case Message3::kBoolean:
return NT_BOOLEAN;
case Message3::kDouble:
return NT_DOUBLE;
case Message3::kString:
return NT_STRING;
case Message3::kRaw:
return NT_RAW;
case Message3::kBooleanArray:
return NT_BOOLEAN_ARRAY;
case Message3::kDoubleArray:
return NT_DOUBLE_ARRAY;
case Message3::kStringArray:
return NT_STRING_ARRAY;
case Message3::kRpcDef:
return NT_RPC;
default:
return EmitError("unrecognized value type");
}
}
std::optional<Value> WDImpl::ReadValue(wpi::span<const uint8_t>* in) {
while (!in->empty()) {
switch (m_valueReader.type) {
case NT_BOOLEAN:
return Value::MakeBoolean(Read8(in) != 0);
case NT_DOUBLE:
if (auto val = m_simpleReader.ReadDouble(in)) {
return Value::MakeDouble(val.value());
}
break;
case NT_STRING:
if (auto val = ReadString(in)) {
return Value::MakeString(std::move(val.value()));
}
break;
case NT_RAW:
case NT_RPC:
if (auto val = ReadRaw(in)) {
return Value::MakeRaw(std::move(val.value()));
}
break;
#if 0
case NT_RPC:
if (auto val = ReadRaw(in)) {
return Value::MakeRpc(std::move(val.value()));
}
break;
#endif
case NT_BOOLEAN_ARRAY:
// size
if (!m_valueReader.haveSize) {
m_valueReader.SetSize(Read8(in));
break;
}
// array values
while (!in->empty() && m_valueReader.ints.size() < m_valueReader.size) {
m_valueReader.ints.emplace_back(Read8(in) ? 1 : 0);
}
if (m_valueReader.ints.size() == m_valueReader.size) {
return Value::MakeBooleanArray(std::move(m_valueReader.ints));
}
break;
case NT_DOUBLE_ARRAY:
// size
if (!m_valueReader.haveSize) {
m_valueReader.SetSize(Read8(in));
break;
}
// array values
while (!in->empty() &&
m_valueReader.doubles.size() < m_valueReader.size) {
if (auto val = m_simpleReader.ReadDouble(in)) {
m_valueReader.doubles.emplace_back(std::move(val.value()));
}
}
if (m_valueReader.doubles.size() == m_valueReader.size) {
return Value::MakeDoubleArray(std::move(m_valueReader.doubles));
}
break;
case NT_STRING_ARRAY:
// size
if (!m_valueReader.haveSize) {
m_valueReader.SetSize(Read8(in));
break;
}
// array values
while (!in->empty() &&
m_valueReader.strings.size() < m_valueReader.size) {
if (auto val = ReadString(in)) {
m_valueReader.strings.emplace_back(std::move(val.value()));
}
}
if (m_valueReader.strings.size() == m_valueReader.size) {
return Value::MakeStringArray(std::move(m_valueReader.strings));
}
break;
default:
return EmitError("invalid type when trying to read value");
}
}
return std::nullopt;
}
struct WireDecoder3::Impl : public WDImpl {
explicit Impl(MessageHandler3& out) : WDImpl{out} {}
};
WireDecoder3::WireDecoder3(MessageHandler3& out) : m_impl{new Impl{out}} {}
WireDecoder3::~WireDecoder3() = default;
bool WireDecoder3::Execute(wpi::span<const uint8_t>* in) {
m_impl->Execute(in);
return m_impl->m_state != Impl::kError;
}
void WireDecoder3::SetError(std::string_view message) {
m_impl->EmitError(message);
}
std::string WireDecoder3::GetError() const {
return m_impl->m_error;
}

View File

@@ -0,0 +1,65 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source 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 <memory>
#include <string>
#include <wpi/span.h>
namespace nt {
class Value;
} // namespace nt
namespace nt::net3 {
class MessageHandler3 {
public:
virtual void KeepAlive() = 0;
virtual void ServerHelloDone() = 0;
virtual void ClientHelloDone() = 0;
virtual void ClearEntries() = 0;
virtual void ProtoUnsup(unsigned int proto_rev) = 0;
virtual void ClientHello(std::string_view self_id,
unsigned int proto_rev) = 0;
virtual void ServerHello(unsigned int flags, std::string_view self_id) = 0;
virtual void EntryAssign(std::string_view name, unsigned int id,
unsigned int seq_num, const Value& value,
unsigned int flags) = 0;
virtual void EntryUpdate(unsigned int id, unsigned int seq_num,
const Value& value) = 0;
virtual void FlagsUpdate(unsigned int id, unsigned int flags) = 0;
virtual void EntryDelete(unsigned int id) = 0;
virtual void ExecuteRpc(unsigned int id, unsigned int uid,
wpi::span<const uint8_t> params) = 0;
virtual void RpcResponse(unsigned int id, unsigned int uid,
wpi::span<const uint8_t> result) = 0;
};
/* Decodes NT3 protocol into native representation. */
class WireDecoder3 {
public:
explicit WireDecoder3(MessageHandler3& out);
~WireDecoder3();
/**
* Executes the decoder. All input data will be consumed unless an error
* occurs.
* @param in input data (updated during parse)
* @return false if error occurred
*/
bool Execute(wpi::span<const uint8_t>* in);
void SetError(std::string_view message);
std::string GetError() const;
private:
struct Impl;
std::unique_ptr<Impl> m_impl;
};
} // namespace nt::net3

View File

@@ -0,0 +1,327 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#include "WireEncoder3.h"
#include <wpi/MathExtras.h>
#include <wpi/SmallVector.h>
#include <wpi/leb128.h>
#include <wpi/raw_ostream.h>
#include <wpi/span.h>
#include "Message3.h"
using namespace nt;
using namespace nt::net3;
static void Write8(wpi::raw_ostream& os, uint8_t val) {
os << val;
}
static void Write16(wpi::raw_ostream& os, uint16_t val) {
os << wpi::span<const uint8_t>{{static_cast<uint8_t>((val >> 8) & 0xff),
static_cast<uint8_t>(val & 0xff)}};
}
static void Write32(wpi::raw_ostream& os, uint32_t val) {
os << wpi::span<const uint8_t>{{static_cast<uint8_t>((val >> 24) & 0xff),
static_cast<uint8_t>((val >> 16) & 0xff),
static_cast<uint8_t>((val >> 8) & 0xff),
static_cast<uint8_t>(val & 0xff)}};
}
static void WriteDouble(wpi::raw_ostream& os, double val) {
// The highest performance way to do this, albeit non-portable.
uint64_t v = wpi::DoubleToBits(val);
os << wpi::span<const uint8_t>{{static_cast<uint8_t>((v >> 56) & 0xff),
static_cast<uint8_t>((v >> 48) & 0xff),
static_cast<uint8_t>((v >> 40) & 0xff),
static_cast<uint8_t>((v >> 32) & 0xff),
static_cast<uint8_t>((v >> 24) & 0xff),
static_cast<uint8_t>((v >> 16) & 0xff),
static_cast<uint8_t>((v >> 8) & 0xff),
static_cast<uint8_t>(v & 0xff)}};
}
static void WriteString(wpi::raw_ostream& os, std::string_view str) {
wpi::WriteUleb128(os, str.size());
os << str;
}
static void WriteRaw(wpi::raw_ostream& os, wpi::span<const uint8_t> str) {
wpi::WriteUleb128(os, str.size());
os << str;
}
static bool WriteType(wpi::raw_ostream& os, NT_Type type) {
char ch;
// Convert from enum to actual byte value.
switch (type) {
case NT_BOOLEAN:
ch = Message3::kBoolean;
break;
case NT_INTEGER:
case NT_FLOAT:
case NT_DOUBLE:
ch = Message3::kDouble;
break;
case NT_STRING:
ch = Message3::kString;
break;
case NT_RAW:
ch = Message3::kRaw;
break;
case NT_BOOLEAN_ARRAY:
ch = Message3::kBooleanArray;
break;
case NT_INTEGER_ARRAY:
case NT_FLOAT_ARRAY:
case NT_DOUBLE_ARRAY:
ch = Message3::kDoubleArray;
break;
case NT_STRING_ARRAY:
ch = Message3::kStringArray;
break;
case NT_RPC:
ch = Message3::kRpcDef;
break;
default:
return false;
}
os << ch;
return true;
}
static bool WriteValue(wpi::raw_ostream& os, const Value& value) {
switch (value.type()) {
case NT_BOOLEAN:
Write8(os, value.GetBoolean() ? 1 : 0);
break;
case NT_INTEGER:
WriteDouble(os, value.GetInteger());
break;
case NT_FLOAT:
WriteDouble(os, value.GetFloat());
break;
case NT_DOUBLE:
WriteDouble(os, value.GetDouble());
break;
case NT_STRING:
WriteString(os, value.GetString());
break;
case NT_RAW:
WriteRaw(os, value.GetRaw());
break;
case NT_RPC:
WriteRaw(os, value.GetRaw());
break;
case NT_BOOLEAN_ARRAY: {
auto v = value.GetBooleanArray();
size_t size = v.size();
if (size > 0xff) {
size = 0xff; // size is only 1 byte, truncate
}
Write8(os, size);
for (size_t i = 0; i < size; ++i) {
Write8(os, v[i] ? 1 : 0);
}
break;
}
case NT_INTEGER_ARRAY: {
auto v = value.GetIntegerArray();
size_t size = v.size();
if (size > 0xff) {
size = 0xff; // size is only 1 byte, truncate
}
Write8(os, size);
for (size_t i = 0; i < size; ++i) {
WriteDouble(os, v[i]);
}
break;
}
case NT_FLOAT_ARRAY: {
auto v = value.GetFloatArray();
size_t size = v.size();
if (size > 0xff) {
size = 0xff; // size is only 1 byte, truncate
}
Write8(os, size);
for (size_t i = 0; i < size; ++i) {
WriteDouble(os, v[i]);
}
break;
}
case NT_DOUBLE_ARRAY: {
auto v = value.GetDoubleArray();
size_t size = v.size();
if (size > 0xff) {
size = 0xff; // size is only 1 byte, truncate
}
Write8(os, size);
for (size_t i = 0; i < size; ++i) {
WriteDouble(os, v[i]);
}
break;
}
case NT_STRING_ARRAY: {
auto v = value.GetStringArray();
size_t size = v.size();
if (size > 0xff) {
size = 0xff; // size is only 1 byte, truncate
}
Write8(os, size);
for (size_t i = 0; i < size; ++i) {
WriteString(os, v[i]);
}
break;
}
default:
return false;
}
return true;
}
void nt::net3::WireEncodeKeepAlive(wpi::raw_ostream& os) {
Write8(os, Message3::kKeepAlive);
}
void nt::net3::WireEncodeServerHelloDone(wpi::raw_ostream& os) {
Write8(os, Message3::kServerHelloDone);
}
void nt::net3::WireEncodeClientHelloDone(wpi::raw_ostream& os) {
Write8(os, Message3::kClientHelloDone);
}
void nt::net3::WireEncodeClearEntries(wpi::raw_ostream& os) {
Write8(os, Message3::kClearEntries);
Write32(os, Message3::kClearAllMagic);
}
void nt::net3::WireEncodeProtoUnsup(wpi::raw_ostream& os,
unsigned int proto_rev) {
Write8(os, Message3::kProtoUnsup);
Write16(os, proto_rev);
}
void nt::net3::WireEncodeClientHello(wpi::raw_ostream& os,
std::string_view self_id,
unsigned int proto_rev) {
Write8(os, Message3::kClientHello);
Write16(os, proto_rev);
WriteString(os, self_id);
}
void nt::net3::WireEncodeServerHello(wpi::raw_ostream& os, unsigned int flags,
std::string_view self_id) {
Write8(os, Message3::kServerHello);
Write8(os, flags);
WriteString(os, self_id);
}
bool nt::net3::WireEncodeEntryAssign(wpi::raw_ostream& os,
std::string_view name, unsigned int id,
unsigned int seq_num, const Value& value,
unsigned int flags) {
Write8(os, Message3::kEntryAssign);
WriteString(os, name);
WriteType(os, value.type());
Write16(os, id);
Write16(os, seq_num);
Write8(os, flags);
return WriteValue(os, value);
}
bool nt::net3::WireEncodeEntryUpdate(wpi::raw_ostream& os, unsigned int id,
unsigned int seq_num, const Value& value) {
Write8(os, Message3::kEntryUpdate);
Write16(os, id);
Write16(os, seq_num);
WriteType(os, value.type());
return WriteValue(os, value);
}
void nt::net3::WireEncodeFlagsUpdate(wpi::raw_ostream& os, unsigned int id,
unsigned int flags) {
Write8(os, Message3::kFlagsUpdate);
Write16(os, id);
Write8(os, flags);
}
void nt::net3::WireEncodeEntryDelete(wpi::raw_ostream& os, unsigned int id) {
Write8(os, Message3::kEntryDelete);
Write16(os, id);
}
void nt::net3::WireEncodeExecuteRpc(wpi::raw_ostream& os, unsigned int id,
unsigned int uid,
wpi::span<const uint8_t> params) {
Write8(os, Message3::kExecuteRpc);
Write16(os, id);
Write16(os, uid);
WriteRaw(os, params);
}
void nt::net3::WireEncodeRpcResponse(wpi::raw_ostream& os, unsigned int id,
unsigned int uid,
wpi::span<const uint8_t> result) {
Write8(os, Message3::kRpcResponse);
Write16(os, id);
Write16(os, uid);
WriteRaw(os, result);
}
bool nt::net3::WireEncode(wpi::raw_ostream& os, const Message3& msg) {
switch (msg.type()) {
case Message3::kKeepAlive:
WireEncodeKeepAlive(os);
break;
case Message3::kServerHelloDone:
WireEncodeServerHelloDone(os);
break;
case Message3::kClientHelloDone:
WireEncodeClientHelloDone(os);
break;
case Message3::kClientHello:
WireEncodeClientHello(os, msg.str(), msg.id());
break;
case Message3::kProtoUnsup:
WireEncodeProtoUnsup(os, msg.id());
break;
case Message3::kServerHello:
WireEncodeServerHello(os, msg.flags(), msg.str());
break;
case Message3::kEntryAssign:
return WireEncodeEntryAssign(os, msg.str(), msg.id(), msg.seq_num_uid(),
msg.value(), msg.flags());
case Message3::kEntryUpdate:
return WireEncodeEntryUpdate(os, msg.id(), msg.seq_num_uid(),
msg.value());
case Message3::kFlagsUpdate:
WireEncodeFlagsUpdate(os, msg.id(), msg.flags());
break;
case Message3::kEntryDelete:
WireEncodeEntryDelete(os, msg.id());
break;
case Message3::kClearEntries:
WireEncodeClearEntries(os);
break;
case Message3::kExecuteRpc:
WireEncodeExecuteRpc(os, msg.id(), msg.seq_num_uid(), msg.bytes());
break;
case Message3::kRpcResponse:
WireEncodeRpcResponse(os, msg.id(), msg.seq_num_uid(), msg.bytes());
break;
case Message3::kUnknown:
return true; // ignore
default:
return false;
}
return true;
}

View File

@@ -0,0 +1,50 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source 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 <string_view>
#include <wpi/span.h>
namespace wpi {
class raw_ostream;
} // namespace wpi
namespace nt {
class Value;
} // namespace nt
namespace nt::net3 {
class Message3;
// encoders for messages (avoids need to construct a Message struct)
void WireEncodeKeepAlive(wpi::raw_ostream& os);
void WireEncodeServerHelloDone(wpi::raw_ostream& os);
void WireEncodeClientHelloDone(wpi::raw_ostream& os);
void WireEncodeClearEntries(wpi::raw_ostream& os);
void WireEncodeProtoUnsup(wpi::raw_ostream& os, unsigned int proto_rev);
void WireEncodeClientHello(wpi::raw_ostream& os, std::string_view self_id,
unsigned int proto_rev);
void WireEncodeServerHello(wpi::raw_ostream& os, unsigned int flags,
std::string_view self_id);
bool WireEncodeEntryAssign(wpi::raw_ostream& os, std::string_view name,
unsigned int id, unsigned int seq_num,
const Value& value, unsigned int flags);
bool WireEncodeEntryUpdate(wpi::raw_ostream& os, unsigned int id,
unsigned int seq_num, const Value& value);
void WireEncodeFlagsUpdate(wpi::raw_ostream& os, unsigned int id,
unsigned int flags);
void WireEncodeEntryDelete(wpi::raw_ostream& os, unsigned int id);
void WireEncodeExecuteRpc(wpi::raw_ostream& os, unsigned int id,
unsigned int uid, wpi::span<const uint8_t> params);
void WireEncodeRpcResponse(wpi::raw_ostream& os, unsigned int id,
unsigned int uid, wpi::span<const uint8_t> result);
bool WireEncode(wpi::raw_ostream& os, const Message3& msg);
} // namespace nt::net3

View File

@@ -12,7 +12,18 @@
#include <wpi/StringExtras.h>
#include <wpi/StringMap.h>
#include "networktables/BooleanArrayTopic.h"
#include "networktables/BooleanTopic.h"
#include "networktables/DoubleArrayTopic.h"
#include "networktables/DoubleTopic.h"
#include "networktables/FloatArrayTopic.h"
#include "networktables/FloatTopic.h"
#include "networktables/IntegerArrayTopic.h"
#include "networktables/IntegerTopic.h"
#include "networktables/NetworkTableInstance.h"
#include "networktables/RawTopic.h"
#include "networktables/StringArrayTopic.h"
#include "networktables/StringTopic.h"
#include "ntcore.h"
using namespace nt;
@@ -78,11 +89,7 @@ NetworkTable::NetworkTable(NT_Inst inst, std::string_view path,
const private_init&)
: m_inst(inst), m_path(path) {}
NetworkTable::~NetworkTable() {
for (auto i : m_listeners) {
RemoveEntryListener(i);
}
}
NetworkTable::~NetworkTable() {}
NetworkTableInstance NetworkTable::GetInstance() const {
return NetworkTableInstance{m_inst};
@@ -99,78 +106,58 @@ NetworkTableEntry NetworkTable::GetEntry(std::string_view key) const {
return NetworkTableEntry{entry};
}
NT_EntryListener NetworkTable::AddEntryListener(TableEntryListener listener,
unsigned int flags) const {
size_t prefix_len = m_path.size() + 1;
return nt::AddEntryListener(
m_inst, fmt::format("{}/", m_path),
[=](const EntryNotification& event) {
auto relative_key = wpi::substr(event.name, prefix_len);
if (relative_key.find(PATH_SEPARATOR_CHAR) != std::string_view::npos) {
return;
}
listener(const_cast<NetworkTable*>(this), relative_key,
NetworkTableEntry{event.entry}, event.value, event.flags);
},
flags);
Topic NetworkTable::GetTopic(std::string_view name) const {
fmt::memory_buffer buf;
fmt::format_to(fmt::appender{buf}, "{}/{}", m_path, name);
return Topic{::nt::GetTopic(m_inst, {buf.data(), buf.size()})};
}
NT_EntryListener NetworkTable::AddEntryListener(std::string_view key,
TableEntryListener listener,
unsigned int flags) const {
size_t prefix_len = m_path.size() + 1;
auto entry = GetEntry(key);
return nt::AddEntryListener(
entry.GetHandle(),
[=](const EntryNotification& event) {
listener(const_cast<NetworkTable*>(this),
wpi::substr(event.name, prefix_len), entry, event.value,
event.flags);
},
flags);
BooleanTopic NetworkTable::GetBooleanTopic(std::string_view name) const {
return BooleanTopic{GetTopic(name)};
}
void NetworkTable::RemoveEntryListener(NT_EntryListener listener) const {
nt::RemoveEntryListener(listener);
IntegerTopic NetworkTable::GetIntegerTopic(std::string_view name) const {
return IntegerTopic{GetTopic(name)};
}
NT_EntryListener NetworkTable::AddSubTableListener(TableListener listener,
bool localNotify) {
size_t prefix_len = m_path.size() + 1;
// The lambda needs to be copyable, but StringMap is not, so use
// a shared_ptr to it.
auto notified_tables = std::make_shared<wpi::StringMap<char>>();
unsigned int flags = NT_NOTIFY_NEW | NT_NOTIFY_IMMEDIATE;
if (localNotify) {
flags |= NT_NOTIFY_LOCAL;
}
NT_EntryListener id = nt::AddEntryListener(
m_inst, fmt::format("{}/", m_path),
[=](const EntryNotification& event) {
auto relative_key = wpi::substr(event.name, prefix_len);
auto end_sub_table = relative_key.find(PATH_SEPARATOR_CHAR);
if (end_sub_table == std::string_view::npos) {
return;
}
auto sub_table_key = wpi::substr(relative_key, 0, end_sub_table);
if (notified_tables->find(sub_table_key) == notified_tables->end()) {
return;
}
notified_tables->insert(std::make_pair(sub_table_key, '\0'));
listener(this, sub_table_key, this->GetSubTable(sub_table_key));
},
flags);
m_listeners.emplace_back(id);
return id;
FloatTopic NetworkTable::GetFloatTopic(std::string_view name) const {
return FloatTopic{GetTopic(name)};
}
void NetworkTable::RemoveTableListener(NT_EntryListener listener) {
nt::RemoveEntryListener(listener);
auto matches_begin =
std::remove(m_listeners.begin(), m_listeners.end(), listener);
m_listeners.erase(matches_begin, m_listeners.end());
DoubleTopic NetworkTable::GetDoubleTopic(std::string_view name) const {
return DoubleTopic{GetTopic(name)};
}
StringTopic NetworkTable::GetStringTopic(std::string_view name) const {
return StringTopic{GetTopic(name)};
}
RawTopic NetworkTable::GetRawTopic(std::string_view name) const {
return RawTopic{GetTopic(name)};
}
BooleanArrayTopic NetworkTable::GetBooleanArrayTopic(
std::string_view name) const {
return BooleanArrayTopic{GetTopic(name)};
}
IntegerArrayTopic NetworkTable::GetIntegerArrayTopic(
std::string_view name) const {
return IntegerArrayTopic{GetTopic(name)};
}
FloatArrayTopic NetworkTable::GetFloatArrayTopic(std::string_view name) const {
return FloatArrayTopic{GetTopic(name)};
}
DoubleArrayTopic NetworkTable::GetDoubleArrayTopic(
std::string_view name) const {
return DoubleArrayTopic{GetTopic(name)};
}
StringArrayTopic NetworkTable::GetStringArrayTopic(
std::string_view name) const {
return StringArrayTopic{GetTopic(name)};
}
std::shared_ptr<NetworkTable> NetworkTable::GetSubTable(
@@ -183,25 +170,52 @@ bool NetworkTable::ContainsKey(std::string_view key) const {
if (key.empty()) {
return false;
}
return GetEntry(key).Exists();
return GetTopic(key).Exists();
}
bool NetworkTable::ContainsSubTable(std::string_view key) const {
return !GetEntryInfo(m_inst, fmt::format("{}/{}/", m_path, key), 0).empty();
return !::nt::GetTopics(m_inst, fmt::format("{}/{}/", m_path, key), 0)
.empty();
}
std::vector<TopicInfo> NetworkTable::GetTopicInfo(int types) const {
std::vector<TopicInfo> infos;
size_t prefix_len = m_path.size() + 1;
for (auto&& info :
::nt::GetTopicInfo(m_inst, fmt::format("{}/", m_path), types)) {
auto relative_key = wpi::substr(info.name, prefix_len);
if (relative_key.find(PATH_SEPARATOR_CHAR) != std::string_view::npos) {
continue;
}
infos.emplace_back(std::move(info));
}
return infos;
}
std::vector<Topic> NetworkTable::GetTopics(int types) const {
std::vector<Topic> topics;
size_t prefix_len = m_path.size() + 1;
for (auto&& info :
::nt::GetTopicInfo(m_inst, fmt::format("{}/", m_path), types)) {
auto relative_key = wpi::substr(info.name, prefix_len);
if (relative_key.find(PATH_SEPARATOR_CHAR) != std::string_view::npos) {
continue;
}
topics.emplace_back(info.topic);
}
return topics;
}
std::vector<std::string> NetworkTable::GetKeys(int types) const {
std::vector<std::string> keys;
size_t prefix_len = m_path.size() + 1;
auto infos = GetEntryInfo(m_inst, fmt::format("{}/", m_path), types);
std::scoped_lock lock(m_mutex);
for (auto& info : infos) {
for (auto&& info :
::nt::GetTopicInfo(m_inst, fmt::format("{}/", m_path), types)) {
auto relative_key = wpi::substr(info.name, prefix_len);
if (relative_key.find(PATH_SEPARATOR_CHAR) != std::string_view::npos) {
continue;
}
keys.emplace_back(relative_key);
m_entries[relative_key] = info.entry;
}
return keys;
}
@@ -209,8 +223,9 @@ std::vector<std::string> NetworkTable::GetKeys(int types) const {
std::vector<std::string> NetworkTable::GetSubTables() const {
std::vector<std::string> keys;
size_t prefix_len = m_path.size() + 1;
for (auto& entry : GetEntryInfo(m_inst, fmt::format("{}/", m_path), 0)) {
auto relative_key = wpi::substr(entry.name, prefix_len);
for (auto&& topic :
::nt::GetTopicInfo(m_inst, fmt::format("{}/", m_path), 0)) {
auto relative_key = wpi::substr(topic.name, prefix_len);
size_t end_subtable = relative_key.find(PATH_SEPARATOR_CHAR);
if (end_subtable == std::string_view::npos) {
continue;
@@ -232,22 +247,6 @@ bool NetworkTable::IsPersistent(std::string_view key) const {
return GetEntry(key).IsPersistent();
}
void NetworkTable::SetFlags(std::string_view key, unsigned int flags) {
GetEntry(key).SetFlags(flags);
}
void NetworkTable::ClearFlags(std::string_view key, unsigned int flags) {
GetEntry(key).ClearFlags(flags);
}
unsigned int NetworkTable::GetFlags(std::string_view key) const {
return GetEntry(key).GetFlags();
}
void NetworkTable::Delete(std::string_view key) {
GetEntry(key).Delete();
}
bool NetworkTable::PutNumber(std::string_view key, double value) {
return GetEntry(key).SetDouble(value);
}
@@ -332,44 +331,34 @@ std::vector<std::string> NetworkTable::GetStringArray(
return GetEntry(key).GetStringArray(defaultValue);
}
bool NetworkTable::PutRaw(std::string_view key, std::string_view value) {
bool NetworkTable::PutRaw(std::string_view key,
wpi::span<const uint8_t> value) {
return GetEntry(key).SetRaw(value);
}
bool NetworkTable::SetDefaultRaw(std::string_view key,
std::string_view defaultValue) {
wpi::span<const uint8_t> defaultValue) {
return GetEntry(key).SetDefaultRaw(defaultValue);
}
std::string NetworkTable::GetRaw(std::string_view key,
std::string_view defaultValue) const {
std::vector<uint8_t> NetworkTable::GetRaw(
std::string_view key, wpi::span<const uint8_t> defaultValue) const {
return GetEntry(key).GetRaw(defaultValue);
}
bool NetworkTable::PutValue(std::string_view key,
std::shared_ptr<Value> value) {
bool NetworkTable::PutValue(std::string_view key, const Value& value) {
return GetEntry(key).SetValue(value);
}
bool NetworkTable::SetDefaultValue(std::string_view key,
std::shared_ptr<Value> defaultValue) {
const Value& defaultValue) {
return GetEntry(key).SetDefaultValue(defaultValue);
}
std::shared_ptr<Value> NetworkTable::GetValue(std::string_view key) const {
Value NetworkTable::GetValue(std::string_view key) const {
return GetEntry(key).GetValue();
}
std::string_view NetworkTable::GetPath() const {
return m_path;
}
const char* NetworkTable::SaveEntries(std::string_view filename) const {
return nt::SaveEntries(m_inst, filename, fmt::format("{}/", m_path));
}
const char* NetworkTable::LoadEntries(
std::string_view filename,
std::function<void(size_t line, const char* msg)> warn) {
return nt::LoadEntries(m_inst, filename, fmt::format("{}/", m_path), warn);
}

View File

@@ -5,9 +5,14 @@
#include "networktables/NetworkTableEntry.h"
#include "networktables/NetworkTableInstance.h"
#include "networktables/Topic.h"
using namespace nt;
NetworkTableInstance NetworkTableEntry::GetInstance() const {
return NetworkTableInstance{GetInstanceFromHandle(m_handle)};
}
Topic NetworkTableEntry::GetTopic() const {
return Topic{::nt::GetTopicFromHandle(m_handle)};
}

View File

@@ -7,8 +7,75 @@
#include <fmt/format.h>
#include <wpi/SmallVector.h>
#include "networktables/BooleanArrayTopic.h"
#include "networktables/BooleanTopic.h"
#include "networktables/DoubleArrayTopic.h"
#include "networktables/DoubleTopic.h"
#include "networktables/FloatArrayTopic.h"
#include "networktables/FloatTopic.h"
#include "networktables/IntegerArrayTopic.h"
#include "networktables/IntegerTopic.h"
#include "networktables/RawTopic.h"
#include "networktables/StringArrayTopic.h"
#include "networktables/StringTopic.h"
using namespace nt;
Topic NetworkTableInstance::GetTopic(std::string_view name) const {
return Topic{::nt::GetTopic(m_handle, name)};
}
BooleanTopic NetworkTableInstance::GetBooleanTopic(
std::string_view name) const {
return BooleanTopic{GetTopic(name)};
}
IntegerTopic NetworkTableInstance::GetIntegerTopic(
std::string_view name) const {
return IntegerTopic{GetTopic(name)};
}
FloatTopic NetworkTableInstance::GetFloatTopic(std::string_view name) const {
return FloatTopic{GetTopic(name)};
}
DoubleTopic NetworkTableInstance::GetDoubleTopic(std::string_view name) const {
return DoubleTopic{GetTopic(name)};
}
StringTopic NetworkTableInstance::GetStringTopic(std::string_view name) const {
return StringTopic{GetTopic(name)};
}
RawTopic NetworkTableInstance::GetRawTopic(std::string_view name) const {
return RawTopic{GetTopic(name)};
}
BooleanArrayTopic NetworkTableInstance::GetBooleanArrayTopic(
std::string_view name) const {
return BooleanArrayTopic{GetTopic(name)};
}
IntegerArrayTopic NetworkTableInstance::GetIntegerArrayTopic(
std::string_view name) const {
return IntegerArrayTopic{GetTopic(name)};
}
FloatArrayTopic NetworkTableInstance::GetFloatArrayTopic(
std::string_view name) const {
return FloatArrayTopic{GetTopic(name)};
}
DoubleArrayTopic NetworkTableInstance::GetDoubleArrayTopic(
std::string_view name) const {
return DoubleArrayTopic{GetTopic(name)};
}
StringArrayTopic NetworkTableInstance::GetStringArrayTopic(
std::string_view name) const {
return StringArrayTopic{GetTopic(name)};
}
std::shared_ptr<NetworkTable> NetworkTableInstance::GetTable(
std::string_view key) const {
if (key.empty() || key == "/") {
@@ -23,29 +90,14 @@ std::shared_ptr<NetworkTable> NetworkTableInstance::GetTable(
}
}
void NetworkTableInstance::StartClient(
wpi::span<const std::string_view> servers, unsigned int port) {
wpi::SmallVector<std::pair<std::string_view, unsigned int>, 8> server_ports;
for (const auto& server : servers) {
server_ports.emplace_back(std::make_pair(server, port));
}
StartClient(server_ports);
}
void NetworkTableInstance::SetServer(wpi::span<const std::string_view> servers,
unsigned int port) {
wpi::SmallVector<std::pair<std::string_view, unsigned int>, 8> server_ports;
std::vector<std::pair<std::string_view, unsigned int>> serversArr;
serversArr.reserve(servers.size());
for (const auto& server : servers) {
server_ports.emplace_back(std::make_pair(server, port));
serversArr.emplace_back(std::string{server}, port);
}
SetServer(server_ports);
}
NT_EntryListener NetworkTableInstance::AddEntryListener(
std::string_view prefix,
std::function<void(const EntryNotification& event)> callback,
unsigned int flags) const {
return ::nt::AddEntryListener(m_handle, prefix, callback, flags);
SetServer(serversArr);
}
NT_ConnectionListener NetworkTableInstance::AddConnectionListener(

View File

@@ -1,13 +0,0 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#include "networktables/RpcCall.h"
#include "networktables/NetworkTableEntry.h"
using namespace nt;
NetworkTableEntry RpcCall::GetEntry() const {
return NetworkTableEntry{m_entry};
}

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.
#include "networktables/Topic.h"
#include <wpi/json.h>
#include "networktables/GenericEntry.h"
using namespace nt;
wpi::json Topic::GetProperty(std::string_view name) const {
return ::nt::GetTopicProperty(m_handle, name);
}
void Topic::SetProperty(std::string_view name, const wpi::json& value) {
::nt::SetTopicProperty(m_handle, name, value);
}
wpi::json Topic::GetProperties() const {
return ::nt::GetTopicProperties(m_handle);
}
GenericSubscriber Topic::GenericSubscribe(
wpi::span<const PubSubOption> options) {
return GenericSubscribe("", options);
}
GenericSubscriber Topic::GenericSubscribe(
std::string_view typeString, wpi::span<const PubSubOption> options) {
return GenericSubscriber{::nt::Subscribe(
m_handle, ::nt::GetTypeFromString(typeString), typeString, options)};
}
GenericPublisher Topic::GenericPublish(std::string_view typeString,
wpi::span<const PubSubOption> options) {
return GenericPublisher{::nt::Publish(
m_handle, ::nt::GetTypeFromString(typeString), typeString, options)};
}
GenericPublisher Topic::GenericPublishEx(
std::string_view typeString, const wpi::json& properties,
wpi::span<const PubSubOption> options) {
return GenericPublisher{::nt::PublishEx(m_handle,
::nt::GetTypeFromString(typeString),
typeString, properties, options)};
}
GenericEntry Topic::GetGenericEntry(wpi::span<const PubSubOption> options) {
return GetGenericEntry("", options);
}
GenericEntry Topic::GetGenericEntry(std::string_view typeString,
wpi::span<const PubSubOption> options) {
return GenericEntry{::nt::GetEntry(
m_handle, ::nt::GetTypeFromString(typeString), typeString, options)};
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -19,23 +19,22 @@ struct NT_String* NT_GetStringForTesting(const char* string, int* struct_size) {
return str;
}
struct NT_EntryInfo* NT_GetEntryInfoForTesting(const char* name,
struct NT_TopicInfo* NT_GetTopicInfoForTesting(const char* name,
enum NT_Type type,
unsigned int flags,
uint64_t last_change,
const char* type_str,
int* struct_size) {
struct NT_EntryInfo* entry_info =
static_cast<NT_EntryInfo*>(wpi::safe_calloc(1, sizeof(NT_EntryInfo)));
nt::ConvertToC(name, &entry_info->name);
entry_info->type = type;
entry_info->flags = flags;
entry_info->last_change = last_change;
*struct_size = sizeof(NT_EntryInfo);
return entry_info;
struct NT_TopicInfo* topic_info =
static_cast<NT_TopicInfo*>(wpi::safe_calloc(1, sizeof(NT_TopicInfo)));
nt::ConvertToC(name, &topic_info->name);
topic_info->type = type;
nt::ConvertToC(type_str, &topic_info->type_str);
*struct_size = sizeof(NT_TopicInfo);
return topic_info;
}
void NT_FreeEntryInfoForTesting(struct NT_EntryInfo* info) {
void NT_FreeTopicInfoForTesting(struct NT_TopicInfo* info) {
std::free(info->name.str);
std::free(info->type_str.str);
std::free(info);
}
@@ -158,88 +157,4 @@ struct NT_Value* NT_GetValueStringArrayForTesting(uint64_t last_change,
}
// No need for free as one already exists in the main library
static void CopyNtValue(const struct NT_Value* copy_from,
struct NT_Value* copy_to) {
auto cpp_value = nt::ConvertFromC(*copy_from);
nt::ConvertToC(*cpp_value, copy_to);
}
static void CopyNtString(const struct NT_String* copy_from,
struct NT_String* copy_to) {
nt::ConvertToC({copy_from->str, copy_from->len}, copy_to);
}
struct NT_RpcParamDef* NT_GetRpcParamDefForTesting(const char* name,
const struct NT_Value* val,
int* struct_size) {
struct NT_RpcParamDef* def =
static_cast<NT_RpcParamDef*>(wpi::safe_calloc(1, sizeof(NT_RpcParamDef)));
nt::ConvertToC(name, &def->name);
CopyNtValue(val, &def->def_value);
*struct_size = sizeof(NT_RpcParamDef);
return def;
}
void NT_FreeRpcParamDefForTesting(struct NT_RpcParamDef* def) {
NT_DisposeValue(&def->def_value);
NT_DisposeString(&def->name);
std::free(def);
}
struct NT_RpcResultDef* NT_GetRpcResultsDefForTesting(const char* name,
enum NT_Type type,
int* struct_size) {
struct NT_RpcResultDef* def = static_cast<NT_RpcResultDef*>(
wpi::safe_calloc(1, sizeof(NT_RpcResultDef)));
nt::ConvertToC(name, &def->name);
def->type = type;
*struct_size = sizeof(NT_RpcResultDef);
return def;
}
void NT_FreeRpcResultsDefForTesting(struct NT_RpcResultDef* def) {
NT_DisposeString(&def->name);
std::free(def);
}
struct NT_RpcDefinition* NT_GetRpcDefinitionForTesting(
unsigned int version, const char* name, size_t num_params,
const struct NT_RpcParamDef* params, size_t num_results,
const struct NT_RpcResultDef* results, int* struct_size) {
struct NT_RpcDefinition* def = static_cast<NT_RpcDefinition*>(
wpi::safe_calloc(1, sizeof(NT_RpcDefinition)));
def->version = version;
nt::ConvertToC(name, &def->name);
def->num_params = num_params;
def->params = static_cast<NT_RpcParamDef*>(
wpi::safe_malloc(num_params * sizeof(NT_RpcParamDef)));
for (size_t i = 0; i < num_params; ++i) {
CopyNtString(&params[i].name, &def->params[i].name);
CopyNtValue(&params[i].def_value, &def->params[i].def_value);
}
def->num_results = num_results;
def->results = static_cast<NT_RpcResultDef*>(
wpi::safe_malloc(num_results * sizeof(NT_RpcResultDef)));
for (size_t i = 0; i < num_results; ++i) {
CopyNtString(&results[i].name, &def->results[i].name);
def->results[i].type = results[i].type;
}
*struct_size = sizeof(NT_RpcDefinition);
return def;
}
// No need for free as one already exists in the main library
struct NT_RpcAnswer* NT_GetRpcAnswerForTesting(
unsigned int rpc_id, unsigned int call_uid, const char* name,
const char* params, size_t params_len, int* struct_size) {
struct NT_RpcAnswer* info =
static_cast<NT_RpcAnswer*>(wpi::safe_calloc(1, sizeof(NT_RpcAnswer)));
info->entry = rpc_id;
info->call = call_uid;
nt::ConvertToC(name, &info->name);
nt::ConvertToC({params, params_len}, &info->params);
*struct_size = sizeof(NT_RpcAnswer);
return info;
}
// No need for free as one already exists in the main library
} // extern "C"