mirror of
https://github.com/wpilibsuite/allwpilib
synced 2026-06-23 01:21:42 +00:00
Remove wpiutil and update to the new build system (#210)
This commit is contained in:
committed by
Peter Johnson
parent
f43675e2bd
commit
5df7463663
579
src/main/native/cpp/Dispatcher.cpp
Normal file
579
src/main/native/cpp/Dispatcher.cpp
Normal file
@@ -0,0 +1,579 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) FIRST 2015. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#include "Dispatcher.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <iterator>
|
||||
|
||||
#include "Log.h"
|
||||
#include "tcpsockets/TCPAcceptor.h"
|
||||
#include "tcpsockets/TCPConnector.h"
|
||||
|
||||
using namespace nt;
|
||||
|
||||
ATOMIC_STATIC_INIT(Dispatcher)
|
||||
|
||||
void Dispatcher::StartServer(llvm::StringRef persist_filename,
|
||||
const char* listen_address, unsigned int port) {
|
||||
DispatcherBase::StartServer(
|
||||
persist_filename,
|
||||
std::unique_ptr<wpi::NetworkAcceptor>(new wpi::TCPAcceptor(
|
||||
static_cast<int>(port), listen_address, Logger::GetInstance())));
|
||||
}
|
||||
|
||||
void Dispatcher::SetServer(const char* server_name, unsigned int port) {
|
||||
std::string server_name_copy(server_name);
|
||||
SetConnector([=]() -> std::unique_ptr<wpi::NetworkStream> {
|
||||
return wpi::TCPConnector::connect(server_name_copy.c_str(),
|
||||
static_cast<int>(port),
|
||||
Logger::GetInstance(), 1);
|
||||
});
|
||||
}
|
||||
|
||||
void Dispatcher::SetServer(
|
||||
ArrayRef<std::pair<StringRef, unsigned int>> servers) {
|
||||
std::vector<Connector> connectors;
|
||||
for (const auto& server : servers) {
|
||||
std::string server_name(server.first);
|
||||
unsigned int port = server.second;
|
||||
connectors.emplace_back([=]() -> std::unique_ptr<wpi::NetworkStream> {
|
||||
return wpi::TCPConnector::connect(server_name.c_str(),
|
||||
static_cast<int>(port),
|
||||
Logger::GetInstance(), 1);
|
||||
});
|
||||
}
|
||||
SetConnector(std::move(connectors));
|
||||
}
|
||||
|
||||
void Dispatcher::SetServerOverride(const char* server_name, unsigned int port) {
|
||||
std::string server_name_copy(server_name);
|
||||
SetConnectorOverride([=]() -> std::unique_ptr<wpi::NetworkStream> {
|
||||
return wpi::TCPConnector::connect(server_name_copy.c_str(),
|
||||
static_cast<int>(port),
|
||||
Logger::GetInstance(), 1);
|
||||
});
|
||||
}
|
||||
|
||||
void Dispatcher::ClearServerOverride() { ClearConnectorOverride(); }
|
||||
|
||||
Dispatcher::Dispatcher()
|
||||
: Dispatcher(Storage::GetInstance(), Notifier::GetInstance()) {}
|
||||
|
||||
DispatcherBase::DispatcherBase(Storage& storage, Notifier& notifier)
|
||||
: m_storage(storage), m_notifier(notifier) {
|
||||
m_active = false;
|
||||
m_update_rate = 100;
|
||||
}
|
||||
|
||||
DispatcherBase::~DispatcherBase() {
|
||||
Logger::GetInstance().SetLogger(nullptr);
|
||||
Stop();
|
||||
}
|
||||
|
||||
unsigned int DispatcherBase::GetNetworkMode() const {
|
||||
return m_networkMode;
|
||||
}
|
||||
|
||||
void DispatcherBase::StartServer(
|
||||
StringRef persist_filename,
|
||||
std::unique_ptr<wpi::NetworkAcceptor> acceptor) {
|
||||
{
|
||||
std::lock_guard<std::mutex> 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, [&](std::size_t line, const char* msg) {
|
||||
if (first) {
|
||||
first = false;
|
||||
WARNING("When reading initial persistent values from '"
|
||||
<< persist_filename << "':");
|
||||
}
|
||||
WARNING(persist_filename << ":" << line << ": " << msg);
|
||||
});
|
||||
}
|
||||
|
||||
using namespace std::placeholders;
|
||||
m_storage.SetOutgoing(std::bind(&Dispatcher::QueueOutgoing, this, _1, _2, _3),
|
||||
(m_networkMode & NT_NET_MODE_SERVER) != 0);
|
||||
|
||||
m_dispatch_thread = std::thread(&Dispatcher::DispatchThreadMain, this);
|
||||
m_clientserver_thread = std::thread(&Dispatcher::ServerThreadMain, this);
|
||||
}
|
||||
|
||||
void DispatcherBase::StartClient() {
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_user_mutex);
|
||||
if (m_active) return;
|
||||
m_active = true;
|
||||
}
|
||||
m_networkMode = NT_NET_MODE_CLIENT | NT_NET_MODE_STARTING;
|
||||
using namespace std::placeholders;
|
||||
m_storage.SetOutgoing(std::bind(&Dispatcher::QueueOutgoing, this, _1, _2, _3),
|
||||
(m_networkMode & NT_NET_MODE_SERVER) != 0);
|
||||
|
||||
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::lock_guard<std::mutex> lock(m_user_mutex);
|
||||
m_client_connectors.resize(0);
|
||||
}
|
||||
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<NetworkConnection>> conns;
|
||||
{
|
||||
std::lock_guard<std::mutex> 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 10 ms or slower than 1 second
|
||||
if (interval < 0.01)
|
||||
interval = 0.01;
|
||||
else if (interval > 1.0)
|
||||
interval = 1.0;
|
||||
m_update_rate = static_cast<unsigned int>(interval * 1000);
|
||||
}
|
||||
|
||||
void DispatcherBase::SetIdentity(llvm::StringRef name) {
|
||||
std::lock_guard<std::mutex> lock(m_user_mutex);
|
||||
m_identity = name;
|
||||
}
|
||||
|
||||
void DispatcherBase::Flush() {
|
||||
auto now = std::chrono::steady_clock::now();
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_flush_mutex);
|
||||
// don't allow flushes more often than every 10 ms
|
||||
if ((now - m_last_flush) < std::chrono::milliseconds(10)) 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::lock_guard<std::mutex> lock(m_user_mutex);
|
||||
for (auto& conn : m_connections) {
|
||||
if (conn->state() != NetworkConnection::kActive) continue;
|
||||
conns.emplace_back(conn->info());
|
||||
}
|
||||
|
||||
return conns;
|
||||
}
|
||||
|
||||
void DispatcherBase::NotifyConnections(
|
||||
ConnectionListenerCallback callback) const {
|
||||
std::lock_guard<std::mutex> lock(m_user_mutex);
|
||||
for (const auto& conn : m_connections) conn->NotifyIfActive(callback);
|
||||
}
|
||||
|
||||
void DispatcherBase::SetConnector(Connector connector) {
|
||||
std::vector<Connector> connectors;
|
||||
connectors.push_back(connector);
|
||||
SetConnector(std::move(connectors));
|
||||
}
|
||||
|
||||
void DispatcherBase::SetConnector(std::vector<Connector>&& connectors) {
|
||||
std::lock_guard<std::mutex> lock(m_user_mutex);
|
||||
m_client_connectors = std::move(connectors);
|
||||
}
|
||||
|
||||
void DispatcherBase::SetConnectorOverride(Connector connector) {
|
||||
std::lock_guard<std::mutex> lock(m_user_mutex);
|
||||
m_client_connector_override = std::move(connector);
|
||||
}
|
||||
|
||||
void DispatcherBase::ClearConnectorOverride() {
|
||||
std::lock_guard<std::mutex> 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<std::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::lock_guard<std::mutex> user_lock(m_user_mutex);
|
||||
bool reconnect = false;
|
||||
|
||||
if (++count > 10) {
|
||||
DEBUG("dispatch running " << m_connections.size() << " connections");
|
||||
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,
|
||||
NetworkConnection* only,
|
||||
NetworkConnection* except) {
|
||||
std::lock_guard<std::mutex> 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;
|
||||
}
|
||||
DEBUG("server: client connection from " << stream->getPeerIP() << " port "
|
||||
<< stream->getPeerPort());
|
||||
|
||||
// add to connections list
|
||||
using namespace std::placeholders;
|
||||
auto conn = std::make_shared<NetworkConnection>(
|
||||
std::move(stream), m_notifier,
|
||||
std::bind(&Dispatcher::ServerHandshake, this, _1, _2, _3),
|
||||
std::bind(&Storage::GetEntryType, &m_storage, _1));
|
||||
conn->set_process_incoming(
|
||||
std::bind(&Storage::ProcessIncoming, &m_storage, _1, _2,
|
||||
std::weak_ptr<NetworkConnection>(conn)));
|
||||
{
|
||||
std::lock_guard<std::mutex> 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() {
|
||||
std::size_t i = 0;
|
||||
while (m_active) {
|
||||
// sleep between retries
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(250));
|
||||
Connector connect;
|
||||
|
||||
// get next server to connect to
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_user_mutex);
|
||||
if (m_client_connector_override) {
|
||||
connect = m_client_connector_override;
|
||||
} else {
|
||||
if (m_client_connectors.empty()) {
|
||||
m_networkMode = NT_NET_MODE_CLIENT | NT_NET_MODE_FAILURE;
|
||||
continue;
|
||||
}
|
||||
if (i >= m_client_connectors.size()) i = 0;
|
||||
connect = m_client_connectors[i++];
|
||||
}
|
||||
}
|
||||
|
||||
// try to connect (with timeout)
|
||||
DEBUG("client trying to connect");
|
||||
auto stream = connect();
|
||||
if (!stream) {
|
||||
m_networkMode = NT_NET_MODE_CLIENT | NT_NET_MODE_FAILURE;
|
||||
continue; // keep retrying
|
||||
}
|
||||
DEBUG("client connected");
|
||||
m_networkMode = NT_NET_MODE_CLIENT;
|
||||
|
||||
std::unique_lock<std::mutex> lock(m_user_mutex);
|
||||
using namespace std::placeholders;
|
||||
auto conn = std::make_shared<NetworkConnection>(
|
||||
std::move(stream), m_notifier,
|
||||
std::bind(&Dispatcher::ClientHandshake, this, _1, _2, _3),
|
||||
std::bind(&Storage::GetEntryType, &m_storage, _1));
|
||||
conn->set_process_incoming(
|
||||
std::bind(&Storage::ProcessIncoming, &m_storage, _1, _2,
|
||||
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(llvm::ArrayRef<std::shared_ptr<Message>>)> send_msgs) {
|
||||
// get identity
|
||||
std::string self_id;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_user_mutex);
|
||||
self_id = m_identity;
|
||||
}
|
||||
|
||||
// send client hello
|
||||
DEBUG("client: sending hello");
|
||||
send_msgs(Message::ClientHello(self_id));
|
||||
|
||||
// wait for response
|
||||
auto msg = get_msg();
|
||||
if (!msg) {
|
||||
// disconnected, retry
|
||||
DEBUG("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
|
||||
DEBUG("client: server disconnected during initial entries");
|
||||
return false;
|
||||
}
|
||||
DEBUG4("received init str=" << msg->str() << " id=" << msg->id()
|
||||
<< " seq_num=" << 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
|
||||
DEBUG("client: received message ("
|
||||
<< msg->type()
|
||||
<< ") other than entry assignment during initial handshake");
|
||||
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 " << conn.stream().getPeerIP() << " port "
|
||||
<< conn.stream().getPeerPort());
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DispatcherBase::ServerHandshake(
|
||||
NetworkConnection& conn, std::function<std::shared_ptr<Message>()> get_msg,
|
||||
std::function<void(llvm::ArrayRef<std::shared_ptr<Message>>)> send_msgs) {
|
||||
// Wait for the client to send us a hello.
|
||||
auto msg = get_msg();
|
||||
if (!msg) {
|
||||
DEBUG("server: client disconnected before sending hello");
|
||||
return false;
|
||||
}
|
||||
if (!msg->Is(Message::kClientHello)) {
|
||||
DEBUG("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) {
|
||||
DEBUG("server: client requested proto > 0x0300");
|
||||
send_msgs(Message::ProtoUnsup());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (proto_rev >= 0x0300) conn.set_remote_id(msg->str());
|
||||
|
||||
// Set the proto version to the client requested version
|
||||
DEBUG("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::lock_guard<std::mutex> 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
|
||||
DEBUG("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
|
||||
DEBUG("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
|
||||
DEBUG("server: received message ("
|
||||
<< msg->type()
|
||||
<< ") other than entry assignment during initial handshake");
|
||||
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: " << conn.stream().getPeerIP() << " port "
|
||||
<< conn.stream().getPeerPort());
|
||||
return true;
|
||||
}
|
||||
|
||||
void DispatcherBase::ClientReconnect(unsigned int proto_rev) {
|
||||
if ((m_networkMode & NT_NET_MODE_SERVER) != 0) return;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_user_mutex);
|
||||
m_reconnect_proto_rev = proto_rev;
|
||||
m_do_reconnect = true;
|
||||
}
|
||||
m_reconnect_cv.notify_one();
|
||||
}
|
||||
145
src/main/native/cpp/Dispatcher.h
Normal file
145
src/main/native/cpp/Dispatcher.h
Normal file
@@ -0,0 +1,145 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) FIRST 2015. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#ifndef NT_DISPATCHER_H_
|
||||
#define NT_DISPATCHER_H_
|
||||
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <condition_variable>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "llvm/StringRef.h"
|
||||
|
||||
#include "support/atomic_static.h"
|
||||
#include "NetworkConnection.h"
|
||||
#include "Notifier.h"
|
||||
#include "Storage.h"
|
||||
|
||||
namespace wpi {
|
||||
class NetworkAcceptor;
|
||||
class NetworkStream;
|
||||
}
|
||||
|
||||
namespace nt {
|
||||
|
||||
class DispatcherBase {
|
||||
friend class DispatcherTest;
|
||||
|
||||
public:
|
||||
typedef std::function<std::unique_ptr<wpi::NetworkStream>()> Connector;
|
||||
|
||||
virtual ~DispatcherBase();
|
||||
|
||||
unsigned int GetNetworkMode() const;
|
||||
void StartServer(llvm::StringRef persist_filename,
|
||||
std::unique_ptr<wpi::NetworkAcceptor> acceptor);
|
||||
void StartClient();
|
||||
void Stop();
|
||||
void SetUpdateRate(double interval);
|
||||
void SetIdentity(llvm::StringRef name);
|
||||
void Flush();
|
||||
std::vector<ConnectionInfo> GetConnections() const;
|
||||
void NotifyConnections(ConnectionListenerCallback callback) const;
|
||||
|
||||
void SetConnector(Connector connector);
|
||||
void SetConnector(std::vector<Connector>&& connectors);
|
||||
|
||||
void SetConnectorOverride(Connector connector);
|
||||
void ClearConnectorOverride();
|
||||
|
||||
bool active() const { return m_active; }
|
||||
|
||||
DispatcherBase(const DispatcherBase&) = delete;
|
||||
DispatcherBase& operator=(const DispatcherBase&) = delete;
|
||||
|
||||
protected:
|
||||
DispatcherBase(Storage& storage, Notifier& notifier);
|
||||
|
||||
private:
|
||||
void DispatchThreadMain();
|
||||
void ServerThreadMain();
|
||||
void ClientThreadMain();
|
||||
|
||||
bool ClientHandshake(
|
||||
NetworkConnection& conn,
|
||||
std::function<std::shared_ptr<Message>()> get_msg,
|
||||
std::function<void(llvm::ArrayRef<std::shared_ptr<Message>>)> send_msgs);
|
||||
bool ServerHandshake(
|
||||
NetworkConnection& conn,
|
||||
std::function<std::shared_ptr<Message>()> get_msg,
|
||||
std::function<void(llvm::ArrayRef<std::shared_ptr<Message>>)> send_msgs);
|
||||
|
||||
void ClientReconnect(unsigned int proto_rev = 0x0300);
|
||||
|
||||
void QueueOutgoing(std::shared_ptr<Message> msg, NetworkConnection* only,
|
||||
NetworkConnection* except);
|
||||
|
||||
Storage& m_storage;
|
||||
Notifier& 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;
|
||||
std::vector<Connector> m_client_connectors;
|
||||
|
||||
// Mutex for user-accessible items
|
||||
mutable std::mutex m_user_mutex;
|
||||
std::vector<std::shared_ptr<NetworkConnection>> 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)
|
||||
std::mutex m_flush_mutex;
|
||||
std::condition_variable m_flush_cv;
|
||||
std::chrono::steady_clock::time_point m_last_flush;
|
||||
bool m_do_flush = false;
|
||||
|
||||
// Condition variable for client reconnect (uses user mutex)
|
||||
std::condition_variable m_reconnect_cv;
|
||||
unsigned int m_reconnect_proto_rev = 0x0300;
|
||||
bool m_do_reconnect = true;
|
||||
};
|
||||
|
||||
class Dispatcher : public DispatcherBase {
|
||||
friend class DispatcherTest;
|
||||
|
||||
public:
|
||||
static Dispatcher& GetInstance() {
|
||||
ATOMIC_STATIC(Dispatcher, instance);
|
||||
return instance;
|
||||
}
|
||||
|
||||
void StartServer(StringRef persist_filename, const char* listen_address,
|
||||
unsigned int port);
|
||||
|
||||
void SetServer(const char* server_name, unsigned int port);
|
||||
void SetServer(ArrayRef<std::pair<StringRef, unsigned int>> servers);
|
||||
|
||||
void SetServerOverride(const char* server_name, unsigned int port);
|
||||
void ClearServerOverride();
|
||||
|
||||
private:
|
||||
Dispatcher();
|
||||
Dispatcher(Storage& storage, Notifier& notifier)
|
||||
: DispatcherBase(storage, notifier) {}
|
||||
|
||||
ATOMIC_STATIC_DECL(Dispatcher)
|
||||
};
|
||||
|
||||
} // namespace nt
|
||||
|
||||
#endif // NT_DISPATCHER_H_
|
||||
150
src/main/native/cpp/DsClient.cpp
Normal file
150
src/main/native/cpp/DsClient.cpp
Normal file
@@ -0,0 +1,150 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) FIRST 2015. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#include "DsClient.h"
|
||||
|
||||
#include "llvm/raw_ostream.h"
|
||||
#include "llvm/SmallString.h"
|
||||
#include "support/raw_socket_istream.h"
|
||||
#include "tcpsockets/TCPConnector.h"
|
||||
|
||||
#include "Dispatcher.h"
|
||||
#include "Log.h"
|
||||
|
||||
using namespace nt;
|
||||
|
||||
ATOMIC_STATIC_INIT(DsClient)
|
||||
|
||||
class DsClient::Thread : public wpi::SafeThread {
|
||||
public:
|
||||
Thread(unsigned int port) : m_port(port) {}
|
||||
|
||||
void Main();
|
||||
|
||||
unsigned int m_port;
|
||||
std::unique_ptr<wpi::NetworkStream> m_stream;
|
||||
};
|
||||
|
||||
void DsClient::Start(unsigned int port) {
|
||||
auto thr = m_owner.GetThread();
|
||||
if (!thr)
|
||||
m_owner.Start(new Thread(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<std::mutex> 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.
|
||||
llvm::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 == llvm::StringRef::npos) continue; // could not find?
|
||||
pos += 9;
|
||||
pos = json.find(':', pos);
|
||||
if (pos == llvm::StringRef::npos) continue; // could not find?
|
||||
size_t endpos = json.find_first_not_of("0123456789", pos + 1);
|
||||
DEBUG3("found robotIP=" << json.slice(pos + 1, endpos));
|
||||
|
||||
// Parse into number
|
||||
unsigned int ip;
|
||||
if (json.slice(pos + 1, endpos).getAsInteger(10, ip)) continue; // error
|
||||
|
||||
// If zero, clear the server override
|
||||
if (ip == 0) {
|
||||
Dispatcher::GetInstance().ClearServerOverride();
|
||||
oldip = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
// If unchanged, don't reconnect
|
||||
if (ip == oldip) continue;
|
||||
oldip = ip;
|
||||
|
||||
// Convert number into dotted quad
|
||||
json.clear();
|
||||
llvm::raw_svector_ostream os{json};
|
||||
os << ((ip >> 24) & 0xff) << "." << ((ip >> 16) & 0xff) << "."
|
||||
<< ((ip >> 8) & 0xff) << "." << (ip & 0xff);
|
||||
INFO("client: DS overriding server IP to " << os.str());
|
||||
Dispatcher::GetInstance().SetServerOverride(json.c_str(), port);
|
||||
}
|
||||
|
||||
// We disconnected from the DS, clear the server override
|
||||
Dispatcher::GetInstance().ClearServerOverride();
|
||||
oldip = 0;
|
||||
}
|
||||
|
||||
done:
|
||||
Dispatcher::GetInstance().ClearServerOverride();
|
||||
}
|
||||
38
src/main/native/cpp/DsClient.h
Normal file
38
src/main/native/cpp/DsClient.h
Normal file
@@ -0,0 +1,38 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) FIRST 2016. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#ifndef NT_DSCLIENT_H_
|
||||
#define NT_DSCLIENT_H_
|
||||
|
||||
#include "support/atomic_static.h"
|
||||
#include "support/SafeThread.h"
|
||||
|
||||
namespace nt {
|
||||
|
||||
class DsClient {
|
||||
public:
|
||||
static DsClient& GetInstance() {
|
||||
ATOMIC_STATIC(DsClient, instance);
|
||||
return instance;
|
||||
}
|
||||
~DsClient() = default;
|
||||
|
||||
void Start(unsigned int port);
|
||||
void Stop();
|
||||
|
||||
private:
|
||||
DsClient() = default;
|
||||
|
||||
class Thread;
|
||||
wpi::SafeThreadOwner<Thread> m_owner;
|
||||
|
||||
ATOMIC_STATIC_DECL(DsClient)
|
||||
};
|
||||
|
||||
} // namespace nt
|
||||
|
||||
#endif // NT_DSCLIENT_H_
|
||||
66
src/main/native/cpp/Log.cpp
Normal file
66
src/main/native/cpp/Log.cpp
Normal file
@@ -0,0 +1,66 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) FIRST 2015. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#include "Log.h"
|
||||
|
||||
#include <cstdio>
|
||||
#ifdef _WIN32
|
||||
#include <cstdlib>
|
||||
#else
|
||||
#include <cstring>
|
||||
#endif
|
||||
|
||||
#ifdef __APPLE__
|
||||
#include <libgen.h>
|
||||
#endif
|
||||
|
||||
#ifdef __ANDROID__
|
||||
#include <libgen.h>
|
||||
#endif
|
||||
|
||||
using namespace nt;
|
||||
|
||||
ATOMIC_STATIC_INIT(Logger)
|
||||
|
||||
static void def_log_func(unsigned int level, const char* file,
|
||||
unsigned int line, const char* msg) {
|
||||
if (level == 20) {
|
||||
std::fprintf(stderr, "NT: %s\n", msg);
|
||||
return;
|
||||
}
|
||||
|
||||
const char* levelmsg;
|
||||
if (level >= 50)
|
||||
levelmsg = "CRITICAL";
|
||||
else if (level >= 40)
|
||||
levelmsg = "ERROR";
|
||||
else if (level >= 30)
|
||||
levelmsg = "WARNING";
|
||||
else
|
||||
return;
|
||||
#ifdef _WIN32
|
||||
char fname[60];
|
||||
char ext[10];
|
||||
_splitpath_s(file, nullptr, 0, nullptr, 0, fname, 60, ext, 10);
|
||||
std::fprintf(stderr, "NT: %s: %s (%s%s:%d)\n", levelmsg, msg, fname, ext,
|
||||
line);
|
||||
#elif __APPLE__
|
||||
int len = strlen(msg) + 1;
|
||||
char* basestr = new char[len + 1];
|
||||
strncpy(basestr, file, len);
|
||||
std::fprintf(stderr, "NT: %s: %s (%s:%d)\n", levelmsg, msg, basename(basestr),
|
||||
line);
|
||||
delete[] basestr;
|
||||
#else
|
||||
std::fprintf(stderr, "NT: %s: %s (%s:%d)\n", levelmsg, msg, basename(file),
|
||||
line);
|
||||
#endif
|
||||
}
|
||||
|
||||
Logger::Logger() { SetLogger(def_log_func); }
|
||||
|
||||
Logger::~Logger() {}
|
||||
45
src/main/native/cpp/Log.h
Normal file
45
src/main/native/cpp/Log.h
Normal file
@@ -0,0 +1,45 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) FIRST 2015. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#ifndef NT_LOG_H_
|
||||
#define NT_LOG_H_
|
||||
|
||||
#include "support/atomic_static.h"
|
||||
#include "support/Logger.h"
|
||||
|
||||
namespace nt {
|
||||
|
||||
class Logger : public wpi::Logger {
|
||||
public:
|
||||
static Logger& GetInstance() {
|
||||
ATOMIC_STATIC(Logger, instance);
|
||||
return instance;
|
||||
}
|
||||
~Logger();
|
||||
|
||||
private:
|
||||
Logger();
|
||||
|
||||
ATOMIC_STATIC_DECL(Logger)
|
||||
};
|
||||
|
||||
#define LOG(level, x) WPI_LOG(nt::Logger::GetInstance(), level, x)
|
||||
|
||||
#undef ERROR
|
||||
#define ERROR(x) WPI_ERROR(nt::Logger::GetInstance(), x)
|
||||
#define WARNING(x) WPI_WARNING(nt::Logger::GetInstance(), x)
|
||||
#define INFO(x) WPI_INFO(nt::Logger::GetInstance(), x)
|
||||
|
||||
#define DEBUG(x) WPI_DEBUG(nt::Logger::GetInstance(), x)
|
||||
#define DEBUG1(x) WPI_DEBUG1(nt::Logger::GetInstance(), x)
|
||||
#define DEBUG2(x) WPI_DEBUG2(nt::Logger::GetInstance(), x)
|
||||
#define DEBUG3(x) WPI_DEBUG3(nt::Logger::GetInstance(), x)
|
||||
#define DEBUG4(x) WPI_DEBUG4(nt::Logger::GetInstance(), x)
|
||||
|
||||
} // namespace nt
|
||||
|
||||
#endif // NT_LOG_H_
|
||||
299
src/main/native/cpp/Message.cpp
Normal file
299
src/main/native/cpp/Message.cpp
Normal file
@@ -0,0 +1,299 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) FIRST 2015. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#include "Message.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;
|
||||
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);
|
||||
}
|
||||
DEBUG4("update message data type: " << 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;
|
||||
}
|
||||
unsigned long 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
|
||||
unsigned long size;
|
||||
if (!decoder.ReadUleb128(&size)) return nullptr;
|
||||
const char* params;
|
||||
if (!decoder.Read(¶ms, size)) return nullptr;
|
||||
msg->m_str = llvm::StringRef(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
|
||||
unsigned long size;
|
||||
if (!decoder.ReadUleb128(&size)) return nullptr;
|
||||
const char* results;
|
||||
if (!decoder.Read(&results, size)) return nullptr;
|
||||
msg->m_str = llvm::StringRef(results, size);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
decoder.set_error("unrecognized message type");
|
||||
INFO("unrecognized message type: " << msg_type);
|
||||
return nullptr;
|
||||
}
|
||||
return msg;
|
||||
}
|
||||
|
||||
std::shared_ptr<Message> Message::ClientHello(llvm::StringRef 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,
|
||||
llvm::StringRef 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(llvm::StringRef 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,
|
||||
llvm::StringRef 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,
|
||||
llvm::StringRef results) {
|
||||
auto msg = std::make_shared<Message>(kRpcResponse, private_init());
|
||||
msg->m_str = results;
|
||||
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;
|
||||
}
|
||||
}
|
||||
117
src/main/native/cpp/Message.h
Normal file
117
src/main/native/cpp/Message.h
Normal file
@@ -0,0 +1,117 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) FIRST 2015. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#ifndef NT_MESSAGE_H_
|
||||
#define NT_MESSAGE_H_
|
||||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include "nt_Value.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
|
||||
};
|
||||
typedef std::function<NT_Type(unsigned int id)> GetEntryTypeFunc;
|
||||
|
||||
Message() : m_type(kUnknown), m_id(0), m_flags(0), m_seq_num_uid(0) {}
|
||||
Message(MsgType type, const private_init&)
|
||||
: m_type(type), m_id(0), m_flags(0), m_seq_num_uid(0) {}
|
||||
|
||||
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.
|
||||
llvm::StringRef 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(llvm::StringRef self_id);
|
||||
static std::shared_ptr<Message> ServerHello(unsigned int flags,
|
||||
llvm::StringRef self_id);
|
||||
static std::shared_ptr<Message> EntryAssign(llvm::StringRef 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,
|
||||
llvm::StringRef params);
|
||||
static std::shared_ptr<Message> RpcResponse(unsigned int id, unsigned int uid,
|
||||
llvm::StringRef results);
|
||||
|
||||
Message(const Message&) = delete;
|
||||
Message& operator=(const Message&) = delete;
|
||||
|
||||
private:
|
||||
MsgType m_type;
|
||||
|
||||
// Message data. Use varies by message type.
|
||||
std::string m_str;
|
||||
std::shared_ptr<Value> m_value;
|
||||
unsigned int m_id; // also used for proto_rev
|
||||
unsigned int m_flags;
|
||||
unsigned int m_seq_num_uid;
|
||||
};
|
||||
|
||||
} // namespace nt
|
||||
|
||||
#endif // NT_MESSAGE_H_
|
||||
330
src/main/native/cpp/NetworkConnection.cpp
Normal file
330
src/main/native/cpp/NetworkConnection.cpp
Normal file
@@ -0,0 +1,330 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) FIRST 2015. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#include "NetworkConnection.h"
|
||||
|
||||
#include "support/raw_socket_istream.h"
|
||||
#include "support/timestamp.h"
|
||||
#include "tcpsockets/NetworkStream.h"
|
||||
#include "Log.h"
|
||||
#include "Notifier.h"
|
||||
#include "WireDecoder.h"
|
||||
#include "WireEncoder.h"
|
||||
|
||||
using namespace nt;
|
||||
|
||||
std::atomic_uint NetworkConnection::s_uid;
|
||||
|
||||
NetworkConnection::NetworkConnection(std::unique_ptr<wpi::NetworkStream> stream,
|
||||
Notifier& notifier,
|
||||
HandshakeFunc handshake,
|
||||
Message::GetEntryTypeFunc get_entry_type)
|
||||
: m_uid(s_uid.fetch_add(1)),
|
||||
m_stream(std::move(stream)),
|
||||
m_notifier(notifier),
|
||||
m_handshake(handshake),
|
||||
m_get_entry_type(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::lock_guard<std::mutex> 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 (" << 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<std::mutex> 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<std::mutex> 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(), m_stream->getPeerIP(),
|
||||
static_cast<unsigned int>(m_stream->getPeerPort()),
|
||||
m_last_update, m_proto_rev};
|
||||
}
|
||||
|
||||
NetworkConnection::State NetworkConnection::state() const {
|
||||
std::lock_guard<std::mutex> lock(m_state_mutex);
|
||||
return m_state;
|
||||
}
|
||||
|
||||
void NetworkConnection::set_state(State state) {
|
||||
std::lock_guard<std::mutex> 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;
|
||||
}
|
||||
|
||||
void NetworkConnection::NotifyIfActive(
|
||||
ConnectionListenerCallback callback) const {
|
||||
std::lock_guard<std::mutex> lock(m_state_mutex);
|
||||
if (m_state == kActive) m_notifier.NotifyConnection(true, info(), callback);
|
||||
}
|
||||
|
||||
std::string NetworkConnection::remote_id() const {
|
||||
std::lock_guard<std::mutex> lock(m_remote_id_mutex);
|
||||
return m_remote_id;
|
||||
}
|
||||
|
||||
void NetworkConnection::set_remote_id(StringRef remote_id) {
|
||||
std::lock_guard<std::mutex> 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);
|
||||
|
||||
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())
|
||||
DEBUG("error reading in handshake: " << decoder.error());
|
||||
return msg;
|
||||
},
|
||||
[&](llvm::ArrayRef<std::shared_ptr<Message>> msgs) {
|
||||
m_outgoing.emplace(msgs);
|
||||
})) {
|
||||
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=" << msg->type() << " with str=" << msg->str()
|
||||
<< " id=" << msg->id()
|
||||
<< " seq_num=" << msg->seq_num_uid());
|
||||
m_last_update = Now();
|
||||
m_process_incoming(std::move(msg), this);
|
||||
}
|
||||
DEBUG2("read thread died (" << this << ")");
|
||||
set_state(kDead);
|
||||
m_active = false;
|
||||
m_outgoing.push(Outgoing()); // also kill write thread
|
||||
|
||||
done:
|
||||
// use condition variable to signal thread shutdown
|
||||
{
|
||||
std::lock_guard<std::mutex> 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 " << msgs.size() << " messages");
|
||||
for (auto& msg : msgs) {
|
||||
if (msg) {
|
||||
DEBUG3("sending type=" << msg->type() << " with str=" << msg->str()
|
||||
<< " id=" << msg->id()
|
||||
<< " seq_num=" << 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 " << encoder.size() << " bytes");
|
||||
}
|
||||
DEBUG2("write thread died (" << 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::lock_guard<std::mutex> lock(m_shutdown_mutex);
|
||||
m_write_shutdown = true;
|
||||
m_write_shutdown_cv.notify_one();
|
||||
}
|
||||
}
|
||||
|
||||
void NetworkConnection::QueueOutgoing(std::shared_ptr<Message> msg) {
|
||||
std::lock_guard<std::mutex> 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
|
||||
std::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
|
||||
std::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::lock_guard<std::mutex> 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;
|
||||
}
|
||||
119
src/main/native/cpp/NetworkConnection.h
Normal file
119
src/main/native/cpp/NetworkConnection.h
Normal file
@@ -0,0 +1,119 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) FIRST 2015. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#ifndef NT_NETWORKCONNECTION_H_
|
||||
#define NT_NETWORKCONNECTION_H_
|
||||
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <memory>
|
||||
#include <thread>
|
||||
|
||||
#include "support/ConcurrentQueue.h"
|
||||
#include "Message.h"
|
||||
#include "ntcore_cpp.h"
|
||||
|
||||
namespace wpi {
|
||||
class NetworkStream;
|
||||
}
|
||||
|
||||
namespace nt {
|
||||
|
||||
class Notifier;
|
||||
|
||||
class NetworkConnection {
|
||||
public:
|
||||
enum State { kCreated, kInit, kHandshake, kSynchronized, kActive, kDead };
|
||||
|
||||
typedef std::function<bool(
|
||||
NetworkConnection& conn,
|
||||
std::function<std::shared_ptr<Message>()> get_msg,
|
||||
std::function<void(llvm::ArrayRef<std::shared_ptr<Message>>)> send_msgs)>
|
||||
HandshakeFunc;
|
||||
typedef std::function<void(std::shared_ptr<Message> msg,
|
||||
NetworkConnection* conn)>
|
||||
ProcessIncomingFunc;
|
||||
typedef std::vector<std::shared_ptr<Message>> Outgoing;
|
||||
typedef wpi::ConcurrentQueue<Outgoing> OutgoingQueue;
|
||||
|
||||
NetworkConnection(std::unique_ptr<wpi::NetworkStream> stream,
|
||||
Notifier& notifier, HandshakeFunc handshake,
|
||||
Message::GetEntryTypeFunc get_entry_type);
|
||||
~NetworkConnection();
|
||||
|
||||
// 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;
|
||||
|
||||
bool active() const { return m_active; }
|
||||
wpi::NetworkStream& stream() { return *m_stream; }
|
||||
|
||||
void QueueOutgoing(std::shared_ptr<Message> msg);
|
||||
void PostOutgoing(bool keep_alive);
|
||||
void NotifyIfActive(ConnectionListenerCallback callback) const;
|
||||
|
||||
unsigned int uid() const { return m_uid; }
|
||||
|
||||
unsigned int proto_rev() const { return m_proto_rev; }
|
||||
void set_proto_rev(unsigned int proto_rev) { m_proto_rev = proto_rev; }
|
||||
|
||||
State state() const;
|
||||
void set_state(State state);
|
||||
|
||||
std::string remote_id() const;
|
||||
void set_remote_id(StringRef remote_id);
|
||||
|
||||
unsigned long long last_update() const { return m_last_update; }
|
||||
|
||||
NetworkConnection(const NetworkConnection&) = delete;
|
||||
NetworkConnection& operator=(const NetworkConnection&) = delete;
|
||||
|
||||
private:
|
||||
void ReadThreadMain();
|
||||
void WriteThreadMain();
|
||||
|
||||
static std::atomic_uint s_uid;
|
||||
|
||||
unsigned int m_uid;
|
||||
std::unique_ptr<wpi::NetworkStream> m_stream;
|
||||
Notifier& m_notifier;
|
||||
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 std::mutex m_state_mutex;
|
||||
State m_state;
|
||||
mutable std::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;
|
||||
|
||||
std::mutex m_pending_mutex;
|
||||
Outgoing m_pending_outgoing;
|
||||
std::vector<std::pair<std::size_t, std::size_t>> m_pending_update;
|
||||
|
||||
// Condition variables for shutdown
|
||||
std::mutex m_shutdown_mutex;
|
||||
std::condition_variable m_read_shutdown_cv;
|
||||
std::condition_variable m_write_shutdown_cv;
|
||||
bool m_read_shutdown = false;
|
||||
bool m_write_shutdown = false;
|
||||
};
|
||||
|
||||
} // namespace nt
|
||||
|
||||
#endif // NT_NETWORKCONNECTION_H_
|
||||
258
src/main/native/cpp/Notifier.cpp
Normal file
258
src/main/native/cpp/Notifier.cpp
Normal file
@@ -0,0 +1,258 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) FIRST 2015. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#include "Notifier.h"
|
||||
|
||||
#include <queue>
|
||||
#include <vector>
|
||||
|
||||
using namespace nt;
|
||||
|
||||
ATOMIC_STATIC_INIT(Notifier)
|
||||
bool Notifier::s_destroyed = false;
|
||||
|
||||
namespace {
|
||||
// Vector which provides an integrated freelist for removal and reuse of
|
||||
// individual elements.
|
||||
template <typename T>
|
||||
class UidVector {
|
||||
public:
|
||||
typedef typename std::vector<T>::size_type size_type;
|
||||
|
||||
size_type size() const { return m_vector.size(); }
|
||||
T& operator[](size_type i) { return m_vector[i]; }
|
||||
const T& operator[](size_type i) const { return m_vector[i]; }
|
||||
|
||||
// Add a new T to the vector. If there are elements on the freelist,
|
||||
// reuses the last one; otherwise adds to the end of the vector.
|
||||
// Returns the resulting element index (+1).
|
||||
template <class... Args>
|
||||
unsigned int emplace_back(Args&&... args) {
|
||||
unsigned int uid;
|
||||
if (m_free.empty()) {
|
||||
uid = m_vector.size();
|
||||
m_vector.emplace_back(std::forward<Args>(args)...);
|
||||
} else {
|
||||
uid = m_free.back();
|
||||
m_free.pop_back();
|
||||
m_vector[uid] = T(std::forward<Args>(args)...);
|
||||
}
|
||||
return uid + 1;
|
||||
}
|
||||
|
||||
// Removes the identified element by replacing it with a default-constructed
|
||||
// one. The element is added to the freelist for later reuse.
|
||||
void erase(unsigned int uid) {
|
||||
--uid;
|
||||
if (uid >= m_vector.size() || !m_vector[uid]) return;
|
||||
m_free.push_back(uid);
|
||||
m_vector[uid] = T();
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<T> m_vector;
|
||||
std::vector<unsigned int> m_free;
|
||||
};
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
class Notifier::Thread : public wpi::SafeThread {
|
||||
public:
|
||||
Thread(std::function<void()> on_start, std::function<void()> on_exit)
|
||||
: m_on_start(on_start), m_on_exit(on_exit) {}
|
||||
|
||||
void Main();
|
||||
|
||||
struct EntryListener {
|
||||
EntryListener() = default;
|
||||
EntryListener(StringRef prefix_, EntryListenerCallback callback_,
|
||||
unsigned int flags_)
|
||||
: prefix(prefix_), callback(callback_), flags(flags_) {}
|
||||
|
||||
explicit operator bool() const { return bool(callback); }
|
||||
|
||||
std::string prefix;
|
||||
EntryListenerCallback callback;
|
||||
unsigned int flags;
|
||||
};
|
||||
UidVector<EntryListener> m_entry_listeners;
|
||||
UidVector<ConnectionListenerCallback> m_conn_listeners;
|
||||
|
||||
struct EntryNotification {
|
||||
EntryNotification(llvm::StringRef name_, std::shared_ptr<Value> value_,
|
||||
unsigned int flags_, EntryListenerCallback only_)
|
||||
: name(name_), value(value_), flags(flags_), only(only_) {}
|
||||
|
||||
std::string name;
|
||||
std::shared_ptr<Value> value;
|
||||
unsigned int flags;
|
||||
EntryListenerCallback only;
|
||||
};
|
||||
std::queue<EntryNotification> m_entry_notifications;
|
||||
|
||||
struct ConnectionNotification {
|
||||
ConnectionNotification(bool connected_, const ConnectionInfo& conn_info_,
|
||||
ConnectionListenerCallback only_)
|
||||
: connected(connected_), conn_info(conn_info_), only(only_) {}
|
||||
|
||||
bool connected;
|
||||
ConnectionInfo conn_info;
|
||||
ConnectionListenerCallback only;
|
||||
};
|
||||
std::queue<ConnectionNotification> m_conn_notifications;
|
||||
|
||||
std::function<void()> m_on_start;
|
||||
std::function<void()> m_on_exit;
|
||||
};
|
||||
|
||||
Notifier::Notifier() {
|
||||
m_local_notifiers = false;
|
||||
s_destroyed = false;
|
||||
}
|
||||
|
||||
Notifier::~Notifier() { s_destroyed = true; }
|
||||
|
||||
void Notifier::Start() {
|
||||
auto thr = m_owner.GetThread();
|
||||
if (!thr) m_owner.Start(new Thread(m_on_start, m_on_exit));
|
||||
}
|
||||
|
||||
void Notifier::Stop() { m_owner.Stop(); }
|
||||
|
||||
void Notifier::Thread::Main() {
|
||||
if (m_on_start) m_on_start();
|
||||
|
||||
std::unique_lock<std::mutex> lock(m_mutex);
|
||||
while (m_active) {
|
||||
while (m_entry_notifications.empty() && m_conn_notifications.empty()) {
|
||||
m_cond.wait(lock);
|
||||
if (!m_active) goto done;
|
||||
}
|
||||
|
||||
// Entry notifications
|
||||
while (!m_entry_notifications.empty()) {
|
||||
if (!m_active) goto done;
|
||||
auto item = std::move(m_entry_notifications.front());
|
||||
m_entry_notifications.pop();
|
||||
|
||||
if (!item.value) continue;
|
||||
StringRef name(item.name);
|
||||
|
||||
if (item.only) {
|
||||
// Don't hold mutex during callback execution!
|
||||
lock.unlock();
|
||||
item.only(0, name, item.value, item.flags);
|
||||
lock.lock();
|
||||
continue;
|
||||
}
|
||||
|
||||
// Use index because iterator might get invalidated.
|
||||
for (std::size_t i = 0; i < m_entry_listeners.size(); ++i) {
|
||||
if (!m_entry_listeners[i]) continue; // removed
|
||||
|
||||
// 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 = m_entry_listeners[i].flags;
|
||||
unsigned int flags = item.flags;
|
||||
unsigned int assign_both = NT_NOTIFY_UPDATE | NT_NOTIFY_FLAGS;
|
||||
if ((flags & assign_both) == assign_both) {
|
||||
if ((listen_flags & assign_both) == 0) continue;
|
||||
listen_flags &= ~assign_both;
|
||||
flags &= ~assign_both;
|
||||
}
|
||||
if ((flags & ~listen_flags) != 0) continue;
|
||||
|
||||
// must match prefix
|
||||
if (!name.startswith(m_entry_listeners[i].prefix)) continue;
|
||||
|
||||
// make a copy of the callback so we can safely release the mutex
|
||||
auto callback = m_entry_listeners[i].callback;
|
||||
|
||||
// Don't hold mutex during callback execution!
|
||||
lock.unlock();
|
||||
callback(i + 1, name, item.value, item.flags);
|
||||
lock.lock();
|
||||
}
|
||||
}
|
||||
|
||||
// Connection notifications
|
||||
while (!m_conn_notifications.empty()) {
|
||||
if (!m_active) goto done;
|
||||
auto item = std::move(m_conn_notifications.front());
|
||||
m_conn_notifications.pop();
|
||||
|
||||
if (item.only) {
|
||||
// Don't hold mutex during callback execution!
|
||||
lock.unlock();
|
||||
item.only(0, item.connected, item.conn_info);
|
||||
lock.lock();
|
||||
continue;
|
||||
}
|
||||
|
||||
// Use index because iterator might get invalidated.
|
||||
for (std::size_t i = 0; i < m_conn_listeners.size(); ++i) {
|
||||
if (!m_conn_listeners[i]) continue; // removed
|
||||
auto callback = m_conn_listeners[i];
|
||||
// Don't hold mutex during callback execution!
|
||||
lock.unlock();
|
||||
callback(i + 1, item.connected, item.conn_info);
|
||||
lock.lock();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
done:
|
||||
if (m_on_exit) m_on_exit();
|
||||
}
|
||||
|
||||
unsigned int Notifier::AddEntryListener(StringRef prefix,
|
||||
EntryListenerCallback callback,
|
||||
unsigned int flags) {
|
||||
Start();
|
||||
auto thr = m_owner.GetThread();
|
||||
if ((flags & NT_NOTIFY_LOCAL) != 0) m_local_notifiers = true;
|
||||
return thr->m_entry_listeners.emplace_back(prefix, callback, flags);
|
||||
}
|
||||
|
||||
void Notifier::RemoveEntryListener(unsigned int entry_listener_uid) {
|
||||
auto thr = m_owner.GetThread();
|
||||
if (!thr) return;
|
||||
thr->m_entry_listeners.erase(entry_listener_uid);
|
||||
}
|
||||
|
||||
void Notifier::NotifyEntry(StringRef name, std::shared_ptr<Value> value,
|
||||
unsigned int flags, EntryListenerCallback only) {
|
||||
// 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;
|
||||
auto thr = m_owner.GetThread();
|
||||
if (!thr) return;
|
||||
thr->m_entry_notifications.emplace(name, value, flags, only);
|
||||
thr->m_cond.notify_one();
|
||||
}
|
||||
|
||||
unsigned int Notifier::AddConnectionListener(
|
||||
ConnectionListenerCallback callback) {
|
||||
Start();
|
||||
auto thr = m_owner.GetThread();
|
||||
return thr->m_conn_listeners.emplace_back(callback);
|
||||
}
|
||||
|
||||
void Notifier::RemoveConnectionListener(unsigned int conn_listener_uid) {
|
||||
auto thr = m_owner.GetThread();
|
||||
if (!thr) return;
|
||||
thr->m_conn_listeners.erase(conn_listener_uid);
|
||||
}
|
||||
|
||||
void Notifier::NotifyConnection(bool connected, const ConnectionInfo& conn_info,
|
||||
ConnectionListenerCallback only) {
|
||||
auto thr = m_owner.GetThread();
|
||||
if (!thr) return;
|
||||
thr->m_conn_notifications.emplace(connected, conn_info, only);
|
||||
thr->m_cond.notify_one();
|
||||
}
|
||||
69
src/main/native/cpp/Notifier.h
Normal file
69
src/main/native/cpp/Notifier.h
Normal file
@@ -0,0 +1,69 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) FIRST 2015. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#ifndef NT_NOTIFIER_H_
|
||||
#define NT_NOTIFIER_H_
|
||||
|
||||
#include <functional>
|
||||
|
||||
#include "support/atomic_static.h"
|
||||
#include "support/SafeThread.h"
|
||||
#include "ntcore_cpp.h"
|
||||
|
||||
namespace nt {
|
||||
|
||||
class Notifier {
|
||||
friend class NotifierTest;
|
||||
|
||||
public:
|
||||
static Notifier& GetInstance() {
|
||||
ATOMIC_STATIC(Notifier, instance);
|
||||
return instance;
|
||||
}
|
||||
~Notifier();
|
||||
|
||||
void Start();
|
||||
void Stop();
|
||||
|
||||
bool local_notifiers() const { return m_local_notifiers; }
|
||||
static bool destroyed() { return s_destroyed; }
|
||||
|
||||
void SetOnStart(std::function<void()> on_start) { m_on_start = on_start; }
|
||||
void SetOnExit(std::function<void()> on_exit) { m_on_exit = on_exit; }
|
||||
|
||||
unsigned int AddEntryListener(llvm::StringRef prefix,
|
||||
EntryListenerCallback callback,
|
||||
unsigned int flags);
|
||||
void RemoveEntryListener(unsigned int entry_listener_uid);
|
||||
|
||||
void NotifyEntry(StringRef name, std::shared_ptr<Value> value,
|
||||
unsigned int flags, EntryListenerCallback only = nullptr);
|
||||
|
||||
unsigned int AddConnectionListener(ConnectionListenerCallback callback);
|
||||
void RemoveConnectionListener(unsigned int conn_listener_uid);
|
||||
|
||||
void NotifyConnection(bool connected, const ConnectionInfo& conn_info,
|
||||
ConnectionListenerCallback only = nullptr);
|
||||
|
||||
private:
|
||||
Notifier();
|
||||
|
||||
class Thread;
|
||||
wpi::SafeThreadOwner<Thread> m_owner;
|
||||
|
||||
std::atomic_bool m_local_notifiers;
|
||||
|
||||
std::function<void()> m_on_start;
|
||||
std::function<void()> m_on_exit;
|
||||
|
||||
ATOMIC_STATIC_DECL(Notifier)
|
||||
static bool s_destroyed;
|
||||
};
|
||||
|
||||
} // namespace nt
|
||||
|
||||
#endif // NT_NOTIFIER_H_
|
||||
151
src/main/native/cpp/RpcServer.cpp
Normal file
151
src/main/native/cpp/RpcServer.cpp
Normal file
@@ -0,0 +1,151 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) FIRST 2015. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#include "RpcServer.h"
|
||||
|
||||
#include <queue>
|
||||
|
||||
#include "Log.h"
|
||||
|
||||
using namespace nt;
|
||||
|
||||
ATOMIC_STATIC_INIT(RpcServer)
|
||||
|
||||
class RpcServer::Thread : public wpi::SafeThread {
|
||||
public:
|
||||
Thread(std::function<void()> on_start, std::function<void()> on_exit)
|
||||
: m_on_start(on_start), m_on_exit(on_exit) {}
|
||||
|
||||
void Main();
|
||||
|
||||
std::queue<RpcCall> m_call_queue;
|
||||
|
||||
std::function<void()> m_on_start;
|
||||
std::function<void()> m_on_exit;
|
||||
};
|
||||
|
||||
RpcServer::RpcServer() { m_terminating = false; }
|
||||
|
||||
RpcServer::~RpcServer() {
|
||||
Logger::GetInstance().SetLogger(nullptr);
|
||||
m_terminating = true;
|
||||
m_poll_cond.notify_all();
|
||||
}
|
||||
|
||||
void RpcServer::Start() {
|
||||
auto thr = m_owner.GetThread();
|
||||
if (!thr) m_owner.Start(new Thread(m_on_start, m_on_exit));
|
||||
}
|
||||
|
||||
void RpcServer::Stop() { m_owner.Stop(); }
|
||||
|
||||
void RpcServer::ProcessRpc(StringRef name, std::shared_ptr<Message> msg,
|
||||
RpcCallback func, unsigned int conn_id,
|
||||
SendMsgFunc send_response,
|
||||
const ConnectionInfo& conn_info) {
|
||||
if (func) {
|
||||
auto thr = m_owner.GetThread();
|
||||
if (!thr) return;
|
||||
thr->m_call_queue.emplace(name, msg, func, conn_id, send_response,
|
||||
conn_info);
|
||||
thr->m_cond.notify_one();
|
||||
} else {
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
m_poll_queue.emplace(name, msg, func, conn_id, send_response, conn_info);
|
||||
m_poll_cond.notify_one();
|
||||
}
|
||||
}
|
||||
|
||||
bool RpcServer::PollRpc(bool blocking, RpcCallInfo* call_info) {
|
||||
return PollRpc(blocking, kTimeout_Indefinite, call_info);
|
||||
}
|
||||
|
||||
bool RpcServer::PollRpc(bool blocking, double time_out,
|
||||
RpcCallInfo* call_info) {
|
||||
std::unique_lock<std::mutex> lock(m_mutex);
|
||||
#if defined(_MSC_VER) && _MSC_VER < 1900
|
||||
auto timeout_time = std::chrono::steady_clock::now() +
|
||||
std::chrono::duration<int64_t, std::nano>(
|
||||
static_cast<int64_t>(time_out * 1e9));
|
||||
#else
|
||||
auto timeout_time = std::chrono::steady_clock::now() +
|
||||
std::chrono::duration<double>(time_out);
|
||||
#endif
|
||||
while (m_poll_queue.empty()) {
|
||||
if (!blocking || m_terminating) return false;
|
||||
if (time_out < 0) {
|
||||
m_poll_cond.wait(lock);
|
||||
} else {
|
||||
auto timed_out = m_poll_cond.wait_until(lock, timeout_time);
|
||||
if (timed_out == std::cv_status::timeout) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (m_terminating) return false;
|
||||
}
|
||||
|
||||
auto& item = m_poll_queue.front();
|
||||
unsigned int call_uid;
|
||||
// do not include conn id if the result came from the server
|
||||
if (item.conn_id != 0xffff)
|
||||
call_uid = (item.conn_id << 16) | item.msg->seq_num_uid();
|
||||
else
|
||||
call_uid = item.msg->seq_num_uid();
|
||||
call_info->rpc_id = item.msg->id();
|
||||
call_info->call_uid = call_uid;
|
||||
call_info->name = std::move(item.name);
|
||||
call_info->params = item.msg->str();
|
||||
m_response_map.insert(std::make_pair(std::make_pair(item.msg->id(), call_uid),
|
||||
item.send_response));
|
||||
m_poll_queue.pop();
|
||||
return true;
|
||||
}
|
||||
|
||||
void RpcServer::PostRpcResponse(unsigned int rpc_id, unsigned int call_uid,
|
||||
llvm::StringRef result) {
|
||||
auto i = m_response_map.find(std::make_pair(rpc_id, call_uid));
|
||||
if (i == m_response_map.end()) {
|
||||
WARNING("posting RPC response to nonexistent call (or duplicate response)");
|
||||
return;
|
||||
}
|
||||
(i->getSecond())(Message::RpcResponse(rpc_id, call_uid, result));
|
||||
m_response_map.erase(i);
|
||||
}
|
||||
|
||||
void RpcServer::Thread::Main() {
|
||||
if (m_on_start) m_on_start();
|
||||
|
||||
std::unique_lock<std::mutex> lock(m_mutex);
|
||||
std::string tmp;
|
||||
while (m_active) {
|
||||
while (m_call_queue.empty()) {
|
||||
m_cond.wait(lock);
|
||||
if (!m_active) goto done;
|
||||
}
|
||||
|
||||
while (!m_call_queue.empty()) {
|
||||
if (!m_active) goto done;
|
||||
auto item = std::move(m_call_queue.front());
|
||||
m_call_queue.pop();
|
||||
|
||||
DEBUG4("rpc calling " << item.name);
|
||||
|
||||
if (item.name.empty() || !item.msg || !item.func || !item.send_response)
|
||||
continue;
|
||||
|
||||
// Don't hold mutex during callback execution!
|
||||
lock.unlock();
|
||||
auto result = item.func(item.name, item.msg->str(), item.conn_info);
|
||||
item.send_response(Message::RpcResponse(item.msg->id(),
|
||||
item.msg->seq_num_uid(), result));
|
||||
lock.lock();
|
||||
}
|
||||
}
|
||||
|
||||
done:
|
||||
if (m_on_exit) m_on_exit();
|
||||
}
|
||||
95
src/main/native/cpp/RpcServer.h
Normal file
95
src/main/native/cpp/RpcServer.h
Normal file
@@ -0,0 +1,95 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) FIRST 2015. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#ifndef NT_RPCSERVER_H_
|
||||
#define NT_RPCSERVER_H_
|
||||
|
||||
#include <atomic>
|
||||
#include <condition_variable>
|
||||
#include <mutex>
|
||||
#include <queue>
|
||||
#include <utility>
|
||||
|
||||
#include "llvm/DenseMap.h"
|
||||
#include "support/atomic_static.h"
|
||||
#include "support/SafeThread.h"
|
||||
#include "Message.h"
|
||||
#include "ntcore_cpp.h"
|
||||
|
||||
namespace nt {
|
||||
|
||||
class RpcServer {
|
||||
friend class RpcServerTest;
|
||||
|
||||
public:
|
||||
static RpcServer& GetInstance() {
|
||||
ATOMIC_STATIC(RpcServer, instance);
|
||||
return instance;
|
||||
}
|
||||
~RpcServer();
|
||||
|
||||
typedef std::function<void(std::shared_ptr<Message>)> SendMsgFunc;
|
||||
|
||||
void Start();
|
||||
void Stop();
|
||||
|
||||
void SetOnStart(std::function<void()> on_start) { m_on_start = on_start; }
|
||||
void SetOnExit(std::function<void()> on_exit) { m_on_exit = on_exit; }
|
||||
|
||||
void ProcessRpc(StringRef name, std::shared_ptr<Message> msg,
|
||||
RpcCallback func, unsigned int conn_id,
|
||||
SendMsgFunc send_response, const ConnectionInfo& conn_info);
|
||||
|
||||
bool PollRpc(bool blocking, RpcCallInfo* call_info);
|
||||
bool PollRpc(bool blocking, double time_out, RpcCallInfo* call_info);
|
||||
void PostRpcResponse(unsigned int rpc_id, unsigned int call_uid,
|
||||
llvm::StringRef result);
|
||||
|
||||
private:
|
||||
RpcServer();
|
||||
|
||||
class Thread;
|
||||
wpi::SafeThreadOwner<Thread> m_owner;
|
||||
|
||||
struct RpcCall {
|
||||
RpcCall(StringRef name_, std::shared_ptr<Message> msg_, RpcCallback func_,
|
||||
unsigned int conn_id_, SendMsgFunc send_response_,
|
||||
const ConnectionInfo conn_info_)
|
||||
: name(name_),
|
||||
msg(msg_),
|
||||
func(func_),
|
||||
conn_id(conn_id_),
|
||||
send_response(send_response_),
|
||||
conn_info(conn_info_) {}
|
||||
|
||||
std::string name;
|
||||
std::shared_ptr<Message> msg;
|
||||
RpcCallback func;
|
||||
unsigned int conn_id;
|
||||
SendMsgFunc send_response;
|
||||
ConnectionInfo conn_info;
|
||||
};
|
||||
|
||||
std::mutex m_mutex;
|
||||
|
||||
std::queue<RpcCall> m_poll_queue;
|
||||
llvm::DenseMap<std::pair<unsigned int, unsigned int>, SendMsgFunc>
|
||||
m_response_map;
|
||||
|
||||
std::condition_variable m_poll_cond;
|
||||
|
||||
std::atomic_bool m_terminating;
|
||||
|
||||
std::function<void()> m_on_start;
|
||||
std::function<void()> m_on_exit;
|
||||
|
||||
ATOMIC_STATIC_DECL(RpcServer)
|
||||
};
|
||||
|
||||
} // namespace nt
|
||||
|
||||
#endif // NT_RPCSERVER_H_
|
||||
30
src/main/native/cpp/SequenceNumber.cpp
Normal file
30
src/main/native/cpp/SequenceNumber.cpp
Normal file
@@ -0,0 +1,30 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) FIRST 2015. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#include "SequenceNumber.h"
|
||||
|
||||
namespace nt {
|
||||
|
||||
bool operator<(const SequenceNumber& lhs, const SequenceNumber& rhs) {
|
||||
if (lhs.m_value < rhs.m_value)
|
||||
return (rhs.m_value - lhs.m_value) < (1u << 15);
|
||||
else if (lhs.m_value > rhs.m_value)
|
||||
return (lhs.m_value - rhs.m_value) > (1u << 15);
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
bool operator>(const SequenceNumber& lhs, const SequenceNumber& rhs) {
|
||||
if (lhs.m_value < rhs.m_value)
|
||||
return (rhs.m_value - lhs.m_value) > (1u << 15);
|
||||
else if (lhs.m_value > rhs.m_value)
|
||||
return (lhs.m_value - rhs.m_value) < (1u << 15);
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace nt
|
||||
63
src/main/native/cpp/SequenceNumber.h
Normal file
63
src/main/native/cpp/SequenceNumber.h
Normal file
@@ -0,0 +1,63 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) FIRST 2015. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#ifndef NT_SEQNUM_H_
|
||||
#define NT_SEQNUM_H_
|
||||
|
||||
namespace nt {
|
||||
|
||||
/* A sequence number per RFC 1982 */
|
||||
class SequenceNumber {
|
||||
public:
|
||||
SequenceNumber() : m_value(0) {}
|
||||
explicit SequenceNumber(unsigned int value) : m_value(value) {}
|
||||
unsigned int value() const { return m_value; }
|
||||
|
||||
SequenceNumber& operator++() {
|
||||
++m_value;
|
||||
if (m_value > 0xffff) m_value = 0;
|
||||
return *this;
|
||||
}
|
||||
SequenceNumber operator++(int) {
|
||||
SequenceNumber tmp(*this);
|
||||
operator++();
|
||||
return tmp;
|
||||
}
|
||||
|
||||
friend bool operator<(const SequenceNumber& lhs, const SequenceNumber& rhs);
|
||||
friend bool operator>(const SequenceNumber& lhs, const SequenceNumber& rhs);
|
||||
friend bool operator<=(const SequenceNumber& lhs, const SequenceNumber& rhs);
|
||||
friend bool operator>=(const SequenceNumber& lhs, const SequenceNumber& rhs);
|
||||
friend bool operator==(const SequenceNumber& lhs, const SequenceNumber& rhs);
|
||||
friend bool operator!=(const SequenceNumber& lhs, const SequenceNumber& rhs);
|
||||
|
||||
private:
|
||||
unsigned int m_value;
|
||||
};
|
||||
|
||||
bool operator<(const SequenceNumber& lhs, const SequenceNumber& rhs);
|
||||
bool operator>(const SequenceNumber& lhs, const SequenceNumber& rhs);
|
||||
|
||||
inline bool operator<=(const SequenceNumber& lhs, const SequenceNumber& rhs) {
|
||||
return lhs == rhs || lhs < rhs;
|
||||
}
|
||||
|
||||
inline bool operator>=(const SequenceNumber& lhs, const SequenceNumber& rhs) {
|
||||
return lhs == rhs || lhs > rhs;
|
||||
}
|
||||
|
||||
inline bool operator==(const SequenceNumber& lhs, const SequenceNumber& rhs) {
|
||||
return lhs.m_value == rhs.m_value;
|
||||
}
|
||||
|
||||
inline bool operator!=(const SequenceNumber& lhs, const SequenceNumber& rhs) {
|
||||
return lhs.m_value != rhs.m_value;
|
||||
}
|
||||
|
||||
} // namespace nt
|
||||
|
||||
#endif // NT_SEQNUM_H_
|
||||
1491
src/main/native/cpp/Storage.cpp
Normal file
1491
src/main/native/cpp/Storage.cpp
Normal file
File diff suppressed because it is too large
Load Diff
183
src/main/native/cpp/Storage.h
Normal file
183
src/main/native/cpp/Storage.h
Normal file
@@ -0,0 +1,183 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) FIRST 2015. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#ifndef NT_STORAGE_H_
|
||||
#define NT_STORAGE_H_
|
||||
|
||||
#include <atomic>
|
||||
#include <cstddef>
|
||||
#include <fstream>
|
||||
#include <functional>
|
||||
#include <iosfwd>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <vector>
|
||||
|
||||
#include "llvm/DenseMap.h"
|
||||
#include "llvm/SmallSet.h"
|
||||
#include "llvm/StringMap.h"
|
||||
#include "support/atomic_static.h"
|
||||
#include "Message.h"
|
||||
#include "Notifier.h"
|
||||
#include "ntcore_cpp.h"
|
||||
#include "RpcServer.h"
|
||||
#include "SequenceNumber.h"
|
||||
|
||||
namespace nt {
|
||||
|
||||
class NetworkConnection;
|
||||
class StorageTest;
|
||||
|
||||
class Storage {
|
||||
friend class StorageTest;
|
||||
|
||||
public:
|
||||
static Storage& GetInstance() {
|
||||
ATOMIC_STATIC(Storage, instance);
|
||||
return instance;
|
||||
}
|
||||
~Storage();
|
||||
|
||||
// Accessors required by Dispatcher. A function pointer is used for
|
||||
// generation of outgoing messages to break a dependency loop between
|
||||
// Storage and Dispatcher; in operation this is always set to
|
||||
// Dispatcher::QueueOutgoing.
|
||||
typedef std::function<void(std::shared_ptr<Message> msg,
|
||||
NetworkConnection* only,
|
||||
NetworkConnection* except)>
|
||||
QueueOutgoingFunc;
|
||||
void SetOutgoing(QueueOutgoingFunc queue_outgoing, bool server);
|
||||
void ClearOutgoing();
|
||||
|
||||
// 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 GetEntryType(unsigned int id) const;
|
||||
|
||||
void ProcessIncoming(std::shared_ptr<Message> msg, NetworkConnection* conn,
|
||||
std::weak_ptr<NetworkConnection> conn_weak);
|
||||
void GetInitialAssignments(NetworkConnection& conn,
|
||||
std::vector<std::shared_ptr<Message>>* msgs);
|
||||
void ApplyInitialAssignments(NetworkConnection& conn,
|
||||
llvm::ArrayRef<std::shared_ptr<Message>> msgs,
|
||||
bool new_server,
|
||||
std::vector<std::shared_ptr<Message>>* out_msgs);
|
||||
|
||||
// User functions. These are the actual implementations of the corresponding
|
||||
// user API functions in ntcore_cpp.
|
||||
std::shared_ptr<Value> GetEntryValue(StringRef name) const;
|
||||
bool SetDefaultEntryValue(StringRef name, std::shared_ptr<Value> value);
|
||||
bool SetEntryValue(StringRef name, std::shared_ptr<Value> value);
|
||||
void SetEntryTypeValue(StringRef name, std::shared_ptr<Value> value);
|
||||
void SetEntryFlags(StringRef name, unsigned int flags);
|
||||
unsigned int GetEntryFlags(StringRef name) const;
|
||||
void DeleteEntry(StringRef name);
|
||||
void DeleteAllEntries();
|
||||
std::vector<EntryInfo> GetEntryInfo(StringRef prefix, unsigned int types);
|
||||
void NotifyEntries(StringRef prefix,
|
||||
EntryListenerCallback only = nullptr) const;
|
||||
|
||||
// Filename-based save/load functions. Used both by periodic saves and
|
||||
// accessible directly via the user API.
|
||||
const char* SavePersistent(StringRef filename, bool periodic) const;
|
||||
const char* LoadPersistent(
|
||||
StringRef filename,
|
||||
std::function<void(std::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(std::ostream& os, bool periodic) const;
|
||||
bool LoadPersistent(
|
||||
std::istream& is,
|
||||
std::function<void(std::size_t line, const char* msg)> warn);
|
||||
|
||||
// RPC configuration needs to come through here as RPC definitions are
|
||||
// actually special Storage value types.
|
||||
void CreateRpc(StringRef name, StringRef def, RpcCallback callback);
|
||||
void CreatePolledRpc(StringRef name, StringRef def);
|
||||
|
||||
unsigned int CallRpc(StringRef name, StringRef params);
|
||||
bool GetRpcResult(bool blocking, unsigned int call_uid, std::string* result);
|
||||
bool GetRpcResult(bool blocking, unsigned int call_uid, double time_out,
|
||||
std::string* result);
|
||||
void CancelBlockingRpcResult(unsigned int call_uid);
|
||||
|
||||
private:
|
||||
Storage();
|
||||
Storage(Notifier& notifier, RpcServer& rpcserver);
|
||||
Storage(const Storage&) = delete;
|
||||
Storage& operator=(const Storage&) = delete;
|
||||
|
||||
// Data for each table entry.
|
||||
struct Entry {
|
||||
Entry(llvm::StringRef name_)
|
||||
: name(name_), flags(0), id(0xffff), rpc_call_uid(0) {}
|
||||
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;
|
||||
|
||||
// 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;
|
||||
|
||||
// Sequence number for update resolution.
|
||||
SequenceNumber seq_num;
|
||||
|
||||
// RPC callback function. Null if either not an RPC or if the RPC is
|
||||
// polled.
|
||||
RpcCallback rpc_callback;
|
||||
|
||||
// Last UID used when calling this RPC (primarily for client use). This
|
||||
// is incremented for each call.
|
||||
unsigned int rpc_call_uid;
|
||||
};
|
||||
|
||||
typedef llvm::StringMap<std::unique_ptr<Entry>> EntriesMap;
|
||||
typedef std::vector<Entry*> IdMap;
|
||||
typedef llvm::DenseMap<std::pair<unsigned int, unsigned int>, std::string>
|
||||
RpcResultMap;
|
||||
typedef llvm::SmallSet<unsigned int, 12> RpcBlockingCallSet;
|
||||
|
||||
mutable std::mutex m_mutex;
|
||||
EntriesMap m_entries;
|
||||
IdMap m_idmap;
|
||||
RpcResultMap m_rpc_results;
|
||||
RpcBlockingCallSet m_rpc_blocking_calls;
|
||||
// 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;
|
||||
std::condition_variable m_rpc_results_cond;
|
||||
|
||||
// configured by dispatcher at startup
|
||||
QueueOutgoingFunc m_queue_outgoing;
|
||||
bool m_server = true;
|
||||
|
||||
// references to singletons (we don't grab them directly for testing purposes)
|
||||
Notifier& m_notifier;
|
||||
RpcServer& m_rpc_server;
|
||||
|
||||
bool GetPersistentEntries(
|
||||
bool periodic,
|
||||
std::vector<std::pair<std::string, std::shared_ptr<Value>>>* entries)
|
||||
const;
|
||||
void DeleteAllEntriesImpl();
|
||||
|
||||
ATOMIC_STATIC_DECL(Storage)
|
||||
};
|
||||
|
||||
} // namespace nt
|
||||
|
||||
#endif // NT_STORAGE_H_
|
||||
210
src/main/native/cpp/Value.cpp
Normal file
210
src/main/native/cpp/Value.cpp
Normal file
@@ -0,0 +1,210 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) FIRST 2015. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#include "nt_Value.h"
|
||||
#include "Value_internal.h"
|
||||
#include "support/timestamp.h"
|
||||
|
||||
using namespace nt;
|
||||
|
||||
Value::Value() {
|
||||
m_val.type = NT_UNASSIGNED;
|
||||
m_val.last_change = wpi::Now();
|
||||
}
|
||||
|
||||
Value::Value(NT_Type type, const private_init&) {
|
||||
m_val.type = type;
|
||||
m_val.last_change = wpi::Now();
|
||||
if (m_val.type == NT_BOOLEAN_ARRAY)
|
||||
m_val.data.arr_boolean.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)
|
||||
m_val.data.arr_string.arr = nullptr;
|
||||
}
|
||||
|
||||
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(llvm::ArrayRef<int> value) {
|
||||
auto val = std::make_shared<Value>(NT_BOOLEAN_ARRAY, 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);
|
||||
return val;
|
||||
}
|
||||
|
||||
std::shared_ptr<Value> Value::MakeDoubleArray(llvm::ArrayRef<double> value) {
|
||||
auto val = std::make_shared<Value>(NT_DOUBLE_ARRAY, 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);
|
||||
return val;
|
||||
}
|
||||
|
||||
std::shared_ptr<Value> Value::MakeStringArray(
|
||||
llvm::ArrayRef<std::string> value) {
|
||||
auto val = std::make_shared<Value>(NT_STRING_ARRAY, private_init());
|
||||
val->m_string_array = value;
|
||||
// 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 (std::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();
|
||||
}
|
||||
return val;
|
||||
}
|
||||
|
||||
std::shared_ptr<Value> Value::MakeStringArray(
|
||||
std::vector<std::string>&& value) {
|
||||
auto val = std::make_shared<Value>(NT_STRING_ARRAY, 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 (std::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();
|
||||
}
|
||||
return val;
|
||||
}
|
||||
|
||||
void nt::ConvertToC(const Value& in, NT_Value* out) {
|
||||
out->type = NT_UNASSIGNED;
|
||||
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);
|
||||
break;
|
||||
case NT_BOOLEAN_ARRAY: {
|
||||
auto v = in.GetBooleanArray();
|
||||
out->data.arr_boolean.arr =
|
||||
static_cast<int*>(std::malloc(v.size() * sizeof(int)));
|
||||
out->data.arr_boolean.size = v.size();
|
||||
std::copy(v.begin(), v.end(), out->data.arr_boolean.arr);
|
||||
break;
|
||||
}
|
||||
case NT_DOUBLE_ARRAY: {
|
||||
auto v = in.GetDoubleArray();
|
||||
out->data.arr_double.arr =
|
||||
static_cast<double*>(std::malloc(v.size() * sizeof(double)));
|
||||
out->data.arr_double.size = v.size();
|
||||
std::copy(v.begin(), v.end(), out->data.arr_double.arr);
|
||||
break;
|
||||
}
|
||||
case NT_STRING_ARRAY: {
|
||||
auto v = in.GetStringArray();
|
||||
out->data.arr_string.arr =
|
||||
static_cast<NT_String*>(std::malloc(v.size() * sizeof(NT_String)));
|
||||
for (size_t i = 0; i < v.size(); ++i)
|
||||
ConvertToC(v[i], &out->data.arr_string.arr[i]);
|
||||
out->data.arr_string.size = v.size();
|
||||
break;
|
||||
}
|
||||
default:
|
||||
// assert(false && "unknown value type");
|
||||
return;
|
||||
}
|
||||
out->type = in.type();
|
||||
}
|
||||
|
||||
void nt::ConvertToC(llvm::StringRef in, NT_String* out) {
|
||||
out->len = in.size();
|
||||
out->str = static_cast<char*>(std::malloc(in.size() + 1));
|
||||
std::memcpy(out->str, in.data(), in.size());
|
||||
out->str[in.size()] = '\0';
|
||||
}
|
||||
|
||||
std::shared_ptr<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);
|
||||
case NT_DOUBLE:
|
||||
return Value::MakeDouble(value.data.v_double);
|
||||
case NT_STRING:
|
||||
return Value::MakeString(ConvertFromC(value.data.v_string));
|
||||
case NT_RAW:
|
||||
return Value::MakeRaw(ConvertFromC(value.data.v_raw));
|
||||
case NT_RPC:
|
||||
return Value::MakeRpc(ConvertFromC(value.data.v_raw));
|
||||
case NT_BOOLEAN_ARRAY:
|
||||
return Value::MakeBooleanArray(llvm::ArrayRef<int>(
|
||||
value.data.arr_boolean.arr, value.data.arr_boolean.size));
|
||||
case NT_DOUBLE_ARRAY:
|
||||
return Value::MakeDoubleArray(llvm::ArrayRef<double>(
|
||||
value.data.arr_double.arr, value.data.arr_double.size));
|
||||
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.push_back(ConvertFromC(value.data.arr_string.arr[i]));
|
||||
return Value::MakeStringArray(std::move(v));
|
||||
}
|
||||
default:
|
||||
// assert(false && "unknown value type");
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
bool nt::operator==(const Value& lhs, const Value& rhs) {
|
||||
if (lhs.type() != rhs.type()) return false;
|
||||
switch (lhs.type()) {
|
||||
case NT_UNASSIGNED:
|
||||
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_DOUBLE:
|
||||
return lhs.m_val.data.v_double == rhs.m_val.data.v_double;
|
||||
case NT_STRING:
|
||||
case NT_RAW:
|
||||
case NT_RPC:
|
||||
return lhs.m_string == rhs.m_string;
|
||||
case NT_BOOLEAN_ARRAY:
|
||||
if (lhs.m_val.data.arr_boolean.size != rhs.m_val.data.arr_boolean.size)
|
||||
return false;
|
||||
return std::memcmp(lhs.m_val.data.arr_boolean.arr,
|
||||
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_DOUBLE_ARRAY:
|
||||
if (lhs.m_val.data.arr_double.size != rhs.m_val.data.arr_double.size)
|
||||
return false;
|
||||
return std::memcmp(lhs.m_val.data.arr_double.arr,
|
||||
rhs.m_val.data.arr_double.arr,
|
||||
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;
|
||||
default:
|
||||
// assert(false && "unknown value type");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
30
src/main/native/cpp/Value_internal.h
Normal file
30
src/main/native/cpp/Value_internal.h
Normal file
@@ -0,0 +1,30 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) FIRST 2015. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#ifndef NT_VALUE_INTERNAL_H_
|
||||
#define NT_VALUE_INTERNAL_H_
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include "llvm/StringRef.h"
|
||||
#include "ntcore_c.h"
|
||||
|
||||
namespace nt {
|
||||
|
||||
class Value;
|
||||
|
||||
void ConvertToC(const Value& in, NT_Value* out);
|
||||
std::shared_ptr<Value> ConvertFromC(const NT_Value& value);
|
||||
void ConvertToC(llvm::StringRef in, NT_String* out);
|
||||
inline llvm::StringRef ConvertFromC(const NT_String& str) {
|
||||
return llvm::StringRef(str.str, str.len);
|
||||
}
|
||||
|
||||
} // namespace nt
|
||||
|
||||
#endif // NT_VALUE_INTERNAL_H_
|
||||
205
src/main/native/cpp/WireDecoder.cpp
Normal file
205
src/main/native/cpp/WireDecoder.cpp
Normal file
@@ -0,0 +1,205 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) FIRST 2015. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#include "WireDecoder.h"
|
||||
|
||||
#include <cassert>
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
|
||||
#include "llvm/MathExtras.h"
|
||||
#include "support/leb128.h"
|
||||
|
||||
using namespace nt;
|
||||
|
||||
static double ReadDouble(const char*& buf) {
|
||||
// Fast but non-portable!
|
||||
std::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 llvm::BitsToDouble(val);
|
||||
}
|
||||
|
||||
WireDecoder::WireDecoder(wpi::raw_istream& is, unsigned int proto_rev)
|
||||
: m_is(is) {
|
||||
// Start with a 1K temporary buffer. Use malloc instead of new so we can
|
||||
// realloc.
|
||||
m_allocated = 1024;
|
||||
m_buf = static_cast<char*>(std::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(std::size_t len) {
|
||||
// Double current buffer size until we have enough space.
|
||||
if (m_allocated >= len) return;
|
||||
std::size_t newlen = m_allocated * 2;
|
||||
while (newlen < len) newlen *= 2;
|
||||
m_buf = static_cast<char*>(std::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 {
|
||||
unsigned long v;
|
||||
if (!ReadUleb128(&v)) return false;
|
||||
len = v;
|
||||
}
|
||||
const char* buf;
|
||||
if (!Read(&buf, len)) return false;
|
||||
*str = llvm::StringRef(buf, len);
|
||||
return true;
|
||||
}
|
||||
147
src/main/native/cpp/WireDecoder.h
Normal file
147
src/main/native/cpp/WireDecoder.h
Normal file
@@ -0,0 +1,147 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) FIRST 2015. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#ifndef NT_WIREDECODER_H_
|
||||
#define NT_WIREDECODER_H_
|
||||
|
||||
#include <cstddef>
|
||||
|
||||
#include "nt_Value.h"
|
||||
#include "support/leb128.h"
|
||||
#include "support/raw_istream.h"
|
||||
//#include "Log.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:
|
||||
explicit WireDecoder(wpi::raw_istream& is, unsigned int proto_rev);
|
||||
~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; }
|
||||
|
||||
/* 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, std::size_t len) {
|
||||
if (len > m_allocated) Realloc(len);
|
||||
*buf = m_buf;
|
||||
m_is.read(m_buf, len);
|
||||
#if 0
|
||||
nt::Logger& logger = nt::Logger::GetInstance();
|
||||
if (logger.min_level() <= NT_LOG_DEBUG4 && logger.HasLogger()) {
|
||||
std::ostringstream oss;
|
||||
oss << "read " << len << " bytes:" << std::hex;
|
||||
if (!rv)
|
||||
oss << "error";
|
||||
else {
|
||||
for (std::size_t i=0; i < len; ++i)
|
||||
oss << ' ' << (unsigned int)((*buf)[i]);
|
||||
}
|
||||
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(unsigned long* 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(unsigned long* 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(std::size_t len);
|
||||
|
||||
/* input stream */
|
||||
wpi::raw_istream& m_is;
|
||||
|
||||
/* temporary buffer */
|
||||
char* m_buf;
|
||||
|
||||
/* allocated size of temporary buffer */
|
||||
std::size_t m_allocated;
|
||||
};
|
||||
|
||||
} // namespace nt
|
||||
|
||||
#endif // NT_WIREDECODER_H_
|
||||
198
src/main/native/cpp/WireEncoder.cpp
Normal file
198
src/main/native/cpp/WireEncoder.cpp
Normal file
@@ -0,0 +1,198 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) FIRST 2015. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#include "WireEncoder.h"
|
||||
|
||||
#include <cassert>
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
|
||||
#include "llvm/MathExtras.h"
|
||||
#include "support/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.
|
||||
std::uint64_t v = llvm::DoubleToBits(val);
|
||||
m_data.append({(char)((v >> 56) & 0xff), (char)((v >> 48) & 0xff),
|
||||
(char)((v >> 40) & 0xff), (char)((v >> 32) & 0xff),
|
||||
(char)((v >> 24) & 0xff), (char)((v >> 16) & 0xff),
|
||||
(char)((v >> 8) & 0xff), (char)(v & 0xff)});
|
||||
}
|
||||
|
||||
void WireEncoder::WriteUleb128(unsigned long 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);
|
||||
}
|
||||
|
||||
std::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
|
||||
std::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
|
||||
std::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();
|
||||
std::size_t size = v.size();
|
||||
if (size > 0xff) size = 0xff; // size is only 1 byte, truncate
|
||||
std::size_t len = 1; // 1-byte size
|
||||
for (std::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();
|
||||
std::size_t size = v.size();
|
||||
if (size > 0xff) size = 0xff; // size is only 1 byte, truncate
|
||||
Write8(size);
|
||||
|
||||
for (std::size_t i = 0; i < size; ++i) Write8(v[i] ? 1 : 0);
|
||||
break;
|
||||
}
|
||||
case NT_DOUBLE_ARRAY: {
|
||||
auto v = value.GetDoubleArray();
|
||||
std::size_t size = v.size();
|
||||
if (size > 0xff) size = 0xff; // size is only 1 byte, truncate
|
||||
Write8(size);
|
||||
|
||||
for (std::size_t i = 0; i < size; ++i) WriteDouble(v[i]);
|
||||
break;
|
||||
}
|
||||
case NT_STRING_ARRAY: {
|
||||
auto v = value.GetStringArray();
|
||||
std::size_t size = v.size();
|
||||
if (size > 0xff) size = 0xff; // size is only 1 byte, truncate
|
||||
Write8(size);
|
||||
|
||||
for (std::size_t i = 0; i < size; ++i) WriteString(v[i]);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
m_error = "unrecognized type when writing value";
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
std::size_t WireEncoder::GetStringSize(llvm::StringRef str) const {
|
||||
if (m_proto_rev < 0x0300u) {
|
||||
std::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(llvm::StringRef str) {
|
||||
// length
|
||||
std::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);
|
||||
}
|
||||
103
src/main/native/cpp/WireEncoder.h
Normal file
103
src/main/native/cpp/WireEncoder.h
Normal file
@@ -0,0 +1,103 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) FIRST 2015. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#ifndef NT_WIREENCODER_H_
|
||||
#define NT_WIREENCODER_H_
|
||||
|
||||
#include <cassert>
|
||||
#include <cstddef>
|
||||
|
||||
#include "llvm/SmallVector.h"
|
||||
#include "llvm/StringRef.h"
|
||||
#include "nt_Value.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. */
|
||||
std::size_t size() const { return m_data.size(); }
|
||||
|
||||
llvm::StringRef ToStringRef() const {
|
||||
return llvm::StringRef(m_data.data(), m_data.size());
|
||||
}
|
||||
|
||||
/* Writes a single byte. */
|
||||
void Write8(unsigned int val) { m_data.push_back((char)(val & 0xff)); }
|
||||
|
||||
/* Writes a 16-bit word. */
|
||||
void Write16(unsigned int val) {
|
||||
m_data.append({(char)((val >> 8) & 0xff), (char)(val & 0xff)});
|
||||
}
|
||||
|
||||
/* Writes a 32-bit word. */
|
||||
void Write32(unsigned long val) {
|
||||
m_data.append({(char)((val >> 24) & 0xff), (char)((val >> 16) & 0xff),
|
||||
(char)((val >> 8) & 0xff), (char)(val & 0xff)});
|
||||
}
|
||||
|
||||
/* Writes a double. */
|
||||
void WriteDouble(double val);
|
||||
|
||||
/* Writes an ULEB128-encoded unsigned integer. */
|
||||
void WriteUleb128(unsigned long val);
|
||||
|
||||
void WriteType(NT_Type type);
|
||||
void WriteValue(const Value& value);
|
||||
void WriteString(llvm::StringRef str);
|
||||
|
||||
/* Utility function to get the written size of a value (without actually
|
||||
* writing it).
|
||||
*/
|
||||
std::size_t GetValueSize(const Value& value) const;
|
||||
|
||||
/* Utility function to get the written size of a string (without actually
|
||||
* writing it).
|
||||
*/
|
||||
std::size_t GetStringSize(llvm::StringRef 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:
|
||||
llvm::SmallVector<char, 256> m_data;
|
||||
};
|
||||
|
||||
} // namespace nt
|
||||
|
||||
#endif // NT_WIREENCODER_H_
|
||||
1749
src/main/native/cpp/jni/NetworkTablesJNI.cpp
Normal file
1749
src/main/native/cpp/jni/NetworkTablesJNI.cpp
Normal file
File diff suppressed because it is too large
Load Diff
582
src/main/native/cpp/networktables/NetworkTable.cpp
Normal file
582
src/main/native/cpp/networktables/NetworkTable.cpp
Normal file
@@ -0,0 +1,582 @@
|
||||
#include "networktables/NetworkTable.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "llvm/raw_ostream.h"
|
||||
#include "llvm/SmallString.h"
|
||||
#include "llvm/StringMap.h"
|
||||
#include "tables/ITableListener.h"
|
||||
#include "tables/TableKeyNotDefinedException.h"
|
||||
#include "ntcore.h"
|
||||
|
||||
using llvm::StringRef;
|
||||
|
||||
const char NetworkTable::PATH_SEPARATOR_CHAR = '/';
|
||||
std::string NetworkTable::s_persistent_filename = "networktables.ini";
|
||||
bool NetworkTable::s_client = false;
|
||||
bool NetworkTable::s_enable_ds = true;
|
||||
bool NetworkTable::s_running = false;
|
||||
unsigned int NetworkTable::s_port = NT_DEFAULT_PORT;
|
||||
|
||||
void NetworkTable::Initialize() {
|
||||
if (s_running) Shutdown();
|
||||
if (s_client) {
|
||||
nt::StartClient();
|
||||
if (s_enable_ds) nt::StartDSClient(s_port);
|
||||
} else
|
||||
nt::StartServer(s_persistent_filename, "", s_port);
|
||||
s_running = true;
|
||||
}
|
||||
|
||||
void NetworkTable::Shutdown() {
|
||||
if (!s_running) return;
|
||||
if (s_client) {
|
||||
nt::StopDSClient();
|
||||
nt::StopClient();
|
||||
} else
|
||||
nt::StopServer();
|
||||
s_running = false;
|
||||
}
|
||||
|
||||
void NetworkTable::SetClientMode() { s_client = true; }
|
||||
|
||||
void NetworkTable::SetServerMode() { s_client = false; }
|
||||
|
||||
void NetworkTable::SetTeam(int team) {
|
||||
std::pair<StringRef, unsigned int> servers[5];
|
||||
|
||||
// 10.te.am.2
|
||||
llvm::SmallString<32> fixed;
|
||||
{
|
||||
llvm::raw_svector_ostream oss{fixed};
|
||||
oss << "10." << static_cast<int>(team / 100) << '.'
|
||||
<< static_cast<int>(team % 100) << ".2";
|
||||
servers[0] = std::make_pair(oss.str(), s_port);
|
||||
}
|
||||
|
||||
// 172.22.11.2
|
||||
servers[1] = std::make_pair("172.22.11.2", s_port);
|
||||
|
||||
// roboRIO-<team>-FRC.local
|
||||
llvm::SmallString<32> mdns;
|
||||
{
|
||||
llvm::raw_svector_ostream oss{mdns};
|
||||
oss << "roboRIO-" << team << "-FRC.local";
|
||||
servers[2] = std::make_pair(oss.str(), s_port);
|
||||
}
|
||||
|
||||
// roboRIO-<team>-FRC.lan
|
||||
llvm::SmallString<32> mdns_lan;
|
||||
{
|
||||
llvm::raw_svector_ostream oss{mdns_lan};
|
||||
oss << "roboRIO-" << team << "-FRC.lan";
|
||||
servers[3] = std::make_pair(oss.str(), s_port);
|
||||
}
|
||||
|
||||
// roboRIO-<team>-FRC.frc-field.local
|
||||
llvm::SmallString<64> field_local;
|
||||
{
|
||||
llvm::raw_svector_ostream oss{field_local};
|
||||
oss << "roboRIO-" << team << "-FRC.frc-field.local";
|
||||
servers[4] = std::make_pair(oss.str(), s_port);
|
||||
}
|
||||
|
||||
nt::SetServer(servers);
|
||||
}
|
||||
|
||||
void NetworkTable::SetIPAddress(StringRef address) {
|
||||
llvm::SmallString<32> addr_copy{address};
|
||||
nt::SetServer(addr_copy.c_str(), s_port);
|
||||
|
||||
// Stop the DS client if we're explicitly connecting to localhost
|
||||
if (address == "localhost" || address == "127.0.0.1")
|
||||
nt::StopDSClient();
|
||||
else if (s_enable_ds)
|
||||
nt::StartDSClient(s_port);
|
||||
}
|
||||
|
||||
void NetworkTable::SetIPAddress(llvm::ArrayRef<std::string> addresses) {
|
||||
llvm::SmallVector<std::pair<StringRef, unsigned int>, 8> servers;
|
||||
for (const auto& ip_address : addresses)
|
||||
servers.emplace_back(std::make_pair(ip_address, s_port));
|
||||
nt::SetServer(servers);
|
||||
|
||||
// Stop the DS client if we're explicitly connecting to localhost
|
||||
if (!addresses.empty() &&
|
||||
(addresses[0] == "localhost" || addresses[0] == "127.0.0.1"))
|
||||
nt::StopDSClient();
|
||||
else if (s_enable_ds)
|
||||
nt::StartDSClient(s_port);
|
||||
}
|
||||
|
||||
void NetworkTable::SetPort(unsigned int port) { s_port = port; }
|
||||
|
||||
void NetworkTable::SetDSClientEnabled(bool enabled) {
|
||||
s_enable_ds = enabled;
|
||||
if (s_enable_ds)
|
||||
nt::StartDSClient(s_port);
|
||||
else
|
||||
nt::StopDSClient();
|
||||
}
|
||||
|
||||
void NetworkTable::SetPersistentFilename(StringRef filename) {
|
||||
s_persistent_filename = filename;
|
||||
}
|
||||
|
||||
void NetworkTable::SetNetworkIdentity(StringRef name) {
|
||||
nt::SetNetworkIdentity(name);
|
||||
}
|
||||
|
||||
void NetworkTable::GlobalDeleteAll() { nt::DeleteAllEntries(); }
|
||||
|
||||
void NetworkTable::Flush() { nt::Flush(); }
|
||||
|
||||
void NetworkTable::SetUpdateRate(double interval) {
|
||||
nt::SetUpdateRate(interval);
|
||||
}
|
||||
|
||||
const char* NetworkTable::SavePersistent(llvm::StringRef filename) {
|
||||
return nt::SavePersistent(filename);
|
||||
}
|
||||
|
||||
const char* NetworkTable::LoadPersistent(
|
||||
llvm::StringRef filename,
|
||||
std::function<void(size_t line, const char* msg)> warn) {
|
||||
return nt::LoadPersistent(filename, warn);
|
||||
}
|
||||
|
||||
std::shared_ptr<NetworkTable> NetworkTable::GetTable(StringRef key) {
|
||||
if (!s_running) Initialize();
|
||||
if (key.empty() || key[0] == PATH_SEPARATOR_CHAR) {
|
||||
return std::make_shared<NetworkTable>(key, private_init());
|
||||
} else {
|
||||
llvm::SmallString<128> path;
|
||||
path += PATH_SEPARATOR_CHAR;
|
||||
path += key;
|
||||
return std::make_shared<NetworkTable>(path, private_init());
|
||||
}
|
||||
}
|
||||
|
||||
NetworkTable::NetworkTable(StringRef path, const private_init&)
|
||||
: m_path(path) {}
|
||||
|
||||
NetworkTable::~NetworkTable() {
|
||||
for (auto& i : m_listeners) nt::RemoveEntryListener(i.second);
|
||||
}
|
||||
|
||||
void NetworkTable::AddTableListener(ITableListener* listener) {
|
||||
AddTableListenerEx(listener, NT_NOTIFY_NEW | NT_NOTIFY_UPDATE);
|
||||
}
|
||||
|
||||
void NetworkTable::AddTableListener(ITableListener* listener,
|
||||
bool immediateNotify) {
|
||||
unsigned int flags = NT_NOTIFY_NEW | NT_NOTIFY_UPDATE;
|
||||
if (immediateNotify) flags |= NT_NOTIFY_IMMEDIATE;
|
||||
AddTableListenerEx(listener, flags);
|
||||
}
|
||||
|
||||
void NetworkTable::AddTableListenerEx(ITableListener* listener,
|
||||
unsigned int flags) {
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
llvm::SmallString<128> path(m_path);
|
||||
path += PATH_SEPARATOR_CHAR;
|
||||
std::size_t prefix_len = path.size();
|
||||
unsigned int id = nt::AddEntryListener(
|
||||
path,
|
||||
[=](unsigned int /*uid*/, StringRef name,
|
||||
std::shared_ptr<nt::Value> value, unsigned int flags_) {
|
||||
StringRef relative_key = name.substr(prefix_len);
|
||||
if (relative_key.find(PATH_SEPARATOR_CHAR) != StringRef::npos) return;
|
||||
listener->ValueChangedEx(this, relative_key, value, flags_);
|
||||
},
|
||||
flags);
|
||||
m_listeners.emplace_back(listener, id);
|
||||
}
|
||||
|
||||
void NetworkTable::AddTableListener(StringRef key, ITableListener* listener,
|
||||
bool immediateNotify) {
|
||||
unsigned int flags = NT_NOTIFY_NEW | NT_NOTIFY_UPDATE;
|
||||
if (immediateNotify) flags |= NT_NOTIFY_IMMEDIATE;
|
||||
AddTableListenerEx(key, listener, flags);
|
||||
}
|
||||
|
||||
void NetworkTable::AddTableListenerEx(StringRef key, ITableListener* listener,
|
||||
unsigned int flags) {
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
llvm::SmallString<128> path(m_path);
|
||||
path += PATH_SEPARATOR_CHAR;
|
||||
std::size_t prefix_len = path.size();
|
||||
path += key;
|
||||
unsigned int id = nt::AddEntryListener(
|
||||
path,
|
||||
[=](unsigned int /*uid*/, StringRef name,
|
||||
std::shared_ptr<nt::Value> value, unsigned int flags_) {
|
||||
if (name != path) return;
|
||||
listener->ValueChangedEx(this, name.substr(prefix_len), value, flags_);
|
||||
},
|
||||
flags);
|
||||
m_listeners.emplace_back(listener, id);
|
||||
}
|
||||
|
||||
void NetworkTable::AddSubTableListener(ITableListener* listener) {
|
||||
AddSubTableListener(listener, false);
|
||||
}
|
||||
|
||||
void NetworkTable::AddSubTableListener(ITableListener* listener,
|
||||
bool localNotify) {
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
llvm::SmallString<128> path(m_path);
|
||||
path += PATH_SEPARATOR_CHAR;
|
||||
std::size_t prefix_len = path.size();
|
||||
|
||||
// The lambda needs to be copyable, but StringMap is not, so use
|
||||
// a shared_ptr to it.
|
||||
auto notified_tables = std::make_shared<llvm::StringMap<char>>();
|
||||
|
||||
unsigned int flags = NT_NOTIFY_NEW | NT_NOTIFY_IMMEDIATE;
|
||||
if (localNotify) flags |= NT_NOTIFY_LOCAL;
|
||||
unsigned int id = nt::AddEntryListener(
|
||||
path,
|
||||
[=](unsigned int /*uid*/, StringRef name,
|
||||
std::shared_ptr<nt::Value> /*value*/, unsigned int flags_) mutable {
|
||||
StringRef relative_key = name.substr(prefix_len);
|
||||
auto end_sub_table = relative_key.find(PATH_SEPARATOR_CHAR);
|
||||
if (end_sub_table == StringRef::npos) return;
|
||||
StringRef sub_table_key = relative_key.substr(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->ValueChangedEx(this, sub_table_key, nullptr, flags_);
|
||||
},
|
||||
flags);
|
||||
m_listeners.emplace_back(listener, id);
|
||||
}
|
||||
|
||||
void NetworkTable::RemoveTableListener(ITableListener* listener) {
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
auto matches_begin =
|
||||
std::remove_if(m_listeners.begin(), m_listeners.end(),
|
||||
[=](const Listener& x) { return x.first == listener; });
|
||||
|
||||
for (auto i = matches_begin; i != m_listeners.end(); ++i)
|
||||
nt::RemoveEntryListener(i->second);
|
||||
m_listeners.erase(matches_begin, m_listeners.end());
|
||||
}
|
||||
|
||||
std::shared_ptr<ITable> NetworkTable::GetSubTable(StringRef key) const {
|
||||
llvm::SmallString<128> path(m_path);
|
||||
path += PATH_SEPARATOR_CHAR;
|
||||
path += key;
|
||||
return std::make_shared<NetworkTable>(path, private_init());
|
||||
}
|
||||
|
||||
bool NetworkTable::ContainsKey(StringRef key) const {
|
||||
llvm::SmallString<128> path(m_path);
|
||||
path += PATH_SEPARATOR_CHAR;
|
||||
path += key;
|
||||
return nt::GetEntryValue(path) != nullptr;
|
||||
}
|
||||
|
||||
bool NetworkTable::ContainsSubTable(StringRef key) const {
|
||||
llvm::SmallString<128> path(m_path);
|
||||
path += PATH_SEPARATOR_CHAR;
|
||||
path += key;
|
||||
path += PATH_SEPARATOR_CHAR;
|
||||
return !nt::GetEntryInfo(path, 0).empty();
|
||||
}
|
||||
|
||||
std::vector<std::string> NetworkTable::GetKeys(int types) const {
|
||||
std::vector<std::string> keys;
|
||||
llvm::SmallString<128> path(m_path);
|
||||
path += PATH_SEPARATOR_CHAR;
|
||||
for (auto& entry : nt::GetEntryInfo(path, types)) {
|
||||
auto relative_key = StringRef(entry.name).substr(path.size());
|
||||
if (relative_key.find(PATH_SEPARATOR_CHAR) != StringRef::npos) continue;
|
||||
keys.push_back(relative_key);
|
||||
}
|
||||
return keys;
|
||||
}
|
||||
|
||||
std::vector<std::string> NetworkTable::GetSubTables() const {
|
||||
std::vector<std::string> keys;
|
||||
llvm::SmallString<128> path(m_path);
|
||||
path += PATH_SEPARATOR_CHAR;
|
||||
for (auto& entry : nt::GetEntryInfo(path, 0)) {
|
||||
auto relative_key = StringRef(entry.name).substr(path.size());
|
||||
std::size_t end_subtable = relative_key.find(PATH_SEPARATOR_CHAR);
|
||||
if (end_subtable == StringRef::npos) continue;
|
||||
keys.push_back(relative_key.substr(0, end_subtable));
|
||||
}
|
||||
return keys;
|
||||
}
|
||||
|
||||
void NetworkTable::SetPersistent(StringRef key) {
|
||||
SetFlags(key, NT_PERSISTENT);
|
||||
}
|
||||
|
||||
void NetworkTable::ClearPersistent(StringRef key) {
|
||||
ClearFlags(key, NT_PERSISTENT);
|
||||
}
|
||||
|
||||
bool NetworkTable::IsPersistent(StringRef key) const {
|
||||
return (GetFlags(key) & NT_PERSISTENT) != 0;
|
||||
}
|
||||
|
||||
void NetworkTable::SetFlags(StringRef key, unsigned int flags) {
|
||||
llvm::SmallString<128> path(m_path);
|
||||
path += PATH_SEPARATOR_CHAR;
|
||||
path += key;
|
||||
nt::SetEntryFlags(path, nt::GetEntryFlags(path) | flags);
|
||||
}
|
||||
|
||||
void NetworkTable::ClearFlags(StringRef key, unsigned int flags) {
|
||||
llvm::SmallString<128> path(m_path);
|
||||
path += PATH_SEPARATOR_CHAR;
|
||||
path += key;
|
||||
nt::SetEntryFlags(path, nt::GetEntryFlags(path) & ~flags);
|
||||
}
|
||||
|
||||
unsigned int NetworkTable::GetFlags(StringRef key) const {
|
||||
llvm::SmallString<128> path(m_path);
|
||||
path += PATH_SEPARATOR_CHAR;
|
||||
path += key;
|
||||
return nt::GetEntryFlags(path);
|
||||
}
|
||||
|
||||
void NetworkTable::Delete(StringRef key) {
|
||||
llvm::SmallString<128> path(m_path);
|
||||
path += PATH_SEPARATOR_CHAR;
|
||||
path += key;
|
||||
nt::DeleteEntry(path);
|
||||
}
|
||||
|
||||
bool NetworkTable::PutNumber(StringRef key, double value) {
|
||||
llvm::SmallString<128> path(m_path);
|
||||
path += PATH_SEPARATOR_CHAR;
|
||||
path += key;
|
||||
return nt::SetEntryValue(path, nt::Value::MakeDouble(value));
|
||||
}
|
||||
|
||||
bool NetworkTable::SetDefaultNumber(StringRef key, double defaultValue) {
|
||||
llvm::SmallString<128> path(m_path);
|
||||
path += PATH_SEPARATOR_CHAR;
|
||||
path += key;
|
||||
return nt::SetDefaultEntryValue(path, nt::Value::MakeDouble(defaultValue));
|
||||
}
|
||||
|
||||
double NetworkTable::GetNumber(StringRef key) const {
|
||||
llvm::SmallString<128> path(m_path);
|
||||
path += PATH_SEPARATOR_CHAR;
|
||||
path += key;
|
||||
auto value = nt::GetEntryValue(path);
|
||||
if (!value || value->type() != NT_DOUBLE)
|
||||
throw TableKeyNotDefinedException(path);
|
||||
return value->GetDouble();
|
||||
}
|
||||
|
||||
double NetworkTable::GetNumber(StringRef key, double defaultValue) const {
|
||||
llvm::SmallString<128> path(m_path);
|
||||
path += PATH_SEPARATOR_CHAR;
|
||||
path += key;
|
||||
auto value = nt::GetEntryValue(path);
|
||||
if (!value || value->type() != NT_DOUBLE) return defaultValue;
|
||||
return value->GetDouble();
|
||||
}
|
||||
|
||||
bool NetworkTable::PutString(StringRef key, StringRef value) {
|
||||
llvm::SmallString<128> path(m_path);
|
||||
path += PATH_SEPARATOR_CHAR;
|
||||
path += key;
|
||||
return nt::SetEntryValue(path, nt::Value::MakeString(value));
|
||||
}
|
||||
|
||||
bool NetworkTable::SetDefaultString(StringRef key, StringRef defaultValue) {
|
||||
llvm::SmallString<128> path(m_path);
|
||||
path += PATH_SEPARATOR_CHAR;
|
||||
path += key;
|
||||
return nt::SetDefaultEntryValue(path, nt::Value::MakeString(defaultValue));
|
||||
}
|
||||
|
||||
std::string NetworkTable::GetString(StringRef key) const {
|
||||
llvm::SmallString<128> path(m_path);
|
||||
path += PATH_SEPARATOR_CHAR;
|
||||
path += key;
|
||||
auto value = nt::GetEntryValue(path);
|
||||
if (!value || value->type() != NT_STRING)
|
||||
throw TableKeyNotDefinedException(path);
|
||||
return value->GetString();
|
||||
}
|
||||
|
||||
std::string NetworkTable::GetString(StringRef key,
|
||||
StringRef defaultValue) const {
|
||||
llvm::SmallString<128> path(m_path);
|
||||
path += PATH_SEPARATOR_CHAR;
|
||||
path += key;
|
||||
auto value = nt::GetEntryValue(path);
|
||||
if (!value || value->type() != NT_STRING) return defaultValue;
|
||||
return value->GetString();
|
||||
}
|
||||
|
||||
bool NetworkTable::PutBoolean(StringRef key, bool value) {
|
||||
llvm::SmallString<128> path(m_path);
|
||||
path += PATH_SEPARATOR_CHAR;
|
||||
path += key;
|
||||
return nt::SetEntryValue(path, nt::Value::MakeBoolean(value));
|
||||
}
|
||||
|
||||
bool NetworkTable::SetDefaultBoolean(StringRef key, bool defaultValue) {
|
||||
llvm::SmallString<128> path(m_path);
|
||||
path += PATH_SEPARATOR_CHAR;
|
||||
path += key;
|
||||
return nt::SetDefaultEntryValue(path, nt::Value::MakeBoolean(defaultValue));
|
||||
}
|
||||
|
||||
bool NetworkTable::GetBoolean(StringRef key) const {
|
||||
llvm::SmallString<128> path(m_path);
|
||||
path += PATH_SEPARATOR_CHAR;
|
||||
path += key;
|
||||
auto value = nt::GetEntryValue(path);
|
||||
if (!value || value->type() != NT_BOOLEAN)
|
||||
throw TableKeyNotDefinedException(path);
|
||||
return value->GetBoolean();
|
||||
}
|
||||
|
||||
bool NetworkTable::GetBoolean(StringRef key, bool defaultValue) const {
|
||||
llvm::SmallString<128> path(m_path);
|
||||
path += PATH_SEPARATOR_CHAR;
|
||||
path += key;
|
||||
auto value = nt::GetEntryValue(path);
|
||||
if (!value || value->type() != NT_BOOLEAN) return defaultValue;
|
||||
return value->GetBoolean();
|
||||
}
|
||||
|
||||
bool NetworkTable::PutBooleanArray(llvm::StringRef key,
|
||||
llvm::ArrayRef<int> value) {
|
||||
llvm::SmallString<128> path(m_path);
|
||||
path += PATH_SEPARATOR_CHAR;
|
||||
path += key;
|
||||
return nt::SetEntryValue(path, nt::Value::MakeBooleanArray(value));
|
||||
}
|
||||
|
||||
bool NetworkTable::SetDefaultBooleanArray(StringRef key,
|
||||
llvm::ArrayRef<int> defaultValue) {
|
||||
llvm::SmallString<128> path(m_path);
|
||||
path += PATH_SEPARATOR_CHAR;
|
||||
path += key;
|
||||
return nt::SetDefaultEntryValue(path,
|
||||
nt::Value::MakeBooleanArray(defaultValue));
|
||||
}
|
||||
|
||||
std::vector<int> NetworkTable::GetBooleanArray(
|
||||
llvm::StringRef key, llvm::ArrayRef<int> defaultValue) const {
|
||||
llvm::SmallString<128> path(m_path);
|
||||
path += PATH_SEPARATOR_CHAR;
|
||||
path += key;
|
||||
auto value = nt::GetEntryValue(path);
|
||||
if (!value || value->type() != NT_BOOLEAN_ARRAY) return defaultValue;
|
||||
return value->GetBooleanArray();
|
||||
}
|
||||
|
||||
bool NetworkTable::PutNumberArray(llvm::StringRef key,
|
||||
llvm::ArrayRef<double> value) {
|
||||
llvm::SmallString<128> path(m_path);
|
||||
path += PATH_SEPARATOR_CHAR;
|
||||
path += key;
|
||||
return nt::SetEntryValue(path, nt::Value::MakeDoubleArray(value));
|
||||
}
|
||||
|
||||
bool NetworkTable::SetDefaultNumberArray(StringRef key,
|
||||
llvm::ArrayRef<double> defaultValue) {
|
||||
llvm::SmallString<128> path(m_path);
|
||||
path += PATH_SEPARATOR_CHAR;
|
||||
path += key;
|
||||
return nt::SetDefaultEntryValue(path,
|
||||
nt::Value::MakeDoubleArray(defaultValue));
|
||||
}
|
||||
|
||||
std::vector<double> NetworkTable::GetNumberArray(
|
||||
llvm::StringRef key, llvm::ArrayRef<double> defaultValue) const {
|
||||
llvm::SmallString<128> path(m_path);
|
||||
path += PATH_SEPARATOR_CHAR;
|
||||
path += key;
|
||||
auto value = nt::GetEntryValue(path);
|
||||
if (!value || value->type() != NT_DOUBLE_ARRAY) return defaultValue;
|
||||
return value->GetDoubleArray();
|
||||
}
|
||||
|
||||
bool NetworkTable::PutStringArray(llvm::StringRef key,
|
||||
llvm::ArrayRef<std::string> value) {
|
||||
llvm::SmallString<128> path(m_path);
|
||||
path += PATH_SEPARATOR_CHAR;
|
||||
path += key;
|
||||
return nt::SetEntryValue(path, nt::Value::MakeStringArray(value));
|
||||
}
|
||||
|
||||
bool NetworkTable::SetDefaultStringArray(
|
||||
StringRef key, llvm::ArrayRef<std::string> defaultValue) {
|
||||
llvm::SmallString<128> path(m_path);
|
||||
path += PATH_SEPARATOR_CHAR;
|
||||
path += key;
|
||||
return nt::SetDefaultEntryValue(path,
|
||||
nt::Value::MakeStringArray(defaultValue));
|
||||
}
|
||||
|
||||
std::vector<std::string> NetworkTable::GetStringArray(
|
||||
llvm::StringRef key, llvm::ArrayRef<std::string> defaultValue) const {
|
||||
llvm::SmallString<128> path(m_path);
|
||||
path += PATH_SEPARATOR_CHAR;
|
||||
path += key;
|
||||
auto value = nt::GetEntryValue(path);
|
||||
if (!value || value->type() != NT_STRING_ARRAY) return defaultValue;
|
||||
return value->GetStringArray();
|
||||
}
|
||||
|
||||
bool NetworkTable::PutRaw(llvm::StringRef key, llvm::StringRef value) {
|
||||
llvm::SmallString<128> path(m_path);
|
||||
path += PATH_SEPARATOR_CHAR;
|
||||
path += key;
|
||||
return nt::SetEntryValue(path, nt::Value::MakeRaw(value));
|
||||
}
|
||||
|
||||
bool NetworkTable::SetDefaultRaw(StringRef key, StringRef defaultValue) {
|
||||
llvm::SmallString<128> path(m_path);
|
||||
path += PATH_SEPARATOR_CHAR;
|
||||
path += key;
|
||||
return nt::SetDefaultEntryValue(path, nt::Value::MakeRaw(defaultValue));
|
||||
}
|
||||
|
||||
std::string NetworkTable::GetRaw(llvm::StringRef key,
|
||||
llvm::StringRef defaultValue) const {
|
||||
llvm::SmallString<128> path(m_path);
|
||||
path += PATH_SEPARATOR_CHAR;
|
||||
path += key;
|
||||
auto value = nt::GetEntryValue(path);
|
||||
if (!value || value->type() != NT_RAW) return defaultValue;
|
||||
return value->GetRaw();
|
||||
}
|
||||
|
||||
bool NetworkTable::PutValue(StringRef key, std::shared_ptr<nt::Value> value) {
|
||||
llvm::SmallString<128> path(m_path);
|
||||
path += PATH_SEPARATOR_CHAR;
|
||||
path += key;
|
||||
return nt::SetEntryValue(path, value);
|
||||
}
|
||||
|
||||
bool NetworkTable::SetDefaultValue(StringRef key,
|
||||
std::shared_ptr<nt::Value> defaultValue) {
|
||||
llvm::SmallString<128> path(m_path);
|
||||
path += PATH_SEPARATOR_CHAR;
|
||||
path += key;
|
||||
return nt::SetDefaultEntryValue(path, defaultValue);
|
||||
}
|
||||
|
||||
std::shared_ptr<nt::Value> NetworkTable::GetValue(StringRef key) const {
|
||||
llvm::SmallString<128> path(m_path);
|
||||
path += PATH_SEPARATOR_CHAR;
|
||||
path += key;
|
||||
return nt::GetEntryValue(path);
|
||||
}
|
||||
|
||||
StringRef NetworkTable::GetPath() const {
|
||||
return m_path;
|
||||
}
|
||||
888
src/main/native/cpp/ntcore_c.cpp
Normal file
888
src/main/native/cpp/ntcore_c.cpp
Normal file
@@ -0,0 +1,888 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) FIRST 2015. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#include "ntcore.h"
|
||||
|
||||
#include <cassert>
|
||||
#include <cstdlib>
|
||||
|
||||
#include "support/timestamp.h"
|
||||
#include "Value_internal.h"
|
||||
|
||||
using namespace nt;
|
||||
|
||||
// Conversion helpers
|
||||
|
||||
static void ConvertToC(llvm::StringRef in, char** out) {
|
||||
*out = static_cast<char*>(std::malloc(in.size() + 1));
|
||||
std::memmove(*out, in.data(), in.size());
|
||||
(*out)[in.size()] = '\0';
|
||||
}
|
||||
|
||||
static void ConvertToC(const EntryInfo& in, NT_EntryInfo* out) {
|
||||
ConvertToC(in.name, &out->name);
|
||||
out->type = in.type;
|
||||
out->flags = in.flags;
|
||||
out->last_change = in.last_change;
|
||||
}
|
||||
|
||||
static void ConvertToC(const ConnectionInfo& in, NT_ConnectionInfo* out) {
|
||||
ConvertToC(in.remote_id, &out->remote_id);
|
||||
ConvertToC(in.remote_ip, &out->remote_ip);
|
||||
out->remote_port = in.remote_port;
|
||||
out->last_update = in.last_update;
|
||||
out->protocol_version = in.protocol_version;
|
||||
}
|
||||
|
||||
static void ConvertToC(const RpcParamDef& in, NT_RpcParamDef* out) {
|
||||
ConvertToC(in.name, &out->name);
|
||||
ConvertToC(*in.def_value, &out->def_value);
|
||||
}
|
||||
|
||||
static void ConvertToC(const RpcResultDef& in, NT_RpcResultDef* out) {
|
||||
ConvertToC(in.name, &out->name);
|
||||
out->type = in.type;
|
||||
}
|
||||
|
||||
static void ConvertToC(const RpcDefinition& in, NT_RpcDefinition* out) {
|
||||
out->version = in.version;
|
||||
ConvertToC(in.name, &out->name);
|
||||
|
||||
out->num_params = in.params.size();
|
||||
out->params = static_cast<NT_RpcParamDef*>(
|
||||
std::malloc(in.params.size() * sizeof(NT_RpcParamDef)));
|
||||
for (size_t i = 0; i < in.params.size(); ++i)
|
||||
ConvertToC(in.params[i], &out->params[i]);
|
||||
|
||||
out->num_results = in.results.size();
|
||||
out->results = static_cast<NT_RpcResultDef*>(
|
||||
std::malloc(in.results.size() * sizeof(NT_RpcResultDef)));
|
||||
for (size_t i = 0; i < in.results.size(); ++i)
|
||||
ConvertToC(in.results[i], &out->results[i]);
|
||||
}
|
||||
|
||||
static void ConvertToC(const RpcCallInfo& in, NT_RpcCallInfo* out) {
|
||||
out->rpc_id = in.rpc_id;
|
||||
out->call_uid = in.call_uid;
|
||||
ConvertToC(in.name, &out->name);
|
||||
ConvertToC(in.params, &out->params);
|
||||
}
|
||||
|
||||
static void DisposeConnectionInfo(NT_ConnectionInfo* info) {
|
||||
std::free(info->remote_id.str);
|
||||
std::free(info->remote_ip.str);
|
||||
}
|
||||
|
||||
static void DisposeEntryInfo(NT_EntryInfo* info) { std::free(info->name.str); }
|
||||
|
||||
static RpcParamDef ConvertFromC(const NT_RpcParamDef& in) {
|
||||
RpcParamDef out;
|
||||
out.name = ConvertFromC(in.name);
|
||||
out.def_value = ConvertFromC(in.def_value);
|
||||
return out;
|
||||
}
|
||||
|
||||
static RpcResultDef ConvertFromC(const NT_RpcResultDef& in) {
|
||||
RpcResultDef out;
|
||||
out.name = ConvertFromC(in.name);
|
||||
out.type = in.type;
|
||||
return out;
|
||||
}
|
||||
|
||||
static RpcDefinition ConvertFromC(const NT_RpcDefinition& in) {
|
||||
RpcDefinition out;
|
||||
out.version = in.version;
|
||||
out.name = ConvertFromC(in.name);
|
||||
|
||||
out.params.reserve(in.num_params);
|
||||
for (size_t i = 0; i < in.num_params; ++i)
|
||||
out.params.push_back(ConvertFromC(in.params[i]));
|
||||
|
||||
out.results.reserve(in.num_results);
|
||||
for (size_t i = 0; i < in.num_results; ++i)
|
||||
out.results.push_back(ConvertFromC(in.results[i]));
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
|
||||
/*
|
||||
* Table Functions
|
||||
*/
|
||||
|
||||
void NT_GetEntryValue(const char* name, size_t name_len,
|
||||
struct NT_Value* value) {
|
||||
NT_InitValue(value);
|
||||
auto v = nt::GetEntryValue(StringRef(name, name_len));
|
||||
if (!v) return;
|
||||
ConvertToC(*v, value);
|
||||
}
|
||||
|
||||
int NT_SetDefaultEntryValue(const char* name, size_t name_len,
|
||||
const struct NT_Value* set_value) {
|
||||
return nt::SetDefaultEntryValue(StringRef(name, name_len),
|
||||
ConvertFromC(*set_value));
|
||||
}
|
||||
|
||||
int NT_SetEntryValue(const char* name, size_t name_len,
|
||||
const struct NT_Value* value) {
|
||||
return nt::SetEntryValue(StringRef(name, name_len), ConvertFromC(*value));
|
||||
}
|
||||
|
||||
void NT_SetEntryTypeValue(const char* name, size_t name_len,
|
||||
const struct NT_Value* value) {
|
||||
nt::SetEntryTypeValue(StringRef(name, name_len), ConvertFromC(*value));
|
||||
}
|
||||
|
||||
void NT_SetEntryFlags(const char* name, size_t name_len, unsigned int flags) {
|
||||
nt::SetEntryFlags(StringRef(name, name_len), flags);
|
||||
}
|
||||
|
||||
unsigned int NT_GetEntryFlags(const char* name, size_t name_len) {
|
||||
return nt::GetEntryFlags(StringRef(name, name_len));
|
||||
}
|
||||
|
||||
void NT_DeleteEntry(const char* name, size_t name_len) {
|
||||
nt::DeleteEntry(StringRef(name, name_len));
|
||||
}
|
||||
|
||||
void NT_DeleteAllEntries(void) { nt::DeleteAllEntries(); }
|
||||
|
||||
struct NT_EntryInfo* NT_GetEntryInfo(const char* prefix, size_t prefix_len,
|
||||
unsigned int types, size_t* count) {
|
||||
auto info_v = nt::GetEntryInfo(StringRef(prefix, prefix_len), types);
|
||||
*count = info_v.size();
|
||||
if (info_v.size() == 0) return nullptr;
|
||||
|
||||
// create array and copy into it
|
||||
NT_EntryInfo* info = static_cast<NT_EntryInfo*>(
|
||||
std::malloc(info_v.size() * sizeof(NT_EntryInfo)));
|
||||
for (size_t i = 0; i < info_v.size(); ++i) ConvertToC(info_v[i], &info[i]);
|
||||
return info;
|
||||
}
|
||||
|
||||
void NT_Flush(void) { nt::Flush(); }
|
||||
|
||||
/*
|
||||
* Callback Creation Functions
|
||||
*/
|
||||
|
||||
void NT_SetListenerOnStart(void (*on_start)(void* data), void* data) {
|
||||
nt::SetListenerOnStart([=]() { on_start(data); });
|
||||
}
|
||||
|
||||
void NT_SetListenerOnExit(void (*on_exit)(void* data), void* data) {
|
||||
nt::SetListenerOnExit([=]() { on_exit(data); });
|
||||
}
|
||||
|
||||
unsigned int NT_AddEntryListener(const char* prefix, size_t prefix_len,
|
||||
void* data, NT_EntryListenerCallback callback,
|
||||
unsigned int flags) {
|
||||
return nt::AddEntryListener(
|
||||
StringRef(prefix, prefix_len),
|
||||
[=](unsigned int uid, StringRef name, std::shared_ptr<Value> value,
|
||||
unsigned int flags_) {
|
||||
callback(uid, data, name.data(), name.size(), &value->value(), flags_);
|
||||
},
|
||||
flags);
|
||||
}
|
||||
|
||||
void NT_RemoveEntryListener(unsigned int entry_listener_uid) {
|
||||
nt::RemoveEntryListener(entry_listener_uid);
|
||||
}
|
||||
|
||||
unsigned int NT_AddConnectionListener(void* data,
|
||||
NT_ConnectionListenerCallback callback,
|
||||
int immediate_notify) {
|
||||
return nt::AddConnectionListener(
|
||||
[=](unsigned int uid, bool connected, const ConnectionInfo& conn) {
|
||||
NT_ConnectionInfo conn_c;
|
||||
ConvertToC(conn, &conn_c);
|
||||
callback(uid, data, connected ? 1 : 0, &conn_c);
|
||||
DisposeConnectionInfo(&conn_c);
|
||||
},
|
||||
immediate_notify != 0);
|
||||
}
|
||||
|
||||
void NT_RemoveConnectionListener(unsigned int conn_listener_uid) {
|
||||
nt::RemoveConnectionListener(conn_listener_uid);
|
||||
}
|
||||
|
||||
int NT_NotifierDestroyed() { return nt::NotifierDestroyed(); }
|
||||
|
||||
/*
|
||||
* Remote Procedure Call Functions
|
||||
*/
|
||||
|
||||
void NT_SetRpcServerOnStart(void (*on_start)(void* data), void* data) {
|
||||
nt::SetRpcServerOnStart([=]() { on_start(data); });
|
||||
}
|
||||
|
||||
void NT_SetRpcServerOnExit(void (*on_exit)(void* data), void* data) {
|
||||
nt::SetRpcServerOnExit([=]() { on_exit(data); });
|
||||
}
|
||||
|
||||
void NT_CreateRpc(const char* name, size_t name_len, const char* def,
|
||||
size_t def_len, void* data, NT_RpcCallback callback) {
|
||||
nt::CreateRpc(StringRef(name, name_len), StringRef(def, def_len),
|
||||
[=](StringRef name, StringRef params,
|
||||
const ConnectionInfo& conn_info) -> std::string {
|
||||
size_t results_len;
|
||||
NT_ConnectionInfo conn_c;
|
||||
ConvertToC(conn_info, &conn_c);
|
||||
char* results_c =
|
||||
callback(data, name.data(), name.size(), params.data(),
|
||||
params.size(), &results_len, &conn_c);
|
||||
std::string results(results_c, results_len);
|
||||
std::free(results_c);
|
||||
DisposeConnectionInfo(&conn_c);
|
||||
return results;
|
||||
});
|
||||
}
|
||||
|
||||
void NT_CreatePolledRpc(const char* name, size_t name_len, const char* def,
|
||||
size_t def_len) {
|
||||
nt::CreatePolledRpc(StringRef(name, name_len), StringRef(def, def_len));
|
||||
}
|
||||
|
||||
int NT_PollRpc(int blocking, NT_RpcCallInfo* call_info) {
|
||||
RpcCallInfo call_info_cpp;
|
||||
if (!nt::PollRpc(blocking != 0, &call_info_cpp)) return 0;
|
||||
ConvertToC(call_info_cpp, call_info);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int NT_PollRpcTimeout(int blocking, double time_out,
|
||||
NT_RpcCallInfo* call_info) {
|
||||
RpcCallInfo call_info_cpp;
|
||||
if (!nt::PollRpc(blocking != 0, time_out, &call_info_cpp)) return 0;
|
||||
ConvertToC(call_info_cpp, call_info);
|
||||
return 1;
|
||||
}
|
||||
|
||||
void NT_PostRpcResponse(unsigned int rpc_id, unsigned int call_uid,
|
||||
const char* result, size_t result_len) {
|
||||
nt::PostRpcResponse(rpc_id, call_uid, StringRef(result, result_len));
|
||||
}
|
||||
|
||||
unsigned int NT_CallRpc(const char* name, size_t name_len, const char* params,
|
||||
size_t params_len) {
|
||||
return nt::CallRpc(StringRef(name, name_len), StringRef(params, params_len));
|
||||
}
|
||||
|
||||
char* NT_GetRpcResult(int blocking, unsigned int call_uid, size_t* result_len) {
|
||||
std::string result;
|
||||
if (!nt::GetRpcResult(blocking != 0, call_uid, &result)) return nullptr;
|
||||
|
||||
// convert result
|
||||
*result_len = result.size();
|
||||
char* result_cstr;
|
||||
ConvertToC(result, &result_cstr);
|
||||
return result_cstr;
|
||||
}
|
||||
|
||||
char* NT_GetRpcResultTimeout(int blocking, unsigned int call_uid,
|
||||
double time_out, size_t* result_len) {
|
||||
std::string result;
|
||||
if (!nt::GetRpcResult(blocking != 0, call_uid, time_out, &result))
|
||||
return nullptr;
|
||||
|
||||
// convert result
|
||||
*result_len = result.size();
|
||||
char* result_cstr;
|
||||
ConvertToC(result, &result_cstr);
|
||||
return result_cstr;
|
||||
}
|
||||
|
||||
void NT_CancelBlockingRpcResult(unsigned int call_uid) {
|
||||
nt::CancelBlockingRpcResult(call_uid);
|
||||
}
|
||||
|
||||
char* NT_PackRpcDefinition(const NT_RpcDefinition* def, size_t* packed_len) {
|
||||
auto packed = nt::PackRpcDefinition(ConvertFromC(*def));
|
||||
|
||||
// convert result
|
||||
*packed_len = packed.size();
|
||||
char* packed_cstr;
|
||||
ConvertToC(packed, &packed_cstr);
|
||||
return packed_cstr;
|
||||
}
|
||||
|
||||
int NT_UnpackRpcDefinition(const char* packed, size_t packed_len,
|
||||
NT_RpcDefinition* def) {
|
||||
nt::RpcDefinition def_v;
|
||||
if (!nt::UnpackRpcDefinition(StringRef(packed, packed_len), &def_v)) return 0;
|
||||
|
||||
// convert result
|
||||
ConvertToC(def_v, def);
|
||||
return 1;
|
||||
}
|
||||
|
||||
char* NT_PackRpcValues(const NT_Value** values, size_t values_len,
|
||||
size_t* packed_len) {
|
||||
// create input vector
|
||||
std::vector<std::shared_ptr<Value>> values_v;
|
||||
values_v.reserve(values_len);
|
||||
for (size_t i = 0; i < values_len; ++i)
|
||||
values_v.push_back(ConvertFromC(*values[i]));
|
||||
|
||||
// make the call
|
||||
auto packed = nt::PackRpcValues(values_v);
|
||||
|
||||
// convert result
|
||||
*packed_len = packed.size();
|
||||
char* packed_cstr;
|
||||
ConvertToC(packed, &packed_cstr);
|
||||
return packed_cstr;
|
||||
}
|
||||
|
||||
NT_Value** NT_UnpackRpcValues(const char* packed, size_t packed_len,
|
||||
const NT_Type* types, size_t types_len) {
|
||||
auto values_v = nt::UnpackRpcValues(StringRef(packed, packed_len),
|
||||
ArrayRef<NT_Type>(types, types_len));
|
||||
if (values_v.size() == 0) return nullptr;
|
||||
|
||||
// create array and copy into it
|
||||
NT_Value** values =
|
||||
static_cast<NT_Value**>(std::malloc(values_v.size() * sizeof(NT_Value*)));
|
||||
for (size_t i = 0; i < values_v.size(); ++i) {
|
||||
values[i] = static_cast<NT_Value*>(std::malloc(sizeof(NT_Value)));
|
||||
ConvertToC(*values_v[i], values[i]);
|
||||
}
|
||||
return values;
|
||||
}
|
||||
|
||||
/*
|
||||
* Client/Server Functions
|
||||
*/
|
||||
|
||||
void NT_SetNetworkIdentity(const char* name, size_t name_len) {
|
||||
nt::SetNetworkIdentity(StringRef(name, name_len));
|
||||
}
|
||||
|
||||
unsigned int NT_GetNetworkMode() {
|
||||
return nt::GetNetworkMode();
|
||||
}
|
||||
|
||||
void NT_StartServer(const char* persist_filename, const char* listen_address,
|
||||
unsigned int port) {
|
||||
nt::StartServer(persist_filename, listen_address, port);
|
||||
}
|
||||
|
||||
void NT_StopServer(void) { nt::StopServer(); }
|
||||
|
||||
void NT_StartClientNone(void) { nt::StartClient(); }
|
||||
|
||||
void NT_StartClient(const char* server_name, unsigned int port) {
|
||||
nt::StartClient(server_name, port);
|
||||
}
|
||||
|
||||
void NT_StartClientMulti(size_t count, const char** server_names,
|
||||
const unsigned int* ports) {
|
||||
std::vector<std::pair<StringRef, unsigned int>> servers;
|
||||
servers.reserve(count);
|
||||
for (size_t i = 0; i < count; ++i)
|
||||
servers.emplace_back(std::make_pair(server_names[i], ports[i]));
|
||||
nt::StartClient(servers);
|
||||
}
|
||||
|
||||
void NT_StopClient(void) { nt::StopClient(); }
|
||||
|
||||
void NT_SetServer(const char* server_name, unsigned int port) {
|
||||
nt::SetServer(server_name, port);
|
||||
}
|
||||
|
||||
void NT_SetServerMulti(size_t count, const char** server_names,
|
||||
const unsigned int* ports) {
|
||||
std::vector<std::pair<StringRef, unsigned int>> servers;
|
||||
servers.reserve(count);
|
||||
for (size_t i = 0; i < count; ++i)
|
||||
servers.emplace_back(std::make_pair(server_names[i], ports[i]));
|
||||
nt::SetServer(servers);
|
||||
}
|
||||
|
||||
void NT_StartDSClient(unsigned int port) { nt::StartDSClient(port); }
|
||||
|
||||
void NT_StopDSClient(void) { nt::StopDSClient(); }
|
||||
|
||||
void NT_StopRpcServer(void) { nt::StopRpcServer(); }
|
||||
|
||||
void NT_StopNotifier(void) { nt::StopNotifier(); }
|
||||
|
||||
void NT_SetUpdateRate(double interval) { nt::SetUpdateRate(interval); }
|
||||
|
||||
struct NT_ConnectionInfo* NT_GetConnections(size_t* count) {
|
||||
auto conn_v = nt::GetConnections();
|
||||
*count = conn_v.size();
|
||||
if (conn_v.size() == 0) return nullptr;
|
||||
|
||||
// create array and copy into it
|
||||
NT_ConnectionInfo* conn = static_cast<NT_ConnectionInfo*>(
|
||||
std::malloc(conn_v.size() * sizeof(NT_ConnectionInfo)));
|
||||
for (size_t i = 0; i < conn_v.size(); ++i) ConvertToC(conn_v[i], &conn[i]);
|
||||
return conn;
|
||||
}
|
||||
|
||||
/*
|
||||
* Persistent Functions
|
||||
*/
|
||||
|
||||
const char* NT_SavePersistent(const char* filename) {
|
||||
return nt::SavePersistent(filename);
|
||||
}
|
||||
|
||||
const char* NT_LoadPersistent(const char* filename,
|
||||
void (*warn)(size_t line, const char* msg)) {
|
||||
return nt::LoadPersistent(filename, warn);
|
||||
}
|
||||
|
||||
/*
|
||||
* Utility Functions
|
||||
*/
|
||||
|
||||
unsigned long long NT_Now() { return wpi::Now(); }
|
||||
|
||||
void NT_SetLogger(NT_LogFunc func, unsigned int min_level) {
|
||||
nt::SetLogger(func, min_level);
|
||||
}
|
||||
|
||||
void NT_DisposeValue(NT_Value* value) {
|
||||
switch (value->type) {
|
||||
case NT_UNASSIGNED:
|
||||
case NT_BOOLEAN:
|
||||
case NT_DOUBLE:
|
||||
break;
|
||||
case NT_STRING:
|
||||
case NT_RAW:
|
||||
case NT_RPC:
|
||||
std::free(value->data.v_string.str);
|
||||
break;
|
||||
case NT_BOOLEAN_ARRAY:
|
||||
std::free(value->data.arr_boolean.arr);
|
||||
break;
|
||||
case NT_DOUBLE_ARRAY:
|
||||
std::free(value->data.arr_double.arr);
|
||||
break;
|
||||
case NT_STRING_ARRAY: {
|
||||
for (size_t i = 0; i < value->data.arr_string.size; i++)
|
||||
std::free(value->data.arr_string.arr[i].str);
|
||||
std::free(value->data.arr_string.arr);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
assert(false && "unknown value type");
|
||||
}
|
||||
value->type = NT_UNASSIGNED;
|
||||
value->last_change = 0;
|
||||
}
|
||||
|
||||
void NT_InitValue(NT_Value* value) {
|
||||
value->type = NT_UNASSIGNED;
|
||||
value->last_change = 0;
|
||||
}
|
||||
|
||||
void NT_DisposeString(NT_String* str) {
|
||||
std::free(str->str);
|
||||
str->str = nullptr;
|
||||
str->len = 0;
|
||||
}
|
||||
|
||||
void NT_InitString(NT_String* str) {
|
||||
str->str = nullptr;
|
||||
str->len = 0;
|
||||
}
|
||||
|
||||
enum NT_Type NT_GetType(const char* name, size_t name_len) {
|
||||
auto v = nt::GetEntryValue(StringRef(name, name_len));
|
||||
if (!v) return NT_Type::NT_UNASSIGNED;
|
||||
return v->type();
|
||||
}
|
||||
|
||||
void NT_DisposeConnectionInfoArray(NT_ConnectionInfo* arr, size_t count) {
|
||||
for (size_t i = 0; i < count; i++) DisposeConnectionInfo(&arr[i]);
|
||||
std::free(arr);
|
||||
}
|
||||
|
||||
void NT_DisposeEntryInfoArray(NT_EntryInfo* arr, size_t count) {
|
||||
for (size_t i = 0; i < count; i++) DisposeEntryInfo(&arr[i]);
|
||||
std::free(arr);
|
||||
}
|
||||
|
||||
void NT_DisposeRpcDefinition(NT_RpcDefinition* def) {
|
||||
NT_DisposeString(&def->name);
|
||||
|
||||
for (size_t i = 0; i < def->num_params; ++i) {
|
||||
NT_DisposeString(&def->params[i].name);
|
||||
NT_DisposeValue(&def->params[i].def_value);
|
||||
}
|
||||
std::free(def->params);
|
||||
def->params = nullptr;
|
||||
def->num_params = 0;
|
||||
|
||||
for (size_t i = 0; i < def->num_results; ++i)
|
||||
NT_DisposeString(&def->results[i].name);
|
||||
std::free(def->results);
|
||||
def->results = nullptr;
|
||||
def->num_results = 0;
|
||||
}
|
||||
|
||||
void NT_DisposeRpcCallInfo(NT_RpcCallInfo* call_info) {
|
||||
NT_DisposeString(&call_info->name);
|
||||
NT_DisposeString(&call_info->params);
|
||||
}
|
||||
|
||||
/* Interop Utility Functions */
|
||||
|
||||
/* Array and Struct Allocations */
|
||||
|
||||
/* Allocates a char array of the specified size.*/
|
||||
char* NT_AllocateCharArray(size_t size) {
|
||||
char* retVal = static_cast<char*>(std::malloc(size * sizeof(char)));
|
||||
return retVal;
|
||||
}
|
||||
|
||||
/* Allocates an integer or boolean array of the specified size. */
|
||||
int* NT_AllocateBooleanArray(size_t size) {
|
||||
int* retVal = static_cast<int*>(std::malloc(size * sizeof(int)));
|
||||
return retVal;
|
||||
}
|
||||
|
||||
/* Allocates a double array of the specified size. */
|
||||
double* NT_AllocateDoubleArray(size_t size) {
|
||||
double* retVal = static_cast<double*>(std::malloc(size * sizeof(double)));
|
||||
return retVal;
|
||||
}
|
||||
|
||||
/* Allocates an NT_String array of the specified size. */
|
||||
struct NT_String* NT_AllocateStringArray(size_t size) {
|
||||
NT_String* retVal =
|
||||
static_cast<NT_String*>(std::malloc(size * sizeof(NT_String)));
|
||||
return retVal;
|
||||
}
|
||||
|
||||
void NT_FreeCharArray(char* v_char) { std::free(v_char); }
|
||||
void NT_FreeDoubleArray(double* v_double) { std::free(v_double); }
|
||||
void NT_FreeBooleanArray(int* v_boolean) { std::free(v_boolean); }
|
||||
void NT_FreeStringArray(struct NT_String* v_string, size_t arr_size) {
|
||||
for (size_t i = 0; i < arr_size; i++) std::free(v_string[i].str);
|
||||
std::free(v_string);
|
||||
}
|
||||
|
||||
int NT_SetEntryDouble(const char* name, size_t name_len, double v_double,
|
||||
int force) {
|
||||
if (force != 0) {
|
||||
nt::SetEntryTypeValue(StringRef(name, name_len),
|
||||
Value::MakeDouble(v_double));
|
||||
return 1;
|
||||
} else {
|
||||
return nt::SetEntryValue(StringRef(name, name_len),
|
||||
Value::MakeDouble(v_double));
|
||||
}
|
||||
}
|
||||
|
||||
int NT_SetEntryBoolean(const char* name, size_t name_len, int v_boolean,
|
||||
int force) {
|
||||
if (force != 0) {
|
||||
nt::SetEntryTypeValue(StringRef(name, name_len),
|
||||
Value::MakeBoolean(v_boolean != 0));
|
||||
return 1;
|
||||
} else {
|
||||
return nt::SetEntryValue(StringRef(name, name_len),
|
||||
Value::MakeBoolean(v_boolean != 0));
|
||||
}
|
||||
}
|
||||
|
||||
int NT_SetEntryString(const char* name, size_t name_len, const char* str,
|
||||
size_t str_len, int force) {
|
||||
if (force != 0) {
|
||||
nt::SetEntryTypeValue(StringRef(name, name_len),
|
||||
Value::MakeString(StringRef(str, str_len)));
|
||||
return 1;
|
||||
} else {
|
||||
return nt::SetEntryValue(StringRef(name, name_len),
|
||||
Value::MakeString(StringRef(str, str_len)));
|
||||
}
|
||||
}
|
||||
|
||||
int NT_SetEntryRaw(const char* name, size_t name_len, const char* raw,
|
||||
size_t raw_len, int force) {
|
||||
if (force != 0) {
|
||||
nt::SetEntryTypeValue(StringRef(name, name_len),
|
||||
Value::MakeRaw(StringRef(raw, raw_len)));
|
||||
return 1;
|
||||
} else {
|
||||
return nt::SetEntryValue(StringRef(name, name_len),
|
||||
Value::MakeRaw(StringRef(raw, raw_len)));
|
||||
}
|
||||
}
|
||||
|
||||
int NT_SetEntryBooleanArray(const char* name, size_t name_len, const int* arr,
|
||||
size_t size, int force) {
|
||||
if (force != 0) {
|
||||
nt::SetEntryTypeValue(
|
||||
StringRef(name, name_len),
|
||||
Value::MakeBooleanArray(llvm::makeArrayRef(arr, size)));
|
||||
return 1;
|
||||
} else {
|
||||
return nt::SetEntryValue(
|
||||
StringRef(name, name_len),
|
||||
Value::MakeBooleanArray(llvm::makeArrayRef(arr, size)));
|
||||
}
|
||||
}
|
||||
|
||||
int NT_SetEntryDoubleArray(const char* name, size_t name_len, const double* arr,
|
||||
size_t size, int force) {
|
||||
if (force != 0) {
|
||||
nt::SetEntryTypeValue(
|
||||
StringRef(name, name_len),
|
||||
Value::MakeDoubleArray(llvm::makeArrayRef(arr, size)));
|
||||
return 1;
|
||||
} else {
|
||||
return nt::SetEntryValue(
|
||||
StringRef(name, name_len),
|
||||
Value::MakeDoubleArray(llvm::makeArrayRef(arr, size)));
|
||||
}
|
||||
}
|
||||
|
||||
int NT_SetEntryStringArray(const char* name, size_t name_len,
|
||||
const struct NT_String* arr, size_t size,
|
||||
int force) {
|
||||
std::vector<std::string> v;
|
||||
v.reserve(size);
|
||||
for (size_t i = 0; i < size; ++i) v.push_back(ConvertFromC(arr[i]));
|
||||
|
||||
if (force != 0) {
|
||||
nt::SetEntryTypeValue(StringRef(name, name_len),
|
||||
Value::MakeStringArray(std::move(v)));
|
||||
return 1;
|
||||
} else {
|
||||
return nt::SetEntryValue(StringRef(name, name_len),
|
||||
Value::MakeStringArray(std::move(v)));
|
||||
}
|
||||
}
|
||||
|
||||
enum NT_Type NT_GetValueType(const struct NT_Value* value) {
|
||||
if (!value) return NT_Type::NT_UNASSIGNED;
|
||||
return value->type;
|
||||
}
|
||||
|
||||
int NT_GetValueBoolean(const struct NT_Value* value,
|
||||
unsigned long long* last_change, int* v_boolean) {
|
||||
if (!value || value->type != NT_Type::NT_BOOLEAN) return 0;
|
||||
*v_boolean = value->data.v_boolean;
|
||||
*last_change = value->last_change;
|
||||
return 1;
|
||||
}
|
||||
|
||||
int NT_GetValueDouble(const struct NT_Value* value,
|
||||
unsigned long long* last_change, double* v_double) {
|
||||
if (!value || value->type != NT_Type::NT_DOUBLE) return 0;
|
||||
*last_change = value->last_change;
|
||||
*v_double = value->data.v_double;
|
||||
return 1;
|
||||
}
|
||||
|
||||
char* NT_GetValueString(const struct NT_Value* value,
|
||||
unsigned long long* last_change, size_t* str_len) {
|
||||
if (!value || value->type != NT_Type::NT_STRING) return nullptr;
|
||||
*last_change = value->last_change;
|
||||
*str_len = value->data.v_string.len;
|
||||
char* str = (char*)std::malloc(value->data.v_string.len + 1);
|
||||
std::memcpy(str, value->data.v_string.str, value->data.v_string.len + 1);
|
||||
return str;
|
||||
}
|
||||
|
||||
char* NT_GetValueRaw(const struct NT_Value* value,
|
||||
unsigned long long* last_change, size_t* raw_len) {
|
||||
if (!value || value->type != NT_Type::NT_RAW) return nullptr;
|
||||
*last_change = value->last_change;
|
||||
*raw_len = value->data.v_string.len;
|
||||
char* raw = (char*)std::malloc(value->data.v_string.len + 1);
|
||||
std::memcpy(raw, value->data.v_string.str, value->data.v_string.len + 1);
|
||||
return raw;
|
||||
}
|
||||
|
||||
int* NT_GetValueBooleanArray(const struct NT_Value* value,
|
||||
unsigned long long* last_change,
|
||||
size_t* arr_size) {
|
||||
if (!value || value->type != NT_Type::NT_BOOLEAN_ARRAY) return nullptr;
|
||||
*last_change = value->last_change;
|
||||
*arr_size = value->data.arr_boolean.size;
|
||||
int* arr = (int*)std::malloc(value->data.arr_boolean.size * sizeof(int));
|
||||
std::memcpy(arr, value->data.arr_boolean.arr,
|
||||
value->data.arr_boolean.size * sizeof(int));
|
||||
return arr;
|
||||
}
|
||||
|
||||
double* NT_GetValueDoubleArray(const struct NT_Value* value,
|
||||
unsigned long long* last_change,
|
||||
size_t* arr_size) {
|
||||
if (!value || value->type != NT_Type::NT_DOUBLE_ARRAY) return nullptr;
|
||||
*last_change = value->last_change;
|
||||
*arr_size = value->data.arr_double.size;
|
||||
double* arr =
|
||||
(double*)std::malloc(value->data.arr_double.size * sizeof(double));
|
||||
std::memcpy(arr, value->data.arr_double.arr,
|
||||
value->data.arr_double.size * sizeof(double));
|
||||
return arr;
|
||||
}
|
||||
|
||||
NT_String* NT_GetValueStringArray(const struct NT_Value* value,
|
||||
unsigned long long* last_change,
|
||||
size_t* arr_size) {
|
||||
if (!value || value->type != NT_Type::NT_STRING_ARRAY) return nullptr;
|
||||
*last_change = value->last_change;
|
||||
*arr_size = value->data.arr_string.size;
|
||||
NT_String* arr = static_cast<NT_String*>(
|
||||
std::malloc(value->data.arr_string.size * sizeof(NT_String)));
|
||||
for (size_t i = 0; i < value->data.arr_string.size; ++i) {
|
||||
size_t len = value->data.arr_string.arr[i].len;
|
||||
arr[i].len = len;
|
||||
arr[i].str = (char*)std::malloc(len + 1);
|
||||
std::memcpy(arr[i].str, value->data.arr_string.arr[i].str, len + 1);
|
||||
}
|
||||
return arr;
|
||||
}
|
||||
|
||||
int NT_SetDefaultEntryBoolean(const char* name, size_t name_len,
|
||||
int default_boolean) {
|
||||
return nt::SetDefaultEntryValue(StringRef(name, name_len),
|
||||
Value::MakeBoolean(default_boolean != 0));
|
||||
}
|
||||
|
||||
int NT_SetDefaultEntryDouble(const char* name, size_t name_len,
|
||||
double default_double) {
|
||||
return nt::SetDefaultEntryValue(StringRef(name, name_len),
|
||||
Value::MakeDouble(default_double));
|
||||
}
|
||||
|
||||
int NT_SetDefaultEntryString(const char* name, size_t name_len,
|
||||
const char* default_value, size_t default_len) {
|
||||
return nt::SetDefaultEntryValue(
|
||||
StringRef(name, name_len),
|
||||
Value::MakeString(StringRef(default_value, default_len)));
|
||||
}
|
||||
|
||||
int NT_SetDefaultEntryRaw(const char* name, size_t name_len,
|
||||
const char* default_value, size_t default_len) {
|
||||
return nt::SetDefaultEntryValue(
|
||||
StringRef(name, name_len),
|
||||
Value::MakeString(StringRef(default_value, default_len)));
|
||||
}
|
||||
|
||||
int NT_SetDefaultEntryBooleanArray(const char* name, size_t name_len,
|
||||
const int* default_value,
|
||||
size_t default_size) {
|
||||
return nt::SetDefaultEntryValue(
|
||||
StringRef(name, name_len),
|
||||
Value::MakeBooleanArray(llvm::makeArrayRef(default_value, default_size)));
|
||||
}
|
||||
|
||||
int NT_SetDefaultEntryDoubleArray(const char* name, size_t name_len,
|
||||
const double* default_value,
|
||||
size_t default_size) {
|
||||
return nt::SetDefaultEntryValue(
|
||||
StringRef(name, name_len),
|
||||
Value::MakeDoubleArray(llvm::makeArrayRef(default_value, default_size)));
|
||||
}
|
||||
|
||||
int NT_SetDefaultEntryStringArray(const char* name, size_t name_len,
|
||||
const struct NT_String* default_value,
|
||||
size_t default_size) {
|
||||
std::vector<std::string> vec;
|
||||
vec.reserve(default_size);
|
||||
for (size_t i = 0; i < default_size; ++i)
|
||||
vec.push_back(ConvertFromC(default_value[i]));
|
||||
|
||||
return nt::SetDefaultEntryValue(StringRef(name, name_len),
|
||||
Value::MakeStringArray(std::move(vec)));
|
||||
}
|
||||
|
||||
int NT_GetEntryBoolean(const char* name, size_t name_len,
|
||||
unsigned long long* last_change, int* v_boolean) {
|
||||
auto v = nt::GetEntryValue(StringRef(name, name_len));
|
||||
if (!v || !v->IsBoolean()) return 0;
|
||||
*v_boolean = v->GetBoolean();
|
||||
*last_change = v->last_change();
|
||||
return 1;
|
||||
}
|
||||
|
||||
int NT_GetEntryDouble(const char* name, size_t name_len,
|
||||
unsigned long long* last_change, double* v_double) {
|
||||
auto v = nt::GetEntryValue(StringRef(name, name_len));
|
||||
if (!v || !v->IsDouble()) return 0;
|
||||
*last_change = v->last_change();
|
||||
*v_double = v->GetDouble();
|
||||
return 1;
|
||||
}
|
||||
|
||||
char* NT_GetEntryString(const char* name, size_t name_len,
|
||||
unsigned long long* last_change, size_t* str_len) {
|
||||
auto v = nt::GetEntryValue(StringRef(name, name_len));
|
||||
if (!v || !v->IsString()) return nullptr;
|
||||
*last_change = v->last_change();
|
||||
struct NT_String v_string;
|
||||
nt::ConvertToC(v->GetString(), &v_string);
|
||||
*str_len = v_string.len;
|
||||
return v_string.str;
|
||||
}
|
||||
|
||||
char* NT_GetEntryRaw(const char* name, size_t name_len,
|
||||
unsigned long long* last_change, size_t* raw_len) {
|
||||
auto v = nt::GetEntryValue(StringRef(name, name_len));
|
||||
if (!v || !v->IsRaw()) return nullptr;
|
||||
*last_change = v->last_change();
|
||||
struct NT_String v_raw;
|
||||
nt::ConvertToC(v->GetRaw(), &v_raw);
|
||||
*raw_len = v_raw.len;
|
||||
return v_raw.str;
|
||||
}
|
||||
|
||||
int* NT_GetEntryBooleanArray(const char* name, size_t name_len,
|
||||
unsigned long long* last_change,
|
||||
size_t* arr_size) {
|
||||
auto v = nt::GetEntryValue(StringRef(name, name_len));
|
||||
if (!v || !v->IsBooleanArray()) return nullptr;
|
||||
*last_change = v->last_change();
|
||||
auto vArr = v->GetBooleanArray();
|
||||
int* arr = static_cast<int*>(std::malloc(vArr.size() * sizeof(int)));
|
||||
*arr_size = vArr.size();
|
||||
std::copy(vArr.begin(), vArr.end(), arr);
|
||||
return arr;
|
||||
}
|
||||
|
||||
double* NT_GetEntryDoubleArray(const char* name, size_t name_len,
|
||||
unsigned long long* last_change,
|
||||
size_t* arr_size) {
|
||||
auto v = nt::GetEntryValue(StringRef(name, name_len));
|
||||
if (!v || !v->IsDoubleArray()) return nullptr;
|
||||
*last_change = v->last_change();
|
||||
auto vArr = v->GetDoubleArray();
|
||||
double* arr = static_cast<double*>(std::malloc(vArr.size() * sizeof(double)));
|
||||
*arr_size = vArr.size();
|
||||
std::copy(vArr.begin(), vArr.end(), arr);
|
||||
return arr;
|
||||
}
|
||||
|
||||
NT_String* NT_GetEntryStringArray(const char* name, size_t name_len,
|
||||
unsigned long long* last_change,
|
||||
size_t* arr_size) {
|
||||
auto v = nt::GetEntryValue(StringRef(name, name_len));
|
||||
if (!v || !v->IsStringArray()) return nullptr;
|
||||
*last_change = v->last_change();
|
||||
auto vArr = v->GetStringArray();
|
||||
NT_String* arr =
|
||||
static_cast<NT_String*>(std::malloc(vArr.size() * sizeof(NT_String)));
|
||||
for (size_t i = 0; i < vArr.size(); ++i) {
|
||||
ConvertToC(vArr[i], &arr[i]);
|
||||
}
|
||||
*arr_size = vArr.size();
|
||||
return arr;
|
||||
}
|
||||
|
||||
} // extern "C"
|
||||
317
src/main/native/cpp/ntcore_cpp.cpp
Normal file
317
src/main/native/cpp/ntcore_cpp.cpp
Normal file
@@ -0,0 +1,317 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) FIRST 2015. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#include "ntcore.h"
|
||||
|
||||
#include <cassert>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
|
||||
#include "support/timestamp.h"
|
||||
#include "Log.h"
|
||||
#include "Dispatcher.h"
|
||||
#include "DsClient.h"
|
||||
#include "Notifier.h"
|
||||
#include "RpcServer.h"
|
||||
#include "Storage.h"
|
||||
#include "WireDecoder.h"
|
||||
#include "WireEncoder.h"
|
||||
|
||||
namespace nt {
|
||||
|
||||
/*
|
||||
* Table Functions
|
||||
*/
|
||||
|
||||
std::shared_ptr<Value> GetEntryValue(StringRef name) {
|
||||
return Storage::GetInstance().GetEntryValue(name);
|
||||
}
|
||||
|
||||
bool SetDefaultEntryValue(StringRef name, std::shared_ptr<Value> value) {
|
||||
return Storage::GetInstance().SetDefaultEntryValue(name, value);
|
||||
}
|
||||
|
||||
bool SetEntryValue(StringRef name, std::shared_ptr<Value> value) {
|
||||
return Storage::GetInstance().SetEntryValue(name, value);
|
||||
}
|
||||
|
||||
void SetEntryTypeValue(StringRef name, std::shared_ptr<Value> value) {
|
||||
Storage::GetInstance().SetEntryTypeValue(name, value);
|
||||
}
|
||||
|
||||
void SetEntryFlags(StringRef name, unsigned int flags) {
|
||||
Storage::GetInstance().SetEntryFlags(name, flags);
|
||||
}
|
||||
|
||||
unsigned int GetEntryFlags(StringRef name) {
|
||||
return Storage::GetInstance().GetEntryFlags(name);
|
||||
}
|
||||
|
||||
void DeleteEntry(StringRef name) { Storage::GetInstance().DeleteEntry(name); }
|
||||
|
||||
void DeleteAllEntries() { Storage::GetInstance().DeleteAllEntries(); }
|
||||
|
||||
std::vector<EntryInfo> GetEntryInfo(StringRef prefix, unsigned int types) {
|
||||
return Storage::GetInstance().GetEntryInfo(prefix, types);
|
||||
}
|
||||
|
||||
void Flush() { Dispatcher::GetInstance().Flush(); }
|
||||
|
||||
/*
|
||||
* Callback Creation Functions
|
||||
*/
|
||||
|
||||
void SetListenerOnStart(std::function<void()> on_start) {
|
||||
Notifier::GetInstance().SetOnStart(on_start);
|
||||
}
|
||||
|
||||
void SetListenerOnExit(std::function<void()> on_exit) {
|
||||
Notifier::GetInstance().SetOnExit(on_exit);
|
||||
}
|
||||
|
||||
unsigned int AddEntryListener(StringRef prefix, EntryListenerCallback callback,
|
||||
unsigned int flags) {
|
||||
unsigned int uid =
|
||||
Notifier::GetInstance().AddEntryListener(prefix, callback, flags);
|
||||
if ((flags & NT_NOTIFY_IMMEDIATE) != 0)
|
||||
Storage::GetInstance().NotifyEntries(prefix, callback);
|
||||
return uid;
|
||||
}
|
||||
|
||||
void RemoveEntryListener(unsigned int entry_listener_uid) {
|
||||
Notifier::GetInstance().RemoveEntryListener(entry_listener_uid);
|
||||
}
|
||||
|
||||
unsigned int AddConnectionListener(ConnectionListenerCallback callback,
|
||||
bool immediate_notify) {
|
||||
unsigned int uid = Notifier::GetInstance().AddConnectionListener(callback);
|
||||
if (immediate_notify) Dispatcher::GetInstance().NotifyConnections(callback);
|
||||
return uid;
|
||||
}
|
||||
|
||||
void RemoveConnectionListener(unsigned int conn_listener_uid) {
|
||||
Notifier::GetInstance().RemoveConnectionListener(conn_listener_uid);
|
||||
}
|
||||
|
||||
bool NotifierDestroyed() { return Notifier::destroyed(); }
|
||||
|
||||
/*
|
||||
* Remote Procedure Call Functions
|
||||
*/
|
||||
|
||||
void SetRpcServerOnStart(std::function<void()> on_start) {
|
||||
RpcServer::GetInstance().SetOnStart(on_start);
|
||||
}
|
||||
|
||||
void SetRpcServerOnExit(std::function<void()> on_exit) {
|
||||
RpcServer::GetInstance().SetOnExit(on_exit);
|
||||
}
|
||||
|
||||
void CreateRpc(StringRef name, StringRef def, RpcCallback callback) {
|
||||
Storage::GetInstance().CreateRpc(name, def, callback);
|
||||
}
|
||||
|
||||
void CreatePolledRpc(StringRef name, StringRef def) {
|
||||
Storage::GetInstance().CreatePolledRpc(name, def);
|
||||
}
|
||||
|
||||
bool PollRpc(bool blocking, RpcCallInfo* call_info) {
|
||||
return RpcServer::GetInstance().PollRpc(blocking, call_info);
|
||||
}
|
||||
|
||||
bool PollRpc(bool blocking, double time_out, RpcCallInfo* call_info) {
|
||||
return RpcServer::GetInstance().PollRpc(blocking, time_out, call_info);
|
||||
}
|
||||
|
||||
void PostRpcResponse(unsigned int rpc_id, unsigned int call_uid,
|
||||
StringRef result) {
|
||||
RpcServer::GetInstance().PostRpcResponse(rpc_id, call_uid, result);
|
||||
}
|
||||
|
||||
unsigned int CallRpc(StringRef name, StringRef params) {
|
||||
return Storage::GetInstance().CallRpc(name, params);
|
||||
}
|
||||
|
||||
bool GetRpcResult(bool blocking, unsigned int call_uid, std::string* result) {
|
||||
return Storage::GetInstance().GetRpcResult(blocking, call_uid, result);
|
||||
}
|
||||
|
||||
bool GetRpcResult(bool blocking, unsigned int call_uid, double time_out,
|
||||
std::string* result) {
|
||||
return Storage::GetInstance().GetRpcResult(blocking, call_uid, time_out,
|
||||
result);
|
||||
}
|
||||
|
||||
void CancelBlockingRpcResult(unsigned int call_uid) {
|
||||
Storage::GetInstance().CancelBlockingRpcResult(call_uid);
|
||||
}
|
||||
|
||||
std::string PackRpcDefinition(const RpcDefinition& def) {
|
||||
WireEncoder enc(0x0300);
|
||||
enc.Write8(def.version);
|
||||
enc.WriteString(def.name);
|
||||
|
||||
// parameters
|
||||
unsigned int params_size = def.params.size();
|
||||
if (params_size > 0xff) params_size = 0xff;
|
||||
enc.Write8(params_size);
|
||||
for (std::size_t i = 0; i < params_size; ++i) {
|
||||
enc.WriteType(def.params[i].def_value->type());
|
||||
enc.WriteString(def.params[i].name);
|
||||
enc.WriteValue(*def.params[i].def_value);
|
||||
}
|
||||
|
||||
// results
|
||||
unsigned int results_size = def.results.size();
|
||||
if (results_size > 0xff) results_size = 0xff;
|
||||
enc.Write8(results_size);
|
||||
for (std::size_t i = 0; i < results_size; ++i) {
|
||||
enc.WriteType(def.results[i].type);
|
||||
enc.WriteString(def.results[i].name);
|
||||
}
|
||||
|
||||
return enc.ToStringRef();
|
||||
}
|
||||
|
||||
bool UnpackRpcDefinition(StringRef packed, RpcDefinition* def) {
|
||||
wpi::raw_mem_istream is(packed.data(), packed.size());
|
||||
WireDecoder dec(is, 0x0300);
|
||||
if (!dec.Read8(&def->version)) return false;
|
||||
if (!dec.ReadString(&def->name)) return false;
|
||||
|
||||
// parameters
|
||||
unsigned int params_size;
|
||||
if (!dec.Read8(¶ms_size)) return false;
|
||||
def->params.resize(0);
|
||||
def->params.reserve(params_size);
|
||||
for (std::size_t i = 0; i < params_size; ++i) {
|
||||
RpcParamDef pdef;
|
||||
NT_Type type;
|
||||
if (!dec.ReadType(&type)) return false;
|
||||
if (!dec.ReadString(&pdef.name)) return false;
|
||||
pdef.def_value = dec.ReadValue(type);
|
||||
if (!pdef.def_value) return false;
|
||||
def->params.emplace_back(std::move(pdef));
|
||||
}
|
||||
|
||||
// results
|
||||
unsigned int results_size;
|
||||
if (!dec.Read8(&results_size)) return false;
|
||||
def->results.resize(0);
|
||||
def->results.reserve(results_size);
|
||||
for (std::size_t i = 0; i < results_size; ++i) {
|
||||
RpcResultDef rdef;
|
||||
if (!dec.ReadType(&rdef.type)) return false;
|
||||
if (!dec.ReadString(&rdef.name)) return false;
|
||||
def->results.emplace_back(std::move(rdef));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string PackRpcValues(ArrayRef<std::shared_ptr<Value>> values) {
|
||||
WireEncoder enc(0x0300);
|
||||
for (auto& value : values) enc.WriteValue(*value);
|
||||
return enc.ToStringRef();
|
||||
}
|
||||
|
||||
std::vector<std::shared_ptr<Value>> UnpackRpcValues(StringRef packed,
|
||||
ArrayRef<NT_Type> types) {
|
||||
wpi::raw_mem_istream is(packed.data(), packed.size());
|
||||
WireDecoder dec(is, 0x0300);
|
||||
std::vector<std::shared_ptr<Value>> vec;
|
||||
for (auto type : types) {
|
||||
auto item = dec.ReadValue(type);
|
||||
if (!item) return std::vector<std::shared_ptr<Value>>();
|
||||
vec.emplace_back(std::move(item));
|
||||
}
|
||||
return vec;
|
||||
}
|
||||
|
||||
/*
|
||||
* Client/Server Functions
|
||||
*/
|
||||
|
||||
void SetNetworkIdentity(StringRef name) {
|
||||
Dispatcher::GetInstance().SetIdentity(name);
|
||||
}
|
||||
|
||||
unsigned int GetNetworkMode() {
|
||||
auto& d = Dispatcher::GetInstance();
|
||||
return d.GetNetworkMode();
|
||||
}
|
||||
|
||||
void StartServer(StringRef persist_filename, const char* listen_address,
|
||||
unsigned int port) {
|
||||
Dispatcher::GetInstance().StartServer(persist_filename, listen_address, port);
|
||||
}
|
||||
|
||||
void StopServer() { Dispatcher::GetInstance().Stop(); }
|
||||
|
||||
void StartClient() { Dispatcher::GetInstance().StartClient(); }
|
||||
|
||||
void StartClient(const char* server_name, unsigned int port) {
|
||||
auto& d = Dispatcher::GetInstance();
|
||||
d.SetServer(server_name, port);
|
||||
d.StartClient();
|
||||
}
|
||||
|
||||
void StartClient(ArrayRef<std::pair<StringRef, unsigned int>> servers) {
|
||||
auto& d = Dispatcher::GetInstance();
|
||||
d.SetServer(servers);
|
||||
d.StartClient();
|
||||
}
|
||||
|
||||
void StopClient() { Dispatcher::GetInstance().Stop(); }
|
||||
|
||||
void SetServer(const char* server_name, unsigned int port) {
|
||||
Dispatcher::GetInstance().SetServer(server_name, port);
|
||||
}
|
||||
|
||||
void SetServer(ArrayRef<std::pair<StringRef, unsigned int>> servers) {
|
||||
Dispatcher::GetInstance().SetServer(servers);
|
||||
}
|
||||
|
||||
void StartDSClient(unsigned int port) { DsClient::GetInstance().Start(port); }
|
||||
|
||||
void StopDSClient() { DsClient::GetInstance().Stop(); }
|
||||
|
||||
void StopRpcServer() { RpcServer::GetInstance().Stop(); }
|
||||
|
||||
void StopNotifier() { Notifier::GetInstance().Stop(); }
|
||||
|
||||
void SetUpdateRate(double interval) {
|
||||
Dispatcher::GetInstance().SetUpdateRate(interval);
|
||||
}
|
||||
|
||||
std::vector<ConnectionInfo> GetConnections() {
|
||||
return Dispatcher::GetInstance().GetConnections();
|
||||
}
|
||||
|
||||
/*
|
||||
* Persistent Functions
|
||||
*/
|
||||
|
||||
const char* SavePersistent(StringRef filename) {
|
||||
return Storage::GetInstance().SavePersistent(filename, false);
|
||||
}
|
||||
|
||||
const char* LoadPersistent(
|
||||
StringRef filename,
|
||||
std::function<void(size_t line, const char* msg)> warn) {
|
||||
return Storage::GetInstance().LoadPersistent(filename, warn);
|
||||
}
|
||||
|
||||
unsigned long long Now() { return wpi::Now(); }
|
||||
|
||||
void SetLogger(LogFunc func, unsigned int min_level) {
|
||||
Logger& logger = Logger::GetInstance();
|
||||
logger.SetLogger(func);
|
||||
logger.set_min_level(min_level);
|
||||
}
|
||||
|
||||
} // namespace nt
|
||||
242
src/main/native/cpp/ntcore_test.cpp
Normal file
242
src/main/native/cpp/ntcore_test.cpp
Normal file
@@ -0,0 +1,242 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) FIRST 2016. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#include "ntcore_test.h"
|
||||
|
||||
#include "Value_internal.h"
|
||||
|
||||
extern "C" {
|
||||
struct NT_String* NT_GetStringForTesting(const char* string, int* struct_size) {
|
||||
struct NT_String* str =
|
||||
static_cast<NT_String*>(std::calloc(1, sizeof(NT_String)));
|
||||
nt::ConvertToC(llvm::StringRef(string), str);
|
||||
*struct_size = sizeof(NT_String);
|
||||
return str;
|
||||
}
|
||||
|
||||
struct NT_EntryInfo* NT_GetEntryInfoForTesting(const char* name,
|
||||
enum NT_Type type,
|
||||
unsigned int flags,
|
||||
unsigned long long last_change,
|
||||
int* struct_size) {
|
||||
struct NT_EntryInfo* entry_info =
|
||||
static_cast<NT_EntryInfo*>(std::calloc(1, sizeof(NT_EntryInfo)));
|
||||
nt::ConvertToC(llvm::StringRef(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;
|
||||
}
|
||||
|
||||
void NT_FreeEntryInfoForTesting(struct NT_EntryInfo* info) {
|
||||
std::free(info->name.str);
|
||||
std::free(info);
|
||||
}
|
||||
|
||||
struct NT_ConnectionInfo* NT_GetConnectionInfoForTesting(
|
||||
const char* remote_id, const char* remote_ip, unsigned int remote_port,
|
||||
unsigned long long last_update, unsigned int protocol_version,
|
||||
int* struct_size) {
|
||||
struct NT_ConnectionInfo* conn_info = static_cast<NT_ConnectionInfo*>(
|
||||
std::calloc(1, sizeof(NT_ConnectionInfo)));
|
||||
nt::ConvertToC(llvm::StringRef(remote_id), &conn_info->remote_id);
|
||||
nt::ConvertToC(llvm::StringRef(remote_ip), &conn_info->remote_ip);
|
||||
conn_info->remote_port = remote_port;
|
||||
conn_info->last_update = last_update;
|
||||
conn_info->protocol_version = protocol_version;
|
||||
*struct_size = sizeof(NT_ConnectionInfo);
|
||||
return conn_info;
|
||||
}
|
||||
|
||||
void NT_FreeConnectionInfoForTesting(struct NT_ConnectionInfo* info) {
|
||||
std::free(info->remote_id.str);
|
||||
std::free(info->remote_ip.str);
|
||||
std::free(info);
|
||||
}
|
||||
|
||||
struct NT_Value* NT_GetValueBooleanForTesting(unsigned long long last_change,
|
||||
int val, int* struct_size) {
|
||||
struct NT_Value* value =
|
||||
static_cast<NT_Value*>(std::calloc(1, sizeof(NT_Value)));
|
||||
value->type = NT_BOOLEAN;
|
||||
value->last_change = last_change;
|
||||
value->data.v_boolean = val;
|
||||
*struct_size = sizeof(NT_Value);
|
||||
return value;
|
||||
}
|
||||
|
||||
struct NT_Value* NT_GetValueDoubleForTesting(unsigned long long last_change,
|
||||
double val, int* struct_size) {
|
||||
struct NT_Value* value =
|
||||
static_cast<NT_Value*>(std::calloc(1, sizeof(NT_Value)));
|
||||
value->type = NT_DOUBLE;
|
||||
value->last_change = last_change;
|
||||
value->data.v_double = val;
|
||||
*struct_size = sizeof(NT_Value);
|
||||
return value;
|
||||
}
|
||||
|
||||
struct NT_Value* NT_GetValueStringForTesting(unsigned long long last_change,
|
||||
const char* str,
|
||||
int* struct_size) {
|
||||
struct NT_Value* value =
|
||||
static_cast<NT_Value*>(std::calloc(1, sizeof(NT_Value)));
|
||||
value->type = NT_STRING;
|
||||
value->last_change = last_change;
|
||||
nt::ConvertToC(llvm::StringRef(str), &value->data.v_string);
|
||||
*struct_size = sizeof(NT_Value);
|
||||
return value;
|
||||
}
|
||||
|
||||
struct NT_Value* NT_GetValueRawForTesting(unsigned long long last_change,
|
||||
const char* raw, int raw_len,
|
||||
int* struct_size) {
|
||||
struct NT_Value* value =
|
||||
static_cast<NT_Value*>(std::calloc(1, sizeof(NT_Value)));
|
||||
value->type = NT_RAW;
|
||||
value->last_change = last_change;
|
||||
nt::ConvertToC(llvm::StringRef(raw, raw_len), &value->data.v_string);
|
||||
*struct_size = sizeof(NT_Value);
|
||||
return value;
|
||||
}
|
||||
|
||||
struct NT_Value* NT_GetValueBooleanArrayForTesting(
|
||||
unsigned long long last_change, const int* arr, size_t array_len,
|
||||
int* struct_size) {
|
||||
struct NT_Value* value =
|
||||
static_cast<NT_Value*>(std::calloc(1, sizeof(NT_Value)));
|
||||
value->type = NT_BOOLEAN_ARRAY;
|
||||
value->last_change = last_change;
|
||||
value->data.arr_boolean.arr = NT_AllocateBooleanArray(array_len);
|
||||
value->data.arr_boolean.size = array_len;
|
||||
std::memcpy(value->data.arr_boolean.arr, arr,
|
||||
value->data.arr_boolean.size * sizeof(int));
|
||||
*struct_size = sizeof(NT_Value);
|
||||
return value;
|
||||
}
|
||||
|
||||
struct NT_Value* NT_GetValueDoubleArrayForTesting(
|
||||
unsigned long long last_change, const double* arr, size_t array_len,
|
||||
int* struct_size) {
|
||||
struct NT_Value* value =
|
||||
static_cast<NT_Value*>(std::calloc(1, sizeof(NT_Value)));
|
||||
value->type = NT_BOOLEAN;
|
||||
value->last_change = last_change;
|
||||
value->data.arr_double.arr = NT_AllocateDoubleArray(array_len);
|
||||
value->data.arr_double.size = array_len;
|
||||
std::memcpy(value->data.arr_double.arr, arr,
|
||||
value->data.arr_double.size * sizeof(int));
|
||||
*struct_size = sizeof(NT_Value);
|
||||
return value;
|
||||
}
|
||||
|
||||
struct NT_Value* NT_GetValueStringArrayForTesting(
|
||||
unsigned long long last_change, const struct NT_String* arr,
|
||||
size_t array_len, int* struct_size) {
|
||||
struct NT_Value* value =
|
||||
static_cast<NT_Value*>(std::calloc(1, sizeof(NT_Value)));
|
||||
value->type = NT_BOOLEAN;
|
||||
value->last_change = last_change;
|
||||
value->data.arr_string.arr = NT_AllocateStringArray(array_len);
|
||||
value->data.arr_string.size = array_len;
|
||||
for (size_t i = 0; i < value->data.arr_string.size; ++i) {
|
||||
size_t len = arr[i].len;
|
||||
value->data.arr_string.arr[i].len = len;
|
||||
value->data.arr_string.arr[i].str = (char*)std::malloc(len + 1);
|
||||
std::memcpy(value->data.arr_string.arr[i].str, arr[i].str, len + 1);
|
||||
}
|
||||
*struct_size = sizeof(NT_Value);
|
||||
return value;
|
||||
}
|
||||
// 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(llvm::StringRef(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*>(std::calloc(1, sizeof(NT_RpcParamDef)));
|
||||
nt::ConvertToC(llvm::StringRef(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*>(std::calloc(1, sizeof(NT_RpcResultDef)));
|
||||
nt::ConvertToC(llvm::StringRef(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*>(std::calloc(1, sizeof(NT_RpcDefinition)));
|
||||
def->version = version;
|
||||
nt::ConvertToC(llvm::StringRef(name), &def->name);
|
||||
def->num_params = num_params;
|
||||
def->params = static_cast<NT_RpcParamDef*>(
|
||||
std::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*>(
|
||||
std::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_RpcCallInfo* NT_GetRpcCallInfoForTesting(
|
||||
unsigned int rpc_id, unsigned int call_uid, const char* name,
|
||||
const char* params, size_t params_len, int* struct_size) {
|
||||
struct NT_RpcCallInfo* info =
|
||||
static_cast<NT_RpcCallInfo*>(std::calloc(1, sizeof(NT_RpcCallInfo)));
|
||||
info->rpc_id = rpc_id;
|
||||
info->call_uid = call_uid;
|
||||
nt::ConvertToC(llvm::StringRef(name), &info->name);
|
||||
nt::ConvertToC(llvm::StringRef(params, params_len), &info->params);
|
||||
*struct_size = sizeof(NT_RpcCallInfo);
|
||||
return info;
|
||||
}
|
||||
// No need for free as one already exists in the main library
|
||||
}
|
||||
9
src/main/native/cpp/tables/ITableListener.cpp
Normal file
9
src/main/native/cpp/tables/ITableListener.cpp
Normal file
@@ -0,0 +1,9 @@
|
||||
#include "tables/ITableListener.h"
|
||||
|
||||
#include "ntcore_c.h"
|
||||
|
||||
void ITableListener::ValueChangedEx(ITable* source, llvm::StringRef key,
|
||||
std::shared_ptr<nt::Value> value,
|
||||
unsigned int flags) {
|
||||
ValueChanged(source, key, value, (flags & NT_NOTIFY_NEW) != 0);
|
||||
}
|
||||
19
src/main/native/cpp/tables/TableKeyNotDefinedException.cpp
Normal file
19
src/main/native/cpp/tables/TableKeyNotDefinedException.cpp
Normal file
@@ -0,0 +1,19 @@
|
||||
/*----------------------------------------------------------------------------*/
|
||||
/* Copyright (c) FIRST 2015. All Rights Reserved. */
|
||||
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
||||
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
||||
/* the project. */
|
||||
/*----------------------------------------------------------------------------*/
|
||||
|
||||
#include "tables/TableKeyNotDefinedException.h"
|
||||
|
||||
TableKeyNotDefinedException::TableKeyNotDefinedException(llvm::StringRef key)
|
||||
: msg("Unknown Table Key: ") {
|
||||
msg += key;
|
||||
}
|
||||
|
||||
const char* TableKeyNotDefinedException::what() const NT_NOEXCEPT {
|
||||
return msg.c_str();
|
||||
}
|
||||
|
||||
TableKeyNotDefinedException::~TableKeyNotDefinedException() NT_NOEXCEPT {}
|
||||
Reference in New Issue
Block a user