[ntcore] Remove NT3 support (#7625)

- Remove StartClient3
- Rename StartClient4 to StartClient
- Remove port3 parameter from StartServer
- Remove 3-suffix constants
- Remove 4 suffix from constants

Also remove Shuffleboard build from CI.
This commit is contained in:
Peter Johnson
2025-01-02 23:05:13 -08:00
committed by GitHub
parent da90ffd24a
commit 1240ee1bf4
58 changed files with 149 additions and 4116 deletions

View File

@@ -43,84 +43,6 @@ jobs:
development
retention-days: 1
# Robotbuilder:
# name: "Build - RobotBuilder"
# needs: [build-artifacts]
# runs-on: ubuntu-24.04
# env:
# DISPLAY: ':10'
# steps:
# - uses: actions/checkout@v4
# with:
# repository: wpilibsuite/robotbuilder
# fetch-depth: 0
# - uses: actions/download-artifact@v4
# with:
# name: MavenArtifacts
# - name: Patch RobotBuilder to use local development
# run: cd src/main/resources/export && echo "wpi.maven.useLocal = false" >> java/build.gradle && echo "wpi.maven.useFrcMavenLocalDevelopment = true" >> java/build.gradle && echo "wpi.versions.wpilibVersion = '$YEAR.424242.+'" >> java/build.gradle && echo "wpi.versions.wpimathVersion = '$YEAR.424242.+'" >> java/build.gradle && echo "wpi.maven.useLocal = false" >> cpp/build.gradle && echo "wpi.maven.useFrcMavenLocalDevelopment = true" >> cpp/build.gradle && echo "wpi.versions.wpilibVersion = '$YEAR.424242.+'" >> cpp/build.gradle && echo "wpi.versions.wpimathVersion = '$YEAR.424242.+'" >> cpp/build.gradle
# - name: Install and run xvfb
# run: sudo apt-get update && sudo apt-get install -y xvfb && Xvfb $DISPLAY &
# - name: Move artifacts
# run: mkdir -p ~/releases/maven/development && cp -r edu ~/releases/maven/development
# - uses: actions/setup-java@v4
# with:
# java-version: 17
# distribution: 'temurin'
# - name: Build RobotBuilder with Gradle
# run: ./gradlew build test --tests 'robotbuilder.exporters.*' -x htmlSanityCheck -PbuildServer -PreleaseMode ; cat build/test-results/test/TEST-robotbuilder.exporters.*.xml ;
# - name: Summarize RobotBuilder Test Results
# uses: EnricoMi/publish-unit-test-result-action@v2
# if: always()
# with:
# files: |
# build/test-results/test/TEST*.xml
# check_run: false
# comment_mode: off
# - uses: actions/upload-artifact@v4
# if: always()
# with:
# name: RobotBuilderTestResults
# path: |
# build/reports/
# - uses: actions/upload-artifact@v4
# with:
# name: RobotBuilder Build
# path: |
# build/libs/
# retention-days: 7
Shuffleboard:
name: "Build - Shuffleboard"
needs: [build-artifacts]
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
with:
repository: wpilibsuite/shuffleboard
fetch-depth: 0
- name: Patch Shuffleboard to use local development
run: sed -i "s/wpilibTools.deps.wpilibVersion.*/wpilibTools.deps.wpilibVersion = \'$YEAR\.424242\.+\'/" app/app.gradle && sed -i "s/wpilibTools.deps.wpilibVersion.*/wpilibTools.deps.wpilibVersion = \'$YEAR\.424242\.+\'/" plugins/cameraserver/cameraserver.gradle && sed -i "s/wpilibTools.deps.wpilibVersion.*/wpilibTools.deps.wpilibVersion = \'$YEAR\.424242\.+\'/" plugins/networktables/networktables.gradle
- uses: actions/download-artifact@v4
with:
name: MavenArtifacts
- uses: actions/setup-java@v4
with:
java-version: 17
distribution: 'temurin'
- name: Move artifacts
run: mkdir -p ~/releases/maven/development && cp -r edu ~/releases/maven/development
- name: Install dependencies
run: sudo apt-get install -y libgtk2.0-0
- name: Build with Gradle
run: ./gradlew build -x Javadoc
- uses: actions/upload-artifact@v4
with:
name: Shuffleboard Build
path: |
build/allOutputs/
retention-days: 7
PathWeaver:
name: "Build - PathWeaver"
needs: [build-artifacts]

View File

@@ -176,7 +176,7 @@ public final class Main {
} else {
System.out.println("Setting up NetworkTables client for team " + team);
ntinst.setServerTeam(team);
ntinst.startClient4("multicameraserver");
ntinst.startClient("multicameraserver");
}
// start cameras

View File

@@ -190,7 +190,7 @@ int main(int argc, char* argv[]) {
ntinst.StartServer();
} else {
wpi::print("Setting up NetworkTables client for team {}\n", team);
ntinst.StartClient4("multicameraserver");
ntinst.StartClient("multicameraserver");
ntinst.SetServerTeam(team);
}

View File

@@ -83,7 +83,7 @@ static std::string MakeTitle(NT_Inst inst, nt::Event event) {
auto numClients = nt::GetConnections(inst).size();
return fmt::format("Glass - {} Client{} Connected", numClients,
(numClients == 1 ? "" : "s"));
} else if (mode & NT_NET_MODE_CLIENT3 || mode & NT_NET_MODE_CLIENT4) {
} else if (mode & NT_NET_MODE_CLIENT) {
if (event.Is(NT_EVENT_CONNECTED)) {
return fmt::format("Glass - Connected ({})",
event.GetConnectionInfo()->remote_ip);

View File

@@ -1997,7 +1997,7 @@ void glass::DisplayNetworkTablesInfo(NetworkTablesModel* model) {
}
auto netMode = inst.GetNetworkMode();
if (netMode == NT_NET_MODE_SERVER || netMode == NT_NET_MODE_CLIENT4) {
if (netMode == NT_NET_MODE_SERVER || netMode == NT_NET_MODE_CLIENT) {
if (CollapsingHeader("Server")) {
PushID("Server");
ImGui::Indent();

View File

@@ -44,66 +44,59 @@ void NetworkTablesSettings::Thread::Main() {
// if just changing servers in client mode, no need to stop and restart
unsigned int curMode = nt::GetNetworkMode(m_inst);
if ((mode == 0 || mode == 3) ||
(mode == 1 && (curMode & NT_NET_MODE_CLIENT4) == 0) ||
(mode == 2 && (curMode & NT_NET_MODE_CLIENT3) == 0)) {
if ((mode == 0 || mode == 2) ||
(mode == 1 && (curMode & NT_NET_MODE_CLIENT) == 0)) {
nt::StopClient(m_inst);
nt::StopServer(m_inst);
nt::StopLocal(m_inst);
}
if ((m_mode == 0 || m_mode == 3) || !dsClient) {
if ((m_mode == 0 || m_mode == 2) || !dsClient) {
nt::StopDSClient(m_inst);
}
lock.lock();
} while (mode != m_mode || dsClient != m_dsClient);
if (m_mode == 1 || m_mode == 2) {
if (m_mode == 1) {
std::string_view serverTeam{m_serverTeam};
std::optional<unsigned int> team;
if (m_mode == 1) {
nt::StartClient4(m_inst, m_clientName);
} else if (m_mode == 2) {
nt::StartClient3(m_inst, m_clientName);
nt::StartClient(m_inst, m_clientName);
}
unsigned int port = m_mode == 1 ? m_port4 : m_port3;
if (!wpi::contains(serverTeam, '.') &&
(team = wpi::parse_integer<unsigned int>(serverTeam, 10))) {
nt::SetServerTeam(m_inst, team.value(), port);
nt::SetServerTeam(m_inst, team.value(), m_port);
} else {
wpi::SmallVector<std::string_view, 4> serverNames;
std::vector<std::pair<std::string_view, unsigned int>> servers;
wpi::split(serverTeam, serverNames, ',', -1, false);
for (auto&& serverName : serverNames) {
servers.emplace_back(serverName, port);
servers.emplace_back(serverName, m_port);
}
nt::SetServer(m_inst, servers);
}
if (m_dsClient) {
nt::StartDSClient(m_inst, port);
nt::StartDSClient(m_inst, m_port);
}
} else if (m_mode == 3) {
nt::StartServer(m_inst, m_iniName.c_str(), m_listenAddress.c_str(),
m_port3, m_port4);
m_port);
}
}
}
NetworkTablesSettings::NetworkTablesSettings(std::string_view clientName,
Storage& storage, NT_Inst inst)
: m_mode{storage.GetString("mode"),
0,
{"Disabled", "Client (NT4)", "Client (NT3)", "Server"}},
: m_mode{storage.GetString("mode"), 0, {"Disabled", "Client", "Server"}},
m_persistentFilename{
storage.GetString("persistentFilename", "networktables.json")},
m_serverTeam{storage.GetString("serverTeam")},
m_listenAddress{storage.GetString("listenAddress")},
m_clientName{storage.GetString("clientName", clientName)},
m_port3{storage.GetInt("port3", NT_DEFAULT_PORT3)},
m_port4{storage.GetInt("port4", NT_DEFAULT_PORT4)},
m_port{storage.GetInt("port", NT_DEFAULT_PORT)},
m_dsClient{storage.GetBool("dsClient", true)} {
m_thread.Start(inst);
}
@@ -122,8 +115,7 @@ void NetworkTablesSettings::Update() {
thr->m_serverTeam = m_serverTeam;
thr->m_listenAddress = m_listenAddress;
thr->m_clientName = m_clientName;
thr->m_port3 = m_port3;
thr->m_port4 = m_port4;
thr->m_port = m_port;
thr->m_dsClient = m_dsClient;
thr->m_cond.notify_one();
}
@@ -137,24 +129,22 @@ static void LimitPortRange(int* port) {
}
bool NetworkTablesSettings::Display() {
m_mode.Combo("Mode", m_serverOption ? 4 : 3);
m_mode.Combo("Mode", m_serverOption ? 3 : 2);
switch (m_mode.GetValue()) {
case 1:
case 2: {
ImGui::InputText("Team/IP", &m_serverTeam);
if (ImGui::IsItemHovered(ImGuiHoveredFlags_DelayNormal)) {
ImGui::SetTooltip("Team number or IP/MDNS address of server");
}
int* port = m_mode.GetValue() == 1 ? &m_port4 : &m_port3;
if (ImGui::InputInt("Port", port)) {
LimitPortRange(port);
if (ImGui::InputInt("Port", &m_port)) {
LimitPortRange(&m_port);
}
if (ImGui::IsItemHovered(ImGuiHoveredFlags_DelayNormal)) {
ImGui::SetTooltip("TCP Port - leave this at the default");
}
ImGui::SameLine();
if (ImGui::SmallButton("Default")) {
*port = m_mode.GetValue() == 1 ? NT_DEFAULT_PORT4 : NT_DEFAULT_PORT3;
m_port = NT_DEFAULT_PORT;
}
ImGui::InputText("Network Identity", &m_clientName);
if (ImGui::IsItemHovered(ImGuiHoveredFlags_DelayNormal)) {
@@ -167,33 +157,22 @@ bool NetworkTablesSettings::Display() {
ImGui::SetTooltip("Attempt to fetch server IP from Driver Station");
}
break;
}
case 3:
case 2:
ImGui::InputText("Listen Address", &m_listenAddress);
if (ImGui::IsItemHovered(ImGuiHoveredFlags_DelayNormal)) {
ImGui::SetTooltip(
"Address for server to listen on. Leave blank to listen on all "
"interfaces.");
}
if (ImGui::InputInt("NT3 port", &m_port3)) {
LimitPortRange(&m_port3);
}
if (ImGui::IsItemHovered(ImGuiHoveredFlags_DelayNormal)) {
ImGui::SetTooltip("TCP Port for NT3. Leave at default if unsure.");
}
ImGui::SameLine();
if (ImGui::SmallButton("Default##default3")) {
m_port3 = NT_DEFAULT_PORT3;
}
if (ImGui::InputInt("NT4 port", &m_port4)) {
LimitPortRange(&m_port4);
if (ImGui::InputInt("Port", &m_port)) {
LimitPortRange(&m_port);
}
if (ImGui::IsItemHovered(ImGuiHoveredFlags_DelayNormal)) {
ImGui::SetTooltip("TCP Port for NT4. Leave at default if unsure.");
}
ImGui::SameLine();
if (ImGui::SmallButton("Default##default4")) {
m_port4 = NT_DEFAULT_PORT4;
m_port = NT_DEFAULT_PORT;
}
ImGui::InputText("Persistent Filename", &m_persistentFilename);
if (ImGui::IsItemHovered(ImGuiHoveredFlags_DelayNormal)) {

View File

@@ -41,8 +41,7 @@ class NetworkTablesSettings {
std::string& m_serverTeam;
std::string& m_listenAddress;
std::string& m_clientName;
int& m_port3;
int& m_port4;
int& m_port;
bool& m_dsClient;
class Thread : public wpi::SafeThread {
@@ -58,8 +57,7 @@ class NetworkTablesSettings {
std::string m_serverTeam;
std::string m_listenAddress;
std::string m_clientName;
int m_port3;
int m_port4;
int m_port;
bool m_dsClient;
};
wpi::SafeThreadOwner<Thread> m_thread;

View File

@@ -16,7 +16,7 @@ void InitializeSystemServer() {
}
ServerInstance = nt::NetworkTableInstance::Create();
ServerInstance.SetServer("localhost", ROBOT_SYSTEM_SERVER_NT_PORT);
ServerInstance.StartClient4("RobotProgram");
ServerInstance.StartClient("RobotProgram");
}
void ShutdownSystemServer() {

View File

@@ -81,8 +81,8 @@ void bench() {
auto server = nt::CreateInstance();
// connect client and server
nt::StartServer(server, "bench.json", "127.0.0.1", 0, 10000);
nt::StartClient4(client, "client");
nt::StartServer(server, "bench.json", "127.0.0.1", 10000);
nt::StartClient(client, "client");
nt::SetServer(client, "127.0.0.1", 10000);
using namespace std::chrono_literals;
@@ -142,11 +142,11 @@ void bench2() {
auto server = nt::CreateInstance();
// connect client and server
nt::StartServer(server, "bench2.json", "127.0.0.1", 10001, 10000);
nt::StartClient4(client1, "client1");
nt::StartClient3(client2, "client2");
nt::StartServer(server, "bench2.json", "127.0.0.1", 10000);
nt::StartClient(client1, "client1");
nt::StartClient(client2, "client2");
nt::SetServer(client1, "127.0.0.1", 10000);
nt::SetServer(client2, "127.0.0.1", 10001);
nt::SetServer(client2, "127.0.0.1", 10000);
using namespace std::chrono_literals;
std::this_thread::sleep_for(1s);
@@ -214,7 +214,7 @@ static std::uniform_real_distribution<double> dist;
void stress() {
auto server = nt::CreateInstance();
nt::StartServer(server, "stress.json", "127.0.0.1", 0, 10000);
nt::StartServer(server, "stress.json", "127.0.0.1", 10000);
nt::SubscribeMultiple(server, {{std::string_view{}}});
using namespace std::chrono_literals;
@@ -228,7 +228,7 @@ void stress() {
std::this_thread::sleep_for(0.1s * dist(gen));
// connect
nt::StartClient4(client, "client");
nt::StartClient(client, "client");
nt::SetServer(client, "127.0.0.1", 10000);
// sleep a random amount of time
@@ -308,7 +308,7 @@ void stress2() {
auto client = nt::NetworkTableInstance::Create();
client.SetServer("localhost");
auto clientName = "test client";
client.StartClient4(clientName);
client.StartClient(clientName);
std::this_thread::sleep_for(2s); // Startup time.
int sentCount = 0;
while (sentCount < count) {

View File

@@ -49,11 +49,8 @@ public final class NetworkTableInstance implements AutoCloseable {
/** Running in server mode. */
kServer(0x01),
/** Running in NT3 client mode. */
kClient3(0x02),
/** Running in NT4 client mode. */
kClient4(0x04),
/** Running in client mode. */
kClient(0x04),
/** Currently starting up (either client or server). */
kStarting(0x08),
@@ -77,11 +74,8 @@ public final class NetworkTableInstance implements AutoCloseable {
}
}
/** The default port that network tables operates on for NT3. */
public static final int kDefaultPort3 = 1735;
/** The default port that network tables operates on for NT4. */
public static final int kDefaultPort4 = 5810;
/** The default port that network tables operates on. */
public static final int kDefaultPort = 5810;
/**
* Construct from native handle.
@@ -897,7 +891,7 @@ public final class NetworkTableInstance implements AutoCloseable {
* @param listenAddress the address to listen on, or empty to listen on any address
*/
public void startServer(String persistFilename, String listenAddress) {
startServer(persistFilename, listenAddress, kDefaultPort3, kDefaultPort4);
startServer(persistFilename, listenAddress, kDefaultPort);
}
/**
@@ -905,22 +899,10 @@ public final class NetworkTableInstance implements AutoCloseable {
*
* @param persistFilename the name of the persist file to use
* @param listenAddress the address to listen on, or empty to listen on any address
* @param port3 port to communicate over (NT3)
* @param port port to communicate over
*/
public void startServer(String persistFilename, String listenAddress, int port3) {
startServer(persistFilename, listenAddress, port3, kDefaultPort4);
}
/**
* Starts a server using the specified filename, listening address, and port.
*
* @param persistFilename the name of the persist file to use
* @param listenAddress the address to listen on, or empty to listen on any address
* @param port3 port to communicate over (NT3)
* @param port4 port to communicate over (NT4)
*/
public void startServer(String persistFilename, String listenAddress, int port3, int port4) {
NetworkTablesJNI.startServer(m_handle, persistFilename, listenAddress, port3, port4);
public void startServer(String persistFilename, String listenAddress, int port) {
NetworkTablesJNI.startServer(m_handle, persistFilename, listenAddress, port);
}
/** Stops the server if it is running. */
@@ -929,21 +911,12 @@ public final class NetworkTableInstance implements AutoCloseable {
}
/**
* Starts a NT3 client. Use SetServer or SetServerTeam to set the server name and port.
* Starts a client. Use SetServer or SetServerTeam to set the server name and port.
*
* @param identity network identity to advertise (cannot be empty string)
*/
public void startClient3(String identity) {
NetworkTablesJNI.startClient3(m_handle, identity);
}
/**
* Starts a NT4 client. Use SetServer or SetServerTeam to set the server name and port.
*
* @param identity network identity to advertise (cannot be empty string)
*/
public void startClient4(String identity) {
NetworkTablesJNI.startClient4(m_handle, identity);
public void startClient(String identity) {
NetworkTablesJNI.startClient(m_handle, identity);
}
/** Stops the client if it is running. */

View File

@@ -864,11 +864,10 @@ public final class NetworkTablesJNI {
* @param inst NT instance handle.
* @param persistFilename the name of the persist file to use
* @param listenAddress the address to listen on, or empty to listen on any address
* @param port3 port to communicate over (NT3)
* @param port4 port to communicate over (NT4)
* @param port port to communicate over
*/
public static native void startServer(
int inst, String persistFilename, String listenAddress, int port3, int port4);
int inst, String persistFilename, String listenAddress, int port);
/**
* Stops the server if it is running.
@@ -878,20 +877,12 @@ public final class NetworkTablesJNI {
public static native void stopServer(int inst);
/**
* Starts a NT3 client. Use SetServer or SetServerTeam to set the server name and port.
* Starts a client. Use SetServer or SetServerTeam to set the server name and port.
*
* @param inst NT instance handle.
* @param identity network identity to advertise (cannot be empty string)
*/
public static native void startClient3(int inst, String identity);
/**
* Starts a NT4 client. Use SetServer or SetServerTeam to set the server name and port.
*
* @param inst NT instance handle.
* @param identity network identity to advertise (cannot be empty string)
*/
public static native void startClient4(int inst, String identity);
public static native void startClient(int inst, String identity);
/**
* Stops the client if it is running.

View File

@@ -49,11 +49,8 @@ public final class NetworkTableInstance implements AutoCloseable {
/** Running in server mode. */
kServer(0x01),
/** Running in NT3 client mode. */
kClient3(0x02),
/** Running in NT4 client mode. */
kClient4(0x04),
/** Running in client mode. */
kClient(0x04),
/** Currently starting up (either client or server). */
kStarting(0x08),
@@ -77,11 +74,8 @@ public final class NetworkTableInstance implements AutoCloseable {
}
}
/** The default port that network tables operates on for NT3. */
public static final int kDefaultPort3 = 1735;
/** The default port that network tables operates on for NT4. */
public static final int kDefaultPort4 = 5810;
/** The default port that network tables operates on. */
public static final int kDefaultPort = 5810;
/**
* Construct from native handle.
@@ -1177,7 +1171,7 @@ public final class NetworkTableInstance implements AutoCloseable {
* @param listenAddress the address to listen on, or empty to listen on any address
*/
public void startServer(String persistFilename, String listenAddress) {
startServer(persistFilename, listenAddress, kDefaultPort3, kDefaultPort4);
startServer(persistFilename, listenAddress, kDefaultPort);
}
/**
@@ -1185,22 +1179,10 @@ public final class NetworkTableInstance implements AutoCloseable {
*
* @param persistFilename the name of the persist file to use
* @param listenAddress the address to listen on, or empty to listen on any address
* @param port3 port to communicate over (NT3)
* @param port port to communicate over
*/
public void startServer(String persistFilename, String listenAddress, int port3) {
startServer(persistFilename, listenAddress, port3, kDefaultPort4);
}
/**
* Starts a server using the specified filename, listening address, and port.
*
* @param persistFilename the name of the persist file to use
* @param listenAddress the address to listen on, or empty to listen on any address
* @param port3 port to communicate over (NT3)
* @param port4 port to communicate over (NT4)
*/
public void startServer(String persistFilename, String listenAddress, int port3, int port4) {
NetworkTablesJNI.startServer(m_handle, persistFilename, listenAddress, port3, port4);
public void startServer(String persistFilename, String listenAddress, int port) {
NetworkTablesJNI.startServer(m_handle, persistFilename, listenAddress, port);
}
/** Stops the server if it is running. */
@@ -1209,21 +1191,12 @@ public final class NetworkTableInstance implements AutoCloseable {
}
/**
* Starts a NT3 client. Use SetServer or SetServerTeam to set the server name and port.
* Starts a client. Use SetServer or SetServerTeam to set the server name and port.
*
* @param identity network identity to advertise (cannot be empty string)
*/
public void startClient3(String identity) {
NetworkTablesJNI.startClient3(m_handle, identity);
}
/**
* Starts a NT4 client. Use SetServer or SetServerTeam to set the server name and port.
*
* @param identity network identity to advertise (cannot be empty string)
*/
public void startClient4(String identity) {
NetworkTablesJNI.startClient4(m_handle, identity);
public void startClient(String identity) {
NetworkTablesJNI.startClient(m_handle, identity);
}
/** Stops the client if it is running. */

View File

@@ -1404,11 +1404,10 @@ public final class NetworkTablesJNI {
* @param inst NT instance handle.
* @param persistFilename the name of the persist file to use
* @param listenAddress the address to listen on, or empty to listen on any address
* @param port3 port to communicate over (NT3)
* @param port4 port to communicate over (NT4)
* @param port port to communicate over
*/
public static native void startServer(
int inst, String persistFilename, String listenAddress, int port3, int port4);
int inst, String persistFilename, String listenAddress, int port);
/**
* Stops the server if it is running.
@@ -1418,20 +1417,12 @@ public final class NetworkTablesJNI {
public static native void stopServer(int inst);
/**
* Starts a NT3 client. Use SetServer or SetServerTeam to set the server name and port.
* Starts a client. Use SetServer or SetServerTeam to set the server name and port.
*
* @param inst NT instance handle.
* @param identity network identity to advertise (cannot be empty string)
*/
public static native void startClient3(int inst, String identity);
/**
* Starts a NT4 client. Use SetServer or SetServerTeam to set the server name and port.
*
* @param inst NT instance handle.
* @param identity network identity to advertise (cannot be empty string)
*/
public static native void startClient4(int inst, String identity);
public static native void startClient(int inst, String identity);
/**
* Stops the client if it is running.

View File

@@ -9,7 +9,7 @@ package edu.wpi.first.networktables;
public final class ConnectionInfo {
/**
* The remote identifier (as set on the remote node by {@link
* NetworkTableInstance#startClient4(String)}).
* NetworkTableInstance#startClient(String)}).
*/
public final String remote_id;

View File

@@ -102,14 +102,14 @@ void InstanceImpl::StopLocal() {
void InstanceImpl::StartServer(std::string_view persistFilename,
std::string_view listenAddress,
unsigned int port3, unsigned int port4) {
unsigned int port) {
std::scoped_lock lock{m_mutex};
if (networkMode != NT_NET_MODE_NONE) {
return;
}
m_networkServer = std::make_shared<NetworkServer>(
persistFilename, listenAddress, port3, port4, localStorage,
connectionList, logger, [this] {
persistFilename, listenAddress, port, localStorage, connectionList,
logger, [this] {
std::scoped_lock lock{m_mutex};
networkMode &= ~NT_NET_MODE_STARTING;
});
@@ -134,20 +134,7 @@ void InstanceImpl::StopServer() {
}
}
void InstanceImpl::StartClient3(std::string_view identity) {
std::scoped_lock lock{m_mutex};
if (networkMode != NT_NET_MODE_NONE) {
return;
}
m_networkClient = std::make_shared<NetworkClient3>(
m_inst, identity, localStorage, connectionList, logger);
if (!m_servers.empty()) {
m_networkClient->SetServers(m_servers);
}
networkMode = NT_NET_MODE_CLIENT3;
}
void InstanceImpl::StartClient4(std::string_view identity) {
void InstanceImpl::StartClient(std::string_view identity) {
std::scoped_lock lock{m_mutex};
if (networkMode != NT_NET_MODE_NONE) {
return;
@@ -169,14 +156,14 @@ void InstanceImpl::StartClient4(std::string_view identity) {
if (!m_servers.empty()) {
m_networkClient->SetServers(m_servers);
}
networkMode = NT_NET_MODE_CLIENT4;
networkMode = NT_NET_MODE_CLIENT;
}
void InstanceImpl::StopClient() {
std::shared_ptr<INetworkClient> client;
{
std::scoped_lock lock{m_mutex};
if ((networkMode & (NT_NET_MODE_CLIENT3 | NT_NET_MODE_CLIENT4)) == 0) {
if ((networkMode & NT_NET_MODE_CLIENT) == 0) {
return;
}
client = std::move(m_networkClient);

View File

@@ -45,11 +45,9 @@ class InstanceImpl {
void StartLocal();
void StopLocal();
void StartServer(std::string_view persistFilename,
std::string_view listenAddress, unsigned int port3,
unsigned int port4);
std::string_view listenAddress, unsigned int port);
void StopServer();
void StartClient3(std::string_view identity);
void StartClient4(std::string_view identity);
void StartClient(std::string_view identity);
void StopClient();
void SetServers(
std::span<const std::pair<std::string, unsigned int>> servers);

View File

@@ -61,7 +61,7 @@ void NetworkClientBase::StartDSClient(unsigned int port) {
if (m_dsClient) {
return;
}
m_dsClientServer.second = port == 0 ? NT_DEFAULT_PORT4 : port;
m_dsClientServer.second = port == 0 ? NT_DEFAULT_PORT : port;
m_dsClient = wpi::DsClient::Create(m_loop, m_logger);
if (m_dsClient) {
m_dsClient->setIp.connect([this](std::string_view ip) {
@@ -142,161 +142,6 @@ void NetworkClientBase::DoDisconnect(std::string_view reason) {
});
}
NetworkClient3::NetworkClient3(int inst, std::string_view id,
net::ILocalStorage& localStorage,
IConnectionList& connList, wpi::Logger& logger)
: NetworkClientBase{inst, id, localStorage, connList, logger} {
m_loopRunner.ExecAsync([this](uv::Loop& loop) {
m_parallelConnect = wpi::ParallelTcpConnector::Create(
loop, kReconnectRate, m_logger,
[this](uv::Tcp& tcp) { TcpConnected(tcp); }, true);
m_sendOutgoingTimer = uv::Timer::Create(loop);
if (m_sendOutgoingTimer) {
m_sendOutgoingTimer->timeout.connect([this] {
if (m_clientImpl) {
HandleLocal();
m_clientImpl->SendPeriodic(m_loop.Now().count(), false);
}
});
}
// set up flush async
m_flush = uv::Async<>::Create(m_loop);
if (m_flush) {
m_flush->wakeup.connect([this] {
if (m_clientImpl) {
HandleLocal();
m_clientImpl->SendPeriodic(m_loop.Now().count(), true);
}
});
}
m_flushAtomic = m_flush.get();
m_flushLocal = uv::Async<>::Create(m_loop);
if (m_flushLocal) {
m_flushLocal->wakeup.connect([this] { HandleLocal(); });
}
m_flushLocalAtomic = m_flushLocal.get();
});
}
NetworkClient3::~NetworkClient3() {
// must explicitly destroy these on loop
m_loopRunner.ExecSync([&](auto&) {
m_clientImpl.reset();
m_wire.reset();
});
// shut down loop here to avoid race
m_loopRunner.Stop();
}
void NetworkClient3::HandleLocal() {
for (;;) {
auto msgs = m_localQueue.ReadQueue(m_localMsgs);
if (msgs.empty()) {
return;
}
if (m_clientImpl) {
m_clientImpl->HandleLocal(msgs);
}
}
}
void NetworkClient3::TcpConnected(uv::Tcp& tcp) {
tcp.SetNoDelay(true);
// create as shared_ptr and capture in lambda because there may be multiple
// simultaneous attempts
auto wire = std::make_shared<net3::UvStreamConnection3>(tcp);
auto clientImpl = std::make_shared<net3::ClientImpl3>(
m_loop.Now().count(), m_inst, *wire, m_logger, [this](uint32_t repeatMs) {
DEBUG4("Setting periodic timer to {}", repeatMs);
if (m_sendOutgoingTimer &&
(!m_sendOutgoingTimer->IsActive() ||
uv::Timer::Time{repeatMs} != m_sendOutgoingTimer->GetRepeat())) {
m_sendOutgoingTimer->Start(uv::Timer::Time{repeatMs},
uv::Timer::Time{repeatMs});
}
});
clientImpl->Start(
m_id, [this, wire,
clientWeak = std::weak_ptr<net3::ClientImpl3>{clientImpl}, &tcp] {
auto clientImpl = clientWeak.lock();
if (!clientImpl) {
return;
}
if (m_connList.IsConnected()) {
tcp.Close(); // no longer needed
return;
}
if (m_parallelConnect) {
m_parallelConnect->Succeeded(tcp);
}
m_wire = std::move(wire);
m_clientImpl = std::move(clientImpl);
ConnectionInfo connInfo;
uv::AddrToName(tcp.GetPeer(), &connInfo.remote_ip,
&connInfo.remote_port);
connInfo.protocol_version = 0x0300;
INFO("CONNECTED NT3 to {} port {}", connInfo.remote_ip,
connInfo.remote_port);
m_connHandle = m_connList.AddConnection(connInfo);
tcp.error.connect([this, &tcp](uv::Error err) {
DEBUG3("NT3 TCP error {}", err.str());
if (!tcp.IsLoopClosing()) {
// we could be in the middle of sending data, so defer disconnect
uv::Timer::SingleShot(m_loop, uv::Timer::Time{0},
[this, reason = std::string{err.str()}] {
DoDisconnect(reason);
});
}
});
tcp.end.connect([this, &tcp] {
DEBUG3("NT3 TCP read ended");
if (!tcp.IsLoopClosing()) {
DoDisconnect("remote end closed connection");
}
});
tcp.closed.connect([this, &tcp] {
DEBUG3("NT3 TCP connection closed");
if (!tcp.IsLoopClosing()) {
DoDisconnect(m_wire ? m_wire->GetDisconnectReason() : "unknown");
}
});
m_clientImpl->SetLocal(&m_localStorage);
m_localStorage.StartNetwork(&m_localQueue);
HandleLocal();
});
tcp.SetData(clientImpl);
tcp.data.connect(
[clientImpl = clientImpl.get()](uv::Buffer& buf, size_t len) {
clientImpl->ProcessIncoming(
{reinterpret_cast<const uint8_t*>(buf.base), len});
});
tcp.StartRead();
}
void NetworkClient3::ForceDisconnect(std::string_view reason) {
if (m_wire) {
m_wire->Disconnect(reason);
}
}
void NetworkClient3::DoDisconnect(std::string_view reason) {
INFO("DISCONNECTED NT3 connection: {}", reason);
m_clientImpl.reset();
m_wire.reset();
NetworkClientBase::DoDisconnect(reason);
}
NetworkClient::NetworkClient(
int inst, std::string_view id, net::ILocalStorage& localStorage,
IConnectionList& connList, wpi::Logger& logger,

View File

@@ -25,8 +25,6 @@
#include "net/ClientMessageQueue.h"
#include "net/Message.h"
#include "net/WebSocketConnection.h"
#include "net3/ClientImpl3.h"
#include "net3/UvStreamConnection3.h"
namespace wpi {
class Logger;
@@ -98,28 +96,6 @@ class NetworkClientBase : public INetworkClient {
wpi::uv::Loop& m_loop;
};
class NetworkClient3 final : public NetworkClientBase {
public:
NetworkClient3(int inst, std::string_view id,
net::ILocalStorage& localStorage, IConnectionList& connList,
wpi::Logger& logger);
~NetworkClient3() final;
void SetServers(
std::span<const std::pair<std::string, unsigned int>> servers) final {
DoSetServers(servers, NT_DEFAULT_PORT3);
}
private:
void HandleLocal();
void TcpConnected(wpi::uv::Tcp& tcp) final;
void ForceDisconnect(std::string_view reason) override;
void DoDisconnect(std::string_view reason) override;
std::shared_ptr<net3::UvStreamConnection3> m_wire;
std::shared_ptr<net3::ClientImpl3> m_clientImpl;
};
class NetworkClient final : public NetworkClientBase {
public:
NetworkClient(
@@ -131,7 +107,7 @@ class NetworkClient final : public NetworkClientBase {
void SetServers(
std::span<const std::pair<std::string, unsigned int>> servers) final {
DoSetServers(servers, NT_DEFAULT_PORT4);
DoSetServers(servers, NT_DEFAULT_PORT);
}
private:

View File

@@ -34,7 +34,6 @@
#include "net/WebSocketConnection.h"
#include "net/WireDecoder.h"
#include "net/WireEncoder.h"
#include "net3/UvStreamConnection3.h"
using namespace nt;
namespace uv = wpi::uv;
@@ -72,16 +71,6 @@ class NetworkServer::ServerConnection {
std::shared_ptr<uv::Timer> m_outgoingTimer;
};
class NetworkServer::ServerConnection3 : public ServerConnection {
public:
ServerConnection3(std::shared_ptr<uv::Stream> stream, NetworkServer& server,
std::string_view addr, unsigned int port,
wpi::Logger& logger);
private:
std::shared_ptr<net3::UvStreamConnection3> m_wire;
};
class NetworkServer::ServerConnection4 final
: public ServerConnection,
public wpi::HttpWebSocketServerConnection<ServerConnection4> {
@@ -134,55 +123,6 @@ void NetworkServer::ServerConnection::ConnectionClosed() {
m_outgoingTimer->Close();
}
NetworkServer::ServerConnection3::ServerConnection3(
std::shared_ptr<uv::Stream> stream, NetworkServer& server,
std::string_view addr, unsigned int port, wpi::Logger& logger)
: ServerConnection{server, addr, port, logger},
m_wire{std::make_shared<net3::UvStreamConnection3>(*stream)} {
m_info.remote_ip = addr;
m_info.remote_port = port;
// TODO: set local flag appropriately
m_clientId = m_server.m_serverImpl.AddClient3(
m_connInfo, false, *m_wire,
[this](std::string_view name, uint16_t proto) {
m_info.remote_id = name;
m_info.protocol_version = proto;
m_server.AddConnection(this, m_info);
INFO("CONNECTED NT3 client '{}' (from {})", name, m_connInfo);
},
[this](uint32_t repeatMs) { UpdateOutgoingTimer(repeatMs); });
stream->error.connect([this](uv::Error err) {
if (!m_wire->GetDisconnectReason().empty()) {
return;
}
m_wire->Disconnect(fmt::format("stream error: {}", err.name()));
m_wire->GetStream().Shutdown([this] { m_wire->GetStream().Close(); });
});
stream->end.connect([this] {
if (!m_wire->GetDisconnectReason().empty()) {
return;
}
m_wire->Disconnect("remote end closed connection");
m_wire->GetStream().Shutdown([this] { m_wire->GetStream().Close(); });
});
stream->closed.connect([this] {
INFO("DISCONNECTED NT3 client '{}' (from {}): {}", m_info.remote_id,
m_connInfo, m_wire->GetDisconnectReason());
ConnectionClosed();
});
stream->data.connect([this](uv::Buffer& buf, size_t size) {
if (m_server.m_serverImpl.ProcessIncomingBinary(
m_clientId, {reinterpret_cast<const uint8_t*>(buf.base), size})) {
m_server.m_idle->Start();
}
});
stream->StartRead();
SetupOutgoingTimer();
}
void NetworkServer::ServerConnection4::ProcessRequest() {
DEBUG1("HTTP request: '{}'", m_request.GetUrl());
wpi::UrlParser url{m_request.GetUrl(),
@@ -311,8 +251,7 @@ void NetworkServer::ServerConnection4::ProcessWsUpgrade() {
}
NetworkServer::NetworkServer(std::string_view persistentFilename,
std::string_view listenAddress, unsigned int port3,
unsigned int port4,
std::string_view listenAddress, unsigned int port,
net::ILocalStorage& localStorage,
IConnectionList& connList, wpi::Logger& logger,
std::function<void()> initDone)
@@ -322,8 +261,7 @@ NetworkServer::NetworkServer(std::string_view persistentFilename,
m_initDone{std::move(initDone)},
m_persistentFilename{persistentFilename},
m_listenAddress{wpi::trim(listenAddress)},
m_port3{port3},
m_port4{port4},
m_port{port},
m_serverImpl{logger},
m_localQueue{logger},
m_loop(*m_loopRunner.GetLoop()) {
@@ -485,46 +423,14 @@ void NetworkServer::Init() {
});
}
INFO("Listening on NT3 port {}, NT4 port {}", m_port3, m_port4);
INFO("Listening on port {}", m_port);
if (m_port3 != 0) {
auto tcp3 = uv::Tcp::Create(m_loop);
tcp3->error.connect([logger = &m_logger](uv::Error err) {
WPI_INFO(*logger, "NT3 server socket error: {}", err.str());
});
tcp3->Bind(m_listenAddress, m_port3);
// when we get a NT3 connection, accept it and start reading
tcp3->connection.connect([this, srv = tcp3.get()] {
auto tcp = srv->Accept();
if (!tcp) {
return;
}
tcp->error.connect([logger = &m_logger](uv::Error err) {
WPI_INFO(*logger, "NT3 socket error: {}", err.str());
});
tcp->SetNoDelay(true);
std::string peerAddr;
unsigned int peerPort = 0;
if (uv::AddrToName(tcp->GetPeer(), &peerAddr, &peerPort) == 0) {
INFO("Got a NT3 connection from {} port {}", peerAddr, peerPort);
} else {
INFO("Got a NT3 connection from unknown");
}
auto conn = std::make_shared<ServerConnection3>(tcp, *this, peerAddr,
peerPort, m_logger);
tcp->SetData(conn);
});
tcp3->Listen();
}
if (m_port4 != 0) {
if (m_port != 0) {
auto tcp4 = uv::Tcp::Create(m_loop);
tcp4->error.connect([logger = &m_logger](uv::Error err) {
WPI_INFO(*logger, "NT4 server socket error: {}", err.str());
});
tcp4->Bind(m_listenAddress, m_port4);
tcp4->Bind(m_listenAddress, m_port);
// when we get a NT4 connection, accept it and start reading
tcp4->connection.connect([this, srv = tcp4.get()] {

View File

@@ -36,10 +36,9 @@ class IConnectionList;
class NetworkServer {
public:
NetworkServer(std::string_view persistentFilename,
std::string_view listenAddress, unsigned int port3,
unsigned int port4, net::ILocalStorage& localStorage,
IConnectionList& connList, wpi::Logger& logger,
std::function<void()> initDone);
std::string_view listenAddress, unsigned int port,
net::ILocalStorage& localStorage, IConnectionList& connList,
wpi::Logger& logger, std::function<void()> initDone);
~NetworkServer();
void FlushLocal();
@@ -47,7 +46,6 @@ class NetworkServer {
private:
class ServerConnection;
class ServerConnection3;
class ServerConnection4;
void ProcessAllLocal();
@@ -64,8 +62,7 @@ class NetworkServer {
std::string m_persistentData;
std::string m_persistentFilename;
std::string m_listenAddress;
unsigned int m_port3;
unsigned int m_port4;
unsigned int m_port;
// used only from loop
std::shared_ptr<wpi::uv::Timer> m_readLocalTimer;

View File

@@ -1173,12 +1173,12 @@ Java_edu_wpi_first_networktables_NetworkTablesJNI_stopLocal
/*
* Class: edu_wpi_first_networktables_NetworkTablesJNI
* Method: startServer
* Signature: (ILjava/lang/String;Ljava/lang/String;II)V
* Signature: (ILjava/lang/String;Ljava/lang/String;I)V
*/
JNIEXPORT void JNICALL
Java_edu_wpi_first_networktables_NetworkTablesJNI_startServer
(JNIEnv* env, jclass, jint inst, jstring persistFilename,
jstring listenAddress, jint port3, jint port4)
jstring listenAddress, jint port)
{
if (!persistFilename) {
nullPointerEx.Throw(env, "persistFilename cannot be null");
@@ -1189,7 +1189,7 @@ Java_edu_wpi_first_networktables_NetworkTablesJNI_startServer
return;
}
nt::StartServer(inst, JStringRef{env, persistFilename}.str(),
JStringRef{env, listenAddress}.c_str(), port3, port4);
JStringRef{env, listenAddress}.c_str(), port);
}
/*
@@ -1206,34 +1206,18 @@ Java_edu_wpi_first_networktables_NetworkTablesJNI_stopServer
/*
* Class: edu_wpi_first_networktables_NetworkTablesJNI
* Method: startClient3
* Method: startClient
* Signature: (ILjava/lang/String;)V
*/
JNIEXPORT void JNICALL
Java_edu_wpi_first_networktables_NetworkTablesJNI_startClient3
Java_edu_wpi_first_networktables_NetworkTablesJNI_startClient
(JNIEnv* env, jclass, jint inst, jstring identity)
{
if (!identity) {
nullPointerEx.Throw(env, "identity cannot be null");
return;
}
nt::StartClient3(inst, JStringRef{env, identity}.str());
}
/*
* Class: edu_wpi_first_networktables_NetworkTablesJNI
* Method: startClient4
* Signature: (ILjava/lang/String;)V
*/
JNIEXPORT void JNICALL
Java_edu_wpi_first_networktables_NetworkTablesJNI_startClient4
(JNIEnv* env, jclass, jint inst, jstring identity)
{
if (!identity) {
nullPointerEx.Throw(env, "identity cannot be null");
return;
}
nt::StartClient4(inst, JStringRef{env, identity}.str());
nt::StartClient(inst, JStringRef{env, identity}.str());
}
/*

View File

@@ -1,468 +0,0 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#include "ClientImpl3.h"
#include <algorithm>
#include <memory>
#include <numeric>
#include <string>
#include <utility>
#include <vector>
#include <fmt/format.h>
#include <wpi/DenseMap.h>
#include <wpi/StringMap.h>
#include <wpi/timestamp.h>
#include "Log.h"
#include "Types_internal.h"
#include "net/Message.h"
#include "net/NetworkInterface.h"
#include "net3/WireEncoder3.h"
#include "networktables/NetworkTableValue.h"
using namespace nt;
using namespace nt::net3;
static constexpr uint32_t kMinPeriodMs = 5;
// maximum amount of time the wire can be not ready to send another
// transmission before we close the connection
static constexpr uint32_t kWireMaxNotReadyUs = 1000000;
wpi::json ClientImpl3::Entry::SetFlags(unsigned int flags_) {
bool wasPersistent = IsPersistent();
flags = flags_;
bool isPersistent = IsPersistent();
if (isPersistent && !wasPersistent) {
properties["persistent"] = true;
return {{"persistent", true}};
} else if (!isPersistent && wasPersistent) {
properties.erase("persistent");
return {{"persistent", wpi::json()}};
} else {
return wpi::json::object();
}
}
ClientImpl3::ClientImpl3(uint64_t curTimeMs, int inst, WireConnection3& wire,
wpi::Logger& logger,
std::function<void(uint32_t repeatMs)> setPeriodic)
: m_wire{wire},
m_logger{logger},
m_setPeriodic{std::move(setPeriodic)},
m_initTimeMs{curTimeMs},
m_nextKeepAliveTimeMs{curTimeMs + kKeepAliveIntervalMs},
m_decoder{*this} {}
ClientImpl3::~ClientImpl3() {
DEBUG4("NT3 ClientImpl destroyed");
}
void ClientImpl3::ProcessIncoming(std::span<const uint8_t> data) {
DEBUG4("received {} bytes", data.size());
if (!m_decoder.Execute(&data)) {
m_wire.Disconnect(m_decoder.GetError());
}
}
void ClientImpl3::HandleLocal(std::span<const net::ClientMessage> msgs) {
for (const auto& elem : msgs) { // NOLINT
// common case is value
if (auto msg = std::get_if<net::ClientValueMsg>(&elem.contents)) {
SetValue(msg->pubuid, msg->value);
} else if (auto msg = std::get_if<net::PublishMsg>(&elem.contents)) {
Publish(msg->pubuid, msg->name, msg->typeStr, msg->properties,
msg->options);
} else if (auto msg = std::get_if<net::UnpublishMsg>(&elem.contents)) {
Unpublish(msg->pubuid);
} else if (auto msg = std::get_if<net::SetPropertiesMsg>(&elem.contents)) {
SetProperties(msg->name, msg->update);
}
}
}
void ClientImpl3::DoSendPeriodic(uint64_t curTimeMs, bool initial, bool flush) {
DEBUG4("SendPeriodic({})", curTimeMs);
// rate limit sends
if (curTimeMs < (m_lastSendMs + kMinPeriodMs)) {
return;
}
auto out = m_wire.Send();
// send keep-alive
if (curTimeMs >= m_nextKeepAliveTimeMs) {
if (!CheckNetworkReady(curTimeMs)) {
return;
}
DEBUG4("Sending keep alive");
WireEncodeKeepAlive(out.stream());
// drift isn't critical here, so just go from current time
m_nextKeepAliveTimeMs = curTimeMs + kKeepAliveIntervalMs;
}
// send any stored-up flags updates
if (!m_outgoingFlags.empty()) {
if (!CheckNetworkReady(curTimeMs)) {
return;
}
for (auto&& p : m_outgoingFlags) {
WireEncodeFlagsUpdate(out.stream(), p.first, p.second);
}
m_outgoingFlags.resize(0);
}
// send any pending updates due to be sent
bool checkedNetwork = false;
for (auto&& pub : m_publishers) {
if (pub && !pub->outValues.empty() &&
(flush || curTimeMs >= pub->nextSendMs)) {
if (!checkedNetwork) {
if (!CheckNetworkReady(curTimeMs)) {
return;
}
checkedNetwork = true;
}
for (auto&& val : pub->outValues) {
SendValue(out, pub->entry, val);
}
pub->outValues.resize(0);
pub->nextSendMs = curTimeMs + pub->periodMs;
}
}
if (initial) {
DEBUG4("Sending ClientHelloDone");
WireEncodeClientHelloDone(out.stream());
}
m_wire.Flush();
m_lastSendMs = curTimeMs;
}
void ClientImpl3::SendValue(Writer& out, Entry* entry, const Value& value) {
DEBUG4("sending value for '{}', seqnum {}", entry->name,
entry->seqNum.value());
// bump sequence number
++entry->seqNum;
// only send assigns during initial handshake
if (entry->id == 0xffff || m_state == kStateInitialAssignments) {
// send assign
WireEncodeEntryAssign(out.stream(), entry->name, entry->id,
entry->seqNum.value(), value, entry->flags);
} else {
// send update
WireEncodeEntryUpdate(out.stream(), entry->id, entry->seqNum.value(),
value);
}
}
bool ClientImpl3::CheckNetworkReady(uint64_t curTimeMs) {
if (!m_wire.Ready()) {
uint64_t lastFlushTime = m_wire.GetLastFlushTime();
uint64_t now = wpi::Now();
if (lastFlushTime != 0 && now > (lastFlushTime + kWireMaxNotReadyUs)) {
m_wire.Disconnect("transmit stalled");
}
return false;
}
return true;
}
void ClientImpl3::Publish(int pubuid, std::string_view name,
std::string_view typeStr, const wpi::json& properties,
const PubSubOptionsImpl& options) {
DEBUG4("Publish('{}', '{}')", name, typeStr);
if (static_cast<unsigned int>(pubuid) >= m_publishers.size()) {
m_publishers.resize(pubuid + 1);
}
auto& publisher = m_publishers[pubuid];
if (!publisher) {
publisher = std::make_unique<PublisherData>(GetOrNewEntry(name));
publisher->entry->typeStr = typeStr;
publisher->entry->type = StringToType3(typeStr);
publisher->entry->publishers.emplace_back(publisher.get());
}
publisher->options = options;
publisher->periodMs = std::lround(options.periodicMs / 10.0) * 10;
if (publisher->periodMs < 10) {
publisher->periodMs = 10;
}
// update period
m_periodMs = std::gcd(m_periodMs, publisher->periodMs);
m_setPeriodic(m_periodMs);
}
void ClientImpl3::Unpublish(int pubuid) {
DEBUG4("Unpublish({})", pubuid);
if (static_cast<unsigned int>(pubuid) >= m_publishers.size()) {
return;
}
auto& publisher = m_publishers[pubuid];
publisher->entry->publishers.erase(
std::remove(publisher->entry->publishers.begin(),
publisher->entry->publishers.end(), publisher.get()),
publisher->entry->publishers.end());
publisher.reset();
// loop over all publishers to update period
m_periodMs = kKeepAliveIntervalMs + 10;
for (auto&& pub : m_publishers) {
if (pub) {
m_periodMs = std::gcd(m_periodMs, pub->periodMs);
}
}
m_setPeriodic(m_periodMs);
}
void ClientImpl3::SetProperties(std::string_view name,
const wpi::json& update) {
DEBUG4("SetProperties({}, {})", name, update.dump());
auto entry = GetOrNewEntry(name);
bool updated = false;
for (auto&& elem : update.items()) {
entry->properties[elem.key()] = elem.value();
if (elem.key() == "persistent") {
if (auto val = elem.value().get_ptr<const bool*>()) {
if (*val) {
entry->flags |= NT_PERSISTENT;
} else {
entry->flags &= ~NT_PERSISTENT;
}
updated = true;
}
}
}
if (updated && entry->id == 0xffff) {
m_outgoingFlags.emplace_back(entry->id, entry->flags);
}
}
void ClientImpl3::SetValue(int pubuid, const Value& value) {
DEBUG4("SetValue({})", pubuid);
assert(static_cast<unsigned int>(pubuid) < m_publishers.size() &&
m_publishers[pubuid]);
auto& publisher = *m_publishers[pubuid];
if (value == publisher.entry->value) {
return;
}
publisher.entry->value = value;
if (publisher.outValues.empty() || publisher.options.sendAll) {
publisher.outValues.emplace_back(value);
} else {
publisher.outValues.back() = value;
}
}
void ClientImpl3::KeepAlive() {
DEBUG4("KeepAlive()");
if (m_state != kStateRunning && m_state != kStateInitialAssignments) {
m_decoder.SetError("received unexpected KeepAlive message");
return;
}
// ignore
}
void ClientImpl3::ServerHelloDone() {
DEBUG4("ServerHelloDone()");
if (m_state != kStateInitialAssignments) {
m_decoder.SetError("received unexpected ServerHelloDone message");
return;
}
// send initial assignments
DoSendPeriodic(m_initTimeMs, true, true);
m_state = kStateRunning;
m_setPeriodic(m_periodMs);
}
void ClientImpl3::ClientHelloDone() {
DEBUG4("ClientHelloDone()");
m_decoder.SetError("received unexpected ClientHelloDone message");
}
void ClientImpl3::ProtoUnsup(unsigned int proto_rev) {
DEBUG4("ProtoUnsup({})", proto_rev);
m_decoder.SetError(fmt::format("received ProtoUnsup(version={})", proto_rev));
}
void ClientImpl3::ClientHello(std::string_view self_id,
unsigned int proto_rev) {
DEBUG4("ClientHello({}, {})", self_id, proto_rev);
m_decoder.SetError("received unexpected ClientHello message");
}
void ClientImpl3::ServerHello(unsigned int flags, std::string_view self_id) {
DEBUG4("ServerHello({}, {})", flags, self_id);
if (m_state != kStateHelloSent) {
m_decoder.SetError("received unexpected ServerHello message");
return;
}
m_state = kStateInitialAssignments;
m_remoteId = self_id;
m_handshakeSucceeded();
m_handshakeSucceeded = nullptr; // no longer required
}
void ClientImpl3::EntryAssign(std::string_view name, unsigned int id,
unsigned int seq_num, const Value& value,
unsigned int flags) {
DEBUG4("EntryAssign({}, {}, {}, value, {})", name, id, seq_num, flags);
if (m_state != kStateInitialAssignments && m_state != kStateRunning) {
m_decoder.SetError("received unexpected EntryAssign message");
return;
}
auto entry = GetOrNewEntry(name);
bool flagsChanged = entry->flags != flags;
bool typeChanged;
bool valueChanged;
// don't update value if we locally published a "strong" value
if (m_state == kStateInitialAssignments && entry->value &&
entry->value.server_time() != 0) {
typeChanged = false;
valueChanged = false;
} else {
typeChanged = entry->type != value.type();
valueChanged = entry->value != value;
if (m_state == kStateInitialAssignments) {
// remove outgoing during initial assignments so we don't get out of sync
for (auto publisher : entry->publishers) {
publisher->outValues.clear();
}
}
}
entry->id = id;
entry->seqNum = SequenceNumber{seq_num};
entry->SetFlags(flags);
if (typeChanged) {
entry->type = value.type();
entry->typeStr = TypeToString(value.type());
}
if (valueChanged) {
entry->value = value;
}
// add to id map
if (id >= m_idMap.size()) {
m_idMap.resize(id + 1);
}
m_idMap[id] = entry;
if (m_local) {
// XXX: need to handle type change specially? (e.g. with unannounce)
if (!entry->topic || flagsChanged || typeChanged) {
DEBUG4("NetworkAnnounce({}, {})", name, entry->typeStr);
entry->topic = m_local->ServerAnnounce(name, 0, entry->typeStr,
entry->properties, std::nullopt);
}
if (valueChanged) {
m_local->ServerSetValue(entry->topic.value(), entry->value);
}
}
}
void ClientImpl3::EntryUpdate(unsigned int id, unsigned int seq_num,
const Value& value) {
DEBUG4("EntryUpdate({}, {}, value)", id, seq_num);
if (m_state != kStateRunning) {
m_decoder.SetError("received EntryUpdate message before ServerHelloDone");
return;
}
if (auto entry = LookupId(id)) {
entry->value = value;
if (m_local && entry->topic) {
m_local->ServerSetValue(entry->topic.value(), entry->value);
}
}
}
void ClientImpl3::FlagsUpdate(unsigned int id, unsigned int flags) {
DEBUG4("FlagsUpdate({}, {})", id, flags);
if (m_state != kStateRunning) {
m_decoder.SetError("received FlagsUpdate message before ServerHelloDone");
return;
}
if (auto entry = LookupId(id)) {
wpi::json update = entry->SetFlags(flags);
if (!update.empty() && m_local) {
m_local->ServerPropertiesUpdate(entry->name, update, false);
}
}
// erase any outgoing flags updates
m_outgoingFlags.erase(
std::remove_if(m_outgoingFlags.begin(), m_outgoingFlags.end(),
[&](const auto& p) { return p.first == id; }),
m_outgoingFlags.end());
}
void ClientImpl3::EntryDelete(unsigned int id) {
DEBUG4("EntryDelete({})", id);
if (m_state != kStateRunning) {
m_decoder.SetError("received EntryDelete message before ServerHelloDone");
return;
}
if (auto entry = LookupId(id)) {
m_idMap[id] = nullptr;
// set id to 0xffff so any future local setvalue will result in assign
entry->id = 0xffff;
entry->value = Value{};
// if we have no local publishers, unannounce
if (entry->publishers.empty() && m_local && entry->topic) {
m_local->ServerUnannounce(entry->name, entry->topic.value());
}
}
// erase any outgoing flags updates
m_outgoingFlags.erase(
std::remove_if(m_outgoingFlags.begin(), m_outgoingFlags.end(),
[&](const auto& p) { return p.first == id; }),
m_outgoingFlags.end());
}
void ClientImpl3::ClearEntries() {
DEBUG4("ClearEntries()");
if (m_state != kStateRunning) {
m_decoder.SetError("received ClearEntries message before ServerHelloDone");
return;
}
for (auto& entry : m_idMap) {
if (entry && entry->id != 0xffff && !entry->IsPersistent()) {
entry->id = 0xffff;
entry->value = Value{};
// if we have no local publishers, unannounce
if (entry->publishers.empty() && m_local && entry->topic) {
m_local->ServerUnannounce(entry->name, entry->topic.value());
}
entry = nullptr; // clear id mapping
}
}
// erase all outgoing flags updates
m_outgoingFlags.resize(0);
}
void ClientImpl3::Start(std::string_view selfId,
std::function<void()> succeeded) {
if (m_state != kStateInitial) {
return;
}
m_handshakeSucceeded = std::move(succeeded);
auto writer = m_wire.Send();
WireEncodeClientHello(writer.stream(), selfId, 0x0300);
m_wire.Flush();
m_state = kStateHelloSent;
}

View File

@@ -1,177 +0,0 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#pragma once
#include <stdint.h>
#include <functional>
#include <memory>
#include <optional>
#include <span>
#include <string>
#include <string_view>
#include <utility>
#include <vector>
#include <wpi/StringMap.h>
#include <wpi/json.h>
#include "PubSubOptions.h"
#include "net/MessageHandler.h"
#include "net3/Message3.h"
#include "net3/SequenceNumber.h"
#include "net3/WireConnection3.h"
#include "net3/WireDecoder3.h"
namespace wpi {
class Logger;
} // namespace wpi
namespace nt::net {
struct ClientMessage;
class LocalInterface;
} // namespace nt::net
namespace nt::net3 {
class WireConnection3;
class ClientImpl3 final : private MessageHandler3 {
public:
explicit ClientImpl3(uint64_t curTimeMs, int inst, WireConnection3& wire,
wpi::Logger& logger,
std::function<void(uint32_t repeatMs)> setPeriodic);
~ClientImpl3() final;
void Start(std::string_view selfId, std::function<void()> succeeded);
void ProcessIncoming(std::span<const uint8_t> data);
void HandleLocal(std::span<const net::ClientMessage> msgs);
void SendPeriodic(uint64_t curTimeMs, bool flush) {
DoSendPeriodic(curTimeMs, false, flush);
}
void SetLocal(net::ServerMessageHandler* local) { m_local = local; }
private:
struct Entry;
struct PublisherData {
explicit PublisherData(Entry* entry) : entry{entry} {}
Entry* entry;
PubSubOptionsImpl options;
// in options as double, but copy here as integer; rounded to the nearest
// 10 ms
uint32_t periodMs;
uint64_t nextSendMs{0};
std::vector<Value> outValues; // outgoing values
};
// data for each entry
struct Entry {
explicit Entry(std::string_view name_) : name(name_) {}
bool IsPersistent() const { return (flags & NT_PERSISTENT) != 0; }
wpi::json SetFlags(unsigned int flags_);
std::string name;
std::string typeStr;
NT_Type type{NT_UNASSIGNED};
wpi::json properties = wpi::json::object();
// The current value and flags
Value value;
unsigned int flags{0};
// Unique ID used in network messages; this is 0xffff until assigned
// by the server.
unsigned int id{0xffff};
// Sequence number for update resolution
SequenceNumber seqNum;
// Local topic id
std::optional<int> topic;
// Local publishers
std::vector<PublisherData*> publishers;
};
void DoSendPeriodic(uint64_t curTimeMs, bool initial, bool flush);
void SendValue(Writer& out, Entry* entry, const Value& value);
bool CheckNetworkReady(uint64_t curTimeMs);
// Outgoing handlers
void Publish(int pubuid, std::string_view name, std::string_view typeStr,
const wpi::json& properties, const PubSubOptionsImpl& options);
void Unpublish(int pubuid);
void SetProperties(std::string_view name, const wpi::json& update);
void SetValue(int pubuid, const Value& value);
// MessageHandler interface
void KeepAlive() final;
void ServerHelloDone() final;
void ClientHelloDone() final;
void ClearEntries() final;
void ProtoUnsup(unsigned int proto_rev) final;
void ClientHello(std::string_view self_id, unsigned int proto_rev) final;
void ServerHello(unsigned int flags, std::string_view self_id) final;
void EntryAssign(std::string_view name, unsigned int id, unsigned int seq_num,
const Value& value, unsigned int flags) final;
void EntryUpdate(unsigned int id, unsigned int seq_num,
const Value& value) final;
void FlagsUpdate(unsigned int id, unsigned int flags) final;
void EntryDelete(unsigned int id) final;
void ExecuteRpc(unsigned int id, unsigned int uid,
std::span<const uint8_t> params) final {}
void RpcResponse(unsigned int id, unsigned int uid,
std::span<const uint8_t> result) final {}
enum State {
kStateInitial,
kStateHelloSent,
kStateInitialAssignments,
kStateRunning
};
WireConnection3& m_wire;
wpi::Logger& m_logger;
net::ServerMessageHandler* m_local{nullptr};
std::function<void(uint32_t repeatMs)> m_setPeriodic;
uint64_t m_initTimeMs;
// periodic sweep handling
static constexpr uint32_t kKeepAliveIntervalMs = 1000;
uint32_t m_periodMs{kKeepAliveIntervalMs + 10};
uint64_t m_lastSendMs{0};
uint64_t m_nextKeepAliveTimeMs;
// indexed by publisher index
std::vector<std::unique_ptr<PublisherData>> m_publishers;
State m_state{kStateInitial};
WireDecoder3 m_decoder;
std::string m_remoteId;
std::function<void()> m_handshakeSucceeded;
std::vector<std::pair<unsigned int, unsigned int>> m_outgoingFlags;
using NameMap = wpi::StringMap<Entry>;
using IdMap = std::vector<Entry*>;
NameMap m_nameMap;
IdMap m_idMap;
Entry* GetOrNewEntry(std::string_view name) {
return &m_nameMap.try_emplace(name, name).first->second;
}
Entry* LookupId(unsigned int id) {
return id < m_idMap.size() ? m_idMap[id] : nullptr;
}
};
} // namespace nt::net3

View File

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

View File

@@ -1,38 +0,0 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#pragma once
#include <compare>
namespace nt::net3 {
/* A sequence number per RFC 1982 */
class SequenceNumber {
public:
SequenceNumber() = default;
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 auto operator<=>(const SequenceNumber& lhs,
const SequenceNumber& rhs) = default;
private:
unsigned int m_value{0};
};
} // namespace nt::net3

View File

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

View File

@@ -1,78 +0,0 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#pragma once
#include <memory>
#include <string>
#include <string_view>
#include <vector>
#include <wpi/SmallVector.h>
#include <wpinet/raw_uv_ostream.h>
#include <wpinet/uv/Buffer.h>
#include <wpinet/uv/Stream.h>
#include "net3/WireConnection3.h"
namespace wpi::uv {
class Stream;
} // namespace wpi::uv
namespace nt::net3 {
class UvStreamConnection3 final
: public WireConnection3,
public std::enable_shared_from_this<UvStreamConnection3> {
static constexpr size_t kAllocSize = 4096;
public:
explicit UvStreamConnection3(wpi::uv::Stream& stream);
~UvStreamConnection3() override;
UvStreamConnection3(const UvStreamConnection3&) = delete;
UvStreamConnection3& operator=(const UvStreamConnection3&) = delete;
bool Ready() const final { return m_sendsActive == 0; }
Writer Send() final { return {m_os, *this}; }
void Flush() final;
uint64_t GetLastFlushTime() const final { return m_lastFlushTime; }
void StopRead() final {
if (m_readActive) {
m_stream.StopRead();
m_readActive = false;
}
}
void StartRead() final {
if (!m_readActive) {
m_stream.StartRead();
m_readActive = true;
}
}
void Disconnect(std::string_view reason) final;
std::string_view GetDisconnectReason() const { return m_reason; }
wpi::uv::Stream& GetStream() { return m_stream; }
private:
void FinishSend() final;
wpi::uv::Buffer AllocBuf();
wpi::uv::Stream& m_stream;
wpi::SmallVector<wpi::uv::Buffer, 4> m_buffers;
std::vector<wpi::uv::Buffer> m_buf_pool;
wpi::raw_uv_ostream m_os;
std::string m_reason;
uint64_t m_lastFlushTime = 0;
int m_sendsActive = 0;
bool m_readActive = true;
};
} // namespace nt::net3

View File

@@ -1,72 +0,0 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#pragma once
#include <stdint.h>
#include <string_view>
namespace wpi {
class raw_ostream;
} // namespace wpi
namespace nt::net3 {
class Writer;
class WireConnection3 {
friend class Writer;
public:
virtual ~WireConnection3() = default;
virtual bool Ready() const = 0;
virtual Writer Send() = 0;
virtual void Flush() = 0;
virtual uint64_t GetLastFlushTime() const = 0; // in microseconds
virtual void StopRead() = 0;
virtual void StartRead() = 0;
virtual void Disconnect(std::string_view reason) = 0;
protected:
virtual void FinishSend() = 0;
};
class Writer {
public:
Writer(wpi::raw_ostream& os, WireConnection3& wire)
: m_os{&os}, m_wire{&wire} {}
Writer(const Writer&) = delete;
Writer(Writer&& rhs) : m_os{rhs.m_os}, m_wire{rhs.m_wire} {
rhs.m_os = nullptr;
rhs.m_wire = nullptr;
}
~Writer() {
if (m_wire) {
m_wire->FinishSend();
}
}
Writer& operator=(const Writer&) = delete;
Writer& operator=(Writer&& rhs) {
m_os = rhs.m_os;
m_wire = rhs.m_wire;
rhs.m_os = nullptr;
rhs.m_wire = nullptr;
return *this;
}
wpi::raw_ostream& stream() { return *m_os; }
private:
wpi::raw_ostream* m_os;
WireConnection3* m_wire;
};
} // namespace nt::net3

View File

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

View File

@@ -1,183 +0,0 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#pragma once
#include <stdint.h>
#include <memory>
#include <optional>
#include <span>
#include <string>
#include <vector>
#include <wpi/leb128.h>
#include "ntcore_c.h"
namespace nt {
class Value;
} // namespace nt
namespace nt::net3 {
class MessageHandler3 {
public:
virtual ~MessageHandler3() = default;
virtual void KeepAlive() = 0;
virtual void ServerHelloDone() = 0;
virtual void ClientHelloDone() = 0;
virtual void ClearEntries() = 0;
virtual void ProtoUnsup(unsigned int proto_rev) = 0;
virtual void ClientHello(std::string_view self_id,
unsigned int proto_rev) = 0;
virtual void ServerHello(unsigned int flags, std::string_view self_id) = 0;
virtual void EntryAssign(std::string_view name, unsigned int id,
unsigned int seq_num, const Value& value,
unsigned int flags) = 0;
virtual void EntryUpdate(unsigned int id, unsigned int seq_num,
const Value& value) = 0;
virtual void FlagsUpdate(unsigned int id, unsigned int flags) = 0;
virtual void EntryDelete(unsigned int id) = 0;
virtual void ExecuteRpc(unsigned int id, unsigned int uid,
std::span<const uint8_t> params) = 0;
virtual void RpcResponse(unsigned int id, unsigned int uid,
std::span<const uint8_t> result) = 0;
};
/* Decodes NT3 protocol into native representation. */
class WireDecoder3 {
public:
explicit WireDecoder3(MessageHandler3& out) : m_out{out} {}
/**
* Executes the decoder. All input data will be consumed unless an error
* occurs.
* @param in input data (updated during parse)
* @return false if error occurred
*/
bool Execute(std::span<const uint8_t>* in) {
DoExecute(in);
return m_state != kError;
}
void SetError(std::string_view message) { EmitError(message); }
std::string GetError() const { return m_error; }
private:
class SimpleValueReader {
public:
std::optional<uint16_t> Read16(std::span<const uint8_t>* in);
std::optional<uint32_t> Read32(std::span<const uint8_t>* in);
std::optional<uint64_t> Read64(std::span<const uint8_t>* in);
std::optional<double> ReadDouble(std::span<const uint8_t>* in);
private:
uint64_t m_value = 0;
int m_count = 0;
};
struct StringReader {
void SetLen(uint64_t len_) {
len = len_;
buf.clear();
}
std::optional<uint64_t> len;
std::string buf;
};
struct RawReader {
void SetLen(uint64_t len_) {
len = len_;
buf.clear();
}
std::optional<uint64_t> len;
std::vector<uint8_t> buf;
};
struct ValueReader {
ValueReader() = default;
explicit ValueReader(NT_Type type_) : type{type_} {}
void SetSize(uint32_t size_) {
haveSize = true;
size = size_;
ints.clear();
doubles.clear();
strings.clear();
}
NT_Type type = NT_UNASSIGNED;
bool haveSize = false;
uint32_t size = 0;
std::vector<int> ints;
std::vector<double> doubles;
std::vector<std::string> strings;
};
MessageHandler3& m_out;
// primary (message) decode state
enum {
kStart,
kClientHello_1ProtoRev,
kClientHello_2Id,
kProtoUnsup_1ProtoRev,
kServerHello_1Flags,
kServerHello_2Id,
kEntryAssign_1Name,
kEntryAssign_2Type,
kEntryAssign_3Id,
kEntryAssign_4SeqNum,
kEntryAssign_5Flags,
kEntryAssign_6Value,
kEntryUpdate_1Id,
kEntryUpdate_2SeqNum,
kEntryUpdate_3Type,
kEntryUpdate_4Value,
kFlagsUpdate_1Id,
kFlagsUpdate_2Flags,
kEntryDelete_1Id,
kClearEntries_1Magic,
kExecuteRpc_1Id,
kExecuteRpc_2Uid,
kExecuteRpc_3Params,
kRpcResponse_1Id,
kRpcResponse_2Uid,
kRpcResponse_3Result,
kError
} m_state = kStart;
// detail decoders
wpi::Uleb128Reader m_ulebReader;
SimpleValueReader m_simpleReader;
StringReader m_stringReader;
RawReader m_rawReader;
ValueReader m_valueReader;
std::string m_error;
std::string m_str;
unsigned int m_id{0}; // also used for proto_rev
unsigned int m_flags{0};
unsigned int m_seq_num_uid{0};
void DoExecute(std::span<const uint8_t>* in);
std::nullopt_t EmitError(std::string_view msg) {
m_state = kError;
m_error = msg;
return std::nullopt;
}
std::optional<std::string> ReadString(std::span<const uint8_t>* in);
std::optional<std::vector<uint8_t>> ReadRaw(std::span<const uint8_t>* in);
std::optional<NT_Type> ReadType(std::span<const uint8_t>* in);
std::optional<Value> ReadValue(std::span<const uint8_t>* in);
};
} // namespace nt::net3

View File

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

View File

@@ -1,49 +0,0 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#pragma once
#include <stdint.h>
#include <span>
#include <string_view>
namespace wpi {
class raw_ostream;
} // namespace wpi
namespace nt {
class Value;
} // namespace nt
namespace nt::net3 {
class Message3;
// encoders for messages (avoids need to construct a Message struct)
void WireEncodeKeepAlive(wpi::raw_ostream& os);
void WireEncodeServerHelloDone(wpi::raw_ostream& os);
void WireEncodeClientHelloDone(wpi::raw_ostream& os);
void WireEncodeClearEntries(wpi::raw_ostream& os);
void WireEncodeProtoUnsup(wpi::raw_ostream& os, unsigned int proto_rev);
void WireEncodeClientHello(wpi::raw_ostream& os, std::string_view self_id,
unsigned int proto_rev);
void WireEncodeServerHello(wpi::raw_ostream& os, unsigned int flags,
std::string_view self_id);
bool WireEncodeEntryAssign(wpi::raw_ostream& os, std::string_view name,
unsigned int id, unsigned int seq_num,
const Value& value, unsigned int flags);
bool WireEncodeEntryUpdate(wpi::raw_ostream& os, unsigned int id,
unsigned int seq_num, const Value& value);
void WireEncodeFlagsUpdate(wpi::raw_ostream& os, unsigned int id,
unsigned int flags);
void WireEncodeEntryDelete(wpi::raw_ostream& os, unsigned int id);
void WireEncodeExecuteRpc(wpi::raw_ostream& os, unsigned int id,
unsigned int uid, std::span<const uint8_t> params);
void WireEncodeRpcResponse(wpi::raw_ostream& os, unsigned int id,
unsigned int uid, std::span<const uint8_t> result);
bool WireEncode(wpi::raw_ostream& os, const Message3& msg);
} // namespace nt::net3

View File

@@ -527,22 +527,18 @@ void NT_StopLocal(NT_Inst inst) {
}
void NT_StartServer(NT_Inst inst, const struct WPI_String* persist_filename,
const struct WPI_String* listen_address, unsigned int port3,
unsigned int port4) {
const struct WPI_String* listen_address,
unsigned int port) {
nt::StartServer(inst, wpi::to_string_view(persist_filename),
wpi::to_string_view(listen_address), port3, port4);
wpi::to_string_view(listen_address), port);
}
void NT_StopServer(NT_Inst inst) {
nt::StopServer(inst);
}
void NT_StartClient3(NT_Inst inst, const struct WPI_String* identity) {
nt::StartClient3(inst, wpi::to_string_view(identity));
}
void NT_StartClient4(NT_Inst inst, const struct WPI_String* identity) {
nt::StartClient4(inst, wpi::to_string_view(identity));
void NT_StartClient(NT_Inst inst, const struct WPI_String* identity) {
nt::StartClient(inst, wpi::to_string_view(identity));
}
void NT_StopClient(NT_Inst inst) {

View File

@@ -633,10 +633,9 @@ void StopLocal(NT_Inst inst) {
}
void StartServer(NT_Inst inst, std::string_view persist_filename,
std::string_view listen_address, unsigned int port3,
unsigned int port4) {
std::string_view listen_address, unsigned int port) {
if (auto ii = InstanceImpl::GetTyped(inst, Handle::kInstance)) {
ii->StartServer(persist_filename, listen_address, port3, port4);
ii->StartServer(persist_filename, listen_address, port);
}
}
@@ -646,15 +645,9 @@ void StopServer(NT_Inst inst) {
}
}
void StartClient3(NT_Inst inst, std::string_view identity) {
void StartClient(NT_Inst inst, std::string_view identity) {
if (auto ii = InstanceImpl::GetTyped(inst, Handle::kInstance)) {
ii->StartClient3(identity);
}
}
void StartClient4(NT_Inst inst, std::string_view identity) {
if (auto ii = InstanceImpl::GetTyped(inst, Handle::kInstance)) {
ii->StartClient4(identity);
ii->StartClient(identity);
}
}

View File

@@ -1,482 +0,0 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#include "ServerClient3.h"
#include <memory>
#include <string>
#include <wpi/timestamp.h>
#include "Log.h"
#include "Types_internal.h"
#include "net3/WireEncoder3.h"
#include "server/ServerImpl.h"
#include "server/ServerPublisher.h"
#include "server/ServerTopic.h"
using namespace nt::server;
// maximum amount of time the wire can be not ready to send another
// transmission before we close the connection
static constexpr uint32_t kWireMaxNotReadyUs = 1000000;
bool ServerClient3::TopicData3::UpdateFlags(ServerTopic* topic) {
unsigned int newFlags = topic->persistent ? NT_PERSISTENT : 0;
bool updated = flags != newFlags;
flags = newFlags;
return updated;
}
bool ServerClient3::ProcessIncomingBinary(std::span<const uint8_t> data) {
if (!m_decoder.Execute(&data)) {
m_wire.Disconnect(m_decoder.GetError());
}
return false;
}
void ServerClient3::SendValue(ServerTopic* topic, const Value& value,
net::ValueSendMode mode) {
if (m_state != kStateRunning) {
if (mode == net::ValueSendMode::kImm) {
mode = net::ValueSendMode::kAll;
}
} else if (m_local) {
mode = net::ValueSendMode::kImm; // always send local immediately
}
TopicData3* topic3 = GetTopic3(topic);
bool added = false;
switch (mode) {
case net::ValueSendMode::kDisabled: // do nothing
break;
case net::ValueSendMode::kImm: // send immediately
++topic3->seqNum;
if (topic3->sentAssign) {
net3::WireEncodeEntryUpdate(m_wire.Send().stream(), topic->id,
topic3->seqNum.value(), value);
} else {
net3::WireEncodeEntryAssign(m_wire.Send().stream(), topic->name,
topic->id, topic3->seqNum.value(), value,
topic3->flags);
topic3->sentAssign = true;
}
if (m_local) {
Flush();
}
break;
case net::ValueSendMode::kNormal: {
// replace, or append if not present
wpi::DenseMap<NT_Topic, size_t>::iterator it;
std::tie(it, added) =
m_outgoingValueMap.try_emplace(topic->id, m_outgoing.size());
if (!added && it->second < m_outgoing.size()) {
auto& msg = m_outgoing[it->second];
if (msg.Is(net3::Message3::kEntryUpdate) ||
msg.Is(net3::Message3::kEntryAssign)) {
if (msg.id() == topic->id) { // should always be true
msg.SetValue(value);
break;
}
}
}
}
// fallthrough
case net::ValueSendMode::kAll: // append to outgoing
if (!added) {
m_outgoingValueMap[topic->id] = m_outgoing.size();
}
++topic3->seqNum;
if (topic3->sentAssign) {
m_outgoing.emplace_back(net3::Message3::EntryUpdate(
topic->id, topic3->seqNum.value(), value));
} else {
m_outgoing.emplace_back(net3::Message3::EntryAssign(
topic->name, topic->id, topic3->seqNum.value(), value,
topic3->flags));
topic3->sentAssign = true;
}
break;
}
}
void ServerClient3::SendAnnounce(ServerTopic* topic,
std::optional<int> pubuid) {
// ignore if we've not yet built the subscriber
if (m_subscribers.empty()) {
return;
}
// subscribe to all non-special topics
if (!topic->special) {
topic->clients[this].AddSubscriber(m_subscribers[0].get());
m_storage.UpdateMetaTopicSub(topic);
}
// NT3 requires a value to send the assign message, so the assign message
// will get sent when the first value is sent (by SendValue).
}
void ServerClient3::SendUnannounce(ServerTopic* topic) {
auto it = m_topics3.find(topic);
if (it == m_topics3.end()) {
return; // never sent to client
}
bool sentAssign = it->second.sentAssign;
m_topics3.erase(it);
if (!sentAssign) {
return; // never sent to client
}
// map to NT3 delete message
if (m_local && m_state == kStateRunning) {
net3::WireEncodeEntryDelete(m_wire.Send().stream(), topic->id);
Flush();
} else {
m_outgoing.emplace_back(net3::Message3::EntryDelete(topic->id));
}
}
void ServerClient3::SendPropertiesUpdate(ServerTopic* topic,
const wpi::json& update, bool ack) {
if (ack) {
return; // we don't ack in NT3
}
auto it = m_topics3.find(topic);
if (it == m_topics3.end()) {
return; // never sent to client
}
TopicData3* topic3 = &it->second;
// Don't send flags update unless we've already sent an assign message.
// The assign message will contain the updated flags when we eventually
// send it.
if (topic3->UpdateFlags(topic) && topic3->sentAssign) {
if (m_local && m_state == kStateRunning) {
net3::WireEncodeFlagsUpdate(m_wire.Send().stream(), topic->id,
topic3->flags);
Flush();
} else {
m_outgoing.emplace_back(
net3::Message3::FlagsUpdate(topic->id, topic3->flags));
}
}
}
void ServerClient3::SendOutgoing(uint64_t curTimeMs, bool flush) {
if (m_outgoing.empty() || m_state != kStateRunning) {
return; // nothing to do
}
// rate limit frequency of transmissions
if (curTimeMs < (m_lastSendMs + kMinPeriodMs)) {
return;
}
if (!m_wire.Ready()) {
uint64_t lastFlushTime = m_wire.GetLastFlushTime();
uint64_t now = wpi::Now();
if (lastFlushTime != 0 && now > (lastFlushTime + kWireMaxNotReadyUs)) {
m_wire.Disconnect("transmit stalled");
}
return;
}
auto out = m_wire.Send();
for (auto&& msg : m_outgoing) {
net3::WireEncode(out.stream(), msg);
}
m_wire.Flush();
m_outgoing.resize(0);
m_outgoingValueMap.clear();
m_lastSendMs = curTimeMs;
}
void ServerClient3::KeepAlive() {
DEBUG4("KeepAlive({})", m_id);
if (m_state != kStateRunning) {
m_decoder.SetError("received unexpected KeepAlive message");
return;
}
// ignore
}
void ServerClient3::ServerHelloDone() {
DEBUG4("ServerHelloDone({})", m_id);
m_decoder.SetError("received unexpected ServerHelloDone message");
}
void ServerClient3::ClientHelloDone() {
DEBUG4("ClientHelloDone({})", m_id);
if (m_state != kStateServerHelloComplete) {
m_decoder.SetError("received unexpected ClientHelloDone message");
return;
}
m_state = kStateRunning;
}
void ServerClient3::ClearEntries() {
DEBUG4("ClearEntries({})", m_id);
if (m_state != kStateRunning) {
m_decoder.SetError("received unexpected ClearEntries message");
return;
}
for (auto topic3it : m_topics3) {
ServerTopic* topic = topic3it.first;
// make sure we send assign the next time
topic3it.second.sentAssign = false;
// unpublish from this client (if it was previously published)
if (topic3it.second.published) {
topic3it.second.published = false;
auto publisherIt = m_publishers.find(topic3it.second.pubuid);
if (publisherIt != m_publishers.end()) {
// remove publisher from topic
topic->RemovePublisher(this, publisherIt->second.get());
// remove publisher from client
m_publishers.erase(publisherIt);
// update meta data
m_storage.UpdateMetaTopicPub(topic);
UpdateMetaClientPub();
}
}
// set retained=false
m_storage.SetProperties(this, topic, {{"retained", false}});
}
}
void ServerClient3::ProtoUnsup(unsigned int proto_rev) {
DEBUG4("ProtoUnsup({})", m_id);
m_decoder.SetError("received unexpected ProtoUnsup message");
}
void ServerClient3::ClientHello(std::string_view self_id,
unsigned int proto_rev) {
DEBUG4("ClientHello({}, '{}', {:04x})", m_id, self_id, proto_rev);
if (m_state != kStateInitial) {
m_decoder.SetError("received unexpected ClientHello message");
return;
}
if (proto_rev != 0x0300) {
net3::WireEncodeProtoUnsup(m_wire.Send().stream(), 0x0300);
Flush();
m_decoder.SetError(
fmt::format("unsupported protocol version {:04x}", proto_rev));
return;
}
// create a unique name including client id
m_name = fmt::format("{}-NT3@{}", self_id, m_connInfo);
m_connected(m_name, 0x0300);
m_connected = nullptr; // no longer required
// create client meta topics
m_metaPub = m_storage.CreateMetaTopic(fmt::format("$clientpub${}", m_name));
m_metaSub = m_storage.CreateMetaTopic(fmt::format("$clientsub${}", m_name));
// subscribe and send initial assignments
auto& sub = m_subscribers[0];
std::string prefix;
PubSubOptions options;
options.prefixMatch = true;
sub = std::make_unique<ServerSubscriber>(
GetName(), std::span<const std::string>{{prefix}}, 0, options);
m_periodMs = net::UpdatePeriodCalc(m_periodMs, sub->GetPeriodMs());
m_setPeriodic(m_periodMs);
{
auto out = m_wire.Send();
net3::WireEncodeServerHello(out.stream(), 0, "server");
m_storage.ForEachTopic([&](ServerTopic* topic) {
if (topic && !topic->special && topic->IsPublished() &&
topic->lastValue) {
DEBUG4("client {}: initial announce of '{}' (id {})", m_id, topic->name,
topic->id);
topic->clients[this].AddSubscriber(sub.get());
m_storage.UpdateMetaTopicSub(topic);
TopicData3* topic3 = GetTopic3(topic);
++topic3->seqNum;
net3::WireEncodeEntryAssign(out.stream(), topic->name, topic->id,
topic3->seqNum.value(), topic->lastValue,
topic3->flags);
topic3->sentAssign = true;
}
});
net3::WireEncodeServerHelloDone(out.stream());
}
Flush();
m_state = kStateServerHelloComplete;
// update meta topics
UpdateMetaClientPub();
UpdateMetaClientSub();
}
void ServerClient3::ServerHello(unsigned int flags, std::string_view self_id) {
DEBUG4("ServerHello({}, {}, {})", m_id, flags, self_id);
m_decoder.SetError("received unexpected ServerHello message");
}
void ServerClient3::EntryAssign(std::string_view name, unsigned int id,
unsigned int seq_num, const Value& value,
unsigned int flags) {
DEBUG4("EntryAssign({}, {}, {}, {}, {})", m_id, id, seq_num,
static_cast<int>(value.type()), flags);
if (id != 0xffff) {
DEBUG3("ignored EntryAssign from {} with non-0xffff id {}", m_id, id);
return;
}
// convert from NT3 info
auto typeStr = TypeToString(value.type());
wpi::json properties = wpi::json::object();
properties["retained"] = true; // treat all NT3 published topics as retained
properties["cached"] = true; // treat all NT3 published topics as cached
if ((flags & NT_PERSISTENT) != 0) {
properties["persistent"] = true;
}
// create topic
auto topic = m_storage.CreateTopic(this, name, typeStr, properties);
TopicData3* topic3 = GetTopic3(topic);
if (topic3->published || topic3->sentAssign) {
WARN("ignoring client {} duplicate publish of '{}'", m_id, name);
return;
}
++topic3->seqNum;
topic3->published = true;
topic3->pubuid = m_nextPubUid++;
topic3->sentAssign = true;
// create publisher
auto [publisherIt, isNew] = m_publishers.try_emplace(
topic3->pubuid,
std::make_unique<ServerPublisher>(GetName(), topic, topic3->pubuid));
if (!isNew) {
return; // shouldn't happen, but just in case...
}
// add publisher to topic
topic->AddPublisher(this, publisherIt->getSecond().get());
// update meta data
m_storage.UpdateMetaTopicPub(topic);
UpdateMetaClientPub();
// acts as an announce + data update
SendAnnounce(topic, topic3->pubuid);
m_storage.SetValue(this, topic, value);
// respond with assign message with assigned topic ID
if (m_local && m_state == kStateRunning) {
net3::WireEncodeEntryAssign(m_wire.Send().stream(), topic->name, topic->id,
topic3->seqNum.value(), value, topic3->flags);
} else {
m_outgoing.emplace_back(net3::Message3::EntryAssign(
topic->name, topic->id, topic3->seqNum.value(), value, topic3->flags));
}
}
void ServerClient3::EntryUpdate(unsigned int id, unsigned int seq_num,
const Value& value) {
DEBUG4("EntryUpdate({}, {}, {}, {})", m_id, id, seq_num,
static_cast<int>(value.type()));
if (m_state != kStateRunning) {
m_decoder.SetError("received unexpected EntryUpdate message");
return;
}
ServerTopic* topic = m_storage.GetTopic(id);
if (!topic || !topic->IsPublished()) {
DEBUG3("ignored EntryUpdate from {} on non-existent topic {}", m_id, id);
return;
}
TopicData3* topic3 = GetTopic3(topic);
if (!topic3->published) {
topic3->published = true;
topic3->pubuid = m_nextPubUid++;
// create publisher
auto [publisherIt, isNew] = m_publishers.try_emplace(
topic3->pubuid,
std::make_unique<ServerPublisher>(GetName(), topic, topic3->pubuid));
if (isNew) {
// add publisher to topic
topic->AddPublisher(this, publisherIt->getSecond().get());
// update meta data
m_storage.UpdateMetaTopicPub(topic);
UpdateMetaClientPub();
}
}
topic3->seqNum = net3::SequenceNumber{seq_num};
m_storage.SetValue(this, topic, value);
}
void ServerClient3::FlagsUpdate(unsigned int id, unsigned int flags) {
DEBUG4("FlagsUpdate({}, {}, {})", m_id, id, flags);
if (m_state != kStateRunning) {
m_decoder.SetError("received unexpected FlagsUpdate message");
return;
}
ServerTopic* topic = m_storage.GetTopic(id);
if (!topic || !topic->IsPublished()) {
DEBUG3("ignored FlagsUpdate from {} on non-existent topic {}", m_id, id);
return;
}
if (topic->special) {
DEBUG3("ignored FlagsUpdate from {} on special topic {}", m_id, id);
return;
}
m_storage.SetFlags(this, topic, flags);
}
void ServerClient3::EntryDelete(unsigned int id) {
DEBUG4("EntryDelete({}, {})", m_id, id);
if (m_state != kStateRunning) {
m_decoder.SetError("received unexpected EntryDelete message");
return;
}
ServerTopic* topic = m_storage.GetTopic(id);
if (!topic || !topic->IsPublished()) {
DEBUG3("ignored EntryDelete from {} on non-existent topic {}", m_id, id);
return;
}
if (topic->special) {
DEBUG3("ignored EntryDelete from {} on special topic {}", m_id, id);
return;
}
auto topic3it = m_topics3.find(topic);
if (topic3it != m_topics3.end()) {
// make sure we send assign the next time
topic3it->second.sentAssign = false;
// unpublish from this client (if it was previously published)
if (topic3it->second.published) {
topic3it->second.published = false;
auto publisherIt = m_publishers.find(topic3it->second.pubuid);
if (publisherIt != m_publishers.end()) {
// remove publisher from topic
topic->RemovePublisher(this, publisherIt->second.get());
// remove publisher from client
m_publishers.erase(publisherIt);
// update meta data
m_storage.UpdateMetaTopicPub(topic);
UpdateMetaClientPub();
}
}
}
// set retained=false
m_storage.SetProperties(this, topic, {{"retained", false}});
}

View File

@@ -1,97 +0,0 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#pragma once
#include <utility>
#include <vector>
#include "ServerClient.h"
#include "net/ClientMessageQueue.h"
#include "net3/Message3.h"
#include "net3/SequenceNumber.h"
#include "net3/WireConnection3.h"
#include "net3/WireDecoder3.h"
#include "server/Functions.h"
namespace nt::server {
class ServerClient3 final : public ServerClient, private net3::MessageHandler3 {
public:
ServerClient3(std::string_view connInfo, bool local,
net3::WireConnection3& wire, Connected3Func connected,
SetPeriodicFunc setPeriodic, ServerStorage& storage, int id,
wpi::Logger& logger)
: ServerClient{"", connInfo, local, setPeriodic, storage, id, logger},
m_connected{std::move(connected)},
m_wire{wire},
m_decoder{*this},
m_incoming{logger} {}
bool ProcessIncomingText(std::string_view data) final { return false; }
bool ProcessIncomingBinary(std::span<const uint8_t> data) final;
bool ProcessIncomingMessages(size_t max) final { return false; }
void SendValue(ServerTopic* topic, const Value& value,
net::ValueSendMode mode) final;
void SendAnnounce(ServerTopic* topic, std::optional<int> pubuid) final;
void SendUnannounce(ServerTopic* topic) final;
void SendPropertiesUpdate(ServerTopic* topic, const wpi::json& update,
bool ack) final;
void SendOutgoing(uint64_t curTimeMs, bool flush) final;
void Flush() final { m_wire.Flush(); }
private:
// MessageHandler3 interface
void KeepAlive() final;
void ServerHelloDone() final;
void ClientHelloDone() final;
void ClearEntries() final;
void ProtoUnsup(unsigned int proto_rev) final;
void ClientHello(std::string_view self_id, unsigned int proto_rev) final;
void ServerHello(unsigned int flags, std::string_view self_id) final;
void EntryAssign(std::string_view name, unsigned int id, unsigned int seq_num,
const Value& value, unsigned int flags) final;
void EntryUpdate(unsigned int id, unsigned int seq_num,
const Value& value) final;
void FlagsUpdate(unsigned int id, unsigned int flags) final;
void EntryDelete(unsigned int id) final;
void ExecuteRpc(unsigned int id, unsigned int uid,
std::span<const uint8_t> params) final {}
void RpcResponse(unsigned int id, unsigned int uid,
std::span<const uint8_t> result) final {}
Connected3Func m_connected;
net3::WireConnection3& m_wire;
enum State { kStateInitial, kStateServerHelloComplete, kStateRunning };
State m_state{kStateInitial};
net3::WireDecoder3 m_decoder;
net::NetworkIncomingClientQueue m_incoming;
std::vector<net3::Message3> m_outgoing;
wpi::DenseMap<NT_Topic, size_t> m_outgoingValueMap;
int64_t m_nextPubUid{1};
uint64_t m_lastSendMs{0};
struct TopicData3 {
explicit TopicData3(ServerTopic* topic) { UpdateFlags(topic); }
unsigned int flags{0};
net3::SequenceNumber seqNum;
bool sentAssign{false};
bool published{false};
int64_t pubuid{0};
bool UpdateFlags(ServerTopic* topic);
};
wpi::DenseMap<ServerTopic*, TopicData3> m_topics3;
TopicData3* GetTopic3(ServerTopic* topic) {
return &m_topics3.try_emplace(topic, topic).first->second;
}
};
} // namespace nt::server

View File

@@ -16,7 +16,6 @@
#include "Log.h"
#include "server/MessagePackWriter.h"
#include "server/ServerClient3.h"
#include "server/ServerClient4.h"
#include "server/ServerClientLocal.h"
@@ -60,20 +59,6 @@ std::pair<std::string, int> ServerImpl::AddClient(std::string_view name,
return {std::move(dedupName), index};
}
int ServerImpl::AddClient3(std::string_view connInfo, bool local,
net3::WireConnection3& wire,
Connected3Func connected,
SetPeriodicFunc setPeriodic) {
size_t index = GetEmptyClientSlot();
m_clients[index] = std::make_unique<ServerClient3>(
connInfo, local, wire, std::move(connected), std::move(setPeriodic),
m_storage, index, m_logger);
DEBUG3("AddClient3('{}') -> {}", connInfo, index);
return index;
}
std::shared_ptr<void> ServerImpl::RemoveClient(int clientId) {
DEBUG3("RemoveClient({})", clientId);
auto& client = m_clients[clientId];

View File

@@ -61,9 +61,6 @@ class ServerImpl final {
std::string_view connInfo, bool local,
net::WireConnection& wire,
SetPeriodicFunc setPeriodic);
int AddClient3(std::string_view connInfo, bool local,
net3::WireConnection3& wire, Connected3Func connected,
SetPeriodicFunc setPeriodic);
std::shared_ptr<void> RemoveClient(int clientId);
void ConnectionsChanged(const std::vector<ConnectionInfo>& conns) {

View File

@@ -74,8 +74,7 @@ class NetworkTableInstance final {
enum NetworkMode {
kNetModeNone = NT_NET_MODE_NONE,
kNetModeServer = NT_NET_MODE_SERVER,
kNetModeClient3 = NT_NET_MODE_CLIENT3,
kNetModeClient4 = NT_NET_MODE_CLIENT4,
kNetModeClient = NT_NET_MODE_CLIENT,
kNetModeLocal = NT_NET_MODE_LOCAL
};
@@ -95,14 +94,9 @@ class NetworkTableInstance final {
};
/**
* The default port that network tables operates on for NT3.
* The default port that network tables operates on.
*/
static constexpr unsigned int kDefaultPort3 = NT_DEFAULT_PORT3;
/**
* The default port that network tables operates on for NT4.
*/
static constexpr unsigned int kDefaultPort4 = NT_DEFAULT_PORT4;
static constexpr unsigned int kDefaultPort = NT_DEFAULT_PORT;
/**
* Construct invalid instance.
@@ -611,14 +605,12 @@ class NetworkTableInstance final {
* null terminated)
* @param listen_address the address to listen on, or null to listen on any
* address (UTF-8 string, null terminated)
* @param port3 port to communicate over (NT3)
* @param port4 port to communicate over (NT4)
* @param port port to communicate over
*/
void StartServer(std::string_view persist_filename = "networktables.json",
const char* listen_address = "",
unsigned int port3 = kDefaultPort3,
unsigned int port4 = kDefaultPort4) {
::nt::StartServer(m_handle, persist_filename, listen_address, port3, port4);
unsigned int port = kDefaultPort) {
::nt::StartServer(m_handle, persist_filename, listen_address, port);
}
/**
@@ -627,23 +619,13 @@ class NetworkTableInstance final {
void StopServer() { ::nt::StopServer(m_handle); }
/**
* Starts a NT3 client. Use SetServer or SetServerTeam to set the server name
* Starts a client. Use SetServer or SetServerTeam to set the server name
* and port.
*
* @param identity network identity to advertise (cannot be empty string)
*/
void StartClient3(std::string_view identity) {
::nt::StartClient3(m_handle, identity);
}
/**
* Starts a NT4 client. Use SetServer or SetServerTeam to set the server name
* and port.
*
* @param identity network identity to advertise (cannot be empty string)
*/
void StartClient4(std::string_view identity) {
::nt::StartClient4(m_handle, identity);
void StartClient(std::string_view identity) {
::nt::StartClient(m_handle, identity);
}
/**

View File

@@ -43,11 +43,8 @@ typedef NT_Handle NT_Topic;
typedef NT_Handle NT_Subscriber;
typedef NT_Handle NT_Publisher;
/** Default network tables port number (NT3) */
#define NT_DEFAULT_PORT3 1735
/** Default network tables port number (NT4) */
#define NT_DEFAULT_PORT4 5810
/** Default network tables port number */
#define NT_DEFAULT_PORT 5810
/** NetworkTables data types. */
enum NT_Type {
@@ -90,8 +87,7 @@ enum NT_LogLevel {
enum NT_NetworkMode {
NT_NET_MODE_NONE = 0x00, /* not running */
NT_NET_MODE_SERVER = 0x01, /* running in server mode */
NT_NET_MODE_CLIENT3 = 0x02, /* running in NT3 client mode */
NT_NET_MODE_CLIENT4 = 0x04, /* running in NT4 client mode */
NT_NET_MODE_CLIENT = 0x04, /* running in client mode */
NT_NET_MODE_STARTING = 0x08, /* flag for starting (either client or server) */
NT_NET_MODE_LOCAL = 0x10, /* running in local-only mode */
};
@@ -190,7 +186,7 @@ struct NT_TopicInfo {
/** NetworkTables Connection Information */
struct NT_ConnectionInfo {
/**
* The remote identifier (as set on the remote node by NT_StartClient4().
* The remote identifier (as set on the remote node by NT_StartClient().
*/
struct WPI_String remote_id;
@@ -1119,12 +1115,10 @@ void NT_StopLocal(NT_Inst inst);
* null terminated)
* @param listen_address the address to listen on, or null to listen on any
* address. (UTF-8 string, null terminated)
* @param port3 port to communicate over (NT3)
* @param port4 port to communicate over (NT4)
* @param port port to communicate over
*/
void NT_StartServer(NT_Inst inst, const struct WPI_String* persist_filename,
const struct WPI_String* listen_address, unsigned int port3,
unsigned int port4);
const struct WPI_String* listen_address, unsigned int port);
/**
* Stops the server if it is running.
@@ -1134,22 +1128,13 @@ void NT_StartServer(NT_Inst inst, const struct WPI_String* persist_filename,
void NT_StopServer(NT_Inst inst);
/**
* Starts a NT3 client. Use NT_SetServer or NT_SetServerTeam to set the server
* Starts a client. Use NT_SetServer or NT_SetServerTeam to set the server
* name and port.
*
* @param inst instance handle
* @param identity network identity to advertise (cannot be empty string)
*/
void NT_StartClient3(NT_Inst inst, const struct WPI_String* identity);
/**
* Starts a NT4 client. Use NT_SetServer or NT_SetServerTeam to set the server
* name and port.
*
* @param inst instance handle
* @param identity network identity to advertise (cannot be empty string)
*/
void NT_StartClient4(NT_Inst inst, const struct WPI_String* identity);
void NT_StartClient(NT_Inst inst, const struct WPI_String* identity);
/**
* Stops the client if it is running.

View File

@@ -120,7 +120,7 @@ struct TopicInfo {
struct ConnectionInfo {
/**
* The remote identifier (as set on the remote node by
* NetworkTableInstance::StartClient4() or nt::StartClient4()).
* NetworkTableInstance::StartClient() or nt::StartClient()).
*/
std::string remote_id;
@@ -1057,12 +1057,10 @@ void StopLocal(NT_Inst inst);
* null terminated)
* @param listen_address the address to listen on, or null to listen on any
* address. (UTF-8 string)
* @param port3 port to communicate over (NT3)
* @param port4 port to communicate over (NT4)
* @param port port to communicate over
*/
void StartServer(NT_Inst inst, std::string_view persist_filename,
std::string_view listen_address, unsigned int port3,
unsigned int port4);
std::string_view listen_address, unsigned int port);
/**
* Stops the server if it is running.
@@ -1072,22 +1070,13 @@ void StartServer(NT_Inst inst, std::string_view persist_filename,
void StopServer(NT_Inst inst);
/**
* Starts a NT3 client. Use SetServer or SetServerTeam to set the server name
* Starts a client. Use SetServer or SetServerTeam to set the server name
* and port.
*
* @param inst instance handle
* @param identity network identity to advertise (cannot be empty string)
*/
void StartClient3(NT_Inst inst, std::string_view identity);
/**
* Starts a NT4 client. Use SetServer or SetServerTeam to set the server name
* and port.
*
* @param inst instance handle
* @param identity network identity to advertise (cannot be empty string)
*/
void StartClient4(NT_Inst inst, std::string_view identity);
void StartClient(NT_Inst inst, std::string_view identity);
/**
* Stops the client if it is running.

View File

@@ -39,8 +39,8 @@ class ConnectionListenerTest {
/** Connect to the server. */
private void connect(int port) {
m_serverInst.startServer("connectionlistenertest.json", "127.0.0.1", 0, port);
m_clientInst.startClient4("client");
m_serverInst.startServer("connectionlistenertest.json", "127.0.0.1", port);
m_clientInst.startClient("client");
m_clientInst.setServer("127.0.0.1", port);
// wait for client to report it's connected, then wait another 0.1 sec
@@ -113,7 +113,7 @@ class ConnectionListenerTest {
@ParameterizedTest
@ValueSource(strings = {"127.0.0.1", "127.0.0.1 ", " 127.0.0.1 "})
void testThreaded(String address) {
m_serverInst.startServer("connectionlistenertest.json", address, 0, threadedPort);
m_serverInst.startServer("connectionlistenertest.json", address, threadedPort);
List<NetworkTableEvent> events = new ArrayList<>();
final int handle =
m_serverInst.addConnectionListener(
@@ -125,7 +125,7 @@ class ConnectionListenerTest {
});
// trigger a connect event
m_clientInst.startClient4("client");
m_clientInst.startClient("client");
m_clientInst.setServer(address, threadedPort);
threadedPort++;

View File

@@ -31,13 +31,13 @@ class LoggerTest {
List<NetworkTableEvent> msgs = new ArrayList<>();
m_clientInst.addLogger(LogMessage.kInfo, 100, msgs::add);
m_clientInst.startClient4("client");
m_clientInst.startClient("client");
m_clientInst.setServer("127.0.0.1", 10000);
// wait for client to report it's started, then wait another 0.1 sec
try {
int count = 0;
while (!m_clientInst.getNetworkMode().contains(NetworkTableInstance.NetworkMode.kClient4)) {
while (!m_clientInst.getNetworkMode().contains(NetworkTableInstance.NetworkMode.kClient)) {
Thread.sleep(100);
count++;
if (count > 30) {

View File

@@ -37,7 +37,7 @@ class TimeSyncTest {
try (var poller = new NetworkTableListenerPoller(m_inst)) {
poller.addTimeSyncListener(false);
m_inst.startServer("timesynctest.json", "127.0.0.1", 0, 10030);
m_inst.startServer("timesynctest.json", "127.0.0.1", 10030);
var offset = m_inst.getServerTimeOffset();
assertTrue(offset.isPresent());
assertEquals(0L, offset.getAsLong());
@@ -61,19 +61,8 @@ class TimeSyncTest {
}
@Test
void testClient3() {
m_inst.startClient3("client");
var offset = m_inst.getServerTimeOffset();
assertFalse(offset.isPresent());
m_inst.stopClient();
offset = m_inst.getServerTimeOffset();
assertFalse(offset.isPresent());
}
@Test
void testClient4() {
m_inst.startClient4("client");
void testClient() {
m_inst.startClient("client");
var offset = m_inst.getServerTimeOffset();
assertFalse(offset.isPresent());

View File

@@ -33,8 +33,8 @@ class TopicListenerTest {
}
private void connect() {
m_serverInst.startServer("topiclistenertest.json", "127.0.0.1", 0, 10010);
m_clientInst.startClient4("client");
m_serverInst.startServer("topiclistenertest.json", "127.0.0.1", 10010);
m_clientInst.startClient("client");
m_clientInst.setServer("127.0.0.1", 10010);
// Use connection listener to ensure we've connected

View File

@@ -23,18 +23,16 @@ class ConnectionListenerTest : public ::testing::Test {
nt::DestroyInstance(client_inst);
}
void Connect(const char* address, unsigned int port3, unsigned int port4);
void Connect(const char* address, unsigned int port4);
protected:
NT_Inst server_inst;
NT_Inst client_inst;
};
void ConnectionListenerTest::Connect(const char* address, unsigned int port3,
unsigned int port4) {
nt::StartServer(server_inst, "connectionlistenertest.ini", address, port3,
port4);
nt::StartClient4(client_inst, "client");
void ConnectionListenerTest::Connect(const char* address, unsigned int port4) {
nt::StartServer(server_inst, "connectionlistenertest.ini", address, port4);
nt::StartClient(client_inst, "client");
nt::SetServer(client_inst, address, port4);
// wait for client to report it's connected, then wait another 0.1 sec
@@ -57,7 +55,7 @@ TEST_F(ConnectionListenerTest, Polled) {
ASSERT_NE(handle, 0u);
// trigger a connect event
Connect("127.0.0.1", 0, 10020);
Connect("127.0.0.1", 10020);
// get the event
bool timed_out = false;
@@ -98,7 +96,7 @@ TEST_P(ConnectionListenerVariantTest, Threaded) {
});
// trigger a connect event
Connect(GetParam().first, 0, 20001 + GetParam().second);
Connect(GetParam().first, 20001 + GetParam().second);
bool timed_out = false;
ASSERT_TRUE(wpi::WaitForObject(handle, 1.0, &timed_out));

View File

@@ -27,7 +27,7 @@ class LoggerTest : public ::testing::Test {
void LoggerTest::Generate() {
// generate info message
nt::StartClient4(m_inst, "");
nt::StartClient(m_inst, "");
// generate error message
nt::Publish(nt::Handle(nt::Handle{m_inst}.GetInst(), 5, nt::Handle::kTopic),

View File

@@ -7,7 +7,6 @@
#include "Handle.h"
#include "PubSubOptions.h"
#include "net/Message.h"
#include "net3/Message3.h"
#include "networktables/NetworkTableValue.h"
#include "ntcore_cpp.h"
@@ -54,59 +53,6 @@ void PrintTo(const Handle& handle, std::ostream* os) {
*os << ", " << handle.GetInst() << ", " << handle.GetIndex() << '}';
}
void PrintTo(const net3::Message3& msg, std::ostream* os) {
*os << "Message{";
switch (msg.type()) {
case net3::Message3::kKeepAlive:
*os << "kKeepAlive";
break;
case net3::Message3::kClientHello:
*os << "kClientHello";
break;
case net3::Message3::kProtoUnsup:
*os << "kProtoUnsup";
break;
case net3::Message3::kServerHelloDone:
*os << "kServerHelloDone";
break;
case net3::Message3::kServerHello:
*os << "kServerHello";
break;
case net3::Message3::kClientHelloDone:
*os << "kClientHelloDone";
break;
case net3::Message3::kEntryAssign:
*os << "kEntryAssign";
break;
case net3::Message3::kEntryUpdate:
*os << "kEntryUpdate";
break;
case net3::Message3::kFlagsUpdate:
*os << "kFlagsUpdate";
break;
case net3::Message3::kEntryDelete:
*os << "kEntryDelete";
break;
case net3::Message3::kClearEntries:
*os << "kClearEntries";
break;
case net3::Message3::kExecuteRpc:
*os << "kExecuteRpc";
break;
case net3::Message3::kRpcResponse:
*os << "kRpcResponse";
break;
default:
*os << "UNKNOWN";
break;
}
*os << ": str=\"" << msg.str() << "\", id=" << msg.id()
<< ", flags=" << msg.flags() << ", seq_num_uid=" << msg.seq_num_uid()
<< ", value=";
PrintTo(msg.value(), os);
*os << '}';
}
void PrintTo(const Value& value, std::ostream* os) {
*os << "Value{";
switch (value.type()) {

View File

@@ -26,7 +26,7 @@ TEST_F(TimeSyncTest, TestServer) {
nt::NetworkTableListenerPoller poller{m_inst};
poller.AddTimeSyncListener(false);
m_inst.StartServer("timesynctest.json", "127.0.0.1", 0, 10030);
m_inst.StartServer("timesynctest.json", "127.0.0.1", 10030);
auto offset = m_inst.GetServerTimeOffset();
ASSERT_TRUE(offset);
ASSERT_EQ(0, *offset);
@@ -50,18 +50,8 @@ TEST_F(TimeSyncTest, TestServer) {
ASSERT_FALSE(data->valid);
}
TEST_F(TimeSyncTest, TestClient3) {
m_inst.StartClient3("client");
auto offset = m_inst.GetServerTimeOffset();
ASSERT_FALSE(offset);
m_inst.StopClient();
offset = m_inst.GetServerTimeOffset();
ASSERT_FALSE(offset);
}
TEST_F(TimeSyncTest, TestClient4) {
m_inst.StartClient4("client");
TEST_F(TimeSyncTest, TestClient) {
m_inst.StartClient("client");
auto offset = m_inst.GetServerTimeOffset();
ASSERT_FALSE(offset);

View File

@@ -50,8 +50,8 @@ class TopicListenerTest : public ::testing::Test {
};
void TopicListenerTest::Connect(unsigned int port) {
nt::StartServer(m_serverInst, "topiclistenertest.json", "127.0.0.1", 0, port);
nt::StartClient4(m_clientInst, "client");
nt::StartServer(m_serverInst, "topiclistenertest.json", "127.0.0.1", port);
nt::StartClient(m_clientInst, "client");
nt::SetServer(m_clientInst, "127.0.0.1", port);
// Use connection listener to ensure we've connected

View File

@@ -1,45 +0,0 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#include "MessageMatcher3.h"
namespace nt::net3 {
bool MessageMatcher::MatchAndExplain(
Message3 msg, ::testing::MatchResultListener* listener) const {
bool match = true;
if (msg.str() != goodmsg.str()) {
*listener << "str mismatch ";
match = false;
}
if ((!msg.value() && goodmsg.value()) || (msg.value() && !goodmsg.value()) ||
(msg.value() && goodmsg.value() && msg.value() != goodmsg.value())) {
*listener << "value mismatch ";
match = false;
}
if (msg.id() != goodmsg.id()) {
*listener << "id mismatch ";
match = false;
}
if (msg.flags() != goodmsg.flags()) {
*listener << "flags mismatch";
match = false;
}
if (msg.seq_num_uid() != goodmsg.seq_num_uid()) {
*listener << "seq_num_uid mismatch";
match = false;
}
return match;
}
void MessageMatcher::DescribeTo(::std::ostream* os) const {
PrintTo(goodmsg, os);
}
void MessageMatcher::DescribeNegationTo(::std::ostream* os) const {
*os << "is not equal to ";
PrintTo(goodmsg, os);
}
} // namespace nt::net3

View File

@@ -1,34 +0,0 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#pragma once
#include <memory>
#include <ostream>
#include <utility>
#include "../TestPrinters.h"
#include "gmock/gmock.h"
#include "net3/Message3.h"
namespace nt::net3 {
class MessageMatcher : public ::testing::MatcherInterface<Message3> {
public:
explicit MessageMatcher(Message3 goodmsg_) : goodmsg(std::move(goodmsg_)) {}
bool MatchAndExplain(Message3 msg,
::testing::MatchResultListener* listener) const override;
void DescribeTo(::std::ostream* os) const override;
void DescribeNegationTo(::std::ostream* os) const override;
private:
Message3 goodmsg;
};
inline ::testing::Matcher<Message3> MessageEq(Message3 goodmsg) {
return ::testing::MakeMatcher(new MessageMatcher(std::move(goodmsg)));
}
} // namespace nt::net3

View File

@@ -1,49 +0,0 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#pragma once
#include <stdint.h>
#include <span>
#include <vector>
#include <wpi/raw_ostream.h>
#include "gmock/gmock.h"
#include "net3/WireConnection3.h"
namespace nt::net3 {
class MockWireConnection3 : public WireConnection3 {
public:
MockWireConnection3() : m_os{m_data} {}
MOCK_METHOD(bool, Ready, (), (const, override));
Writer Send() override { return {m_os, *this}; }
MOCK_METHOD(void, Data, (std::span<const uint8_t> data));
MOCK_METHOD(void, Flush, (), (override));
MOCK_METHOD(uint64_t, GetLastFlushTime, (), (const, override));
MOCK_METHOD(void, StopRead, (), (override));
MOCK_METHOD(void, StartRead, (), (override));
MOCK_METHOD(void, Disconnect, (std::string_view reason), (override));
protected:
void FinishSend() override {
Data(m_data);
m_data.resize(0);
}
private:
std::vector<uint8_t> m_data;
wpi::raw_uvector_ostream m_os;
};
} // namespace nt::net3

View File

@@ -1,279 +0,0 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#include <stdint.h>
#include <cfloat>
#include <climits>
#include <string>
#include <string_view>
#include <utility>
#include <vector>
#include <gtest/gtest.h>
#include <wpi/SpanMatcher.h>
#include "../TestPrinters.h"
#include "../ValueMatcher.h"
#include "gmock/gmock.h"
#include "net3/WireDecoder3.h"
#include "networktables/NetworkTableValue.h"
using namespace std::string_view_literals;
using testing::_;
using testing::MockFunction;
using testing::StrictMock;
namespace nt {
class MockMessageHandler3 : public net3::MessageHandler3 {
public:
MOCK_METHOD0(KeepAlive, void());
MOCK_METHOD0(ServerHelloDone, void());
MOCK_METHOD0(ClientHelloDone, void());
MOCK_METHOD0(ClearEntries, void());
MOCK_METHOD1(ProtoUnsup, void(unsigned int proto_rev));
MOCK_METHOD2(ClientHello,
void(std::string_view self_id, unsigned int proto_rev));
MOCK_METHOD2(ServerHello, void(unsigned int flags, std::string_view self_id));
MOCK_METHOD5(EntryAssign, void(std::string_view name, unsigned int id,
unsigned int seq_num, const Value& value,
unsigned int flags));
MOCK_METHOD3(EntryUpdate,
void(unsigned int id, unsigned int seq_num, const Value& value));
MOCK_METHOD2(FlagsUpdate, void(unsigned int id, unsigned int flags));
MOCK_METHOD1(EntryDelete, void(unsigned int id));
MOCK_METHOD3(ExecuteRpc, void(unsigned int id, unsigned int uid,
std::span<const uint8_t> params));
MOCK_METHOD3(RpcResponse, void(unsigned int id, unsigned int uid,
std::span<const uint8_t> result));
};
class WireDecoder3Test : public ::testing::Test {
protected:
StrictMock<MockMessageHandler3> handler;
net3::WireDecoder3 decoder{handler};
void DecodeComplete(std::span<const uint8_t> in) {
decoder.Execute(&in);
EXPECT_TRUE(in.empty());
ASSERT_EQ(decoder.GetError(), "");
}
};
TEST_F(WireDecoder3Test, KeepAlive) {
EXPECT_CALL(handler, KeepAlive());
DecodeComplete("\x00"_us);
}
TEST_F(WireDecoder3Test, ClientHello) {
EXPECT_CALL(handler, ClientHello(std::string_view{"hello"}, 0x0300u));
DecodeComplete("\x01\x03\x00\x05hello"_us);
}
TEST_F(WireDecoder3Test, ProtoUnsup) {
EXPECT_CALL(handler, ProtoUnsup(0x0300u));
EXPECT_CALL(handler, ProtoUnsup(0x0200u));
DecodeComplete("\x02\x03\x00\x02\x02\x00"_us);
}
TEST_F(WireDecoder3Test, ServerHelloDone) {
EXPECT_CALL(handler, ServerHelloDone());
DecodeComplete("\x03"_us);
}
TEST_F(WireDecoder3Test, ServerHello) {
EXPECT_CALL(handler, ServerHello(0x03, std::string_view{"hello"}));
DecodeComplete("\x04\x03\x05hello"_us);
}
TEST_F(WireDecoder3Test, ClientHelloDone) {
EXPECT_CALL(handler, ClientHelloDone());
DecodeComplete("\x05"_us);
}
TEST_F(WireDecoder3Test, FlagsUpdate) {
EXPECT_CALL(handler, FlagsUpdate(0x5678, 0x03));
DecodeComplete("\x12\x56\x78\x03"_us);
}
TEST_F(WireDecoder3Test, EntryDelete) {
EXPECT_CALL(handler, EntryDelete(0x5678));
DecodeComplete("\x13\x56\x78"_us);
}
TEST_F(WireDecoder3Test, ClearEntries) {
EXPECT_CALL(handler, ClearEntries());
DecodeComplete("\x14\xd0\x6c\xb2\x7a"_us);
}
TEST_F(WireDecoder3Test, ClearEntriesInvalid) {
auto in = "\x14\xd0\x6c\xb2\x7b"_us;
decoder.Execute(&in);
EXPECT_EQ(decoder.GetError(), "received incorrect CLEAR_ENTRIES magic value");
}
TEST_F(WireDecoder3Test, ExecuteRpc) {
EXPECT_CALL(handler, ExecuteRpc(0x5678, 0x1234, wpi::SpanEq("hello"_us)));
DecodeComplete("\x20\x56\x78\x12\x34\x05hello"_us);
}
TEST_F(WireDecoder3Test, RpcResponse) {
EXPECT_CALL(handler, RpcResponse(0x5678, 0x1234, wpi::SpanEq("hello"_us)));
DecodeComplete("\x21\x56\x78\x12\x34\x05hello"_us);
}
TEST_F(WireDecoder3Test, UnknownMessage) {
auto in = "\x23"_us;
decoder.Execute(&in);
EXPECT_EQ(decoder.GetError(), "unrecognized message type: 35");
}
TEST_F(WireDecoder3Test, EntryAssignBoolean) {
EXPECT_CALL(handler, EntryAssign("test"sv, 0x5678, 0x1234,
Value::MakeBoolean(true), 0x9a));
DecodeComplete("\x10\x04test\x00\x56\x78\x12\x34\x9a\x01"_us);
}
TEST_F(WireDecoder3Test, EntryAssignDouble) {
EXPECT_CALL(handler, EntryAssign("test"sv, 0x5678, 0x1234,
Value::MakeDouble(2.3e5), 0x9a));
DecodeComplete(
"\x10\x04test\x01\x56\x78\x12\x34"
"\x9a\x41\x0c\x13\x80\x00\x00\x00\x00"_us);
}
TEST_F(WireDecoder3Test, EntryUpdateBoolean) {
EXPECT_CALL(handler, EntryUpdate(0x5678, 0x1234, Value::MakeBoolean(true)));
DecodeComplete("\x11\x56\x78\x12\x34\x00\x01"_us);
}
TEST_F(WireDecoder3Test, EntryUpdateDouble) {
// values except min and max from
// http://www.binaryconvert.com/result_double.html
EXPECT_CALL(handler, EntryUpdate(0x5678, 0x1234, Value::MakeDouble(0.0)));
DecodeComplete("\x11\x56\x78\x12\x34\x01\x00\x00\x00\x00\x00\x00\x00\x00"_us);
EXPECT_CALL(handler, EntryUpdate(0x5678, 0x1234, Value::MakeDouble(2.3e5)));
DecodeComplete("\x11\x56\x78\x12\x34\x01\x41\x0c\x13\x80\x00\x00\x00\x00"_us);
EXPECT_CALL(
handler,
EntryUpdate(0x5678, 0x1234,
Value::MakeDouble(std::numeric_limits<double>::infinity())));
DecodeComplete("\x11\x56\x78\x12\x34\x01\x7f\xf0\x00\x00\x00\x00\x00\x00"_us);
EXPECT_CALL(handler, EntryUpdate(0x5678, 0x1234, Value::MakeDouble(DBL_MIN)));
DecodeComplete("\x11\x56\x78\x12\x34\x01\x00\x10\x00\x00\x00\x00\x00\x00"_us);
EXPECT_CALL(handler, EntryUpdate(0x5678, 0x1234, Value::MakeDouble(DBL_MAX)));
DecodeComplete("\x11\x56\x78\x12\x34\x01\x7f\xef\xff\xff\xff\xff\xff\xff"_us);
}
TEST_F(WireDecoder3Test, EntryUpdateString) {
EXPECT_CALL(handler,
EntryUpdate(0x5678, 0x1234, Value::MakeString("hello"sv)));
DecodeComplete("\x11\x56\x78\x12\x34\x02\x05hello"_us);
}
TEST_F(WireDecoder3Test, EntryUpdateString2) {
std::vector<uint8_t> in{0x11, 0x56, 0x78, 0x12, 0x34, 0x02, 0x7f};
in.insert(in.end(), 127, '*');
std::string out(127, '*');
EXPECT_CALL(handler,
EntryUpdate(0x5678, 0x1234, Value::MakeString(std::move(out))));
DecodeComplete(in);
}
TEST_F(WireDecoder3Test, EntryUpdateStringLarge) {
std::vector<uint8_t> in{0x11, 0x56, 0x78, 0x12, 0x34, 0x02, 0x80, 0x01};
in.insert(in.end(), 127, '*');
in.push_back('x');
std::string out(127, '*');
out.push_back('x');
EXPECT_CALL(handler,
EntryUpdate(0x5678, 0x1234, Value::MakeString(std::move(out))));
DecodeComplete(in);
}
TEST_F(WireDecoder3Test, EntryUpdateStringHuge) {
std::vector<uint8_t> in{0x11, 0x56, 0x78, 0x12, 0x34, 0x02, 0x81, 0x80, 0x04};
in.insert(in.end(), 65534, '*');
in.insert(in.end(), 3, 'x');
std::string out(65534, '*');
out.append(3, 'x');
EXPECT_CALL(handler,
EntryUpdate(0x5678, 0x1234, Value::MakeString(std::move(out))));
DecodeComplete(in);
}
TEST_F(WireDecoder3Test, EntryUpdateRaw) {
EXPECT_CALL(handler, EntryUpdate(0x5678, 0x1234, Value::MakeRaw("hello"_us)));
DecodeComplete("\x11\x56\x78\x12\x34\x03\x05hello"_us);
}
TEST_F(WireDecoder3Test, EntryUpdateBooleanArray) {
EXPECT_CALL(handler,
EntryUpdate(0x5678, 0x1234,
Value::MakeBooleanArray({false, true, false})));
DecodeComplete("\x11\x56\x78\x12\x34\x10\x03\x00\x01\x00"_us);
}
TEST_F(WireDecoder3Test, EntryUpdateBooleanArrayLarge) {
std::vector<uint8_t> in{0x11, 0x56, 0x78, 0x12, 0x34, 0x10, 0xff};
in.insert(in.end(), 255, 0);
std::vector<int> out(255, 0);
EXPECT_CALL(handler, EntryUpdate(0x5678, 0x1234,
Value::MakeBooleanArray(std::move(out))));
DecodeComplete(in);
}
TEST_F(WireDecoder3Test, EntryUpdateDoubleArray) {
EXPECT_CALL(handler,
EntryUpdate(0x5678, 0x1234, Value::MakeDoubleArray({0.5, 0.25})));
DecodeComplete(
"\x11\x56\x78\x12\x34\x11\x02"
"\x3f\xe0\x00\x00\x00\x00\x00\x00"
"\x3f\xd0\x00\x00\x00\x00\x00\x00"_us);
}
TEST_F(WireDecoder3Test, EntryUpdateDoubleArrayLarge) {
std::vector<uint8_t> in{0x11, 0x56, 0x78, 0x12, 0x34, 0x11, 0xff};
in.insert(in.end(), 255 * 8, 0);
std::vector<double> out(255, 0.0);
EXPECT_CALL(handler, EntryUpdate(0x5678, 0x1234,
Value::MakeDoubleArray(std::move(out))));
DecodeComplete(in);
}
TEST_F(WireDecoder3Test, EntryUpdateStringArray) {
EXPECT_CALL(handler, EntryUpdate(0x5678, 0x1234,
Value::MakeStringArray({"hello", "bye"})));
DecodeComplete(
"\x11\x56\x78\x12\x34\x12\x02\x05hello\x03"
"bye"_us);
}
TEST_F(WireDecoder3Test, EntryUpdateStringArrayLarge) {
std::vector<uint8_t> in{0x11, 0x56, 0x78, 0x12, 0x34, 0x12, 0xff};
in.insert(in.end(), 255, 0);
std::vector<std::string> out(255, "");
EXPECT_CALL(handler, EntryUpdate(0x5678, 0x1234,
Value::MakeStringArray(std::move(out))));
DecodeComplete(in);
}
TEST_F(WireDecoder3Test, EntryUpdateRpc) {
// RPC values are decoded as raw
EXPECT_CALL(handler, EntryUpdate(0x5678, 0x1234, Value::MakeRaw("hello"_us)));
DecodeComplete("\x11\x56\x78\x12\x34\x20\x05hello"_us);
}
TEST_F(WireDecoder3Test, EntryUpdateTypeError) {
auto in = "\x11\x56\x78\x12\x34\x30\x11"_us;
decoder.Execute(&in);
ASSERT_EQ(decoder.GetError(), "unrecognized value type");
}
} // namespace nt

View File

@@ -1,284 +0,0 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#include <cfloat>
#include <climits>
#include <string>
#include <string_view>
#include <utility>
#include <vector>
#include <gtest/gtest.h>
#include <wpi/SpanMatcher.h>
#include <wpi/raw_ostream.h>
#include "../TestPrinters.h"
#include "net3/Message3.h"
#include "net3/WireEncoder3.h"
#include "networktables/NetworkTableValue.h"
using namespace std::string_view_literals;
namespace nt {
class WireEncoder3Test : public ::testing::Test {
protected:
std::vector<uint8_t> out;
wpi::raw_uvector_ostream os{out};
};
TEST_F(WireEncoder3Test, Unknown) {
net3::WireEncode(os, net3::Message3{});
ASSERT_TRUE(out.empty());
}
TEST_F(WireEncoder3Test, KeepAlive) {
net3::WireEncode(os, net3::Message3::KeepAlive());
ASSERT_THAT(out, wpi::SpanEq("\x00"_us));
}
TEST_F(WireEncoder3Test, ClientHello) {
net3::WireEncode(os, net3::Message3::ClientHello("hello"));
ASSERT_THAT(out, wpi::SpanEq("\x01\x03\x00\x05hello"_us));
}
TEST_F(WireEncoder3Test, ProtoUnsup) {
net3::WireEncode(os, net3::Message3::ProtoUnsup());
net3::WireEncode(os, net3::Message3::ProtoUnsup(0x0200u));
ASSERT_THAT(out, wpi::SpanEq("\x02\x03\x00\x02\x02\x00"_us));
}
TEST_F(WireEncoder3Test, ServerHelloDone) {
net3::WireEncode(os, net3::Message3::ServerHelloDone());
ASSERT_THAT(out, wpi::SpanEq("\x03"_us));
}
TEST_F(WireEncoder3Test, ServerHello) {
net3::WireEncode(os, net3::Message3::ServerHello(0x03, "hello"));
ASSERT_THAT(out, wpi::SpanEq("\x04\x03\x05hello"_us));
}
TEST_F(WireEncoder3Test, ClientHelloDone) {
net3::WireEncode(os, net3::Message3::ClientHelloDone());
ASSERT_THAT(out, wpi::SpanEq("\x05"_us));
}
TEST_F(WireEncoder3Test, FlagsUpdate) {
net3::WireEncode(os, net3::Message3::FlagsUpdate(0x5678, 0x03));
ASSERT_THAT(out, wpi::SpanEq("\x12\x56\x78\x03"_us));
}
TEST_F(WireEncoder3Test, EntryDelete) {
net3::WireEncode(os, net3::Message3::EntryDelete(0x5678));
ASSERT_THAT(out, wpi::SpanEq("\x13\x56\x78"_us));
}
TEST_F(WireEncoder3Test, ClearEntries) {
net3::WireEncode(os, net3::Message3::ClearEntries());
ASSERT_THAT(out, wpi::SpanEq("\x14\xd0\x6c\xb2\x7a"_us));
}
TEST_F(WireEncoder3Test, ExecuteRpc) {
net3::WireEncode(os, net3::Message3::ExecuteRpc(0x5678, 0x1234, "hello"_us));
ASSERT_THAT(out, wpi::SpanEq("\x20\x56\x78\x12\x34\x05hello"_us));
}
TEST_F(WireEncoder3Test, RpcResponse) {
net3::WireEncode(os, net3::Message3::RpcResponse(0x5678, 0x1234, "hello"_us));
ASSERT_THAT(out, wpi::SpanEq("\x21\x56\x78\x12\x34\x05hello"_us));
}
TEST_F(WireEncoder3Test, EntryAssignBoolean) {
net3::WireEncode(os,
net3::Message3::EntryAssign("test"sv, 0x5678, 0x1234,
Value::MakeBoolean(true), 0x9a));
ASSERT_THAT(out, wpi::SpanEq("\x10\x04test\x00\x56\x78\x12\x34\x9a\x01"_us));
}
TEST_F(WireEncoder3Test, EntryAssignDouble) {
net3::WireEncode(os,
net3::Message3::EntryAssign("test"sv, 0x5678, 0x1234,
Value::MakeDouble(2.3e5), 0x9a));
ASSERT_THAT(out, wpi::SpanEq("\x10\x04test\x01\x56\x78\x12\x34"
"\x9a\x41\x0c\x13\x80\x00\x00\x00\x00"_us));
}
TEST_F(WireEncoder3Test, EntryUpdateBoolean) {
net3::WireEncode(os, net3::Message3::EntryUpdate(0x5678, 0x1234,
Value::MakeBoolean(true)));
ASSERT_THAT(out, wpi::SpanEq("\x11\x56\x78\x12\x34\x00\x01"_us));
}
TEST_F(WireEncoder3Test, EntryUpdateDouble) {
// values except min and max from
// http://www.binaryconvert.com/result_double.html
net3::WireEncode(
os, net3::Message3::EntryUpdate(0x5678, 0x1234, Value::MakeDouble(0.0)));
ASSERT_THAT(
out, wpi::SpanEq(
"\x11\x56\x78\x12\x34\x01\x00\x00\x00\x00\x00\x00\x00\x00"_us));
out.clear();
net3::WireEncode(os, net3::Message3::EntryUpdate(0x5678, 0x1234,
Value::MakeDouble(2.3e5)));
ASSERT_THAT(
out, wpi::SpanEq(
"\x11\x56\x78\x12\x34\x01\x41\x0c\x13\x80\x00\x00\x00\x00"_us));
out.clear();
net3::WireEncode(
os, net3::Message3::EntryUpdate(
0x5678, 0x1234,
Value::MakeDouble(std::numeric_limits<double>::infinity())));
ASSERT_THAT(
out, wpi::SpanEq(
"\x11\x56\x78\x12\x34\x01\x7f\xf0\x00\x00\x00\x00\x00\x00"_us));
out.clear();
net3::WireEncode(os, net3::Message3::EntryUpdate(0x5678, 0x1234,
Value::MakeDouble(DBL_MIN)));
ASSERT_THAT(
out, wpi::SpanEq(
"\x11\x56\x78\x12\x34\x01\x00\x10\x00\x00\x00\x00\x00\x00"_us));
out.clear();
net3::WireEncode(os, net3::Message3::EntryUpdate(0x5678, 0x1234,
Value::MakeDouble(DBL_MAX)));
ASSERT_THAT(
out, wpi::SpanEq(
"\x11\x56\x78\x12\x34\x01\x7f\xef\xff\xff\xff\xff\xff\xff"_us));
}
TEST_F(WireEncoder3Test, EntryUpdateString) {
net3::WireEncode(os, net3::Message3::EntryUpdate(
0x5678, 0x1234, Value::MakeString("hello"sv)));
ASSERT_THAT(out, wpi::SpanEq("\x11\x56\x78\x12\x34\x02\x05hello"_us));
}
TEST_F(WireEncoder3Test, EntryUpdateString2) {
std::vector<uint8_t> ex{0x11, 0x56, 0x78, 0x12, 0x34, 0x02, 0x7f};
ex.insert(ex.end(), 127, '*');
std::string in(127, '*');
net3::WireEncode(os, net3::Message3::EntryUpdate(
0x5678, 0x1234, Value::MakeString(std::move(in))));
ASSERT_THAT(out, ex);
}
TEST_F(WireEncoder3Test, EntryUpdateStringLarge) {
std::vector<uint8_t> ex{0x11, 0x56, 0x78, 0x12, 0x34, 0x02, 0x80, 0x01};
ex.insert(ex.end(), 127, '*');
ex.push_back('x');
std::string in(127, '*');
in.push_back('x');
net3::WireEncode(os, net3::Message3::EntryUpdate(
0x5678, 0x1234, Value::MakeString(std::move(in))));
ASSERT_THAT(out, ex);
}
TEST_F(WireEncoder3Test, EntryUpdateStringHuge) {
std::vector<uint8_t> ex{0x11, 0x56, 0x78, 0x12, 0x34, 0x02, 0x81, 0x80, 0x04};
ex.insert(ex.end(), 65534, '*');
ex.insert(ex.end(), 3, 'x');
std::string in(65534, '*');
in.append(3, 'x');
net3::WireEncode(os, net3::Message3::EntryUpdate(
0x5678, 0x1234, Value::MakeString(std::move(in))));
ASSERT_THAT(out, ex);
}
TEST_F(WireEncoder3Test, EntryUpdateRaw) {
net3::WireEncode(os, net3::Message3::EntryUpdate(0x5678, 0x1234,
Value::MakeRaw("hello"_us)));
ASSERT_THAT(out, wpi::SpanEq("\x11\x56\x78\x12\x34\x03\x05hello"_us));
}
TEST_F(WireEncoder3Test, EntryUpdateBooleanArray) {
net3::WireEncode(
os, net3::Message3::EntryUpdate(
0x5678, 0x1234, Value::MakeBooleanArray({false, true, false})));
ASSERT_THAT(out, wpi::SpanEq("\x11\x56\x78\x12\x34\x10\x03\x00\x01\x00"_us));
}
TEST_F(WireEncoder3Test, EntryUpdateBooleanArrayLarge) {
std::vector<uint8_t> ex{0x11, 0x56, 0x78, 0x12, 0x34, 0x10, 0xff};
ex.insert(ex.end(), 255, 0);
std::vector<int> in(255, 0);
net3::WireEncode(
os, net3::Message3::EntryUpdate(0x5678, 0x1234,
Value::MakeBooleanArray(std::move(in))));
ASSERT_THAT(out, ex);
}
TEST_F(WireEncoder3Test, EntryUpdateBooleanArrayTrunc) {
std::vector<uint8_t> ex{0x11, 0x56, 0x78, 0x12, 0x34, 0x10, 0xff};
ex.insert(ex.end(), 255, 0);
std::vector<int> in(256, 0);
net3::WireEncode(
os, net3::Message3::EntryUpdate(0x5678, 0x1234,
Value::MakeBooleanArray(std::move(in))));
ASSERT_THAT(out, ex);
}
TEST_F(WireEncoder3Test, EntryUpdateDoubleArray) {
net3::WireEncode(
os, net3::Message3::EntryUpdate(0x5678, 0x1234,
Value::MakeDoubleArray({0.5, 0.25})));
ASSERT_THAT(out, wpi::SpanEq("\x11\x56\x78\x12\x34\x11\x02"
"\x3f\xe0\x00\x00\x00\x00\x00\x00"
"\x3f\xd0\x00\x00\x00\x00\x00\x00"_us));
}
TEST_F(WireEncoder3Test, EntryUpdateDoubleArrayLarge) {
std::vector<uint8_t> ex{0x11, 0x56, 0x78, 0x12, 0x34, 0x11, 0xff};
ex.insert(ex.end(), 255 * 8, 0);
std::vector<double> in(255, 0.0);
net3::WireEncode(
os, net3::Message3::EntryUpdate(0x5678, 0x1234,
Value::MakeDoubleArray(std::move(in))));
ASSERT_THAT(out, ex);
}
TEST_F(WireEncoder3Test, EntryUpdateDoubleArrayTrunc) {
std::vector<uint8_t> ex{0x11, 0x56, 0x78, 0x12, 0x34, 0x11, 0xff};
ex.insert(ex.end(), 255 * 8, 0);
std::vector<double> in(256, 0.0);
net3::WireEncode(
os, net3::Message3::EntryUpdate(0x5678, 0x1234,
Value::MakeDoubleArray(std::move(in))));
ASSERT_THAT(out, ex);
}
TEST_F(WireEncoder3Test, EntryUpdateStringArray) {
net3::WireEncode(
os, net3::Message3::EntryUpdate(
0x5678, 0x1234, Value::MakeStringArray({"hello", "bye"})));
ASSERT_THAT(out, wpi::SpanEq("\x11\x56\x78\x12\x34\x12\x02\x05hello\x03"
"bye"_us));
}
TEST_F(WireEncoder3Test, EntryUpdateStringArrayLarge) {
std::vector<uint8_t> ex{0x11, 0x56, 0x78, 0x12, 0x34, 0x12, 0xff};
ex.insert(ex.end(), 255, 0);
std::vector<std::string> in(255, "");
net3::WireEncode(
os, net3::Message3::EntryUpdate(0x5678, 0x1234,
Value::MakeStringArray(std::move(in))));
ASSERT_THAT(out, ex);
}
TEST_F(WireEncoder3Test, EntryUpdateStringArrayTrunc) {
std::vector<uint8_t> ex{0x11, 0x56, 0x78, 0x12, 0x34, 0x12, 0xff};
ex.insert(ex.end(), 255, 0);
std::vector<std::string> in(256, "");
net3::WireEncode(
os, net3::Message3::EntryUpdate(0x5678, 0x1234,
Value::MakeStringArray(std::move(in))));
ASSERT_THAT(out, ex);
}
} // namespace nt

View File

@@ -208,8 +208,7 @@ NT_SetTopicPersistent
NT_SetTopicProperties
NT_SetTopicProperty
NT_SetTopicRetained
NT_StartClient3
NT_StartClient4
NT_StartClient
NT_StartConnectionDataLog
NT_StartDSClient
NT_StartEntryDataLog

View File

@@ -50,7 +50,7 @@ static std::string MakeTitle(NT_Inst inst, nt::Event event) {
auto numClients = nt::GetConnections(inst).size();
return fmt::format("OutlineViewer - {} Client{} Connected", numClients,
(numClients == 1 ? "" : "s"));
} else if (mode & NT_NET_MODE_CLIENT3 || mode & NT_NET_MODE_CLIENT4) {
} else if (mode & NT_NET_MODE_CLIENT) {
if (event.Is(NT_EVENT_CONNECTED)) {
return fmt::format("OutlineViewer - Connected ({})",
event.GetConnectionInfo()->remote_ip);

View File

@@ -52,7 +52,7 @@ class PreferencesTest {
fail(ex);
}
m_inst.startServer(filepath.toString(), "", 0, 0);
m_inst.startServer(filepath.toString(), "", 0);
try {
int count = 0;
while (m_inst.getNetworkMode().contains(NetworkTableInstance.NetworkMode.kStarting)) {