mirror of
https://github.com/wpilibsuite/allwpilib
synced 2026-06-21 01:01:43 +00:00
[ntcore] NetworkTables 4 (#3217)
This commit is contained in:
338
ntcore/src/main/native/cpp/ConnectionList.cpp
Normal file
338
ntcore/src/main/native/cpp/ConnectionList.cpp
Normal 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());
|
||||
}
|
||||
}
|
||||
56
ntcore/src/main/native/cpp/ConnectionList.h
Normal file
56
ntcore/src/main/native/cpp/ConnectionList.h
Normal 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
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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_
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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_
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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_
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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_
|
||||
@@ -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_
|
||||
|
||||
54
ntcore/src/main/native/cpp/HandleMap.h
Normal file
54
ntcore/src/main/native/cpp/HandleMap.h
Normal 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
|
||||
24
ntcore/src/main/native/cpp/IConnectionList.h
Normal file
24
ntcore/src/main/native/cpp/IConnectionList.h
Normal 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
|
||||
@@ -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_
|
||||
@@ -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_
|
||||
@@ -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_
|
||||
32
ntcore/src/main/native/cpp/INetworkClient.h
Normal file
32
ntcore/src/main/native/cpp/INetworkClient.h
Normal 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
|
||||
@@ -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_
|
||||
@@ -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_
|
||||
@@ -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_
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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_
|
||||
|
||||
2489
ntcore/src/main/native/cpp/LocalStorage.cpp
Normal file
2489
ntcore/src/main/native/cpp/LocalStorage.cpp
Normal file
File diff suppressed because it is too large
Load Diff
250
ntcore/src/main/native/cpp/LocalStorage.h
Normal file
250
ntcore/src/main/native/cpp/LocalStorage.h
Normal 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
|
||||
@@ -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_
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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_
|
||||
|
||||
@@ -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(¶ms, 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;
|
||||
}
|
||||
}
|
||||
@@ -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_
|
||||
530
ntcore/src/main/native/cpp/NetworkClient.cpp
Normal file
530
ntcore/src/main/native/cpp/NetworkClient.cpp
Normal 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();
|
||||
}
|
||||
}
|
||||
68
ntcore/src/main/native/cpp/NetworkClient.h
Normal file
68
ntcore/src/main/native/cpp/NetworkClient.h
Normal 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
|
||||
@@ -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
|
||||
@@ -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_
|
||||
556
ntcore/src/main/native/cpp/NetworkServer.cpp
Normal file
556
ntcore/src/main/native/cpp/NetworkServer.cpp
Normal 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();
|
||||
}
|
||||
}
|
||||
42
ntcore/src/main/native/cpp/NetworkServer.h
Normal file
42
ntcore/src/main/native/cpp/NetworkServer.h
Normal 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
|
||||
36
ntcore/src/main/native/cpp/PubSubOptions.cpp
Normal file
36
ntcore/src/main/native/cpp/PubSubOptions.cpp
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
27
ntcore/src/main/native/cpp/PubSubOptions.h
Normal file
27
ntcore/src/main/native/cpp/PubSubOptions.h
Normal 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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
@@ -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_
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
83
ntcore/src/main/native/cpp/Types_internal.cpp
Normal file
83
ntcore/src/main/native/cpp/Types_internal.cpp
Normal 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;
|
||||
}
|
||||
}
|
||||
17
ntcore/src/main/native/cpp/Types_internal.h
Normal file
17
ntcore/src/main/native/cpp/Types_internal.h
Normal 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
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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_
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
487
ntcore/src/main/native/cpp/net/ClientImpl.cpp
Normal file
487
ntcore/src/main/native/cpp/net/ClientImpl.cpp
Normal 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);
|
||||
}
|
||||
}
|
||||
79
ntcore/src/main/native/cpp/net/ClientImpl.h
Normal file
79
ntcore/src/main/native/cpp/net/ClientImpl.h
Normal 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
|
||||
100
ntcore/src/main/native/cpp/net/Message.h
Normal file
100
ntcore/src/main/native/cpp/net/Message.h
Normal 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
|
||||
68
ntcore/src/main/native/cpp/net/NetworkInterface.h
Normal file
68
ntcore/src/main/native/cpp/net/NetworkInterface.h
Normal 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
|
||||
54
ntcore/src/main/native/cpp/net/NetworkLoopQueue.cpp
Normal file
54
ntcore/src/main/native/cpp/net/NetworkLoopQueue.cpp
Normal 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}});
|
||||
}
|
||||
55
ntcore/src/main/native/cpp/net/NetworkLoopQueue.h
Normal file
55
ntcore/src/main/native/cpp/net/NetworkLoopQueue.h
Normal 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"
|
||||
70
ntcore/src/main/native/cpp/net/NetworkLoopQueue.inc
Normal file
70
ntcore/src/main/native/cpp/net/NetworkLoopQueue.inc
Normal 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
|
||||
2340
ntcore/src/main/native/cpp/net/ServerImpl.cpp
Normal file
2340
ntcore/src/main/native/cpp/net/ServerImpl.cpp
Normal file
File diff suppressed because it is too large
Load Diff
94
ntcore/src/main/native/cpp/net/ServerImpl.h
Normal file
94
ntcore/src/main/native/cpp/net/ServerImpl.h
Normal 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
|
||||
122
ntcore/src/main/native/cpp/net/WebSocketConnection.cpp
Normal file
122
ntcore/src/main/native/cpp/net/WebSocketConnection.cpp
Normal 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);
|
||||
}
|
||||
66
ntcore/src/main/native/cpp/net/WebSocketConnection.h
Normal file
66
ntcore/src/main/native/cpp/net/WebSocketConnection.h
Normal 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
|
||||
110
ntcore/src/main/native/cpp/net/WireConnection.h
Normal file
110
ntcore/src/main/native/cpp/net/WireConnection.h
Normal 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
|
||||
564
ntcore/src/main/native/cpp/net/WireDecoder.cpp
Normal file
564
ntcore/src/main/native/cpp/net/WireDecoder.cpp
Normal 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;
|
||||
}
|
||||
65
ntcore/src/main/native/cpp/net/WireDecoder.h
Normal file
65
ntcore/src/main/native/cpp/net/WireDecoder.h
Normal 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
|
||||
316
ntcore/src/main/native/cpp/net/WireEncoder.cpp
Normal file
316
ntcore/src/main/native/cpp/net/WireEncoder.cpp
Normal 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;
|
||||
}
|
||||
62
ntcore/src/main/native/cpp/net/WireEncoder.h
Normal file
62
ntcore/src/main/native/cpp/net/WireEncoder.h
Normal 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
|
||||
672
ntcore/src/main/native/cpp/net3/ClientImpl3.cpp
Normal file
672
ntcore/src/main/native/cpp/net3/ClientImpl3.cpp
Normal 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);
|
||||
}
|
||||
73
ntcore/src/main/native/cpp/net3/ClientImpl3.h
Normal file
73
ntcore/src/main/native/cpp/net3/ClientImpl3.h
Normal 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
|
||||
155
ntcore/src/main/native/cpp/net3/Message3.h
Normal file
155
ntcore/src/main/native/cpp/net3/Message3.h
Normal 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
|
||||
@@ -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
|
||||
@@ -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
|
||||
50
ntcore/src/main/native/cpp/net3/UvStreamConnection3.cpp
Normal file
50
ntcore/src/main/native/cpp/net3/UvStreamConnection3.cpp
Normal 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);
|
||||
}
|
||||
57
ntcore/src/main/native/cpp/net3/UvStreamConnection3.h
Normal file
57
ntcore/src/main/native/cpp/net3/UvStreamConnection3.h
Normal 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
|
||||
65
ntcore/src/main/native/cpp/net3/WireConnection3.h
Normal file
65
ntcore/src/main/native/cpp/net3/WireConnection3.h
Normal 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
|
||||
600
ntcore/src/main/native/cpp/net3/WireDecoder3.cpp
Normal file
600
ntcore/src/main/native/cpp/net3/WireDecoder3.cpp
Normal 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;
|
||||
}
|
||||
65
ntcore/src/main/native/cpp/net3/WireDecoder3.h
Normal file
65
ntcore/src/main/native/cpp/net3/WireDecoder3.h
Normal 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
|
||||
327
ntcore/src/main/native/cpp/net3/WireEncoder3.cpp
Normal file
327
ntcore/src/main/native/cpp/net3/WireEncoder3.cpp
Normal 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;
|
||||
}
|
||||
50
ntcore/src/main/native/cpp/net3/WireEncoder3.h
Normal file
50
ntcore/src/main/native/cpp/net3/WireEncoder3.h
Normal 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
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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)};
|
||||
}
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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};
|
||||
}
|
||||
58
ntcore/src/main/native/cpp/networktables/Topic.cpp
Normal file
58
ntcore/src/main/native/cpp/networktables/Topic.cpp
Normal 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
@@ -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(¶ms[i].name, &def->params[i].name);
|
||||
CopyNtValue(¶ms[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"
|
||||
|
||||
Reference in New Issue
Block a user