Squashed 'third_party/allwpilib_2019/' content from commit bd05dfa1c
Change-Id: I2b1c2250cdb9b055133780c33593292098c375b7
git-subtree-dir: third_party/allwpilib_2019
git-subtree-split: bd05dfa1c7cca74c4fac451e7b9d6a37e7b53447
diff --git a/ntcore/.styleguide b/ntcore/.styleguide
new file mode 100644
index 0000000..4c00fa9
--- /dev/null
+++ b/ntcore/.styleguide
@@ -0,0 +1,31 @@
+cHeaderFileInclude {
+ _c\.h$
+}
+
+cppHeaderFileInclude {
+ (?<!_c)\.h$
+ \.inc$
+}
+
+cppSrcFileInclude {
+ \.cpp$
+}
+
+generatedFileExclude {
+ ntcore/doc/
+}
+
+repoRootNameOverride {
+ ntcore
+}
+
+includeGuardRoots {
+ ntcore/src/main/native/cpp/
+ ntcore/src/main/native/include/
+ ntcore/src/test/native/cpp/
+}
+
+includeOtherLibs {
+ ^support/
+ ^wpi/
+}
diff --git a/ntcore/CMakeLists.txt b/ntcore/CMakeLists.txt
new file mode 100644
index 0000000..47e2264
--- /dev/null
+++ b/ntcore/CMakeLists.txt
@@ -0,0 +1,72 @@
+project(ntcore)
+
+file(GLOB
+ ntcore_native_src src/main/native/cpp/*.cpp
+ ntcore_native_src src/main/native/cpp/networktables/*.cpp
+ ntcore_native_src src/main/native/cpp/tables/*.cpp)
+add_library(ntcore ${ntcore_native_src})
+set_target_properties(ntcore PROPERTIES DEBUG_POSTFIX "d")
+target_include_directories(ntcore PUBLIC
+ $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/src/main/native/include>
+ $<INSTALL_INTERFACE:${include_dest}/ntcore>)
+target_link_libraries(ntcore PUBLIC wpiutil)
+
+set_property(TARGET ntcore PROPERTY FOLDER "libraries")
+
+install(TARGETS ntcore EXPORT ntcore DESTINATION "${main_lib_dest}")
+install(DIRECTORY src/main/native/include/ DESTINATION "${include_dest}/ntcore")
+
+if (MSVC)
+ set (ntcore_config_dir ${wpilib_dest})
+else()
+ set (ntcore_config_dir share/ntcore)
+endif()
+
+install(FILES ntcore-config.cmake DESTINATION ${ntcore_config_dir})
+install(EXPORT ntcore DESTINATION ${ntcore_config_dir})
+
+# Java bindings
+if (NOT WITHOUT_JAVA)
+ find_package(Java REQUIRED)
+ find_package(JNI REQUIRED)
+ include(UseJava)
+ set(CMAKE_JAVA_COMPILE_FLAGS "-Xlint:unchecked")
+
+ file(GLOB
+ ntcore_jni_src src/main/native/cpp/jni/NetworkTablesJNI.cpp)
+
+ file(GLOB_RECURSE JAVA_SOURCES src/main/java/*.java)
+ set(CMAKE_JNI_TARGET true)
+
+ if(${CMAKE_VERSION} VERSION_LESS "3.11.0")
+ set(CMAKE_JAVA_COMPILE_FLAGS "-h" "${CMAKE_CURRENT_BINARY_DIR}/jniheaders")
+ add_jar(ntcore_jar ${JAVA_SOURCES} INCLUDE_JARS wpiutil_jar OUTPUT_NAME ntcore)
+ else()
+ add_jar(ntcore_jar ${JAVA_SOURCES} INCLUDE_JARS wpiutil_jar OUTPUT_NAME ntcore GENERATE_NATIVE_HEADERS ntcore_jni_headers)
+ endif()
+
+ get_property(NTCORE_JAR_FILE TARGET ntcore_jar PROPERTY JAR_FILE)
+ install(FILES ${NTCORE_JAR_FILE} DESTINATION "${java_lib_dest}")
+
+ set_property(TARGET ntcore_jar PROPERTY FOLDER "java")
+
+ add_library(ntcorejni ${ntcore_jni_src})
+ target_link_libraries(ntcorejni PUBLIC ntcore wpiutil)
+
+ set_property(TARGET ntcorejni PROPERTY FOLDER "libraries")
+
+ if (MSVC)
+ install(TARGETS ntcorejni RUNTIME DESTINATION "${jni_lib_dest}" COMPONENT Runtime)
+ endif()
+
+ if(${CMAKE_VERSION} VERSION_LESS "3.11.0")
+ target_include_directories(ntcorejni PRIVATE ${JNI_INCLUDE_DIRS})
+ target_include_directories(ntcorejni PRIVATE "${CMAKE_CURRENT_BINARY_DIR}/jniheaders")
+ else()
+ target_link_libraries(ntcorejni PRIVATE ntcore_jni_headers)
+ endif()
+ add_dependencies(ntcorejni ntcore_jar)
+
+ install(TARGETS ntcorejni EXPORT ntcorejni DESTINATION "${main_lib_dest}")
+
+endif()
diff --git a/ntcore/build.gradle b/ntcore/build.gradle
new file mode 100644
index 0000000..659b0f9
--- /dev/null
+++ b/ntcore/build.gradle
@@ -0,0 +1,49 @@
+ext {
+ nativeName = 'ntcore'
+ devMain = 'edu.wpi.first.ntcore.DevMain'
+}
+
+apply from: "${rootDir}/shared/jni/setupBuild.gradle"
+
+model {
+ // Exports config is a utility to enable exporting all symbols in a C++ library on windows to a DLL.
+ // This removes the need for DllExport on a library. However, the gradle C++ builder has a bug
+ // where some extra symbols are added that cannot be resolved at link time. This configuration
+ // lets you specify specific symbols to exlude from exporting.
+ exportsConfigs {
+ ntcore(ExportsConfig) {
+ x86ExcludeSymbols = ['_CT??_R0?AV_System_error', '_CT??_R0?AVexception', '_CT??_R0?AVfailure',
+ '_CT??_R0?AVbad_cast',
+ '_CT??_R0?AVruntime_error', '_CT??_R0?AVsystem_error', '_CTA5?AVfailure',
+ '_TI5?AVfailure']
+ x64ExcludeSymbols = ['_CT??_R0?AV_System_error', '_CT??_R0?AVexception', '_CT??_R0?AVfailure',
+ '_CT??_R0?AVbad_cast',
+ '_CT??_R0?AVruntime_error', '_CT??_R0?AVsystem_error', '_CTA5?AVfailure',
+ '_TI5?AVfailure']
+ }
+ ntcoreJNI(ExportsConfig) {
+ x86SymbolFilter = { symbols ->
+ def retList = []
+ symbols.each { symbol ->
+ if (symbol.startsWith('NT_')) {
+ retList << symbol
+ }
+ }
+ return retList
+ }
+ x64SymbolFilter = { symbols ->
+ def retList = []
+ symbols.each { symbol ->
+ if (symbol.startsWith('NT_')) {
+ retList << symbol
+ }
+ }
+ return retList
+ }
+ }
+ }
+}
+
+pmdMain {
+ pmdMain.enabled = false
+}
diff --git a/ntcore/doc/alloy-model.adoc b/ntcore/doc/alloy-model.adoc
new file mode 100644
index 0000000..35ca7d8
--- /dev/null
+++ b/ntcore/doc/alloy-model.adoc
@@ -0,0 +1,198 @@
+= Network Tables Alloy Model
+
+Alloy (http://alloy.mit.edu/alloy/) is a formal logic tool that can analyze
+first-order logic expressions. Under the proposed sequence number -based
+protocol, assuming that all nodes start from the same state, Alloy is unable to
+find a way where two nodes with the same sequence number have different state
+when activity ceases.
+
+The model used is included below. Although Alloy cannot test all cases, since
+such an exhaustive search is intractable, it provides a high level of
+confidence in the proposed protocol.
+
+----
+--- Models a distributed, centralized hash table system called NetworkTables
+--- System state is protected by sequence numbers; the server's value for a certain sequence number always wins
+--- Paul Malmsten, 2012 pmalmsten@gmail.com
+
+open util/ordering[Time] as TO
+open util/natural as natural
+
+sig Time {}
+sig State {}
+
+--- Define nodes and server
+sig Node {
+ state: State -> Time,
+ sequenceNumber: Natural -> Time
+}
+
+--- Only one server
+one sig Server extends Node {
+}
+
+--- Define possible events
+abstract sig Event {
+ pre,post: Time,
+ receiver: one Node
+}
+
+// For all events, event.post is the time directly following event.pre
+fact {
+ all e:Event {
+ e.post = e.pre.next
+ }
+}
+
+// Represents that state has changed on a node
+sig StateChangeEvent extends Event {
+}
+
+// Represents that state has been transferred from one node to another
+sig StateTransferEvent extends Event {
+ sender: one Node
+}
+
+fact {
+ --- Every node must assume at most one state
+ all t:Time, n:Node | #n.state.t = 1
+
+ --- Every node must assume one sequence number
+ all t:Time, n:Node | #n.sequenceNumber.t = 1
+
+ --- Sequence numbers may only increment
+ all t:Time - last, n:Node | let t' = t.next | natural/gte[n.sequenceNumber.t', n.sequenceNumber.t]
+}
+
+
+fact stateChangedImpliesAStateTransfer {
+ all sce:StateChangeEvent {
+ // A StateChange on a client causes a transfer to the Server if its sequence number is greater than the server's
+ sce.receiver in Node - Server and natural/gt[sce.receiver.sequenceNumber.(sce.post), Server.sequenceNumber.(sce.post)]
+ implies
+ some ste:StateTransferEvent {
+ ste.pre = sce.post and ste.sender = sce.receiver and ste.receiver = Server
+ }
+ }
+
+ all sce:StateChangeEvent {
+ // A StateChange on the server causes a transfer to all clients
+ sce.receiver = Server implies
+ all n:Node - Server {
+ some ste:StateTransferEvent {
+ ste.pre = sce.post and ste.sender = Server and ste.receiver = n
+ }
+ }
+ }
+
+ all sce:StateTransferEvent {
+ // A StateTransfer to the server causes a transfer to all clients
+ sce.receiver = Server implies
+ all n:Node - Server {
+ some ste:StateTransferEvent {
+ ste.pre = sce.post and ste.sender = Server and ste.receiver = n
+ }
+ }
+ }
+}
+
+fact stateTransferEventsMoveState {
+ all ste:StateTransferEvent {
+ ste.sender = Server and not ste.receiver = Server or ste.receiver = Server and not ste.sender = Server
+
+ // Nodes can only post to the server if their sequence number is greater than the servers
+ ste.receiver = Server implies natural/gt[ste.sender.sequenceNumber.(ste.pre), ste.receiver.sequenceNumber.(ste.pre)]
+
+ // Server can only post to clients if its sequence number is greater than or equal to the client
+ ste.sender = Server implies natural/gte[ste.sender.sequenceNumber.(ste.pre), ste.receiver.sequenceNumber.(ste.pre)]
+
+ // Actual transfer
+ (ste.receiver.state.(ste.post) = ste.sender.state.(ste.pre) and
+ ste.receiver.sequenceNumber.(ste.post) = ste.sender.sequenceNumber.(ste.pre))
+ }
+}
+
+
+fact noEventsPendingAtEnd {
+ no e:Event {
+ e.pre = last
+ }
+}
+
+fact noDuplicateEvents {
+ all e,e2:Event {
+ // Two different events with the same receiver imply they occurred at different times
+ e.receiver = e2.receiver and e != e2 implies e.pre != e2.pre
+ }
+}
+
+fact noStateTransfersToSelf {
+ all ste:StateTransferEvent {
+ ste.sender != ste.receiver
+ }
+}
+
+fact noDuplicateStateTransferEvents {
+ all ste,ste2:StateTransferEvent {
+ // Two state transfer events with the same nodes imply that they occurred at different times
+ ste.sender = ste2.sender and ste.receiver = ste2.receiver and ste != ste2 implies ste.pre != ste2.pre
+ }
+}
+
+--- Trace (time invariant)
+fact trace {
+ all t:Time - last | let t' = t.next {
+ all n:Node {
+ // A node in (pre.t).receiver means it is being pointed to by some event that will happen over the next time step
+ n in (pre.t).receiver implies n.state.t' != n.state.t and n.sequenceNumber.t' != n.sequenceNumber.t // A node which receives some sort of event at time t causes it to change state
+ else n.state.t' = n.state.t and n.sequenceNumber.t' = n.sequenceNumber.t // Otherwise, it does not change state
+ }
+ }
+}
+
+--- Things we might like to be true, but are not always true
+
+pred atLeastOneEvent {
+ #Event >= 1
+}
+
+pred allNodesStartAtSameStateAndSequenceNumber {
+ all n,n2:Node {
+ n.state.first = n2.state.first and n.sequenceNumber.first = n2.sequenceNumber.first
+ }
+}
+
+pred noStateChangeEventsAtEnd {
+ no e:StateChangeEvent {
+ e.post = last
+ }
+}
+
+--- Demonstration (Alloy will try to satisfy this)
+
+pred show {
+ atLeastOneEvent
+}
+run show
+
+--- Assertions (Alloy will try to break these)
+
+assert allNodesConsistentAtEnd {
+ allNodesStartAtSameStateAndSequenceNumber implies
+ all n,n2:Node {
+ // At the end of a sequence (last) all nodes with the same sequence number have the same state
+ n.sequenceNumber.last = n2.sequenceNumber.last implies n.state.last = n2.state.last
+ }
+}
+check allNodesConsistentAtEnd for 3 Event, 10 Node, 3 State, 5 Time, 5 Natural
+check allNodesConsistentAtEnd for 8 Event, 2 Node, 5 State, 9 Time, 9 Natural
+
+assert serverHasHighestSeqNumAtEnd {
+ allNodesStartAtSameStateAndSequenceNumber implies
+ all n:Node - Server{
+ // At the end of a sequence (last) all nodes with the same sequence number have the same state
+ natural/gte[Server.sequenceNumber.last, n.sequenceNumber.last]
+ }
+}
+check serverHasHighestSeqNumAtEnd for 3 Event, 10 Node, 3 State, 5 Time, 5 Natural
+----
diff --git a/ntcore/doc/networktables2.adoc b/ntcore/doc/networktables2.adoc
new file mode 100644
index 0000000..8471f86
--- /dev/null
+++ b/ntcore/doc/networktables2.adoc
@@ -0,0 +1,456 @@
+= Network Tables Protocol Specification, Version 2.0
+WPILib Developers <wpilib@wpi.edu>
+Protocol Revision 2.0 (0x0200), 1/8/2013
+:toc:
+:toc-placement: preamble
+:sectanchors:
+
+This document defines a network protocol for a key-value store that may be read
+from and written to by multiple remote clients. A central server, most often
+running on a FIRST FRC robot controller, is responsible for providing
+information consistency and for facilitating communication between clients.
+This document describes protocol revision 2.0 (0x0200).
+
+Information consistency is guaranteed through the use of a sequence number
+associated with each key-value pair. An update of a key-value pair increments
+the associated sequence number, and this update information is shared with all
+participating clients. The central server only applies and redistributes
+updates which have a larger sequence number than its own, which guarantees that
+a client must have received a server's most recent state before it can replace
+it with a new value.
+
+This is a backwards-incompatible rework of the Network Tables network protocol
+originally introduced for the 2012 FIRST Robotics Competition. Note that this
+revision of the Network Tables protocol no longer includes the concept of
+sub-tables. We suggest that instead of representing sub-tables as first-class
+data types in the network protocol, it would be easy for an implementation to
+provide a similar API abstraction by adding prefixes to keys. For example, we
+suggest using Unix-style path strings to define sub-table hierarchies. The
+prefix ensures that sub-table namespaces do not collide in a global hashtable
+without requiring an explicit sub-table representation.
+
+In addition, the explicit concept of grouping multiple updates such that they
+are all visible at the same time to user code on a remote device was discarded.
+Instead, array types for all common elements are provided. By using an array
+data type, users may achieve the same level of atomicity for common operations
+(e.g. sending a cartesian coordinate pair) without requiring the complexity of
+arbitrarily grouped updates.
+
+This document conforms to <<rfc2119>> - Key words for use in RFCs to Indicate
+Requirement Levels.
+
+[[references]]
+== References
+
+[[rfc1982,RFC 1982]]
+* RFC 1982, Serial Number Arithmetic, http://tools.ietf.org/html/rfc1982
+
+[[rfc2119,RFC 2119]]
+* RFC 2119, Key words for use in RFCs to Indicate Requirement Levels,
+http://tools.ietf.org/html/rfc2119
+
+[[definitions]]
+== Definitions
+
+[[def-client]]
+Client:: An implementation of this protocol running in client configuration.
+Any number of Clients may exist for a given Network.
+
+[[def-entry]]
+Entry:: A data value identified by a string name.
+
+[[def-entry-id]]
+Entry ID:: An unsigned 2-byte ID by which the Server and Clients refer to an
+Entry across the network instead of using the full string key for the Entry.
+Entry IDs range from 0x0000 to 0xFFFE (0xFFFF is reserved for an Entry
+Assignment issued by a Client).
+
+[[def-server]]
+Server:: An implementation of this protocol running in server configuration.
+One and only one Server must exist for a given Network.
+
+[[def-network]]
+Network:: One or more Client nodes connected to a Server.
+
+[[def-user-code]]
+User Code:: User-supplied code which may interact with a Client or Server. User
+Code should be executed on the same computer as the Client or Server instance
+it interacts with.
+
+[[def-sequence-number]]
+Sequence Number:: An unsigned number which allows the Server to resolve update
+conflicts between Clients and/or the Server. Sequence numbers may overflow.
+Sequential arithmetic comparisons, which must be used with Sequence Numbers,
+are defined by RFC 1982.
+
+[[def-protocol-revision]]
+Protocol Revision:: A 16-bit unsigned integer which indicates the version of
+the network tables protocol that a client wishes to use. The protocol revision
+assigned to this version of the network tables specification is listed at the
+top of this document. This number is listed in dot-decimal notation as well as
+its equivalent hexadecimal value.
+
+== Transport Layer
+
+Conventional implementations of this protocol should use TCP for reliable
+communication; the Server should listen on TCP port 1735 for incoming
+connections.
+
+== Example Exchanges
+
+[[exchange-connect]]
+=== Client Connects to the Server
+
+Directly after client establishes a connection with the Server, the following
+procedure must be followed:
+
+. The Client sends a <<msg-client-hello>> message to the Server
+
+. The Server sends one <<msg-assign>> for every field it currently recognizes.
+
+. The Server sends a <<msg-server-hello-complete>> message.
+
+. For all Entries the Client recognizes that the Server did not identify with a
+Entry Assignment, the client follows the <<exchange-client-creates-entry>>
+protocol.
+
+In the event that the Server does not support the protocol revision that the
+Client has requested in a Client Hello message, the Server must instead issue a
+<<msg-protocol-unsupported>> message to the joining client and close the
+connection.
+
+[[exchange-client-creates-entry]]
+=== Client Creates an Entry
+
+When User Code on a Client assigns a value to an Entry that the Server has not
+yet issued a Entry Assignment for, the following procedure must be followed:
+
+. The Client sends an <<msg-assign>> with an Entry ID of 0xFFFF.
+
+. The Server issues an <<msg-assign>> to all Clients (including the sender) for
+the new field containing a real Entry ID and Sequence Number for the new field.
+
+In the event that User Code on the Client updates the value of the
+to-be-announced field again before the expected Entry Assignment is received,
+then the Client must save the new value and take no other action (the most
+recent value of the field should be issued when the Entry Assignment arrives,
+if it differs from the value contained in the received Entry Assignment).
+
+In the event that the Client receives a Entry Assignment from the Server for
+the Entry that it intended to issue an Entry Assignment for, before it issued
+its own Entry Assignment, the procedure may end early.
+
+In the event that the Server receives a duplicate Entry Assignment from a
+Client (likely due to the client having not yet received the Server's Entry
+Assignment), the Server should ignore the duplicate Entry Assignment.
+
+[[exchange-client-updates-entry]]
+=== Client Updates an Entry
+
+When User Code on a Client updates the value of an Entry, the Client must send
+an <<msg-update>> message to the Server. The Sequence Number included in the
+Entry Update message must be the most recently received Sequence Number for the
+Entry to be updated incremented by one.
+
+.Example:
+
+. Client receives Entry Assignment message for Entry "a" with integer value 1,
+Entry ID of 0, and Sequence Number 1.
+
+. User Code on Client updates value of Entry "a" to 16 (arbitrary).
+
+. Client sends Entry Update message to Server for Entry 0 with a Sequence
+Number of 2 and a value of 16.
+
+When the Server receives an Entry Update message, it first checks the Sequence
+Number in the message against the Server's value for the Sequence Number
+associated with the Entry to be updated. If the received Sequence Number is
+strictly greater than (aside: see definition of "greater than" under the
+definition of Sequence Number) the Server's Sequence Number for the Entry to be
+updated, the Server must apply the new value for the indicated Entry and repeat
+the Entry Update message to all other connected Clients.
+
+If the received Sequence Number is less than or equal (see definition of "less
+than or equal" in RFC 1982) to the Server's Sequence Number for the Entry to be
+updated, this implies that the Client which issued the Entry Update message has
+not yet received one or more Entry Update message(s) that the Server recently
+sent to it; therefore, the Server must ignore the received Entry Update
+message. In the event that comparison between two Sequence Numbers is undefined
+(see RFC 1982), then the Server must always win (it ignores the Entry Update
+message under consideration).
+
+[[update-rate]]
+NOTE: If User Code modifies the value of an Entry too quickly, 1) users may not
+see every value appear on remote machines, and 2) the consistency protection
+offered by the Entry's Sequence Number may be lost (by overflowing before
+remote devices hear recent values). It is recommended that implementations
+detect when user code updates an Entry more frequently than once every 5
+milliseconds and print a warning message to the user (and/or offer some other
+means of informing User Code of this condition).
+
+[[exchange-server-creates-entry]]
+=== Server Creates an Entry
+
+When User Code on the Server assigns a value to a Entry which does not exist,
+the Server must issue an <<msg-assign>> message to all connected clients.
+
+[[exchange-server-updates-entry]]
+=== Server Updates an Entry
+
+When User Code on the Server updates the value of an Entry, the Server must
+apply the new value to the Entry immediately, increment the associated Entry's
+Sequence Number, and issue a <<msg-update>> message containing the new value
+and Sequence Number of the associated Entry to all connected Clients.
+
+NOTE: See <<update-rate,Note>> under <<exchange-client-updates-entry>>.
+
+[[exchange-keep-alive]]
+=== Keep Alive
+
+To maintain a connection and prove a socket is still open, a Client or Server
+may issue <<msg-keep-alive>> messages. Clients and the Server should ignore
+incoming Keep Alive messages.
+
+The intent is that by writing a Keep Alive to a socket, a Client forces its
+network layer (TCP) to reevaluate the state of the network connection as it
+attempts to deliver the Keep Alive message. In the event that a connection is
+no longer usable, a Client's network layer should inform the Client that it is
+no longer usable within a few attempts to send a Keep Alive message.
+
+To provide timely connection status information, Clients should send a Keep
+Alive message to the Server after every 1 second period of connection
+inactivity (i.e. no information is being sent to the Server). Clients should
+not send Keep Alive messages more frequently than once every 100 milliseconds.
+
+Since the Server does not require as timely information about the status of a
+connection, it is not required to send Keep Alive messages during a period of
+inactivity.
+
+[[bandwidth]]
+== Bandwidth and Latency Considerations
+
+To reduce unnecessary bandwidth usage, implementations of this protocol should:
+
+* Send an Entry Update if and only if the value of an Entry is changed to a
+value that is different from its prior value.
+
+* Buffer messages and transmit them in groups, when possible, to reduce
+transport protocol overhead.
+
+* Only send the most recent value of an Entry. When User Code updates the value
+of an Entry more than once before the new value is transmitted, only the latest
+value of the Entry should be sent.
+
+It is important to note that these behaviors will increase the latency between
+when a Client or Server updates the value of an Entry and when all Clients
+reflect the new value. The exact behavior of this buffering is left to
+implementations to determine, although the chosen scheme should reflect the
+needs of User Code. Implementations may include a method by which User Code can
+specify the maximum tolerable send latency.
+
+[[entry-types]]
+== Entry Types
+
+Entry Type must assume one the following values:
+
+[cols="1,3"]
+|===
+|Numeric Value |Type
+
+|0x00
+|Boolean
+
+|0x01
+|Double
+
+|0x02
+|String
+
+|0x10
+|Boolean Array
+
+|0x11
+|Double Array
+
+|0x12
+|String Array
+|===
+
+[[entry-values]]
+== Entry Values
+
+Entry Value must assume the following structure as indicated by Entry Type:
+
+[cols="1,3"]
+|===
+|Entry Type |Entry Value Format
+
+|[[entry-value-boolean]]Boolean
+|1 byte, unsigned; True = 0x01, False = 0x00
+
+|[[entry-value-double]]Double
+|8 bytes, IEEE 754 floating-point "double format" bit layout; (big endian)
+
+|[[entry-value-string]]String
+|2 byte, unsigned length prefix (big endian) of the number of raw bytes to
+follow, followed by the string encoded in UTF-8
+
+|[[entry-value-boolean-array]]Boolean Array
+|1 byte, unsigned, number of elements within the array to follow
+
+N bytes - The raw bytes representing each Boolean element contained within the
+array, beginning with the item at index 0 within the array.
+
+|[[entry-value-double-array]]Double Array
+|1 byte, unsigned, number of elements within the array to follow
+
+N bytes - The raw bytes representing each Double element contained within the
+array, beginning with the item at index 0 within the array.
+
+|[[entry-value-string-array]]String Array
+|1 byte, unsigned, number of elements within the array to follow
+
+N bytes - The raw bytes representing each String element contained within the
+array, beginning with the item at index 0 within the array.
+|===
+
+== Message Structures
+
+All messages are of the following format:
+
+[cols="1,3"]
+|===
+|Field Name |Field Type
+
+|Message Type
+|1 byte, unsigned
+
+|Message Data
+|N bytes (length determined by Message Type)
+|===
+
+[[msg-keep-alive]]
+=== Keep Alive
+
+Indicates that the remote party is checking the status of a network connection.
+
+[cols="1,3"]
+|===
+|Field Name |Field Type
+
+|0x00 - Keep Alive
+|1 byte, unsigned; Message Type
+|===
+
+[[msg-client-hello]]
+=== Client Hello
+
+A Client issues a Client Hello message when first establishing a connection.
+The Client Protocol Revision field specifies the Network Table protocol
+revision that the Client would like to use.
+
+[cols="1,3"]
+|===
+|Field Name |Field Type
+
+|0x01 - Client Hello
+|1 byte, unsigned; Message Type
+
+|Client Protocol Revision
+|2 bytes, Unsigned 16-bit integer (big-endian).
+
+See <<def-protocol-revision,Protocol Revision>>
+|===
+
+[[msg-protocol-unsupported]]
+=== Protocol Version Unsupported
+
+A Server issues a Protocol Version Unsupported message to a Client to inform it
+that the requested protocol revision is not supported. It also includes the
+most recent protocol revision which it supports, such that a Client may
+reconnect under a prior protocol revision if able.
+
+[cols="1,3"]
+|===
+|Field Name |Field Type
+
+|0x02 - Protocol Version Unsupported
+|1 byte, unsigned; Message Type
+
+|Server Supported Protocol Revision
+|2 bytes, Unsigned 16-bit integer (big-endian).
+
+See <<def-protocol-revision,Protocol Revision>>
+|===
+
+[[msg-server-hello-complete]]
+=== Server Hello Complete
+
+A Server issues a Server Hello Complete message when it has finished informing
+a newly-connected client of all of the fields it currently recognizes.
+Following the receipt of this message, a Client should inform the Server of
+any/all additional fields that it recognizes that the Server did not announce.
+
+[cols="1,3"]
+|===
+|Field Name |Field Type
+
+|0x03 - Server Hello Complete
+|1 byte, unsigned; Message Type
+|===
+
+[[msg-assign]]
+=== Entry Assignment
+
+A Entry Assignment message informs the remote party of a new Entry. An Entry
+Assignment's value field must be the most recent value of the field being
+assigned at the time that the Entry Assignment is sent.
+
+[cols="1,3"]
+|===
+|Field Name |Field Type
+
+|0x10 - Entry Assignment
+|1 byte, unsigned; Message Type
+
+|Entry Name
+|<<entry-value-string,String>>
+
+|Entry Type
+|1 byte, unsigned; see <<entry-types,Entry Types>>
+
+|Entry ID
+|2 bytes, unsigned
+
+|Entry Sequence Number
+|2 bytes, unsigned
+
+|Entry Value
+|N bytes, length depends on Entry Type
+|===
+
+If the Entry ID is 0xFFFF, then this assignment represents a request from a
+Client to the Server. In this event, the Entry ID field and the Entry Sequence
+Number field must not be stored or relied upon as they otherwise would be.
+
+[[msg-update]]
+=== Entry Update
+
+An Entry Update message informs a remote party of a new value for an Entry.
+
+[cols="1,3"]
+|===
+|Field Name |Field Type
+
+|0x11 - Entry Update
+|1 byte, unsigned; Message Type
+
+|Entry ID
+|2 bytes, unsigned
+
+|Entry Sequence Number
+|2 bytes, unsigned
+
+|Entry Value
+|N bytes, length dependent on value type
+|===
diff --git a/ntcore/doc/networktables3.adoc b/ntcore/doc/networktables3.adoc
new file mode 100644
index 0000000..af8b953
--- /dev/null
+++ b/ntcore/doc/networktables3.adoc
@@ -0,0 +1,885 @@
+= Network Tables Protocol Specification, Version 3.0
+WPILib Developers <wpilib@wpi.edu>
+Protocol Revision 3.0 (0x0300), 6/12/2015
+:toc:
+:toc-placement: preamble
+:sectanchors:
+
+This document defines a network protocol for a key-value store that may be read
+from and written to by multiple remote clients. A central server, most often
+running on a FIRST FRC robot controller, is responsible for providing
+information consistency and for facilitating communication between clients.
+This document describes protocol revision 3.0 (0x0300).
+
+Information consistency is guaranteed through the use of a sequence number
+associated with each key-value pair. An update of a key-value pair increments
+the associated sequence number, and this update information is shared with all
+participating clients. The central server only applies and redistributes
+updates which have a larger sequence number than its own, which guarantees that
+a client must have received a server's most recent state before it can replace
+it with a new value.
+
+This is a backwards-compatible update of <<networktables2,version 2.0>> of the
+Network Tables network protocol. The protocol is designed such that 3.0 clients
+and servers can interoperate with 2.0 clients and servers with the only loss of
+functionality being the extended features introduced in 3.0.
+
+This document conforms to <<rfc2119>> - Key words for use in RFCs to Indicate
+Requirement Levels.
+
+== Summary of Changes from 2.0 to 3.0
+
+3 way connection handshake:: When a Client establishes a connection, after
+receiving the <<msg-server-hello-complete>> message and sending its local
+entries, it finishes with a <<msg-client-hello-complete>> message to the
+server. This enables the Server to be aware of when the Client is fully
+synchronized.
+
+String length encoding:: String length is now encoded as unsigned <<leb128>>
+rather than as a 2-byte unsigned integer. This both allows string lengths
+longer than 64K and is more space efficient for the common case of short
+strings (<128 byte strings only require a single byte for length).
+
+Entry deletion:: Entries may now be deleted by any member of the Network using
+the <<msg-delete>> and <<msg-clear-all>> messages. Note that in a Network
+consisting of mixed 2.0 and 3.0 Clients, deletion may be ineffective because
+the deletion message will not be propagated to the 2.0 Clients.
+
+// TODO: needs more description in the text of how these messages are
+// propagated
+
+Remote procedure call:: The Server may create specially-typed entries that
+inform Clients of remotely callable functions on the Server. Clients can then
+execute these functions via the Network Tables protocol. See <<rpc-operation>>.
+
+Raw data type:: An arbitrary data type has been added. While string could be
+used to encode raw data, the reason for a different data type is so that
+dashboards can choose not to display the raw data (or display it in a different
+format).
+
+Client and server self-identification:: Clients self-identify with a
+user-defined string name when connecting to the Server (this is part of the new
+<<msg-client-hello-complete>> message). This provides a more reliable method
+than simply the remote IP address for determining on the Server side whether or
+not a particular Client is connected. While Clients are less likely to care
+what Server they are connected to, for completeness a similar Server
+self-identification string has been added to the Server Hello Complete message.
+Note that Server connection information is not provided from the Server to
+Clients (at least in a way built into the protocol), so it is not possible for
+a Client to determine what other Clients are connected to the Server.
+
+Server reboot detection:: The Server keeps an internal list of all Client
+identity strings that have ever connected to it (this list is always empty at
+Server start). During the initial connection process, the Server sends the
+Client a flag (as part of the new <<msg-server-hello>> message) that indicates
+whether or not the Client was already on this list. Clients use this flag to
+determine whether the Server has rebooted since the previous connection.
+
+Entry flags:: Each Entry now has an 8-bit flags value associated with it (see
+<<entry-flags>>). The initial value of the flags are provided as part of the
+<<msg-assign>> message. The value of the flags may be updated by any member of
+the Network via use of the <<msg-flags-update>> message.
+
+Entry persistence:: The Server is required to provide a feature to
+automatically save entries (including their last known values) across Server
+restarts. By default, no values are automatically saved in this manner, but
+any member of the Network may set the “Persistent” Entry Flag on an Entry to
+indicate to the server that the Entry must be persisted by the Server. The
+Server must periodically save such flagged Entries to a file; on Server start,
+the Server reads the file to create the initial set of Server Entries.
+
+More robust Entry Update message encoding:: The entry type has been added to
+the <<msg-update>> message. This is used only to specify the length of value
+encoded in the Entry Update message, and has no effect on the Client or Server
+handling of Entry Updates. Clients and Servers must ignore Entry Update
+messages with mismatching type to their currently stored value. This increases
+robustness of Entry Updates in the presence of Entry Assignments with varying
+type (which should be uncommon, but this fixes a weakness in the 2.0 protocol).
+
+////
+TODO
+
+Synchronization on reconnect:: The approach to how Clients should handle
+conflicting values when reconnecting to a Server has been clarified.
+
+////
+
+[[references]]
+== References
+
+[[networktables2]]
+* <<networktables2.adoc#,Network Tables Protocol Specification, Protocol
+Revision 2.0 (0x0200)>>, dated 1/8/2013.
+
+[[leb128,LEB128]]
+* LEB128 definition in DWARF Specification 3.0
+(http://dwarfstd.org/doc/Dwarf3.pdf, section 7.6 and Appendix C, also explained
+in http://en.wikipedia.org/wiki/LEB128)
+
+[[rfc1982,RFC1982]]
+* RFC 1982, Serial Number Arithmetic, http://tools.ietf.org/html/rfc1982
+
+[[rfc2119,RFC2119]]
+* RFC 2119, Key words for use in RFCs to Indicate Requirement Levels,
+http://tools.ietf.org/html/rfc2119
+
+[[definitions]]
+== Definitions
+
+[[def-client]]
+Client:: An implementation of this protocol running in client configuration.
+Any number of Clients may exist for a given Network.
+
+[[def-entry]]
+Entry:: A data value identified by a string name.
+
+[[def-entry-id]]
+Entry ID:: An unsigned 2-byte ID by which the Server and Clients refer to an
+Entry across the network instead of using the full string key for the Entry.
+Entry IDs range from 0x0000 to 0xFFFE (0xFFFF is reserved for an Entry
+Assignment issued by a Client).
+
+[[def-server]]
+Server:: An implementation of this protocol running in server configuration.
+One and only one Server must exist for a given Network.
+
+[[def-network]]
+Network:: One or more Client nodes connected to a Server.
+
+[[def-user-code]]
+User Code:: User-supplied code which may interact with a Client or Server. User
+Code should be executed on the same computer as the Client or Server instance
+it interacts with.
+
+[[def-sequence-number]]
+Sequence Number:: An unsigned number which allows the Server to resolve update
+conflicts between Clients and/or the Server. Sequence numbers may overflow.
+Sequential arithmetic comparisons, which must be used with Sequence Numbers,
+are defined by <<rfc1982>>.
+
+[[def-protocol-revision]]
+Protocol Revision:: A 16-bit unsigned integer which indicates the version of
+the network tables protocol that a client wishes to use. The protocol revision
+assigned to this version of the network tables specification is listed at the
+top of this document. This number is listed in dot-decimal notation as well as
+its equivalent hexadecimal value.
+
+== Transport Layer
+
+Conventional implementations of this protocol should use TCP for reliable
+communication; the Server should listen on TCP port 1735 for incoming
+connections.
+
+== Example Exchanges
+
+[[exchange-connect]]
+=== Client Connects to the Server
+
+Directly after client establishes a connection with the Server, the following
+procedure must be followed:
+
+. The Client sends a <<msg-client-hello>> message to the Server
+
+. The Server sends a <<msg-server-hello>> message.
+
+. The Server sends one <<msg-assign>> for every field it currently recognizes.
+
+. The Server sends a <<msg-server-hello-complete>> message.
+
+. For all Entries the Client recognizes that the Server did not identify with a
+Entry Assignment, the client follows the <<exchange-client-creates-entry>>
+protocol.
+
+. The Client sends a <<msg-client-hello-complete>> message.
+
+In the event that the Server does not support the protocol revision that the
+Client has requested in a Client Hello message, the Server must instead issue a
+<<msg-protocol-unsupported>> message to the joining client and close the
+connection.
+
+[[exchange-client-creates-entry]]
+=== Client Creates an Entry
+
+When User Code on a Client assigns a value to an Entry that the Server has not
+yet issued a Entry Assignment for, the following procedure must be followed:
+
+. The Client sends an <<msg-assign>> with an Entry ID of 0xFFFF.
+
+. The Server issues an <<msg-assign>> to all Clients (including the sender) for
+the new field containing a real Entry ID and Sequence Number for the new field.
+
+In the event that User Code on the Client updates the value of the
+to-be-announced field again before the expected Entry Assignment is received,
+then the Client must save the new value and take no other action (the most
+recent value of the field should be issued when the Entry Assignment arrives,
+if it differs from the value contained in the received Entry Assignment).
+
+In the event that the Client receives a Entry Assignment from the Server for
+the Entry that it intended to issue an Entry Assignment for, before it issued
+its own Entry Assignment, the procedure may end early.
+
+In the event that the Server receives a duplicate Entry Assignment from a
+Client (likely due to the client having not yet received the Server's Entry
+Assignment), the Server should ignore the duplicate Entry Assignment.
+
+[[exchange-client-updates-entry]]
+=== Client Updates an Entry
+
+When User Code on a Client updates the value of an Entry, the Client must send
+an <<msg-update>> message to the Server. The Sequence Number included in the
+Entry Update message must be the most recently received Sequence Number for the
+Entry to be updated incremented by one.
+
+.Example:
+
+. Client receives Entry Assignment message for Entry "a" with integer value 1,
+Entry ID of 0, and Sequence Number 1.
+
+. User Code on Client updates value of Entry "a" to 16 (arbitrary).
+
+. Client sends Entry Update message to Server for Entry 0 with a Sequence
+Number of 2 and a value of 16.
+
+When the Server receives an Entry Update message, it first checks the Sequence
+Number in the message against the Server's value for the Sequence Number
+associated with the Entry to be updated. If the received Sequence Number is
+strictly greater than (aside: see definition of "greater than" under the
+definition of Sequence Number) the Server's Sequence Number for the Entry to be
+updated, the Server must apply the new value for the indicated Entry and repeat
+the Entry Update message to all other connected Clients.
+
+If the received Sequence Number is less than or equal (see definition of "less
+than or equal" in RFC 1982) to the Server's Sequence Number for the Entry to be
+updated, this implies that the Client which issued the Entry Update message has
+not yet received one or more Entry Update message(s) that the Server recently
+sent to it; therefore, the Server must ignore the received Entry Update
+message. In the event that comparison between two Sequence Numbers is undefined
+(see RFC 1982), then the Server must always win (it ignores the Entry Update
+message under consideration).
+
+[[update-rate]]
+NOTE: If User Code modifies the value of an Entry too quickly, 1) users may not
+see every value appear on remote machines, and 2) the consistency protection
+offered by the Entry's Sequence Number may be lost (by overflowing before
+remote devices hear recent values). It is recommended that implementations
+detect when user code updates an Entry more frequently than once every 5
+milliseconds and print a warning message to the user (and/or offer some other
+means of informing User Code of this condition).
+
+[[exchange-client-updates-flags]]
+=== Client Updates an Entry's Flags
+
+When User Code on a Client updates an Entry's flags, the Client must apply the
+new flags to the Entry immediately, and send an <<msg-flags-update>> message to
+the Server.
+
+When the Server receives an Entry Flags Update message, it must apply the new
+flags to the indicated Entry and repeat the Entry Flags Update message to all
+other connected Clients.
+
+[[exchange-client-deletes-entry]]
+=== Client Deletes an Entry
+
+When User Code on a Client deletes an Entry, the Client must immediately delete
+the Entry, and send an <<msg-delete>> message to the Server.
+
+When the Server receives an Entry Delete message, it must delete the indicated
+Entry and repeat the Entry Delete message to all other connected Clients.
+
+[[exchange-server-creates-entry]]
+=== Server Creates an Entry
+
+When User Code on the Server assigns a value to a Entry which does not exist,
+the Server must issue an <<msg-assign>> message to all connected clients.
+
+[[exchange-server-updates-entry]]
+=== Server Updates an Entry
+
+When User Code on the Server updates the value of an Entry, the Server must
+apply the new value to the Entry immediately, increment the associated Entry's
+Sequence Number, and issue a <<msg-update>> message containing the new value
+and Sequence Number of the associated Entry to all connected Clients.
+
+NOTE: See <<update-rate,Note>> under <<exchange-client-updates-entry>>.
+
+[[exchange-server-updates-flags]]
+=== Server Updates an Entry's Flags
+
+When User Code on the Server updates an Entry's flags, the Server must apply
+the new flags to the Entry immediately, and issue a <<msg-flags-update>>
+message containing the new flags value to all connected Clients.
+
+[[exchange-server-deletes-entry]]
+=== Server Deletes an Entry
+
+When User Code on the Server deletes an Entry, the Server must immediately
+delete the Entry, and issue a <<msg-delete>> message to all connected Clients.
+
+[[exchange-keep-alive]]
+=== Keep Alive
+
+To maintain a connection and prove a socket is still open, a Client or Server
+may issue <<msg-keep-alive>> messages. Clients and the Server should ignore
+incoming Keep Alive messages.
+
+The intent is that by writing a Keep Alive to a socket, a Client forces its
+network layer (TCP) to reevaluate the state of the network connection as it
+attempts to deliver the Keep Alive message. In the event that a connection is
+no longer usable, a Client's network layer should inform the Client that it is
+no longer usable within a few attempts to send a Keep Alive message.
+
+To provide timely connection status information, Clients should send a Keep
+Alive message to the Server after every 1 second period of connection
+inactivity (i.e. no information is being sent to the Server). Clients should
+not send Keep Alive messages more frequently than once every 100 milliseconds.
+
+Since the Server does not require as timely information about the status of a
+connection, it is not required to send Keep Alive messages during a period of
+inactivity.
+
+[[bandwidth]]
+== Bandwidth and Latency Considerations
+
+To reduce unnecessary bandwidth usage, implementations of this protocol should:
+
+* Send an Entry Update if and only if the value of an Entry is changed to a
+value that is different from its prior value.
+
+* Buffer messages and transmit them in groups, when possible, to reduce
+transport protocol overhead.
+
+* Only send the most recent value of an Entry. When User Code updates the value
+of an Entry more than once before the new value is transmitted, only the latest
+value of the Entry should be sent.
+
+It is important to note that these behaviors will increase the latency between
+when a Client or Server updates the value of an Entry and when all Clients
+reflect the new value. The exact behavior of this buffering is left to
+implementations to determine, although the chosen scheme should reflect the
+needs of User Code. Implementations may include a method by which User Code can
+specify the maximum tolerable send latency.
+
+[[entry-types]]
+== Entry Types
+
+Entry Type must assume one the following values:
+
+[cols="1,3"]
+|===
+|Numeric Value |Type
+
+|0x00
+|Boolean
+
+|0x01
+|Double
+
+|0x02
+|String
+
+|0x03
+|Raw Data
+
+|0x10
+|Boolean Array
+
+|0x11
+|Double Array
+
+|0x12
+|String Array
+
+|0x20
+|Remote Procedure Call Definition
+|===
+
+[[entry-values]]
+== Entry Values
+
+Entry Value must assume the following structure as indicated by Entry Type:
+
+[cols="1,3"]
+|===
+|Entry Type |Entry Value Format
+
+|[[entry-value-boolean]]Boolean
+|1 byte, unsigned; True = 0x01, False = 0x00
+
+|[[entry-value-double]]Double
+|8 bytes, IEEE 754 floating-point "double format" bit layout; (big endian)
+
+|[[entry-value-string]]String
+|N bytes, unsigned <<leb128>> encoded length of the number of raw bytes to
+follow, followed by the string encoded in UTF-8
+
+|[[entry-value-raw]]Raw Data
+|N bytes, unsigned LEB128 encoded length of the number of raw bytes to follow,
+followed by the raw bytes.
+
+While the raw data definition is unspecified, it's recommended that users use
+the first byte of the raw data to "tag" the type of data actually being stored.
+
+|[[entry-value-boolean-array]]Boolean Array
+|1 byte, unsigned, number of elements within the array to follow
+
+N bytes - The raw bytes representing each Boolean element contained within the
+array, beginning with the item at index 0 within the array.
+
+|[[entry-value-double-array]]Double Array
+|1 byte, unsigned, number of elements within the array to follow
+
+N bytes - The raw bytes representing each Double element contained within the
+array, beginning with the item at index 0 within the array.
+
+|[[entry-value-string-array]]String Array
+|1 byte, unsigned, number of elements within the array to follow
+
+N bytes - The raw bytes representing each String element contained within the
+array, beginning with the item at index 0 within the array.
+
+|[[entry-value-rpc]]Remote Procedure Call Definition
+|N bytes, unsigned LEB128 encoded length of the number of raw bytes to follow.
+
+N bytes - data as defined in Remote Procedure Call Definition Data
+|===
+
+[[entry-flags]]
+== Entry Flags
+
+Entry Flags are as follows:
+
+[cols="1,3"]
+|===
+|Bit Mask |Bit Value Meaning
+
+|[[entry-flag-persistent]]0x01 (least significant bit) - Persistent
+
+|0x00: Entry is not persistent. The entry and its value will not be retained
+across a server restart.
+
+0x01: Entry is persistent. Updates to the value are automatically saved and
+the entry will be automatically created and the last known value restored when
+the server starts.
+
+|0xFE
+|Reserved
+|===
+
+== Message Structures
+
+All messages are of the following format:
+
+[cols="1,3"]
+|===
+|Field Name |Field Type
+
+|Message Type
+|1 byte, unsigned
+
+|Message Data
+|N bytes (length determined by Message Type)
+|===
+
+[[msg-keep-alive]]
+=== Keep Alive
+
+Indicates that the remote party is checking the status of a network connection.
+
+[cols="1,3"]
+|===
+|Field Name |Field Type
+
+|0x00 - Keep Alive
+|1 byte, unsigned; Message Type
+|===
+
+[[msg-client-hello]]
+=== Client Hello
+
+A Client issues a Client Hello message when first establishing a connection.
+The Client Protocol Revision field specifies the Network Table protocol
+revision that the Client would like to use.
+
+[cols="1,3"]
+|===
+|Field Name |Field Type
+
+|0x01 - Client Hello
+|1 byte, unsigned; Message Type
+
+|Client Protocol Revision
+|2 bytes, Unsigned 16-bit integer (big-endian). See
+<<def-protocol-revision,Protocol Revision>>.
+
+|Client identity (name)
+|<<entry-value-string,String>>
+|===
+
+[[msg-protocol-unsupported]]
+=== Protocol Version Unsupported
+
+A Server issues a Protocol Version Unsupported message to a Client to inform it
+that the requested protocol revision is not supported. It also includes the
+most recent protocol revision which it supports, such that a Client may
+reconnect under a prior protocol revision if able.
+
+[cols="1,3"]
+|===
+|Field Name |Field Type
+
+|0x02 - Protocol Version Unsupported
+|1 byte, unsigned; Message Type
+
+|Server Supported Protocol Revision
+|2 bytes, Unsigned 16-bit integer (big-endian). See
+<<def-protocol-revision,Protocol Revision>>.
+|===
+
+[[msg-server-hello-complete]]
+=== Server Hello Complete
+
+A Server issues a Server Hello Complete message when it has finished informing
+a newly-connected client of all of the fields it currently recognizes.
+Following the receipt of this message, a Client should inform the Server of
+any/all additional fields that it recognizes that the Server did not announce.
+
+[cols="1,3"]
+|===
+|Field Name |Field Type
+
+|0x03 - Server Hello Complete
+|1 byte, unsigned; Message Type
+|===
+
+[[msg-server-hello]]
+=== Server Hello
+
+A Server issues a Server Hello message in response to a Client Hello message,
+immediately prior to informing a newly-connected client of all of the fields it
+currently recognizes.
+
+[cols="1,3"]
+|===
+|Field Name |Field Type
+
+|0x04 - Server Hello
+|1 byte, unsigned; Message Type
+
+|Flags
+a|1 byte, unsigned.
+
+Least Significant Bit (bit 0): reconnect flag
+
+* 0 if this is the first time (since server start) the server has seen the
+client
+
+* 1 if the server has previously seen (since server start) the client (as
+identified in the <<msg-client-hello,Client Hello>> message)
+
+Bits 1-7: Reserved, set to 0.
+
+|Server identity (name)
+|<<entry-value-string,String>>
+|===
+
+[[msg-client-hello-complete]]
+=== Client Hello Complete
+
+A Client issues a Client Hello Complete message when it has finished informing
+the Server of any/all of the additional fields it recognizes that the Server
+did not announce.
+
+[cols="1,3"]
+|===
+|Field Name |Field Type
+
+|0x05 - Client Hello Complete
+|1 byte, unsigned; Message Type
+|===
+
+[[msg-assign]]
+=== Entry Assignment
+
+A Entry Assignment message informs the remote party of a new Entry. An Entry
+Assignment's value field must be the most recent value of the field being
+assigned at the time that the Entry Assignment is sent.
+
+[cols="1,3"]
+|===
+|Field Name |Field Type
+
+|0x10 - Entry Assignment
+|1 byte, unsigned; Message Type
+
+|Entry Name
+|<<entry-value-string,String>>
+
+|Entry Type
+|1 byte, unsigned; see <<entry-types,Entry Types>>
+
+|Entry ID
+|2 bytes, unsigned
+
+|Entry Sequence Number
+|2 bytes, unsigned
+
+|Entry Flags
+|1 byte, unsigned; see <<entry-flags,Entry Flags>>
+
+|Entry Value
+|N bytes, length depends on Entry Type
+|===
+
+If the Entry ID is 0xFFFF, then this assignment represents a request from a
+Client to the Server. In this event, the Entry ID field and the Entry Sequence
+Number field must not be stored or relied upon as they otherwise would be.
+
+[[msg-update]]
+=== Entry Update
+
+An Entry Update message informs a remote party of a new value for an Entry.
+
+[cols="1,3"]
+|===
+|Field Name |Field Type
+
+|0x11 - Entry Update
+|1 byte, unsigned; Message Type
+
+|Entry ID
+|2 bytes, unsigned
+
+|Entry Sequence Number
+|2 bytes, unsigned
+
+|Entry Type
+|1 byte, unsigned; see <<entry-types,Entry Types>>.
+
+Note this type is only used to determine the length of the entry value, and
+does NOT change the stored entry type if it is different (due to an intervening
+Entry Assignment); Clients and Servers must ignore Entry Update messages with
+mismatching entry type.
+
+|Entry Value
+|N bytes, length dependent on value type
+|===
+
+[[msg-flags-update]]
+=== Entry Flags Update
+
+An Entry Flags Update message informs a remote party of new flags for an Entry.
+
+[cols="1,3"]
+|===
+|Field Name |Field Type
+
+|0x12 - Entry Flags Update
+|1 byte, unsigned; Message Type
+
+|Entry ID
+|2 bytes, unsigned
+
+|Entry Flags
+|1 byte, unsigned; see <<entry-flags,Entry Flags>>
+|===
+
+Entries may be globally deleted using the following messages. These messages
+must be rebroadcast by the server in the same fashion as the Entry Update
+message. Clients and servers must remove the requested entry/entries from
+their local tables. Update messages received after the Entry Delete message
+for the deleted Entry ID must be ignored by Clients and Servers until a new
+Assignment message for that Entry ID is issued.
+
+[[msg-delete]]
+=== Entry Delete
+
+Deletes a single entry or procedure.
+
+[cols="1,3"]
+|===
+|Field Name |Field Type
+
+|0x13 - Entry Delete
+|1 byte, unsigned; message type
+
+|Entry ID
+|2 bytes, unsigned
+|===
+
+[[msg-clear-all]]
+=== Clear All Entries
+
+Deletes all entries. The magic value is required to be exactly this value
+(this is to avoid accidental misinterpretation of the message).
+
+[cols="1,3"]
+|===
+|Field Name |Field Type
+
+|0x14 - Clear All Entries
+|1 byte, unsigned; message type
+
+|Magic Value (0xD06CB27A)
+|4 bytes; exact value required (big endian)
+|===
+
+[[msg-rpc-execute]]
+=== Remote Procedure Call (RPC) Execute
+
+Executes a remote procedure. Intended for client to server use only.
+
+The client shall provide a value for every RPC parameter specified in the
+corresponding RPC entry definition.
+
+The server shall ignore any Execute RPC message whose decoding does not match
+the parameters defined in the corresponding RPC entry definition.
+
+Note that the parameter length is encoded the same way regardless of the RPC
+version and encapsulates the entirety of the parameters, so protocol layer
+decoders do not need to know the RPC details in order to process the message.
+
+[cols="1,3"]
+|===
+|Field Name |Field Type
+
+|0x20 - Execute RPC
+|1 byte, unsigned; message type
+
+|RPC Definition Entry ID
+|2 bytes, unsigned
+
+|Unique ID
+|2 bytes, unsigned; incremented value for matching return values to call.
+
+|Parameter Value Length
+|N bytes, unsigned <<leb128>> encoded length of:
+
+RPC definition version 0: total number of raw bytes in this message
+
+RPC definition version 1: total number of bytes of parameter values in this
+message
+|Parameter Value(s)
+|RPC definition version 0: N raw bytes.
+
+RPC definition version 1: Array of values; N bytes for each parameter (length
+dependent on the parameter type defined in the
+<<rpc-definition,RPC entry definition>>).
+|===
+
+[[msg-rpc-response]]
+=== RPC Response
+
+Return responses from a remote procedure call. Even calls with zero outputs
+will respond.
+
+Note that the result length is encoded the same way regardless of the RPC
+version and encapsulates the entirety of the result, so protocol layer decoders
+do not need to know the RPC details in order to process the message.
+
+[cols="1,3"]
+|===
+|Field Name |Field Type
+
+|0x21 - RPC Response
+|1 byte, unsigned; message type
+
+|RPC Definition Entry ID
+|2 bytes, unsigned
+
+|Unique ID
+|2 bytes, unsigned; matching ID from <<msg-rpc-execute,RPC Execute>> message
+
+|Result Value Length
+|N bytes, unsigned <<leb128>> encoded length of:
+
+RPC definition version 0: total number of raw bytes in this message
+
+RPC definition version 1: total number of bytes of result values in this
+message
+|Result Value(s)
+|RPC definition version 0: N raw bytes.
+
+RPC definition version 1: Array of values; N bytes for each result (length
+dependent on the result type defined in the
+<<rpc-definition,RPC entry definition>>).
+|===
+
+[[rpc-operation]]
+== Remote Procedure Call (RPC) Operation
+
+Remote procedure call entries shall only be assigned by the server.
+
+Remote procedure call execute messages will result in asynchronous execution of
+the corresponding function on the server.
+
+Client implementations shall not transmit an Execute RPC message and return an
+error to user code that attempts to call an undefined RPC, call one with
+incorrectly typed parameters, or attempts to make a call when the Client is not
+connected to a Server.
+
+Remote procedure calls cannot be persisted.
+
+[[rpc-definition]]
+=== Remote Procedure Call Definition Data
+
+There are currently two versions of RPC definitions: version 0 and version 1.
+The first byte in the RPC definition entry determines the version.
+
+[[rpc-definition-v0]]
+==== Version 0
+
+RPC version 0 is the most straightforward: the data provided in the RPC
+definition entry consists of just a single 0 byte (indicating RPC
+definition version 0). RPC version 0 execute and response messages do
+not contain discrete parameter and result values respectively; instead the
+entire parameter value or result value is treated as a raw byte sequence; the
+interpretation of the raw bytes is application specific--users are encouraged
+to consider using encodings such as CBOR or MessagePack for more complex
+self-describing data structures.
+
+[cols="1,3"]
+|===
+|RPC Definition Version
+|1 byte, unsigned; set to 0, indicating version 0
+|===
+
+[[rpc-definition-v1]]
+==== Version 1
+
+The data provided in the RPC version 1 definition entry is more
+complex and consists of:
+
+[cols="1,3"]
+|===
+|RPC Definition Version
+|1 byte, unsigned; set to 1, indicating version 1
+
+|Procedure (Entry) Name
+|<<entry-value-string,String>>
+
+|Number of Parameters
+|1 byte, unsigned (may be 0)
+
+2+s|Parameter Specification (one set per input parameter)
+
+|Parameter Type
+|1 byte, unsigned; <<entry-types,Entry Type>> for parameter value
+
+|Parameter Name
+|<<entry-value-string,String>>
+
+|Parameter Default Value
+|N bytes; length based on parameter type (encoded consistent with corresponding
+<<entry-values,Entry Value>> definition)
+
+|Number of Output Results
+|1 byte, unsigned (may be 0)
+
+2+s|Result Specification (one set per output)
+
+|Result Type
+|1 byte, unsigned; <<entry-types,Entry Type>> for value
+
+|Result Name
+|<<entry-value-string,String>>
+|===
diff --git a/ntcore/manualTests/java/Client.java b/ntcore/manualTests/java/Client.java
new file mode 100644
index 0000000..1d5f181
--- /dev/null
+++ b/ntcore/manualTests/java/Client.java
@@ -0,0 +1,46 @@
+/*----------------------------------------------------------------------------*/
+/* Copyright (c) 2017-2018 FIRST. All Rights Reserved. */
+/* Open Source Software - may be modified and shared by FRC teams. The code */
+/* must be accompanied by the FIRST BSD license file in the root directory of */
+/* the project. */
+/*----------------------------------------------------------------------------*/
+
+import edu.wpi.first.wpilibj.networktables.*;
+import edu.wpi.first.wpilibj.tables.*;
+
+public class Client {
+ private static class MyLogger implements NetworkTablesJNI.LoggerFunction {
+ public void apply(int level, String file, int line, String msg) {
+ System.err.println(msg);
+ }
+ }
+
+ public static void main(String[] args) {
+ NetworkTablesJNI.setLogger(new MyLogger(), 0);
+ NetworkTable.setIPAddress("127.0.0.1");
+ NetworkTable.setPort(10000);
+ NetworkTable.setClientMode();
+ NetworkTable nt = NetworkTable.getTable("");
+ try { Thread.sleep(2000); } catch (InterruptedException e) {}
+ try {
+ System.out.println("Got foo: " + nt.getNumber("foo"));
+ } catch(TableKeyNotDefinedException ex) {
+ }
+ nt.putBoolean("bar", false);
+ nt.setFlags("bar", NetworkTable.PERSISTENT);
+ nt.putBoolean("bar2", true);
+ nt.putBoolean("bar2", false);
+ nt.putBoolean("bar2", true);
+ nt.putString("str", "hello world");
+ double[] nums = new double[3];
+ nums[0] = 0.5;
+ nums[1] = 1.2;
+ nums[2] = 3.0;
+ nt.putNumberArray("numarray", nums);
+ String[] strs = new String[2];
+ strs[0] = "Hello";
+ strs[1] = "World";
+ nt.putStringArray("strarray", strs);
+ try { Thread.sleep(10000); } catch (InterruptedException e) {}
+ }
+}
diff --git a/ntcore/manualTests/java/Server.java b/ntcore/manualTests/java/Server.java
new file mode 100644
index 0000000..7f4dbc6
--- /dev/null
+++ b/ntcore/manualTests/java/Server.java
@@ -0,0 +1,33 @@
+/*----------------------------------------------------------------------------*/
+/* Copyright (c) 2017-2018 FIRST. All Rights Reserved. */
+/* Open Source Software - may be modified and shared by FRC teams. The code */
+/* must be accompanied by the FIRST BSD license file in the root directory of */
+/* the project. */
+/*----------------------------------------------------------------------------*/
+
+import edu.wpi.first.wpilibj.networktables.*;
+import edu.wpi.first.wpilibj.tables.*;
+
+public class Server {
+ private static class MyLogger implements NetworkTablesJNI.LoggerFunction {
+ public void apply(int level, String file, int line, String msg) {
+ System.err.println(msg);
+ }
+ }
+
+ public static void main(String[] args) {
+ NetworkTablesJNI.setLogger(new MyLogger(), 0);
+ NetworkTable.setIPAddress("127.0.0.1");
+ NetworkTable.setPort(10000);
+ NetworkTable.setServerMode();
+ NetworkTable nt = NetworkTable.getTable("");
+ try { Thread.sleep(1000); } catch (InterruptedException e) {}
+ nt.putNumber("foo", 0.5);
+ nt.setFlags("foo", NetworkTable.PERSISTENT);
+ nt.putNumber("foo2", 0.5);
+ nt.putNumber("foo2", 0.7);
+ nt.putNumber("foo2", 0.6);
+ nt.putNumber("foo2", 0.5);
+ try { Thread.sleep(10000); } catch (InterruptedException e) {}
+ }
+}
diff --git a/ntcore/manualTests/native/client.cpp b/ntcore/manualTests/native/client.cpp
new file mode 100644
index 0000000..3bcb7c0
--- /dev/null
+++ b/ntcore/manualTests/native/client.cpp
@@ -0,0 +1,40 @@
+/*----------------------------------------------------------------------------*/
+/* Copyright (c) 2017-2018 FIRST. All Rights Reserved. */
+/* Open Source Software - may be modified and shared by FRC teams. The code */
+/* must be accompanied by the FIRST BSD license file in the root directory of */
+/* the project. */
+/*----------------------------------------------------------------------------*/
+
+#include <chrono>
+#include <climits>
+#include <cstdio>
+#include <thread>
+
+#include "ntcore.h"
+
+int main() {
+ auto inst = nt::GetDefaultInstance();
+ nt::AddLogger(inst,
+ [](const nt::LogMessage& msg) {
+ std::fputs(msg.message.c_str(), stderr);
+ std::fputc('\n', stderr);
+ },
+ 0, UINT_MAX);
+ nt::StartClient(inst, "127.0.0.1", 10000);
+ std::this_thread::sleep_for(std::chrono::seconds(2));
+
+ auto foo = nt::GetEntry(inst, "/foo");
+ auto foo_val = nt::GetEntryValue(foo);
+ if (foo_val && foo_val->IsDouble())
+ std::printf("Got foo: %g\n", foo_val->GetDouble());
+
+ auto bar = nt::GetEntry(inst, "/bar");
+ nt::SetEntryValue(bar, nt::Value::MakeBoolean(false));
+ nt::SetEntryFlags(bar, NT_PERSISTENT);
+
+ auto bar2 = nt::GetEntry(inst, "/bar2");
+ nt::SetEntryValue(bar2, nt::Value::MakeBoolean(true));
+ nt::SetEntryValue(bar2, nt::Value::MakeBoolean(false));
+ nt::SetEntryValue(bar2, nt::Value::MakeBoolean(true));
+ std::this_thread::sleep_for(std::chrono::seconds(10));
+}
diff --git a/ntcore/manualTests/native/rpc_local.cpp b/ntcore/manualTests/native/rpc_local.cpp
new file mode 100644
index 0000000..b8352d6
--- /dev/null
+++ b/ntcore/manualTests/native/rpc_local.cpp
@@ -0,0 +1,66 @@
+/*----------------------------------------------------------------------------*/
+/* Copyright (c) 2017-2018 FIRST. All Rights Reserved. */
+/* Open Source Software - may be modified and shared by FRC teams. The code */
+/* must be accompanied by the FIRST BSD license file in the root directory of */
+/* the project. */
+/*----------------------------------------------------------------------------*/
+
+#include <chrono>
+#include <climits>
+#include <cstdio>
+#include <thread>
+
+#include <support/json.h>
+
+#include "ntcore.h"
+
+void callback1(const nt::RpcAnswer& answer) {
+ wpi::json params;
+ try {
+ params = wpi::json::from_cbor(answer.params);
+ } catch (wpi::json::parse_error err) {
+ std::fputs("could not decode params?\n", stderr);
+ return;
+ }
+ if (!params.is_number()) {
+ std::fputs("did not get number\n", stderr);
+ return;
+ }
+ double val = params.get<double>();
+ std::fprintf(stderr, "called with %g\n", val);
+
+ answer.PostResponse(wpi::json::to_cbor(val + 1.2));
+}
+
+int main() {
+ auto inst = nt::GetDefaultInstance();
+ nt::AddLogger(inst,
+ [](const nt::LogMessage& msg) {
+ std::fputs(msg.message.c_str(), stderr);
+ std::fputc('\n', stderr);
+ },
+ 0, UINT_MAX);
+
+ nt::StartServer(inst, "rpc_local.ini", "", 10000);
+ auto entry = nt::GetEntry(inst, "func1");
+ nt::CreateRpc(entry, nt::StringRef("", 1), callback1);
+ std::fputs("calling rpc\n", stderr);
+ unsigned int call1_uid = nt::CallRpc(entry, wpi::json::to_cbor(2.0));
+ std::string call1_result_str;
+ std::fputs("waiting for rpc result\n", stderr);
+ nt::GetRpcResult(entry, call1_uid, &call1_result_str);
+ wpi::json call1_result;
+ try {
+ call1_result = wpi::json::from_cbor(call1_result_str);
+ } catch (wpi::json::parse_error err) {
+ std::fputs("could not decode result?\n", stderr);
+ return 1;
+ }
+ if (!call1_result.is_number()) {
+ std::fputs("result is not number?\n", stderr);
+ return 1;
+ }
+ std::fprintf(stderr, "got %g\n", call1_result.get<double>());
+
+ return 0;
+}
diff --git a/ntcore/manualTests/native/rpc_speed.cpp b/ntcore/manualTests/native/rpc_speed.cpp
new file mode 100644
index 0000000..70558ef
--- /dev/null
+++ b/ntcore/manualTests/native/rpc_speed.cpp
@@ -0,0 +1,75 @@
+/*----------------------------------------------------------------------------*/
+/* Copyright (c) 2017-2018 FIRST. All Rights Reserved. */
+/* Open Source Software - may be modified and shared by FRC teams. The code */
+/* must be accompanied by the FIRST BSD license file in the root directory of */
+/* the project. */
+/*----------------------------------------------------------------------------*/
+
+#include <chrono>
+#include <climits>
+#include <cstdio>
+#include <iostream>
+#include <thread>
+
+#include <support/json.h>
+
+#include "ntcore.h"
+
+void callback1(const nt::RpcAnswer& answer) {
+ wpi::json params;
+ try {
+ params = wpi::json::from_cbor(answer.params);
+ } catch (wpi::json::parse_error err) {
+ std::fputs("could not decode params?\n", stderr);
+ return;
+ }
+ if (!params.is_number()) {
+ std::fputs("did not get number\n", stderr);
+ return;
+ }
+ double val = params.get<double>();
+ answer.PostResponse(wpi::json::to_cbor(val + 1.2));
+}
+
+int main() {
+ auto inst = nt::GetDefaultInstance();
+ nt::StartServer(inst, "rpc_speed.ini", "", 10000);
+ auto entry = nt::GetEntry(inst, "func1");
+ nt::CreateRpc(entry, nt::StringRef("", 1), callback1);
+ std::string call1_result_str;
+
+ auto start2 = std::chrono::high_resolution_clock::now();
+ auto start = nt::Now();
+ for (int i = 0; i < 10000; ++i) {
+ unsigned int call1_uid = nt::CallRpc(entry, wpi::json::to_cbor(i));
+ nt::GetRpcResult(entry, call1_uid, &call1_result_str);
+ wpi::json call1_result;
+ try {
+ call1_result = wpi::json::from_cbor(call1_result_str);
+ } catch (wpi::json::parse_error err) {
+ std::fputs("could not decode result?\n", stderr);
+ return 1;
+ }
+ if (!call1_result.is_number()) {
+ std::fputs("result is not number?\n", stderr);
+ return 1;
+ }
+ }
+ auto end2 = std::chrono::high_resolution_clock::now();
+ auto end = nt::Now();
+ std::cerr << "nt::Now start=" << start << " end=" << end << '\n';
+ std::cerr << "std::chrono start="
+ << std::chrono::duration_cast<std::chrono::nanoseconds>(
+ start2.time_since_epoch())
+ .count()
+ << " end="
+ << std::chrono::duration_cast<std::chrono::nanoseconds>(
+ end2.time_since_epoch())
+ .count()
+ << '\n';
+ std::fprintf(stderr, "time/call = %g us\n", (end - start) / 10.0 / 10000.0);
+ std::chrono::duration<double, std::micro> diff = end2 - start2;
+ std::cerr << "time/call = " << (diff.count() / 10000.0) << " us\n";
+
+ return 0;
+}
diff --git a/ntcore/manualTests/native/server.cpp b/ntcore/manualTests/native/server.cpp
new file mode 100644
index 0000000..9513bf8
--- /dev/null
+++ b/ntcore/manualTests/native/server.cpp
@@ -0,0 +1,37 @@
+/*----------------------------------------------------------------------------*/
+/* Copyright (c) 2017-2018 FIRST. All Rights Reserved. */
+/* Open Source Software - may be modified and shared by FRC teams. The code */
+/* must be accompanied by the FIRST BSD license file in the root directory of */
+/* the project. */
+/*----------------------------------------------------------------------------*/
+
+#include <chrono>
+#include <climits>
+#include <cstdio>
+#include <thread>
+
+#include "ntcore.h"
+
+int main() {
+ auto inst = nt::GetDefaultInstance();
+ nt::AddLogger(inst,
+ [](const nt::LogMessage& msg) {
+ std::fputs(msg.message.c_str(), stderr);
+ std::fputc('\n', stderr);
+ },
+ 0, UINT_MAX);
+ nt::StartServer(inst, "persistent.ini", "", 10000);
+ std::this_thread::sleep_for(std::chrono::seconds(1));
+
+ auto foo = nt::GetEntry(inst, "/foo");
+ nt::SetEntryValue(foo, nt::Value::MakeDouble(0.5));
+ nt::SetEntryFlags(foo, NT_PERSISTENT);
+
+ auto foo2 = nt::GetEntry(inst, "/foo2");
+ nt::SetEntryValue(foo2, nt::Value::MakeDouble(0.5));
+ nt::SetEntryValue(foo2, nt::Value::MakeDouble(0.7));
+ nt::SetEntryValue(foo2, nt::Value::MakeDouble(0.6));
+ nt::SetEntryValue(foo2, nt::Value::MakeDouble(0.5));
+
+ std::this_thread::sleep_for(std::chrono::seconds(10));
+}
diff --git a/ntcore/ntcore-config.cmake b/ntcore/ntcore-config.cmake
new file mode 100644
index 0000000..6be1dda
--- /dev/null
+++ b/ntcore/ntcore-config.cmake
@@ -0,0 +1,5 @@
+include(CMakeFindDependencyMacro)
+find_dependency(wpiutil)
+
+get_filename_component(SELF_DIR "${CMAKE_CURRENT_LIST_FILE}" PATH)
+include(${SELF_DIR}/ntcore.cmake)
diff --git a/ntcore/src/dev/java/edu/wpi/first/ntcore/DevMain.java b/ntcore/src/dev/java/edu/wpi/first/ntcore/DevMain.java
new file mode 100644
index 0000000..547af0a
--- /dev/null
+++ b/ntcore/src/dev/java/edu/wpi/first/ntcore/DevMain.java
@@ -0,0 +1,25 @@
+/*----------------------------------------------------------------------------*/
+/* Copyright (c) 2017-2018 FIRST. All Rights Reserved. */
+/* Open Source Software - may be modified and shared by FRC teams. The code */
+/* must be accompanied by the FIRST BSD license file in the root directory of */
+/* the project. */
+/*----------------------------------------------------------------------------*/
+
+package edu.wpi.first.ntcore;
+
+import edu.wpi.first.networktables.NetworkTablesJNI;
+import edu.wpi.first.wpiutil.RuntimeDetector;
+
+public final class DevMain {
+ /**
+ * Main method.
+ */
+ public static void main(String[] args) {
+ System.out.println("Hello World!");
+ System.out.println(RuntimeDetector.getPlatformPath());
+ NetworkTablesJNI.flush(NetworkTablesJNI.getDefaultInstance());
+ }
+
+ private DevMain() {
+ }
+}
diff --git a/ntcore/src/dev/native/cpp/main.cpp b/ntcore/src/dev/native/cpp/main.cpp
new file mode 100644
index 0000000..1153347
--- /dev/null
+++ b/ntcore/src/dev/native/cpp/main.cpp
@@ -0,0 +1,18 @@
+/*----------------------------------------------------------------------------*/
+/* Copyright (c) 2017-2018 FIRST. All Rights Reserved. */
+/* Open Source Software - may be modified and shared by FRC teams. The code */
+/* must be accompanied by the FIRST BSD license file in the root directory of */
+/* the project. */
+/*----------------------------------------------------------------------------*/
+
+#include <iostream>
+
+#include "ntcore.h"
+
+int main() {
+ auto myValue = nt::GetEntry(nt::GetDefaultInstance(), "MyValue");
+
+ nt::SetEntryValue(myValue, nt::Value::MakeString("Hello World"));
+
+ std::cout << nt::GetEntryValue(myValue)->GetString() << std::endl;
+}
diff --git a/ntcore/src/main/java/edu/wpi/first/networktables/ConnectionInfo.java b/ntcore/src/main/java/edu/wpi/first/networktables/ConnectionInfo.java
new file mode 100644
index 0000000..b060f2f
--- /dev/null
+++ b/ntcore/src/main/java/edu/wpi/first/networktables/ConnectionInfo.java
@@ -0,0 +1,64 @@
+/*----------------------------------------------------------------------------*/
+/* Copyright (c) 2017-2018 FIRST. All Rights Reserved. */
+/* Open Source Software - may be modified and shared by FRC teams. The code */
+/* must be accompanied by the FIRST BSD license file in the root directory of */
+/* the project. */
+/*----------------------------------------------------------------------------*/
+
+package edu.wpi.first.networktables;
+
+/**
+ * NetworkTables Connection information.
+ */
+public final class ConnectionInfo {
+ /**
+ * The remote identifier (as set on the remote node by
+ * {@link NetworkTableInstance#setNetworkIdentity(String)}).
+ */
+ @SuppressWarnings("MemberName")
+ public final String remote_id;
+
+ /**
+ * The IP address of the remote node.
+ */
+ @SuppressWarnings("MemberName")
+ public final String remote_ip;
+
+ /**
+ * The port number of the remote node.
+ */
+ @SuppressWarnings("MemberName")
+ public final int remote_port;
+
+ /**
+ * The last time any update was received from the remote node (same scale as
+ * returned by {@link NetworkTablesJNI#now()}).
+ */
+ @SuppressWarnings("MemberName")
+ public final long last_update;
+
+ /**
+ * The protocol version being used for this connection. This is in protocol
+ * layer format, so 0x0200 = 2.0, 0x0300 = 3.0).
+ */
+ @SuppressWarnings("MemberName")
+ public final int protocol_version;
+
+ /** Constructor.
+ * This should generally only be used internally to NetworkTables.
+ *
+ * @param remoteId Remote identifier
+ * @param remoteIp Remote IP address
+ * @param remotePort Remote port number
+ * @param lastUpdate Last time an update was received
+ * @param protocolVersion The protocol version used for the connection
+ */
+ public ConnectionInfo(String remoteId, String remoteIp, int remotePort, long lastUpdate,
+ int protocolVersion) {
+ remote_id = remoteId;
+ remote_ip = remoteIp;
+ remote_port = remotePort;
+ last_update = lastUpdate;
+ protocol_version = protocolVersion;
+ }
+}
diff --git a/ntcore/src/main/java/edu/wpi/first/networktables/ConnectionNotification.java b/ntcore/src/main/java/edu/wpi/first/networktables/ConnectionNotification.java
new file mode 100644
index 0000000..129bf1a
--- /dev/null
+++ b/ntcore/src/main/java/edu/wpi/first/networktables/ConnectionNotification.java
@@ -0,0 +1,53 @@
+/*----------------------------------------------------------------------------*/
+/* Copyright (c) 2017-2018 FIRST. All Rights Reserved. */
+/* Open Source Software - may be modified and shared by FRC teams. The code */
+/* must be accompanied by the FIRST BSD license file in the root directory of */
+/* the project. */
+/*----------------------------------------------------------------------------*/
+
+package edu.wpi.first.networktables;
+
+/**
+ * NetworkTables Connection notification.
+ */
+public final class ConnectionNotification {
+ /**
+ * Listener that was triggered.
+ */
+ @SuppressWarnings("MemberName")
+ public final int listener;
+
+ /**
+ * True if event is due to connection being established.
+ */
+ @SuppressWarnings("MemberName")
+ public final boolean connected;
+
+ /**
+ * Connection information.
+ */
+ @SuppressWarnings("MemberName")
+ public final ConnectionInfo conn;
+
+ /** Constructor.
+ * This should generally only be used internally to NetworkTables.
+ *
+ * @param inst Instance
+ * @param listener Listener that was triggered
+ * @param connected Connected if true
+ * @param conn Connection information
+ */
+ public ConnectionNotification(NetworkTableInstance inst, int listener, boolean connected,
+ ConnectionInfo conn) {
+ this.m_inst = inst;
+ this.listener = listener;
+ this.connected = connected;
+ this.conn = conn;
+ }
+
+ private final NetworkTableInstance m_inst;
+
+ public NetworkTableInstance getInstance() {
+ return m_inst;
+ }
+}
diff --git a/ntcore/src/main/java/edu/wpi/first/networktables/EntryInfo.java b/ntcore/src/main/java/edu/wpi/first/networktables/EntryInfo.java
new file mode 100644
index 0000000..b516cc7
--- /dev/null
+++ b/ntcore/src/main/java/edu/wpi/first/networktables/EntryInfo.java
@@ -0,0 +1,71 @@
+/*----------------------------------------------------------------------------*/
+/* Copyright (c) 2017-2018 FIRST. All Rights Reserved. */
+/* Open Source Software - may be modified and shared by FRC teams. The code */
+/* must be accompanied by the FIRST BSD license file in the root directory of */
+/* the project. */
+/*----------------------------------------------------------------------------*/
+
+package edu.wpi.first.networktables;
+
+/**
+ * NetworkTables Entry information.
+ */
+public final class EntryInfo {
+ /** Entry handle. */
+ @SuppressWarnings("MemberName")
+ public final int entry;
+
+ /** Entry name. */
+ @SuppressWarnings("MemberName")
+ public final String name;
+
+ /** Entry type. */
+ @SuppressWarnings("MemberName")
+ public final NetworkTableType type;
+
+ /** Entry flags. */
+ @SuppressWarnings("MemberName")
+ public final int flags;
+
+ /** Timestamp of last change to entry (type or value). */
+ @SuppressWarnings("MemberName")
+ public final long last_change;
+
+ /** Constructor.
+ * This should generally only be used internally to NetworkTables.
+ *
+ * @param inst Instance
+ * @param entry Entry handle
+ * @param name Name
+ * @param type Type (integer version of {@link NetworkTableType})
+ * @param flags Flags
+ * @param lastChange Timestamp of last change
+ */
+ public EntryInfo(NetworkTableInstance inst, int entry, String name, int type, int flags,
+ long lastChange) {
+ this.m_inst = inst;
+ this.entry = entry;
+ this.name = name;
+ this.type = NetworkTableType.getFromInt(type);
+ this.flags = flags;
+ this.last_change = lastChange;
+ }
+
+ /* Network table instance. */
+ private final NetworkTableInstance m_inst;
+
+ /* Cached entry object. */
+ private NetworkTableEntry m_entryObject;
+
+ /**
+ * Get the entry as an object.
+ *
+ * @return NetworkTableEntry for this entry.
+ */
+ NetworkTableEntry getEntry() {
+ if (m_entryObject == null) {
+ m_entryObject = new NetworkTableEntry(m_inst, entry);
+ }
+ return m_entryObject;
+ }
+}
diff --git a/ntcore/src/main/java/edu/wpi/first/networktables/EntryListenerFlags.java b/ntcore/src/main/java/edu/wpi/first/networktables/EntryListenerFlags.java
new file mode 100644
index 0000000..9cdc0f0
--- /dev/null
+++ b/ntcore/src/main/java/edu/wpi/first/networktables/EntryListenerFlags.java
@@ -0,0 +1,72 @@
+/*----------------------------------------------------------------------------*/
+/* Copyright (c) 2017-2018 FIRST. All Rights Reserved. */
+/* Open Source Software - may be modified and shared by FRC teams. The code */
+/* must be accompanied by the FIRST BSD license file in the root directory of */
+/* the project. */
+/*----------------------------------------------------------------------------*/
+
+package edu.wpi.first.networktables;
+
+/**
+ * Flag values for use with entry listeners.
+ *
+ * <p>The flags are a bitmask and must be OR'ed together to indicate the
+ * combination of events desired to be received.
+ *
+ * <p>The constants kNew, kDelete, kUpdate, and kFlags represent different events
+ * that can occur to entries.
+ *
+ * <p>By default, notifications are only generated for remote changes occurring
+ * after the listener is created. The constants kImmediate and kLocal are
+ * modifiers that cause notifications to be generated at other times.
+ */
+public interface EntryListenerFlags {
+ /**
+ * Initial listener addition.
+ *
+ * <p>Set this flag to receive immediate notification of entries matching the
+ * flag criteria (generally only useful when combined with kNew).
+ */
+ int kImmediate = 0x01;
+
+ /**
+ * Changed locally.
+ *
+ * <p>Set this flag to receive notification of both local changes and changes
+ * coming from remote nodes. By default, notifications are only generated
+ * for remote changes. Must be combined with some combination of kNew,
+ * kDelete, kUpdate, and kFlags to receive notifications of those respective
+ * events.
+ */
+ int kLocal = 0x02;
+
+ /**
+ * Newly created entry.
+ *
+ * <p>Set this flag to receive a notification when an entry is created.
+ */
+ int kNew = 0x04;
+
+ /**
+ * Entry was deleted.
+ *
+ * <p>Set this flag to receive a notification when an entry is deleted.
+ */
+ int kDelete = 0x08;
+
+ /**
+ * Entry's value changed.
+ *
+ * <p>Set this flag to receive a notification when an entry's value (or type)
+ * changes.
+ */
+ int kUpdate = 0x10;
+
+ /**
+ * Entry's flags changed.
+ *
+ * <p>Set this flag to receive a notification when an entry's flags value
+ * changes.
+ */
+ int kFlags = 0x20;
+}
diff --git a/ntcore/src/main/java/edu/wpi/first/networktables/EntryNotification.java b/ntcore/src/main/java/edu/wpi/first/networktables/EntryNotification.java
new file mode 100644
index 0000000..159b968
--- /dev/null
+++ b/ntcore/src/main/java/edu/wpi/first/networktables/EntryNotification.java
@@ -0,0 +1,82 @@
+/*----------------------------------------------------------------------------*/
+/* Copyright (c) 2017-2018 FIRST. All Rights Reserved. */
+/* Open Source Software - may be modified and shared by FRC teams. The code */
+/* must be accompanied by the FIRST BSD license file in the root directory of */
+/* the project. */
+/*----------------------------------------------------------------------------*/
+
+package edu.wpi.first.networktables;
+
+/**
+ * NetworkTables Entry notification.
+ */
+public final class EntryNotification {
+ /**
+ * Listener that was triggered.
+ */
+ @SuppressWarnings("MemberName")
+ public final int listener;
+
+ /**
+ * Entry handle.
+ */
+ @SuppressWarnings("MemberName")
+ public final int entry;
+
+ /**
+ * Entry name.
+ */
+ @SuppressWarnings("MemberName")
+ public final String name;
+
+ /**
+ * The new value.
+ */
+ @SuppressWarnings("MemberName")
+ public final NetworkTableValue value;
+
+ /**
+ * Update flags. For example, {@link EntryListenerFlags#kNew} if the key did
+ * not previously exist.
+ */
+ @SuppressWarnings("MemberName")
+ public final int flags;
+
+ /** Constructor.
+ * This should generally only be used internally to NetworkTables.
+ *
+ * @param inst Instance
+ * @param listener Listener that was triggered
+ * @param entry Entry handle
+ * @param name Entry name
+ * @param value The new value
+ * @param flags Update flags
+ */
+ public EntryNotification(NetworkTableInstance inst, int listener, int entry, String name,
+ NetworkTableValue value, int flags) {
+ this.m_inst = inst;
+ this.listener = listener;
+ this.entry = entry;
+ this.name = name;
+ this.value = value;
+ this.flags = flags;
+ }
+
+ /* Network table instance. */
+ private final NetworkTableInstance m_inst;
+
+ /* Cached entry object. */
+ NetworkTableEntry m_entryObject;
+
+ /**
+ * Get the entry as an object.
+ *
+ * @return NetworkTableEntry for this entry.
+ */
+ public NetworkTableEntry getEntry() {
+ if (m_entryObject == null) {
+ m_entryObject = new NetworkTableEntry(m_inst, entry);
+ }
+ return m_entryObject;
+ }
+}
diff --git a/ntcore/src/main/java/edu/wpi/first/networktables/LogMessage.java b/ntcore/src/main/java/edu/wpi/first/networktables/LogMessage.java
new file mode 100644
index 0000000..2cf22ec
--- /dev/null
+++ b/ntcore/src/main/java/edu/wpi/first/networktables/LogMessage.java
@@ -0,0 +1,82 @@
+/*----------------------------------------------------------------------------*/
+/* Copyright (c) 2017-2018 FIRST. All Rights Reserved. */
+/* Open Source Software - may be modified and shared by FRC teams. The code */
+/* must be accompanied by the FIRST BSD license file in the root directory of */
+/* the project. */
+/*----------------------------------------------------------------------------*/
+
+package edu.wpi.first.networktables;
+
+/**
+ * NetworkTables log message.
+ */
+public final class LogMessage {
+ /**
+ * Logging levels.
+ */
+ public static final int kCritical = 50;
+ public static final int kError = 40;
+ public static final int kWarning = 30;
+ public static final int kInfo = 20;
+ public static final int kDebug = 10;
+ public static final int kDebug1 = 9;
+ public static final int kDebug2 = 8;
+ public static final int kDebug3 = 7;
+ public static final int kDebug4 = 6;
+
+ /**
+ * The logger that generated the message.
+ */
+ @SuppressWarnings("MemberName")
+ public final int logger;
+
+ /**
+ * Log level of the message.
+ */
+ @SuppressWarnings("MemberName")
+ public final int level;
+
+ /**
+ * The filename of the source file that generated the message.
+ */
+ @SuppressWarnings("MemberName")
+ public final String filename;
+
+ /**
+ * The line number in the source file that generated the message.
+ */
+ @SuppressWarnings("MemberName")
+ public final int line;
+
+ /**
+ * The message.
+ */
+ @SuppressWarnings("MemberName")
+ public final String message;
+
+ /** Constructor.
+ * This should generally only be used internally to NetworkTables.
+ *
+ * @param inst Instance
+ * @param logger Logger
+ * @param level Log level
+ * @param filename Filename
+ * @param line Line number
+ * @param message Message
+ */
+ public LogMessage(NetworkTableInstance inst, int logger, int level, String filename, int line,
+ String message) {
+ this.m_inst = inst;
+ this.logger = logger;
+ this.level = level;
+ this.filename = filename;
+ this.line = line;
+ this.message = message;
+ }
+
+ private final NetworkTableInstance m_inst;
+
+ NetworkTableInstance getInstance() {
+ return m_inst;
+ }
+}
diff --git a/ntcore/src/main/java/edu/wpi/first/networktables/NetworkTable.java b/ntcore/src/main/java/edu/wpi/first/networktables/NetworkTable.java
new file mode 100644
index 0000000..1cd21bf
--- /dev/null
+++ b/ntcore/src/main/java/edu/wpi/first/networktables/NetworkTable.java
@@ -0,0 +1,423 @@
+/*----------------------------------------------------------------------------*/
+/* Copyright (c) 2017-2018 FIRST. All Rights Reserved. */
+/* Open Source Software - may be modified and shared by FRC teams. The code */
+/* must be accompanied by the FIRST BSD license file in the root directory of */
+/* the project. */
+/*----------------------------------------------------------------------------*/
+
+package edu.wpi.first.networktables;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.function.Consumer;
+
+/**
+ * A network table that knows its subtable path.
+ */
+public final class NetworkTable {
+ /**
+ * The path separator for sub-tables and keys.
+ */
+ public static final char PATH_SEPARATOR = '/';
+
+ private final String m_path;
+ private final String m_pathWithSep;
+ private final NetworkTableInstance m_inst;
+
+ /**
+ * Gets the "base name" of a key. For example, "/foo/bar" becomes "bar".
+ * If the key has a trailing slash, returns an empty string.
+ *
+ * @param key key
+ * @return base name
+ */
+ public static String basenameKey(String key) {
+ final int slash = key.lastIndexOf(PATH_SEPARATOR);
+ if (slash == -1) {
+ return key;
+ }
+ return key.substring(slash + 1);
+ }
+
+ /**
+ * Normalizes an network table key to contain no consecutive slashes and
+ * optionally start with a leading slash. For example:
+ *
+ * <pre><code>
+ * normalizeKey("/foo/bar", true) == "/foo/bar"
+ * normalizeKey("foo/bar", true) == "/foo/bar"
+ * normalizeKey("/foo/bar", false) == "foo/bar"
+ * normalizeKey("foo//bar", false) == "foo/bar"
+ * </code></pre>
+ *
+ * @param key the key to normalize
+ * @param withLeadingSlash whether or not the normalized key should begin
+ * with a leading slash
+ * @return normalized key
+ */
+ public static String normalizeKey(String key, boolean withLeadingSlash) {
+ String normalized;
+ if (withLeadingSlash) {
+ normalized = PATH_SEPARATOR + key;
+ } else {
+ normalized = key;
+ }
+ normalized = normalized.replaceAll(PATH_SEPARATOR + "{2,}", String.valueOf(PATH_SEPARATOR));
+
+ if (!withLeadingSlash && normalized.charAt(0) == PATH_SEPARATOR) {
+ // remove leading slash, if present
+ normalized = normalized.substring(1);
+ }
+ return normalized;
+ }
+
+ /**
+ * Normalizes a network table key to start with exactly one leading slash
+ * ("/") and contain no consecutive slashes. For example,
+ * {@code "//foo/bar/"} becomes {@code "/foo/bar/"} and
+ * {@code "///a/b/c"} becomes {@code "/a/b/c"}.
+ *
+ * <p>This is equivalent to {@code normalizeKey(key, true)}
+ *
+ * @param key the key to normalize
+ * @return normalized key
+ */
+ public static String normalizeKey(String key) {
+ return normalizeKey(key, true);
+ }
+
+ /**
+ * Gets a list of the names of all the super tables of a given key. For
+ * example, the key "/foo/bar/baz" has a hierarchy of "/", "/foo",
+ * "/foo/bar", and "/foo/bar/baz".
+ *
+ * @param key the key
+ * @return List of super tables
+ */
+ public static List<String> getHierarchy(String key) {
+ final String normal = normalizeKey(key, true);
+ List<String> hierarchy = new ArrayList<>();
+ if (normal.length() == 1) {
+ hierarchy.add(normal);
+ return hierarchy;
+ }
+ for (int i = 1; ; i = normal.indexOf(PATH_SEPARATOR, i + 1)) {
+ if (i == -1) {
+ // add the full key
+ hierarchy.add(normal);
+ break;
+ } else {
+ hierarchy.add(normal.substring(0, i));
+ }
+ }
+ return hierarchy;
+ }
+
+ /**
+ * Constructor. Use NetworkTableInstance.getTable() or getSubTable() instead.
+ */
+ NetworkTable(NetworkTableInstance inst, String path) {
+ m_path = path;
+ m_pathWithSep = path + PATH_SEPARATOR;
+ m_inst = inst;
+ }
+
+ /**
+ * Gets the instance for the table.
+ *
+ * @return Instance
+ */
+ public NetworkTableInstance getInstance() {
+ return m_inst;
+ }
+
+ @Override
+ public String toString() {
+ return "NetworkTable: " + m_path;
+ }
+
+ private final ConcurrentMap<String, NetworkTableEntry> m_entries = new ConcurrentHashMap<>();
+
+ /**
+ * Gets the entry for a sub key.
+ *
+ * @param key the key name
+ * @return Network table entry.
+ */
+ public NetworkTableEntry getEntry(String key) {
+ NetworkTableEntry entry = m_entries.get(key);
+ if (entry == null) {
+ entry = m_inst.getEntry(m_pathWithSep + key);
+ m_entries.putIfAbsent(key, entry);
+ }
+ return entry;
+ }
+
+ /**
+ * Listen to keys only within this table.
+ *
+ * @param listener listener to add
+ * @param flags {@link EntryListenerFlags} bitmask
+ * @return Listener handle
+ */
+ public int addEntryListener(TableEntryListener listener, int flags) {
+ final int prefixLen = m_path.length() + 1;
+ return m_inst.addEntryListener(m_pathWithSep, event -> {
+ String relativeKey = event.name.substring(prefixLen);
+ if (relativeKey.indexOf(PATH_SEPARATOR) != -1) {
+ // part of a sub table
+ return;
+ }
+ listener.valueChanged(this, relativeKey, event.getEntry(), event.value, event.flags);
+ }, flags);
+ }
+
+ /**
+ * Listen to a single key.
+ *
+ * @param key the key name
+ * @param listener listener to add
+ * @param flags {@link EntryListenerFlags} bitmask
+ * @return Listener handle
+ */
+ public int addEntryListener(String key, TableEntryListener listener, int flags) {
+ final NetworkTableEntry entry = getEntry(key);
+ return m_inst.addEntryListener(entry,
+ event -> listener.valueChanged(this, key, entry, event.value, event.flags), flags);
+ }
+
+ /**
+ * Remove an entry listener.
+ *
+ * @param listener listener handle
+ */
+ public void removeEntryListener(int listener) {
+ m_inst.removeEntryListener(listener);
+ }
+
+ /**
+ * Listen for sub-table creation.
+ * This calls the listener once for each newly created sub-table.
+ * It immediately calls the listener for any existing sub-tables.
+ *
+ * @param listener listener to add
+ * @param localNotify notify local changes as well as remote
+ * @return Listener handle
+ */
+ public int addSubTableListener(TableListener listener, boolean localNotify) {
+ int flags = EntryListenerFlags.kNew | EntryListenerFlags.kImmediate;
+ if (localNotify) {
+ flags |= EntryListenerFlags.kLocal;
+ }
+
+ final int prefixLen = m_path.length() + 1;
+ final NetworkTable parent = this;
+
+ return m_inst.addEntryListener(m_pathWithSep, new Consumer<EntryNotification>() {
+ final Set<String> m_notifiedTables = new HashSet<>();
+
+ @Override
+ public void accept(EntryNotification event) {
+ String relativeKey = event.name.substring(prefixLen);
+ int endSubTable = relativeKey.indexOf(PATH_SEPARATOR);
+ if (endSubTable == -1) {
+ return;
+ }
+ String subTableKey = relativeKey.substring(0, endSubTable);
+ if (m_notifiedTables.contains(subTableKey)) {
+ return;
+ }
+ m_notifiedTables.add(subTableKey);
+ listener.tableCreated(parent, subTableKey, parent.getSubTable(subTableKey));
+ }
+ }, flags);
+ }
+
+ /**
+ * Remove a sub-table listener.
+ *
+ * @param listener listener handle
+ */
+ public void removeTableListener(int listener) {
+ m_inst.removeEntryListener(listener);
+ }
+
+ /**
+ * Returns the table at the specified key. If there is no table at the
+ * specified key, it will create a new table
+ *
+ * @param key the name of the table relative to this one
+ * @return a sub table relative to this one
+ */
+ public NetworkTable getSubTable(String key) {
+ return new NetworkTable(m_inst, m_pathWithSep + key);
+ }
+
+ /**
+ * Checks the table and tells if it contains the specified key.
+ *
+ * @param key the key to search for
+ * @return true if the table as a value assigned to the given key
+ */
+ public boolean containsKey(String key) {
+ return !("".equals(key)) && getEntry(key).exists();
+ }
+
+ /**
+ * Checks the table and tells if it contains the specified sub table.
+ *
+ * @param key the key to search for
+ * @return true if there is a subtable with the key which contains at least one key/subtable of
+ * its own
+ */
+ public boolean containsSubTable(String key) {
+ int[] handles = NetworkTablesJNI.getEntries(m_inst.getHandle(),
+ m_pathWithSep + key + PATH_SEPARATOR, 0);
+ return handles.length != 0;
+ }
+
+ /**
+ * Gets all keys in the table (not including sub-tables).
+ *
+ * @param types bitmask of types; 0 is treated as a "don't care".
+ * @return keys currently in the table
+ */
+ public Set<String> getKeys(int types) {
+ Set<String> keys = new HashSet<>();
+ int prefixLen = m_path.length() + 1;
+ for (EntryInfo info : m_inst.getEntryInfo(m_pathWithSep, types)) {
+ String relativeKey = info.name.substring(prefixLen);
+ if (relativeKey.indexOf(PATH_SEPARATOR) != -1) {
+ continue;
+ }
+ keys.add(relativeKey);
+ // populate entries as we go
+ if (m_entries.get(relativeKey) == null) {
+ m_entries.putIfAbsent(relativeKey, new NetworkTableEntry(m_inst, info.entry));
+ }
+ }
+ return keys;
+ }
+
+ /**
+ * Gets all keys in the table (not including sub-tables).
+ *
+ * @return keys currently in the table
+ */
+ public Set<String> getKeys() {
+ return getKeys(0);
+ }
+
+ /**
+ * Gets the names of all subtables in the table.
+ *
+ * @return subtables currently in the table
+ */
+ public Set<String> getSubTables() {
+ Set<String> keys = new HashSet<>();
+ int prefixLen = m_path.length() + 1;
+ for (EntryInfo info : m_inst.getEntryInfo(m_pathWithSep, 0)) {
+ String relativeKey = info.name.substring(prefixLen);
+ int endSubTable = relativeKey.indexOf(PATH_SEPARATOR);
+ if (endSubTable == -1) {
+ continue;
+ }
+ keys.add(relativeKey.substring(0, endSubTable));
+ }
+ return keys;
+ }
+
+ /**
+ * Deletes the specified key in this table. The key can
+ * not be null.
+ *
+ * @param key the key name
+ */
+ public void delete(String key) {
+ getEntry(key).delete();
+ }
+
+ /**
+ * Put a value in the table.
+ *
+ * @param key the key to be assigned to
+ * @param value the value that will be assigned
+ * @return False if the table key already exists with a different type
+ */
+ boolean putValue(String key, NetworkTableValue value) {
+ return getEntry(key).setValue(value);
+ }
+
+ /**
+ * Gets the current value in the table, setting it if it does not exist.
+ *
+ * @param key the key
+ * @param defaultValue the default value to set if key doesn't exist.
+ * @returns False if the table key exists with a different type
+ */
+ boolean setDefaultValue(String key, NetworkTableValue defaultValue) {
+ return getEntry(key).setDefaultValue(defaultValue);
+ }
+
+ /**
+ * Gets the value associated with a key as an object.
+ *
+ * @param key the key of the value to look up
+ * @return the value associated with the given key, or nullptr if the key does not exist
+ */
+ NetworkTableValue getValue(String key) {
+ return getEntry(key).getValue();
+ }
+
+ /**
+ * Get the path of the NetworkTable.
+ */
+ public String getPath() {
+ return m_path;
+ }
+
+ /**
+ * Save table values to a file. The file format used is identical to
+ * that used for SavePersistent.
+ *
+ * @param filename filename
+ * @throws PersistentException if error saving file
+ */
+ public void saveEntries(String filename) throws PersistentException {
+ m_inst.saveEntries(filename, m_pathWithSep);
+ }
+
+ /**
+ * Load table values from a file. The file format used is identical to
+ * that used for SavePersistent / LoadPersistent.
+ *
+ * @param filename filename
+ * @return List of warnings (errors result in an exception instead)
+ * @throws PersistentException if error saving file
+ */
+ public String[] loadEntries(String filename) throws PersistentException {
+ return m_inst.loadEntries(filename, m_pathWithSep);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+ if (!(other instanceof NetworkTable)) {
+ return false;
+ }
+ NetworkTable ntOther = (NetworkTable) other;
+ return m_inst.equals(ntOther.m_inst) && m_path.equals(ntOther.m_path);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(m_inst, m_path);
+ }
+}
diff --git a/ntcore/src/main/java/edu/wpi/first/networktables/NetworkTableEntry.java b/ntcore/src/main/java/edu/wpi/first/networktables/NetworkTableEntry.java
new file mode 100644
index 0000000..63ed984
--- /dev/null
+++ b/ntcore/src/main/java/edu/wpi/first/networktables/NetworkTableEntry.java
@@ -0,0 +1,875 @@
+/*----------------------------------------------------------------------------*/
+/* Copyright (c) 2017-2018 FIRST. All Rights Reserved. */
+/* Open Source Software - may be modified and shared by FRC teams. The code */
+/* must be accompanied by the FIRST BSD license file in the root directory of */
+/* the project. */
+/*----------------------------------------------------------------------------*/
+
+package edu.wpi.first.networktables;
+
+import java.nio.ByteBuffer;
+import java.util.function.Consumer;
+
+/**
+ * NetworkTables Entry.
+ */
+public final class NetworkTableEntry {
+ /**
+ * Flag values (as returned by {@link #getFlags()}).
+ */
+ public static final int kPersistent = 0x01;
+
+ /**
+ * Construct from native handle.
+ *
+ * @param inst Instance
+ * @param handle Native handle
+ */
+ public NetworkTableEntry(NetworkTableInstance inst, int handle) {
+ m_inst = inst;
+ m_handle = handle;
+ }
+
+ /**
+ * Determines if the native handle is valid.
+ *
+ * @return True if the native handle is valid, false otherwise.
+ */
+ public boolean isValid() {
+ return m_handle != 0;
+ }
+
+ /**
+ * Gets the native handle for the entry.
+ *
+ * @return Native handle
+ */
+ public int getHandle() {
+ return m_handle;
+ }
+
+ /**
+ * Gets the instance for the entry.
+ *
+ * @return Instance
+ */
+ public NetworkTableInstance getInstance() {
+ return m_inst;
+ }
+
+ /**
+ * Determines if the entry currently exists.
+ *
+ * @return True if the entry exists, false otherwise.
+ */
+ public boolean exists() {
+ return NetworkTablesJNI.getType(m_handle) != 0;
+ }
+
+ /**
+ * Gets the name of the entry (the key).
+ *
+ * @return the entry's name
+ */
+ public String getName() {
+ return NetworkTablesJNI.getEntryName(m_handle);
+ }
+
+ /**
+ * Gets the type of the entry.
+ *
+ * @return the entry's type
+ */
+ public NetworkTableType getType() {
+ return NetworkTableType.getFromInt(NetworkTablesJNI.getType(m_handle));
+ }
+
+ /**
+ * Returns the flags.
+ *
+ * @return the flags (bitmask)
+ */
+ public int getFlags() {
+ return NetworkTablesJNI.getEntryFlags(m_handle);
+ }
+
+ /**
+ * Gets the last time the entry's value was changed.
+ *
+ * @return Entry last change time
+ */
+ public long getLastChange() {
+ return NetworkTablesJNI.getEntryLastChange(m_handle);
+ }
+
+ /**
+ * Gets combined information about the entry.
+ *
+ * @return Entry information
+ */
+ public EntryInfo getInfo() {
+ return NetworkTablesJNI.getEntryInfoHandle(m_inst, m_handle);
+ }
+
+ /**
+ * Gets the entry's value.
+ * Returns a value with type NetworkTableType.kUnassigned if the value
+ * does not exist.
+ *
+ * @return the entry's value
+ */
+ public NetworkTableValue getValue() {
+ return NetworkTablesJNI.getValue(m_handle);
+ }
+
+ /**
+ * Gets the entry's value as a boolean. If the entry does not exist or is of
+ * different type, it will return the default value.
+ *
+ * @param defaultValue the value to be returned if no value is found
+ * @return the entry's value or the given default value
+ */
+ public boolean getBoolean(boolean defaultValue) {
+ return NetworkTablesJNI.getBoolean(m_handle, defaultValue);
+ }
+
+ /**
+ * Gets the entry's value as a double. If the entry does not exist or is of
+ * different type, it will return the default value.
+ *
+ * @param defaultValue the value to be returned if no value is found
+ * @return the entry's value or the given default value
+ */
+ public double getDouble(double defaultValue) {
+ return NetworkTablesJNI.getDouble(m_handle, defaultValue);
+ }
+
+ /**
+ * Gets the entry's value as a double. If the entry does not exist or is of
+ * different type, it will return the default value.
+ *
+ * @param defaultValue the value to be returned if no value is found
+ * @return the entry's value or the given default value
+ */
+ public Number getNumber(Number defaultValue) {
+ return NetworkTablesJNI.getDouble(m_handle, defaultValue.doubleValue());
+ }
+
+ /**
+ * Gets the entry's value as a string. If the entry does not exist or is of
+ * different type, it will return the default value.
+ *
+ * @param defaultValue the value to be returned if no value is found
+ * @return the entry's value or the given default value
+ */
+ public String getString(String defaultValue) {
+ return NetworkTablesJNI.getString(m_handle, defaultValue);
+ }
+
+ /**
+ * Gets the entry's value as a raw value (byte array). If the entry does not
+ * exist or is of different type, it will return the default value.
+ *
+ * @param defaultValue the value to be returned if no value is found
+ * @return the entry's value or the given default value
+ */
+ public byte[] getRaw(byte[] defaultValue) {
+ return NetworkTablesJNI.getRaw(m_handle, defaultValue);
+ }
+
+ /**
+ * Gets the entry's value as a boolean array. If the entry does not exist
+ * or is of different type, it will return the default value.
+ *
+ * @param defaultValue the value to be returned if no value is found
+ * @return the entry's value or the given default value
+ */
+ public boolean[] getBooleanArray(boolean[] defaultValue) {
+ return NetworkTablesJNI.getBooleanArray(m_handle, defaultValue);
+ }
+
+ /**
+ * Gets the entry's value as a boolean array. If the entry does not exist
+ * or is of different type, it will return the default value.
+ *
+ * @param defaultValue the value to be returned if no value is found
+ * @return the entry's value or the given default value
+ */
+ public Boolean[] getBooleanArray(Boolean[] defaultValue) {
+ return NetworkTableValue.fromNative(NetworkTablesJNI.getBooleanArray(m_handle,
+ NetworkTableValue.toNative(defaultValue)));
+ }
+
+ /**
+ * Gets the entry's value as a double array. If the entry does not exist
+ * or is of different type, it will return the default value.
+ *
+ * @param defaultValue the value to be returned if no value is found
+ * @return the entry's value or the given default value
+ */
+ public double[] getDoubleArray(double[] defaultValue) {
+ return NetworkTablesJNI.getDoubleArray(m_handle, defaultValue);
+ }
+
+ /**
+ * Gets the entry's value as a double array. If the entry does not exist
+ * or is of different type, it will return the default value.
+ *
+ * @param defaultValue the value to be returned if no value is found
+ * @return the entry's value or the given default value
+ */
+ public Double[] getDoubleArray(Double[] defaultValue) {
+ return NetworkTableValue.fromNative(NetworkTablesJNI.getDoubleArray(m_handle,
+ NetworkTableValue.toNative(defaultValue)));
+ }
+
+ /**
+ * Gets the entry's value as a double array. If the entry does not exist
+ * or is of different type, it will return the default value.
+ *
+ * @param defaultValue the value to be returned if no value is found
+ * @return the entry's value or the given default value
+ */
+ public Number[] getNumberArray(Number[] defaultValue) {
+ return NetworkTableValue.fromNative(NetworkTablesJNI.getDoubleArray(m_handle,
+ NetworkTableValue.toNative(defaultValue)));
+ }
+
+ /**
+ * Gets the entry's value as a string array. If the entry does not exist
+ * or is of different type, it will return the default value.
+ *
+ * @param defaultValue the value to be returned if no value is found
+ * @return the entry's value or the given default value
+ */
+ public String[] getStringArray(String[] defaultValue) {
+ return NetworkTablesJNI.getStringArray(m_handle, defaultValue);
+ }
+
+ /**
+ * Checks if a data value is of a type that can be placed in a NetworkTable entry.
+ *
+ * @param data the data to check
+ * @return true if the data can be placed in an entry, false if it cannot
+ */
+ public static boolean isValidDataType(Object data) {
+ return data instanceof Number
+ || data instanceof Boolean
+ || data instanceof String
+ || data instanceof double[]
+ || data instanceof Double[]
+ || data instanceof Number[]
+ || data instanceof boolean[]
+ || data instanceof Boolean[]
+ || data instanceof String[]
+ || data instanceof byte[]
+ || data instanceof Byte[];
+ }
+
+ /**
+ * Sets the entry's value if it does not exist.
+ *
+ * @param defaultValue the default value to set
+ * @return False if the entry exists with a different type
+ * @throws IllegalArgumentException if the value is not a known type
+ */
+ public boolean setDefaultValue(Object defaultValue) {
+ if (defaultValue instanceof NetworkTableValue) {
+ long time = ((NetworkTableValue) defaultValue).getTime();
+ Object otherValue = ((NetworkTableValue) defaultValue).getValue();
+ switch (((NetworkTableValue) defaultValue).getType()) {
+ case kBoolean:
+ return NetworkTablesJNI.setDefaultBoolean(m_handle, time,
+ ((Boolean) otherValue).booleanValue());
+ case kDouble:
+ return NetworkTablesJNI.setDefaultDouble(m_handle, time,
+ ((Number) otherValue).doubleValue());
+ case kString:
+ return NetworkTablesJNI.setDefaultString(m_handle, time, (String) otherValue);
+ case kRaw:
+ return NetworkTablesJNI.setDefaultRaw(m_handle, time, (byte[]) otherValue);
+ case kBooleanArray:
+ return NetworkTablesJNI.setDefaultBooleanArray(m_handle, time, (boolean[]) otherValue);
+ case kDoubleArray:
+ return NetworkTablesJNI.setDefaultDoubleArray(m_handle, time, (double[]) otherValue);
+ case kStringArray:
+ return NetworkTablesJNI.setDefaultStringArray(m_handle, time, (String[]) otherValue);
+ case kRpc:
+ // TODO
+ default:
+ return true;
+ }
+ } else if (defaultValue instanceof Boolean) {
+ return setDefaultBoolean((Boolean) defaultValue);
+ } else if (defaultValue instanceof Number) {
+ return setDefaultNumber((Number) defaultValue);
+ } else if (defaultValue instanceof String) {
+ return setDefaultString((String) defaultValue);
+ } else if (defaultValue instanceof byte[]) {
+ return setDefaultRaw((byte[]) defaultValue);
+ } else if (defaultValue instanceof boolean[]) {
+ return setDefaultBooleanArray((boolean[]) defaultValue);
+ } else if (defaultValue instanceof double[]) {
+ return setDefaultDoubleArray((double[]) defaultValue);
+ } else if (defaultValue instanceof Boolean[]) {
+ return setDefaultBooleanArray((Boolean[]) defaultValue);
+ } else if (defaultValue instanceof Number[]) {
+ return setDefaultNumberArray((Number[]) defaultValue);
+ } else if (defaultValue instanceof String[]) {
+ return setDefaultStringArray((String[]) defaultValue);
+ } else {
+ throw new IllegalArgumentException("Value of type " + defaultValue.getClass().getName()
+ + " cannot be put into a table");
+ }
+ }
+
+ /**
+ * Sets the entry's value if it does not exist.
+ *
+ * @param defaultValue the default value to set
+ * @return False if the entry exists with a different type
+ */
+ public boolean setDefaultBoolean(boolean defaultValue) {
+ return NetworkTablesJNI.setDefaultBoolean(m_handle, 0, defaultValue);
+ }
+
+ /**
+ * Sets the entry's value if it does not exist.
+ *
+ * @param defaultValue the default value to set
+ * @return False if the entry exists with a different type
+ */
+ public boolean setDefaultDouble(double defaultValue) {
+ return NetworkTablesJNI.setDefaultDouble(m_handle, 0, defaultValue);
+ }
+
+ /**
+ * Sets the entry's value if it does not exist.
+ *
+ * @param defaultValue the default value to set
+ * @return False if the entry exists with a different type
+ */
+ public boolean setDefaultNumber(Number defaultValue) {
+ return NetworkTablesJNI.setDefaultDouble(m_handle, 0, defaultValue.doubleValue());
+ }
+
+ /**
+ * Sets the entry's value if it does not exist.
+ *
+ * @param defaultValue the default value to set
+ * @return False if the entry exists with a different type
+ */
+ public boolean setDefaultString(String defaultValue) {
+ return NetworkTablesJNI.setDefaultString(m_handle, 0, defaultValue);
+ }
+
+ /**
+ * Sets the entry's value if it does not exist.
+ *
+ * @param defaultValue the default value to set
+ * @return False if the entry exists with a different type
+ */
+ public boolean setDefaultRaw(byte[] defaultValue) {
+ return NetworkTablesJNI.setDefaultRaw(m_handle, 0, defaultValue);
+ }
+
+ /**
+ * Sets the entry's value if it does not exist.
+ *
+ * @param defaultValue the default value to set
+ * @return False if the entry exists with a different type
+ */
+ public boolean setDefaultBooleanArray(boolean[] defaultValue) {
+ return NetworkTablesJNI.setDefaultBooleanArray(m_handle, 0, defaultValue);
+ }
+
+ /**
+ * Sets the entry's value if it does not exist.
+ *
+ * @param defaultValue the default value to set
+ * @return False if the entry exists with a different type
+ */
+ public boolean setDefaultBooleanArray(Boolean[] defaultValue) {
+ return NetworkTablesJNI.setDefaultBooleanArray(m_handle,
+ 0, NetworkTableValue.toNative(defaultValue));
+ }
+
+ /**
+ * Sets the entry's value if it does not exist.
+ *
+ * @param defaultValue the default value to set
+ * @return False if the entry exists with a different type
+ */
+ public boolean setDefaultDoubleArray(double[] defaultValue) {
+ return NetworkTablesJNI.setDefaultDoubleArray(m_handle, 0, defaultValue);
+ }
+
+ /**
+ * Sets the entry's value if it does not exist.
+ *
+ * @param defaultValue the default value to set
+ * @return False if the entry exists with a different type
+ */
+ public boolean setDefaultNumberArray(Number[] defaultValue) {
+ return NetworkTablesJNI.setDefaultDoubleArray(m_handle,
+ 0, NetworkTableValue.toNative(defaultValue));
+ }
+
+ /**
+ * Sets the entry's value if it does not exist.
+ *
+ * @param defaultValue the default value to set
+ * @return False if the entry exists with a different type
+ */
+ public boolean setDefaultStringArray(String[] defaultValue) {
+ return NetworkTablesJNI.setDefaultStringArray(m_handle, 0, defaultValue);
+ }
+
+ /**
+ * Sets the entry's value.
+ *
+ * @param value the value that will be assigned
+ * @return False if the table key already exists with a different type
+ * @throws IllegalArgumentException if the value is not a known type
+ */
+ public boolean setValue(Object value) {
+ if (value instanceof NetworkTableValue) {
+ long time = ((NetworkTableValue) value).getTime();
+ Object otherValue = ((NetworkTableValue) value).getValue();
+ switch (((NetworkTableValue) value).getType()) {
+ case kBoolean:
+ return NetworkTablesJNI.setBoolean(m_handle, time, ((Boolean) otherValue).booleanValue(),
+ false);
+ case kDouble:
+ return NetworkTablesJNI.setDouble(m_handle, time, ((Number) otherValue).doubleValue(),
+ false);
+ case kString:
+ return NetworkTablesJNI.setString(m_handle, time, (String) otherValue, false);
+ case kRaw:
+ return NetworkTablesJNI.setRaw(m_handle, time, (byte[]) otherValue, false);
+ case kBooleanArray:
+ return NetworkTablesJNI.setBooleanArray(m_handle, time, (boolean[]) otherValue, false);
+ case kDoubleArray:
+ return NetworkTablesJNI.setDoubleArray(m_handle, time, (double[]) otherValue, false);
+ case kStringArray:
+ return NetworkTablesJNI.setStringArray(m_handle, time, (String[]) otherValue, false);
+ case kRpc:
+ // TODO
+ default:
+ return true;
+ }
+ } else if (value instanceof Boolean) {
+ return setBoolean((Boolean) value);
+ } else if (value instanceof Number) {
+ return setNumber((Number) value);
+ } else if (value instanceof String) {
+ return setString((String) value);
+ } else if (value instanceof byte[]) {
+ return setRaw((byte[]) value);
+ } else if (value instanceof boolean[]) {
+ return setBooleanArray((boolean[]) value);
+ } else if (value instanceof double[]) {
+ return setDoubleArray((double[]) value);
+ } else if (value instanceof Boolean[]) {
+ return setBooleanArray((Boolean[]) value);
+ } else if (value instanceof Number[]) {
+ return setNumberArray((Number[]) value);
+ } else if (value instanceof String[]) {
+ return setStringArray((String[]) value);
+ } else {
+ throw new IllegalArgumentException("Value of type " + value.getClass().getName()
+ + " cannot be put into a table");
+ }
+ }
+
+ /**
+ * Sets the entry's value.
+ *
+ * @param value the value to set
+ * @return False if the entry exists with a different type
+ */
+ public boolean setBoolean(boolean value) {
+ return NetworkTablesJNI.setBoolean(m_handle, 0, value, false);
+ }
+
+ /**
+ * Sets the entry's value.
+ *
+ * @param value the value to set
+ * @return False if the entry exists with a different type
+ */
+ public boolean setDouble(double value) {
+ return NetworkTablesJNI.setDouble(m_handle, 0, value, false);
+ }
+
+ /**
+ * Sets the entry's value.
+ *
+ * @param value the value to set
+ * @return False if the entry exists with a different type
+ */
+ public boolean setNumber(Number value) {
+ return NetworkTablesJNI.setDouble(m_handle, 0, value.doubleValue(), false);
+ }
+
+ /**
+ * Sets the entry's value.
+ *
+ * @param value the value to set
+ * @return False if the entry exists with a different type
+ */
+ public boolean setString(String value) {
+ return NetworkTablesJNI.setString(m_handle, 0, value, false);
+ }
+
+ /**
+ * Sets the entry's value.
+ *
+ * @param value the value to set
+ * @return False if the entry exists with a different type
+ */
+ public boolean setRaw(byte[] value) {
+ return NetworkTablesJNI.setRaw(m_handle, 0, value, false);
+ }
+
+ /**
+ * Sets the entry's value.
+ *
+ * @param value the value to set
+ * @param len the length of the value
+ * @return False if the entry exists with a different type
+ */
+ public boolean setRaw(ByteBuffer value, int len) {
+ if (!value.isDirect()) {
+ throw new IllegalArgumentException("must be a direct buffer");
+ }
+ if (value.capacity() < len) {
+ throw new IllegalArgumentException("buffer is too small, must be at least " + len);
+ }
+ return NetworkTablesJNI.setRaw(m_handle, 0, value, len, false);
+ }
+
+ /**
+ * Sets the entry's value.
+ *
+ * @param value the value to set
+ * @return False if the entry exists with a different type
+ */
+ public boolean setBooleanArray(boolean[] value) {
+ return NetworkTablesJNI.setBooleanArray(m_handle, 0, value, false);
+ }
+
+ /**
+ * Sets the entry's value.
+ *
+ * @param value the value to set
+ * @return False if the entry exists with a different type
+ */
+ public boolean setBooleanArray(Boolean[] value) {
+ return NetworkTablesJNI.setBooleanArray(m_handle, 0, NetworkTableValue.toNative(value), false);
+ }
+
+ /**
+ * Sets the entry's value.
+ *
+ * @param value the value to set
+ * @return False if the entry exists with a different type
+ */
+ public boolean setDoubleArray(double[] value) {
+ return NetworkTablesJNI.setDoubleArray(m_handle, 0, value, false);
+ }
+
+ /**
+ * Sets the entry's value.
+ *
+ * @param value the value to set
+ * @return False if the entry exists with a different type
+ */
+ public boolean setNumberArray(Number[] value) {
+ return NetworkTablesJNI.setDoubleArray(m_handle, 0, NetworkTableValue.toNative(value), false);
+ }
+
+ /**
+ * Sets the entry's value.
+ *
+ * @param value the value to set
+ * @return False if the entry exists with a different type
+ */
+ public boolean setStringArray(String[] value) {
+ return NetworkTablesJNI.setStringArray(m_handle, 0, value, false);
+ }
+
+ /**
+ * Sets the entry's value. If the value is of different type, the type is
+ * changed to match the new value.
+ *
+ * @param value the value to set
+ * @throws IllegalArgumentException if the value is not a known type
+ */
+ public void forceSetValue(Object value) {
+ if (value instanceof NetworkTableValue) {
+ long time = ((NetworkTableValue) value).getTime();
+ Object otherValue = ((NetworkTableValue) value).getValue();
+ switch (((NetworkTableValue) value).getType()) {
+ case kBoolean:
+ NetworkTablesJNI.setBoolean(m_handle, time, ((Boolean) otherValue).booleanValue(), true);
+ return;
+ case kDouble:
+ NetworkTablesJNI.setDouble(m_handle, time, ((Number) otherValue).doubleValue(), true);
+ return;
+ case kString:
+ NetworkTablesJNI.setString(m_handle, time, (String) otherValue, true);
+ return;
+ case kRaw:
+ NetworkTablesJNI.setRaw(m_handle, time, (byte[]) otherValue, true);
+ return;
+ case kBooleanArray:
+ NetworkTablesJNI.setBooleanArray(m_handle, time, (boolean[]) otherValue, true);
+ return;
+ case kDoubleArray:
+ NetworkTablesJNI.setDoubleArray(m_handle, time, (double[]) otherValue, true);
+ return;
+ case kStringArray:
+ NetworkTablesJNI.setStringArray(m_handle, time, (String[]) otherValue, true);
+ return;
+ case kRpc:
+ // TODO
+ default:
+ return;
+ }
+ } else if (value instanceof Boolean) {
+ forceSetBoolean((Boolean) value);
+ } else if (value instanceof Number) {
+ forceSetNumber((Number) value);
+ } else if (value instanceof String) {
+ forceSetString((String) value);
+ } else if (value instanceof byte[]) {
+ forceSetRaw((byte[]) value);
+ } else if (value instanceof boolean[]) {
+ forceSetBooleanArray((boolean[]) value);
+ } else if (value instanceof double[]) {
+ forceSetDoubleArray((double[]) value);
+ } else if (value instanceof Boolean[]) {
+ forceSetBooleanArray((Boolean[]) value);
+ } else if (value instanceof Number[]) {
+ forceSetNumberArray((Number[]) value);
+ } else if (value instanceof String[]) {
+ forceSetStringArray((String[]) value);
+ } else {
+ throw new IllegalArgumentException("Value of type " + value.getClass().getName()
+ + " cannot be put into a table");
+ }
+ }
+
+ /**
+ * Sets the entry's value. If the value is of different type, the type is
+ * changed to match the new value.
+ *
+ * @param value the value to set
+ */
+ public void forceSetBoolean(boolean value) {
+ NetworkTablesJNI.setBoolean(m_handle, 0, value, true);
+ }
+
+ /**
+ * Sets the entry's value. If the value is of different type, the type is
+ * changed to match the new value.
+ *
+ * @param value the value to set
+ */
+ public void forceSetDouble(double value) {
+ NetworkTablesJNI.setDouble(m_handle, 0, value, true);
+ }
+
+ /**
+ * Sets the entry's value. If the value is of different type, the type is
+ * changed to match the new value.
+ *
+ * @param value the value to set
+ */
+ public void forceSetNumber(Number value) {
+ NetworkTablesJNI.setDouble(m_handle, 0, value.doubleValue(), true);
+ }
+
+ /**
+ * Sets the entry's value. If the value is of different type, the type is
+ * changed to match the new value.
+ *
+ * @param value the value to set
+ */
+ public void forceSetString(String value) {
+ NetworkTablesJNI.setString(m_handle, 0, value, true);
+ }
+
+ /**
+ * Sets the entry's value. If the value is of different type, the type is
+ * changed to match the new value.
+ *
+ * @param value the value to set
+ */
+ public void forceSetRaw(byte[] value) {
+ NetworkTablesJNI.setRaw(m_handle, 0, value, true);
+ }
+
+ /**
+ * Sets the entry's value. If the value is of different type, the type is
+ * changed to match the new value.
+ *
+ * @param value the value to set
+ */
+ public void forceSetBooleanArray(boolean[] value) {
+ NetworkTablesJNI.setBooleanArray(m_handle, 0, value, true);
+ }
+
+ /**
+ * Sets the entry's value. If the value is of different type, the type is
+ * changed to match the new value.
+ *
+ * @param value the value to set
+ */
+ public void forceSetBooleanArray(Boolean[] value) {
+ NetworkTablesJNI.setBooleanArray(m_handle, 0, NetworkTableValue.toNative(value), true);
+ }
+
+ /**
+ * Sets the entry's value. If the value is of different type, the type is
+ * changed to match the new value.
+ *
+ * @param value the value to set
+ */
+ public void forceSetDoubleArray(double[] value) {
+ NetworkTablesJNI.setDoubleArray(m_handle, 0, value, true);
+ }
+
+ /**
+ * Sets the entry's value. If the value is of different type, the type is
+ * changed to match the new value.
+ *
+ * @param value the value to set
+ */
+ public void forceSetNumberArray(Number[] value) {
+ NetworkTablesJNI.setDoubleArray(m_handle, 0, NetworkTableValue.toNative(value), true);
+ }
+
+ /**
+ * Sets the entry's value. If the value is of different type, the type is
+ * changed to match the new value.
+ *
+ * @param value the value to set
+ */
+ public void forceSetStringArray(String[] value) {
+ NetworkTablesJNI.setStringArray(m_handle, 0, value, true);
+ }
+
+ /**
+ * Sets flags.
+ *
+ * @param flags the flags to set (bitmask)
+ */
+ public void setFlags(int flags) {
+ NetworkTablesJNI.setEntryFlags(m_handle, getFlags() | flags);
+ }
+
+ /**
+ * Clears flags.
+ *
+ * @param flags the flags to clear (bitmask)
+ */
+ public void clearFlags(int flags) {
+ NetworkTablesJNI.setEntryFlags(m_handle, getFlags() & ~flags);
+ }
+
+ /**
+ * Make value persistent through program restarts.
+ */
+ public void setPersistent() {
+ setFlags(kPersistent);
+ }
+
+ /**
+ * Stop making value persistent through program restarts.
+ */
+ public void clearPersistent() {
+ clearFlags(kPersistent);
+ }
+
+ /**
+ * Returns whether the value is persistent through program restarts.
+ *
+ * @return True if the value is persistent.
+ */
+ public boolean isPersistent() {
+ return (getFlags() & kPersistent) != 0;
+ }
+
+ /**
+ * Deletes the entry.
+ */
+ public void delete() {
+ NetworkTablesJNI.deleteEntry(m_handle);
+ }
+
+ /**
+ * Create a callback-based RPC entry point. Only valid to use on the server.
+ * The callback function will be called when the RPC is called.
+ * This function creates RPC version 0 definitions (raw data in and out).
+ *
+ * @param callback callback function
+ */
+ public void createRpc(Consumer<RpcAnswer> callback) {
+ m_inst.createRpc(this, callback);
+ }
+
+ /**
+ * Call a RPC function. May be used on either the client or server.
+ * This function is non-blocking. Either {@link RpcCall#getResult()} or
+ * {@link RpcCall#cancelResult()} must be called on the return value to either
+ * get or ignore the result of the call.
+ *
+ * @param params parameter
+ * @return RPC call object.
+ */
+ public RpcCall callRpc(byte[] params) {
+ return new RpcCall(this, NetworkTablesJNI.callRpc(m_handle, params));
+ }
+
+ /**
+ * Add a listener for changes to the entry.
+ *
+ * @param listener the listener to add
+ * @param flags bitmask specifying desired notifications
+ * @return listener handle
+ */
+ public int addListener(Consumer<EntryNotification> listener, int flags) {
+ return m_inst.addEntryListener(this, listener, flags);
+ }
+
+ /**
+ * Remove a listener from receiving entry events.
+ *
+ * @param listener the listener to be removed
+ */
+ public void removeListener(int listener) {
+ m_inst.removeEntryListener(listener);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+ if (!(other instanceof NetworkTableEntry)) {
+ return false;
+ }
+
+ return m_handle == ((NetworkTableEntry) other).m_handle;
+ }
+
+ @Override
+ public int hashCode() {
+ return m_handle;
+ }
+
+ private NetworkTableInstance m_inst;
+ private int m_handle;
+}
diff --git a/ntcore/src/main/java/edu/wpi/first/networktables/NetworkTableInstance.java b/ntcore/src/main/java/edu/wpi/first/networktables/NetworkTableInstance.java
new file mode 100644
index 0000000..2564f70
--- /dev/null
+++ b/ntcore/src/main/java/edu/wpi/first/networktables/NetworkTableInstance.java
@@ -0,0 +1,1165 @@
+/*----------------------------------------------------------------------------*/
+/* Copyright (c) 2017-2018 FIRST. All Rights Reserved. */
+/* Open Source Software - may be modified and shared by FRC teams. The code */
+/* must be accompanied by the FIRST BSD license file in the root directory of */
+/* the project. */
+/*----------------------------------------------------------------------------*/
+
+package edu.wpi.first.networktables;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.locks.Condition;
+import java.util.concurrent.locks.ReentrantLock;
+import java.util.function.Consumer;
+
+/**
+ * NetworkTables Instance.
+ *
+ * <p>Instances are completely independent from each other. Table operations on
+ * one instance will not be visible to other instances unless the instances are
+ * connected via the network. The main limitation on instances is that you
+ * cannot have two servers on the same network port. The main utility of
+ * instances is for unit testing, but they can also enable one program to
+ * connect to two different NetworkTables networks.
+ *
+ * <p>The global "default" instance (as returned by {@link #getDefault()}) is
+ * always available, and is intended for the common case when there is only
+ * a single NetworkTables instance being used in the program.
+ *
+ * <p>Additional instances can be created with the {@link #create()} function.
+ * A reference must be kept to the NetworkTableInstance returned by this
+ * function to keep it from being garbage collected.
+ */
+public final class NetworkTableInstance implements AutoCloseable {
+ /**
+ * Client/server mode flag values (as returned by {@link #getNetworkMode()}).
+ * This is a bitmask.
+ */
+ public static final int kNetModeNone = 0x00;
+ public static final int kNetModeServer = 0x01;
+ public static final int kNetModeClient = 0x02;
+ public static final int kNetModeStarting = 0x04;
+ public static final int kNetModeFailure = 0x08;
+
+ /**
+ * The default port that network tables operates on.
+ */
+ public static final int kDefaultPort = 1735;
+
+ /**
+ * Construct from native handle.
+ *
+ * @param handle Native handle
+ */
+ private NetworkTableInstance(int handle) {
+ m_owned = false;
+ m_handle = handle;
+ }
+
+ @Deprecated
+ public void free() {
+ close();
+ }
+
+ /**
+ * Destroys the instance (if created by {@link #create()}).
+ */
+ @Override
+ public synchronized void close() {
+ if (m_owned && m_handle != 0) {
+ NetworkTablesJNI.destroyInstance(m_handle);
+ }
+ }
+
+ /**
+ * Determines if the native handle is valid.
+ *
+ * @return True if the native handle is valid, false otherwise.
+ */
+ public boolean isValid() {
+ return m_handle != 0;
+ }
+
+ /* The default instance. */
+ private static NetworkTableInstance s_defaultInstance;
+
+ /**
+ * Get global default instance.
+ *
+ * @return Global default instance
+ */
+ public static synchronized NetworkTableInstance getDefault() {
+ if (s_defaultInstance == null) {
+ s_defaultInstance = new NetworkTableInstance(NetworkTablesJNI.getDefaultInstance());
+ }
+ return s_defaultInstance;
+ }
+
+ /**
+ * Create an instance.
+ * Note: A reference to the returned instance must be retained to ensure the
+ * instance is not garbage collected.
+ *
+ * @return Newly created instance
+ */
+ public static NetworkTableInstance create() {
+ NetworkTableInstance inst = new NetworkTableInstance(NetworkTablesJNI.createInstance());
+ inst.m_owned = true;
+ return inst;
+ }
+
+ /**
+ * Gets the native handle for the entry.
+ *
+ * @return Native handle
+ */
+ public int getHandle() {
+ return m_handle;
+ }
+
+ /**
+ * Gets the entry for a key.
+ *
+ * @param name Key
+ * @return Network table entry.
+ */
+ public NetworkTableEntry getEntry(String name) {
+ return new NetworkTableEntry(this, NetworkTablesJNI.getEntry(m_handle, name));
+ }
+
+ /**
+ * Get entries starting with the given prefix.
+ * The results are optionally filtered by string prefix and entry type to
+ * only return a subset of all entries.
+ *
+ * @param prefix entry name required prefix; only entries whose name
+ * starts with this string are returned
+ * @param types bitmask of types; 0 is treated as a "don't care"
+ * @return Array of entries.
+ */
+ public NetworkTableEntry[] getEntries(String prefix, int types) {
+ int[] handles = NetworkTablesJNI.getEntries(m_handle, prefix, types);
+ NetworkTableEntry[] entries = new NetworkTableEntry[handles.length];
+ for (int i = 0; i < handles.length; i++) {
+ entries[i] = new NetworkTableEntry(this, handles[i]);
+ }
+ return entries;
+ }
+
+ /**
+ * Get information about entries starting with the given prefix.
+ * The results are optionally filtered by string prefix and entry type to
+ * only return a subset of all entries.
+ *
+ * @param prefix entry name required prefix; only entries whose name
+ * starts with this string are returned
+ * @param types bitmask of types; 0 is treated as a "don't care"
+ * @return Array of entry information.
+ */
+ public EntryInfo[] getEntryInfo(String prefix, int types) {
+ return NetworkTablesJNI.getEntryInfo(this, m_handle, prefix, types);
+ }
+
+ /* Cache of created tables. */
+ private final ConcurrentMap<String, NetworkTable> m_tables = new ConcurrentHashMap<>();
+
+ /**
+ * Gets the table with the specified key.
+ *
+ * @param key the key name
+ * @return The network table
+ */
+ public NetworkTable getTable(String key) {
+ // prepend leading / if not present
+ String theKey;
+ if (key.isEmpty() || key.equals("/")) {
+ theKey = "";
+ } else if (key.charAt(0) == NetworkTable.PATH_SEPARATOR) {
+ theKey = key;
+ } else {
+ theKey = NetworkTable.PATH_SEPARATOR + key;
+ }
+
+ // cache created tables
+ NetworkTable table = m_tables.get(theKey);
+ if (table == null) {
+ table = new NetworkTable(this, theKey);
+ NetworkTable oldTable = m_tables.putIfAbsent(theKey, table);
+ if (oldTable != null) {
+ table = oldTable;
+ }
+ }
+ return table;
+ }
+
+ /**
+ * Deletes ALL keys in ALL subtables (except persistent values).
+ * Use with caution!
+ */
+ public void deleteAllEntries() {
+ NetworkTablesJNI.deleteAllEntries(m_handle);
+ }
+
+ /*
+ * Callback Creation Functions
+ */
+
+ private static class EntryConsumer<T> {
+ final NetworkTableEntry m_entry;
+ final Consumer<T> m_consumer;
+
+ EntryConsumer(NetworkTableEntry entry, Consumer<T> consumer) {
+ m_entry = entry;
+ m_consumer = consumer;
+ }
+ }
+
+ private final ReentrantLock m_entryListenerLock = new ReentrantLock();
+ private final Map<Integer, EntryConsumer<EntryNotification>> m_entryListeners = new HashMap<>();
+ private Thread m_entryListenerThread;
+ private int m_entryListenerPoller;
+ private boolean m_entryListenerWaitQueue;
+ private final Condition m_entryListenerWaitQueueCond = m_entryListenerLock.newCondition();
+
+ private void startEntryListenerThread() {
+ m_entryListenerThread = new Thread(() -> {
+ boolean wasInterrupted = false;
+ while (!Thread.interrupted()) {
+ EntryNotification[] events;
+ try {
+ events = NetworkTablesJNI.pollEntryListener(this, m_entryListenerPoller);
+ } catch (InterruptedException ex) {
+ m_entryListenerLock.lock();
+ try {
+ if (m_entryListenerWaitQueue) {
+ m_entryListenerWaitQueue = false;
+ m_entryListenerWaitQueueCond.signalAll();
+ continue;
+ }
+ } finally {
+ m_entryListenerLock.unlock();
+ }
+ Thread.currentThread().interrupt();
+ // don't try to destroy poller, as its handle is likely no longer valid
+ wasInterrupted = true;
+ break;
+ }
+ for (EntryNotification event : events) {
+ EntryConsumer<EntryNotification> listener;
+ m_entryListenerLock.lock();
+ try {
+ listener = m_entryListeners.get(event.listener);
+ } finally {
+ m_entryListenerLock.unlock();
+ }
+ if (listener != null) {
+ event.m_entryObject = listener.m_entry;
+ try {
+ listener.m_consumer.accept(event);
+ } catch (Throwable throwable) {
+ System.err.println("Unhandled exception during entry listener callback: "
+ + throwable.toString());
+ throwable.printStackTrace();
+ }
+ }
+ }
+ }
+ m_entryListenerLock.lock();
+ try {
+ if (!wasInterrupted) {
+ NetworkTablesJNI.destroyEntryListenerPoller(m_entryListenerPoller);
+ }
+ m_entryListenerPoller = 0;
+ } finally {
+ m_entryListenerLock.unlock();
+ }
+ }, "NTEntryListener");
+ m_entryListenerThread.setDaemon(true);
+ m_entryListenerThread.start();
+ }
+
+ /**
+ * Add a listener for all entries starting with a certain prefix.
+ *
+ * @param prefix UTF-8 string prefix
+ * @param listener listener to add
+ * @param flags {@link EntryListenerFlags} bitmask
+ * @return Listener handle
+ */
+ public int addEntryListener(String prefix, Consumer<EntryNotification> listener, int flags) {
+ m_entryListenerLock.lock();
+ try {
+ if (m_entryListenerPoller == 0) {
+ m_entryListenerPoller = NetworkTablesJNI.createEntryListenerPoller(m_handle);
+ startEntryListenerThread();
+ }
+ int handle = NetworkTablesJNI.addPolledEntryListener(m_entryListenerPoller, prefix, flags);
+ m_entryListeners.put(handle, new EntryConsumer<>(null, listener));
+ return handle;
+ } finally {
+ m_entryListenerLock.unlock();
+ }
+ }
+
+ /**
+ * Add a listener for a particular entry.
+ *
+ * @param entry the entry
+ * @param listener listener to add
+ * @param flags {@link EntryListenerFlags} bitmask
+ * @return Listener handle
+ */
+ public int addEntryListener(NetworkTableEntry entry,
+ Consumer<EntryNotification> listener,
+ int flags) {
+ if (!equals(entry.getInstance())) {
+ throw new IllegalArgumentException("entry does not belong to this instance");
+ }
+ m_entryListenerLock.lock();
+ try {
+ if (m_entryListenerPoller == 0) {
+ m_entryListenerPoller = NetworkTablesJNI.createEntryListenerPoller(m_handle);
+ startEntryListenerThread();
+ }
+ int handle = NetworkTablesJNI.addPolledEntryListener(m_entryListenerPoller, entry.getHandle(),
+ flags);
+ m_entryListeners.put(handle, new EntryConsumer<>(entry, listener));
+ return handle;
+ } finally {
+ m_entryListenerLock.unlock();
+ }
+ }
+
+ /**
+ * Remove an entry listener.
+ *
+ * @param listener Listener handle to remove
+ */
+ public void removeEntryListener(int listener) {
+ NetworkTablesJNI.removeEntryListener(listener);
+ }
+
+ /**
+ * Wait for the entry listener queue to be empty. This is primarily useful
+ * for deterministic testing. This blocks until either the entry listener
+ * queue is empty (e.g. there are no more events that need to be passed along
+ * to callbacks or poll queues) or the timeout expires.
+ *
+ * @param timeout timeout, in seconds. Set to 0 for non-blocking behavior,
+ * or a negative value to block indefinitely
+ * @return False if timed out, otherwise true.
+ */
+ public boolean waitForEntryListenerQueue(double timeout) {
+ if (!NetworkTablesJNI.waitForEntryListenerQueue(m_handle, timeout)) {
+ return false;
+ }
+ m_entryListenerLock.lock();
+ try {
+ if (m_entryListenerPoller != 0) {
+ m_entryListenerWaitQueue = true;
+ NetworkTablesJNI.cancelPollEntryListener(m_entryListenerPoller);
+ while (m_entryListenerWaitQueue) {
+ try {
+ if (timeout < 0) {
+ m_entryListenerWaitQueueCond.await();
+ } else {
+ return m_entryListenerWaitQueueCond.await((long) (timeout * 1e9),
+ TimeUnit.NANOSECONDS);
+ }
+ } catch (InterruptedException ex) {
+ Thread.currentThread().interrupt();
+ return true;
+ }
+ }
+ }
+ } finally {
+ m_entryListenerLock.unlock();
+ }
+ return true;
+ }
+
+ private final ReentrantLock m_connectionListenerLock = new ReentrantLock();
+ private final Map<Integer, Consumer<ConnectionNotification>> m_connectionListeners
+ = new HashMap<>();
+ private Thread m_connectionListenerThread;
+ private int m_connectionListenerPoller;
+ private boolean m_connectionListenerWaitQueue;
+ private final Condition m_connectionListenerWaitQueueCond
+ = m_connectionListenerLock.newCondition();
+
+ private void startConnectionListenerThread() {
+ m_connectionListenerThread = new Thread(() -> {
+ boolean wasInterrupted = false;
+ while (!Thread.interrupted()) {
+ ConnectionNotification[] events;
+ try {
+ events = NetworkTablesJNI.pollConnectionListener(this, m_connectionListenerPoller);
+ } catch (InterruptedException ex) {
+ m_connectionListenerLock.lock();
+ try {
+ if (m_connectionListenerWaitQueue) {
+ m_connectionListenerWaitQueue = false;
+ m_connectionListenerWaitQueueCond.signalAll();
+ continue;
+ }
+ } finally {
+ m_connectionListenerLock.unlock();
+ }
+ Thread.currentThread().interrupt();
+ // don't try to destroy poller, as its handle is likely no longer valid
+ wasInterrupted = true;
+ break;
+ }
+ for (ConnectionNotification event : events) {
+ Consumer<ConnectionNotification> listener;
+ m_connectionListenerLock.lock();
+ try {
+ listener = m_connectionListeners.get(event.listener);
+ } finally {
+ m_connectionListenerLock.unlock();
+ }
+ if (listener != null) {
+ try {
+ listener.accept(event);
+ } catch (Throwable throwable) {
+ System.err.println("Unhandled exception during connection listener callback: "
+ + throwable.toString());
+ throwable.printStackTrace();
+ }
+ }
+ }
+ }
+ m_connectionListenerLock.lock();
+ try {
+ if (!wasInterrupted) {
+ NetworkTablesJNI.destroyConnectionListenerPoller(m_connectionListenerPoller);
+ }
+ m_connectionListenerPoller = 0;
+ } finally {
+ m_connectionListenerLock.unlock();
+ }
+ }, "NTConnectionListener");
+ m_connectionListenerThread.setDaemon(true);
+ m_connectionListenerThread.start();
+ }
+
+ /**
+ * Add a connection listener.
+ *
+ * @param listener Listener to add
+ * @param immediateNotify Notify listener of all existing connections
+ * @return Listener handle
+ */
+ public int addConnectionListener(Consumer<ConnectionNotification> listener,
+ boolean immediateNotify) {
+ m_connectionListenerLock.lock();
+ try {
+ if (m_connectionListenerPoller == 0) {
+ m_connectionListenerPoller = NetworkTablesJNI.createConnectionListenerPoller(m_handle);
+ startConnectionListenerThread();
+ }
+ int handle = NetworkTablesJNI.addPolledConnectionListener(m_connectionListenerPoller,
+ immediateNotify);
+ m_connectionListeners.put(handle, listener);
+ return handle;
+ } finally {
+ m_connectionListenerLock.unlock();
+ }
+ }
+
+ /**
+ * Remove a connection listener.
+ *
+ * @param listener Listener handle to remove
+ */
+ public void removeConnectionListener(int listener) {
+ m_connectionListenerLock.lock();
+ try {
+ m_connectionListeners.remove(listener);
+ } finally {
+ m_connectionListenerLock.unlock();
+ }
+ NetworkTablesJNI.removeConnectionListener(listener);
+ }
+
+ /**
+ * Wait for the connection listener queue to be empty. This is primarily useful
+ * for deterministic testing. This blocks until either the connection listener
+ * queue is empty (e.g. there are no more events that need to be passed along
+ * to callbacks or poll queues) or the timeout expires.
+ *
+ * @param timeout timeout, in seconds. Set to 0 for non-blocking behavior,
+ * or a negative value to block indefinitely
+ * @return False if timed out, otherwise true.
+ */
+ public boolean waitForConnectionListenerQueue(double timeout) {
+ if (!NetworkTablesJNI.waitForConnectionListenerQueue(m_handle, timeout)) {
+ return false;
+ }
+ m_connectionListenerLock.lock();
+ try {
+ if (m_connectionListenerPoller != 0) {
+ m_connectionListenerWaitQueue = true;
+ NetworkTablesJNI.cancelPollConnectionListener(m_connectionListenerPoller);
+ while (m_connectionListenerWaitQueue) {
+ try {
+ if (timeout < 0) {
+ m_connectionListenerWaitQueueCond.await();
+ } else {
+ return m_connectionListenerWaitQueueCond.await((long) (timeout * 1e9),
+ TimeUnit.NANOSECONDS);
+ }
+ } catch (InterruptedException ex) {
+ Thread.currentThread().interrupt();
+ return true;
+ }
+ }
+ }
+ } finally {
+ m_connectionListenerLock.unlock();
+ }
+ return true;
+ }
+
+ /*
+ * Remote Procedure Call Functions
+ */
+
+ private final ReentrantLock m_rpcCallLock = new ReentrantLock();
+ private final Map<Integer, EntryConsumer<RpcAnswer>> m_rpcCalls = new HashMap<>();
+ private Thread m_rpcCallThread;
+ private int m_rpcCallPoller;
+ private boolean m_rpcCallWaitQueue;
+ private final Condition m_rpcCallWaitQueueCond = m_rpcCallLock.newCondition();
+
+ private void startRpcCallThread() {
+ m_rpcCallThread = new Thread(() -> {
+ boolean wasInterrupted = false;
+ while (!Thread.interrupted()) {
+ RpcAnswer[] events;
+ try {
+ events = NetworkTablesJNI.pollRpc(this, m_rpcCallPoller);
+ } catch (InterruptedException ex) {
+ m_rpcCallLock.lock();
+ try {
+ if (m_rpcCallWaitQueue) {
+ m_rpcCallWaitQueue = false;
+ m_rpcCallWaitQueueCond.signalAll();
+ continue;
+ }
+ } finally {
+ m_rpcCallLock.unlock();
+ }
+ Thread.currentThread().interrupt();
+ // don't try to destroy poller, as its handle is likely no longer valid
+ wasInterrupted = true;
+ break;
+ }
+ for (RpcAnswer event : events) {
+ EntryConsumer<RpcAnswer> listener;
+ m_rpcCallLock.lock();
+ try {
+ listener = m_rpcCalls.get(event.entry);
+ } finally {
+ m_rpcCallLock.unlock();
+ }
+ if (listener != null) {
+ event.m_entryObject = listener.m_entry;
+ try {
+ listener.m_consumer.accept(event);
+ } catch (Throwable throwable) {
+ System.err.println("Unhandled exception during RPC callback: "
+ + throwable.toString());
+ throwable.printStackTrace();
+ }
+ event.finish();
+ }
+ }
+ }
+ m_rpcCallLock.lock();
+ try {
+ if (!wasInterrupted) {
+ NetworkTablesJNI.destroyRpcCallPoller(m_rpcCallPoller);
+ }
+ m_rpcCallPoller = 0;
+ } finally {
+ m_rpcCallLock.unlock();
+ }
+ }, "NTRpcCall");
+ m_rpcCallThread.setDaemon(true);
+ m_rpcCallThread.start();
+ }
+
+ private static final byte[] rev0def = new byte[] {0};
+
+ /**
+ * Create a callback-based RPC entry point. Only valid to use on the server.
+ * The callback function will be called when the RPC is called.
+ * This function creates RPC version 0 definitions (raw data in and out).
+ *
+ * @param entry the entry
+ * @param callback callback function
+ */
+ public void createRpc(NetworkTableEntry entry, Consumer<RpcAnswer> callback) {
+ m_rpcCallLock.lock();
+ try {
+ if (m_rpcCallPoller == 0) {
+ m_rpcCallPoller = NetworkTablesJNI.createRpcCallPoller(m_handle);
+ startRpcCallThread();
+ }
+ NetworkTablesJNI.createPolledRpc(entry.getHandle(), rev0def, m_rpcCallPoller);
+ m_rpcCalls.put(entry.getHandle(), new EntryConsumer<>(entry, callback));
+ } finally {
+ m_rpcCallLock.unlock();
+ }
+ }
+
+ /**
+ * Wait for the incoming RPC call queue to be empty. This is primarily useful
+ * for deterministic testing. This blocks until either the RPC call
+ * queue is empty (e.g. there are no more events that need to be passed along
+ * to callbacks or poll queues) or the timeout expires.
+ *
+ * @param timeout timeout, in seconds. Set to 0 for non-blocking behavior,
+ * or a negative value to block indefinitely
+ * @return False if timed out, otherwise true.
+ */
+ public boolean waitForRpcCallQueue(double timeout) {
+ if (!NetworkTablesJNI.waitForRpcCallQueue(m_handle, timeout)) {
+ return false;
+ }
+ m_rpcCallLock.lock();
+ try {
+ if (m_rpcCallPoller != 0) {
+ m_rpcCallWaitQueue = true;
+ NetworkTablesJNI.cancelPollRpc(m_rpcCallPoller);
+ while (m_rpcCallWaitQueue) {
+ try {
+ if (timeout < 0) {
+ m_rpcCallWaitQueueCond.await();
+ } else {
+ return m_rpcCallWaitQueueCond.await((long) (timeout * 1e9), TimeUnit.NANOSECONDS);
+ }
+ } catch (InterruptedException ex) {
+ Thread.currentThread().interrupt();
+ return true;
+ }
+ }
+ }
+ } finally {
+ m_rpcCallLock.unlock();
+ }
+ return true;
+ }
+
+ /*
+ * Client/Server Functions
+ */
+
+ /**
+ * Set the network identity of this node.
+ * This is the name used during the initial connection handshake, and is
+ * visible through ConnectionInfo on the remote node.
+ *
+ * @param name identity to advertise
+ */
+ public void setNetworkIdentity(String name) {
+ NetworkTablesJNI.setNetworkIdentity(m_handle, name);
+ }
+
+ /**
+ * Get the current network mode.
+ *
+ * @return Bitmask of NetworkMode.
+ */
+ public int getNetworkMode() {
+ return NetworkTablesJNI.getNetworkMode(m_handle);
+ }
+
+ /**
+ * Starts a server using the networktables.ini as the persistent file,
+ * using the default listening address and port.
+ */
+ public void startServer() {
+ startServer("networktables.ini");
+ }
+
+ /**
+ * Starts a server using the specified persistent filename, using the default
+ * listening address and port.
+ *
+ * @param persistFilename the name of the persist file to use
+ */
+ public void startServer(String persistFilename) {
+ startServer(persistFilename, "");
+ }
+
+ /**
+ * Starts a server using the specified filename and listening address,
+ * using the default 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
+ */
+ public void startServer(String persistFilename, String listenAddress) {
+ startServer(persistFilename, listenAddress, kDefaultPort);
+ }
+
+ /**
+ * 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 port port to communicate over
+ */
+ public void startServer(String persistFilename, String listenAddress, int port) {
+ NetworkTablesJNI.startServer(m_handle, persistFilename, listenAddress, port);
+ }
+
+ /**
+ * Stops the server if it is running.
+ */
+ public void stopServer() {
+ NetworkTablesJNI.stopServer(m_handle);
+ }
+
+ /**
+ * Starts a client. Use SetServer to set the server name and port.
+ */
+ public void startClient() {
+ NetworkTablesJNI.startClient(m_handle);
+ }
+
+ /**
+ * Starts a client using the specified server and the default port.
+ *
+ * @param serverName server name
+ */
+ public void startClient(String serverName) {
+ startClient(serverName, kDefaultPort);
+ }
+
+ /**
+ * Starts a client using the specified server and port.
+ *
+ * @param serverName server name
+ * @param port port to communicate over
+ */
+ public void startClient(String serverName, int port) {
+ NetworkTablesJNI.startClient(m_handle, serverName, port);
+ }
+
+ /**
+ * Starts a client using the specified servers and default port. The
+ * client will attempt to connect to each server in round robin fashion.
+ *
+ * @param serverNames array of server names
+ */
+ public void startClient(String[] serverNames) {
+ startClient(serverNames, kDefaultPort);
+ }
+
+ /**
+ * Starts a client using the specified servers and port number. The
+ * client will attempt to connect to each server in round robin fashion.
+ *
+ * @param serverNames array of server names
+ * @param port port to communicate over
+ */
+ public void startClient(String[] serverNames, int port) {
+ int[] ports = new int[serverNames.length];
+ for (int i = 0; i < serverNames.length; i++) {
+ ports[i] = port;
+ }
+ startClient(serverNames, ports);
+ }
+
+ /**
+ * Starts a client using the specified (server, port) combinations. The
+ * client will attempt to connect to each server in round robin fashion.
+ *
+ * @param serverNames array of server names
+ * @param ports array of port numbers
+ */
+ public void startClient(String[] serverNames, int[] ports) {
+ NetworkTablesJNI.startClient(m_handle, serverNames, ports);
+ }
+
+ /**
+ * Starts a client using commonly known robot addresses for the specified
+ * team using the default port number.
+ *
+ * @param team team number
+ */
+ public void startClientTeam(int team) {
+ startClientTeam(team, kDefaultPort);
+ }
+
+ /**
+ * Starts a client using commonly known robot addresses for the specified
+ * team.
+ *
+ * @param team team number
+ * @param port port to communicate over
+ */
+ public void startClientTeam(int team, int port) {
+ NetworkTablesJNI.startClientTeam(m_handle, team, port);
+ }
+
+ /**
+ * Stops the client if it is running.
+ */
+ public void stopClient() {
+ NetworkTablesJNI.stopClient(m_handle);
+ }
+
+ /**
+ * Sets server address and port for client (without restarting client).
+ * Changes the port to the default port.
+ *
+ * @param serverName server name
+ */
+ public void setServer(String serverName) {
+ setServer(serverName, kDefaultPort);
+ }
+
+ /**
+ * Sets server address and port for client (without restarting client).
+ *
+ * @param serverName server name
+ * @param port port to communicate over
+ */
+ public void setServer(String serverName, int port) {
+ NetworkTablesJNI.setServer(m_handle, serverName, port);
+ }
+
+ /**
+ * Sets server addresses and port for client (without restarting client).
+ * Changes the port to the default port. The client will attempt to connect
+ * to each server in round robin fashion.
+ *
+ * @param serverNames array of server names
+ */
+ public void setServer(String[] serverNames) {
+ setServer(serverNames, kDefaultPort);
+ }
+
+ /**
+ * Sets server addresses and port for client (without restarting client).
+ * The client will attempt to connect to each server in round robin fashion.
+ *
+ * @param serverNames array of server names
+ * @param port port to communicate over
+ */
+ public void setServer(String[] serverNames, int port) {
+ int[] ports = new int[serverNames.length];
+ for (int i = 0; i < serverNames.length; i++) {
+ ports[i] = port;
+ }
+ setServer(serverNames, ports);
+ }
+
+ /**
+ * Sets server addresses and ports for client (without restarting client).
+ * The client will attempt to connect to each server in round robin fashion.
+ *
+ * @param serverNames array of server names
+ * @param ports array of port numbers
+ */
+ public void setServer(String[] serverNames, int[] ports) {
+ NetworkTablesJNI.setServer(m_handle, serverNames, ports);
+ }
+
+ /**
+ * Sets server addresses and port for client (without restarting client).
+ * Changes the port to the default port. The client will attempt to connect
+ * to each server in round robin fashion.
+ *
+ * @param team team number
+ */
+ public void setServerTeam(int team) {
+ setServerTeam(team, kDefaultPort);
+ }
+
+ /**
+ * Sets server addresses and port for client (without restarting client).
+ * Connects using commonly known robot addresses for the specified team.
+ *
+ * @param team team number
+ * @param port port to communicate over
+ */
+ public void setServerTeam(int team, int port) {
+ NetworkTablesJNI.setServerTeam(m_handle, team, port);
+ }
+
+ /**
+ * Starts requesting server address from Driver Station.
+ * This connects to the Driver Station running on localhost to obtain the
+ * server IP address, and connects with the default port.
+ */
+ public void startDSClient() {
+ startDSClient(kDefaultPort);
+ }
+
+ /**
+ * Starts requesting server address from Driver Station.
+ * This connects to the Driver Station running on localhost to obtain the
+ * server IP address.
+ *
+ * @param port server port to use in combination with IP from DS
+ */
+ public void startDSClient(int port) {
+ NetworkTablesJNI.startDSClient(m_handle, port);
+ }
+
+ /**
+ * Stops requesting server address from Driver Station.
+ */
+ public void stopDSClient() {
+ NetworkTablesJNI.stopDSClient(m_handle);
+ }
+
+ /**
+ * Set the periodic update rate.
+ * Sets how frequently updates are sent to other nodes over the network.
+ *
+ * @param interval update interval in seconds (range 0.01 to 1.0)
+ */
+ public void setUpdateRate(double interval) {
+ NetworkTablesJNI.setUpdateRate(m_handle, interval);
+ }
+
+ /**
+ * Flushes all updated values immediately to the network.
+ * Note: This is rate-limited to protect the network from flooding.
+ * This is primarily useful for synchronizing network updates with
+ * user code.
+ */
+ public void flush() {
+ NetworkTablesJNI.flush(m_handle);
+ }
+
+ /**
+ * Gets information on the currently established network connections.
+ * If operating as a client, this will return either zero or one values.
+ *
+ * @return array of connection information
+ */
+ public ConnectionInfo[] getConnections() {
+ return NetworkTablesJNI.getConnections(m_handle);
+ }
+
+ /**
+ * Return whether or not the instance is connected to another node.
+ *
+ * @return True if connected.
+ */
+ public boolean isConnected() {
+ return NetworkTablesJNI.isConnected(m_handle);
+ }
+
+ /**
+ * Saves persistent keys to a file. The server does this automatically.
+ *
+ * @param filename file name
+ * @throws PersistentException if error saving file
+ */
+ public void savePersistent(String filename) throws PersistentException {
+ NetworkTablesJNI.savePersistent(m_handle, filename);
+ }
+
+ /**
+ * Loads persistent keys from a file. The server does this automatically.
+ *
+ * @param filename file name
+ * @return List of warnings (errors result in an exception instead)
+ * @throws PersistentException if error reading file
+ */
+ public String[] loadPersistent(String filename) throws PersistentException {
+ return NetworkTablesJNI.loadPersistent(m_handle, filename);
+ }
+
+ /**
+ * Save table values to a file. The file format used is identical to
+ * that used for SavePersistent.
+ *
+ * @param filename filename
+ * @param prefix save only keys starting with this prefix
+ * @throws PersistentException if error saving file
+ */
+ public void saveEntries(String filename, String prefix) throws PersistentException {
+ NetworkTablesJNI.saveEntries(m_handle, filename, prefix);
+ }
+
+ /**
+ * Load table values from a file. The file format used is identical to
+ * that used for SavePersistent / LoadPersistent.
+ *
+ * @param filename filename
+ * @param prefix load only keys starting with this prefix
+ * @return List of warnings (errors result in an exception instead)
+ * @throws PersistentException if error saving file
+ */
+ public String[] loadEntries(String filename, String prefix) throws PersistentException {
+ return NetworkTablesJNI.loadEntries(m_handle, filename, prefix);
+ }
+
+ private final ReentrantLock m_loggerLock = new ReentrantLock();
+ private final Map<Integer, Consumer<LogMessage>> m_loggers = new HashMap<>();
+ private Thread m_loggerThread;
+ private int m_loggerPoller;
+ private boolean m_loggerWaitQueue;
+ private final Condition m_loggerWaitQueueCond = m_loggerLock.newCondition();
+
+ private void startLogThread() {
+ m_loggerThread = new Thread(() -> {
+ boolean wasInterrupted = false;
+ while (!Thread.interrupted()) {
+ LogMessage[] events;
+ try {
+ events = NetworkTablesJNI.pollLogger(this, m_loggerPoller);
+ } catch (InterruptedException ex) {
+ Thread.currentThread().interrupt();
+ // don't try to destroy poller, as its handle is likely no longer valid
+ wasInterrupted = true;
+ break;
+ }
+ for (LogMessage event : events) {
+ Consumer<LogMessage> logger;
+ m_loggerLock.lock();
+ try {
+ logger = m_loggers.get(event.logger);
+ } finally {
+ m_loggerLock.unlock();
+ }
+ if (logger != null) {
+ try {
+ logger.accept(event);
+ } catch (Throwable throwable) {
+ System.err.println("Unhandled exception during logger callback: "
+ + throwable.toString());
+ throwable.printStackTrace();
+ }
+ }
+ }
+ }
+ m_loggerLock.lock();
+ try {
+ if (!wasInterrupted) {
+ NetworkTablesJNI.destroyLoggerPoller(m_loggerPoller);
+ }
+ m_rpcCallPoller = 0;
+ } finally {
+ m_loggerLock.unlock();
+ }
+ }, "NTLogger");
+ m_loggerThread.setDaemon(true);
+ m_loggerThread.start();
+ }
+
+ /**
+ * Add logger callback function. By default, log messages are sent to stderr;
+ * this function sends log messages with the specified levels to the provided
+ * callback function instead. The callback function will only be called for
+ * log messages with level greater than or equal to minLevel and less than or
+ * equal to maxLevel; messages outside this range will be silently ignored.
+ *
+ * @param func log callback function
+ * @param minLevel minimum log level
+ * @param maxLevel maximum log level
+ * @return Logger handle
+ */
+ public int addLogger(Consumer<LogMessage> func, int minLevel, int maxLevel) {
+ m_loggerLock.lock();
+ try {
+ if (m_loggerPoller == 0) {
+ m_loggerPoller = NetworkTablesJNI.createLoggerPoller(m_handle);
+ startLogThread();
+ }
+ int handle = NetworkTablesJNI.addPolledLogger(m_loggerPoller, minLevel, maxLevel);
+ m_loggers.put(handle, func);
+ return handle;
+ } finally {
+ m_loggerLock.unlock();
+ }
+ }
+
+ /**
+ * Remove a logger.
+ *
+ * @param logger Logger handle to remove
+ */
+ public void removeLogger(int logger) {
+ m_loggerLock.lock();
+ try {
+ m_loggers.remove(logger);
+ } finally {
+ m_loggerLock.unlock();
+ }
+ NetworkTablesJNI.removeLogger(logger);
+ }
+
+ /**
+ * Wait for the incoming log event queue to be empty. This is primarily useful
+ * for deterministic testing. This blocks until either the log event
+ * queue is empty (e.g. there are no more events that need to be passed along
+ * to callbacks or poll queues) or the timeout expires.
+ *
+ * @param timeout timeout, in seconds. Set to 0 for non-blocking behavior,
+ * or a negative value to block indefinitely
+ * @return False if timed out, otherwise true.
+ */
+ public boolean waitForLoggerQueue(double timeout) {
+ if (!NetworkTablesJNI.waitForLoggerQueue(m_handle, timeout)) {
+ return false;
+ }
+ m_loggerLock.lock();
+ try {
+ if (m_loggerPoller != 0) {
+ m_loggerWaitQueue = true;
+ NetworkTablesJNI.cancelPollLogger(m_loggerPoller);
+ while (m_loggerWaitQueue) {
+ try {
+ if (timeout < 0) {
+ m_loggerWaitQueueCond.await();
+ } else {
+ return m_loggerWaitQueueCond.await((long) (timeout * 1e9), TimeUnit.NANOSECONDS);
+ }
+ } catch (InterruptedException ex) {
+ Thread.currentThread().interrupt();
+ return true;
+ }
+ }
+ }
+ } finally {
+ m_loggerLock.unlock();
+ }
+ return true;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+ if (!(other instanceof NetworkTableInstance)) {
+ return false;
+ }
+
+ return m_handle == ((NetworkTableInstance) other).m_handle;
+ }
+
+ @Override
+ public int hashCode() {
+ return m_handle;
+ }
+
+ private boolean m_owned;
+ private int m_handle;
+}
diff --git a/ntcore/src/main/java/edu/wpi/first/networktables/NetworkTableType.java b/ntcore/src/main/java/edu/wpi/first/networktables/NetworkTableType.java
new file mode 100644
index 0000000..54a9f55
--- /dev/null
+++ b/ntcore/src/main/java/edu/wpi/first/networktables/NetworkTableType.java
@@ -0,0 +1,54 @@
+/*----------------------------------------------------------------------------*/
+/* Copyright (c) 2017-2018 FIRST. All Rights Reserved. */
+/* Open Source Software - may be modified and shared by FRC teams. The code */
+/* must be accompanied by the FIRST BSD license file in the root directory of */
+/* the project. */
+/*----------------------------------------------------------------------------*/
+
+package edu.wpi.first.networktables;
+
+/**
+ * Network table data types.
+ */
+public enum NetworkTableType {
+ kUnassigned(0),
+ kBoolean(0x01),
+ kDouble(0x02),
+ kString(0x04),
+ kRaw(0x08),
+ kBooleanArray(0x10),
+ kDoubleArray(0x20),
+ kStringArray(0x40),
+ kRpc(0x80);
+
+ @SuppressWarnings("MemberName")
+ private final int value;
+
+ NetworkTableType(int value) {
+ this.value = value;
+ }
+
+ public int getValue() {
+ return value;
+ }
+
+ /**
+ * Convert from the numerical representation of type to an enum type.
+ *
+ * @param value The numerical representation of kind
+ * @return The kind
+ */
+ public static NetworkTableType getFromInt(int value) {
+ switch (value) {
+ case 0x01: return kBoolean;
+ case 0x02: return kDouble;
+ case 0x04: return kString;
+ case 0x08: return kRaw;
+ case 0x10: return kBooleanArray;
+ case 0x20: return kDoubleArray;
+ case 0x40: return kStringArray;
+ case 0x80: return kRpc;
+ default: return kUnassigned;
+ }
+ }
+}
diff --git a/ntcore/src/main/java/edu/wpi/first/networktables/NetworkTableValue.java b/ntcore/src/main/java/edu/wpi/first/networktables/NetworkTableValue.java
new file mode 100644
index 0000000..e1179ea
--- /dev/null
+++ b/ntcore/src/main/java/edu/wpi/first/networktables/NetworkTableValue.java
@@ -0,0 +1,516 @@
+/*----------------------------------------------------------------------------*/
+/* Copyright (c) 2017-2018 FIRST. All Rights Reserved. */
+/* Open Source Software - may be modified and shared by FRC teams. The code */
+/* must be accompanied by the FIRST BSD license file in the root directory of */
+/* the project. */
+/*----------------------------------------------------------------------------*/
+
+package edu.wpi.first.networktables;
+
+import java.util.Objects;
+
+/**
+ * A network table entry value.
+ */
+public final class NetworkTableValue {
+ NetworkTableValue(NetworkTableType type, Object value, long time) {
+ m_type = type;
+ m_value = value;
+ m_time = time;
+ }
+
+ NetworkTableValue(NetworkTableType type, Object value) {
+ this(type, value, NetworkTablesJNI.now());
+ }
+
+ NetworkTableValue(int type, Object value, long time) {
+ this(NetworkTableType.getFromInt(type), value, time);
+ }
+
+ /**
+ * Get the data type.
+ *
+ * @return The type.
+ */
+ public NetworkTableType getType() {
+ return m_type;
+ }
+
+ /**
+ * Get the data value stored.
+ *
+ * @return The type.
+ */
+ public Object getValue() {
+ return m_value;
+ }
+
+ /**
+ * Get the creation time of the value.
+ *
+ * @return The time, in the units returned by NetworkTablesJNI.now().
+ */
+ public long getTime() {
+ return m_time;
+ }
+
+ /*
+ * Type Checkers
+ */
+
+ /**
+ * Determine if entry value contains a value or is unassigned.
+ *
+ * @return True if the entry value contains a value.
+ */
+ public boolean isValid() {
+ return m_type != NetworkTableType.kUnassigned;
+ }
+
+ /**
+ * Determine if entry value contains a boolean.
+ *
+ * @return True if the entry value is of boolean type.
+ */
+ public boolean isBoolean() {
+ return m_type == NetworkTableType.kBoolean;
+ }
+
+ /**
+ * Determine if entry value contains a double.
+ *
+ * @return True if the entry value is of double type.
+ */
+ public boolean isDouble() {
+ return m_type == NetworkTableType.kDouble;
+ }
+
+ /**
+ * Determine if entry value contains a string.
+ *
+ * @return True if the entry value is of string type.
+ */
+ public boolean isString() {
+ return m_type == NetworkTableType.kString;
+ }
+
+ /**
+ * Determine if entry value contains a raw.
+ *
+ * @return True if the entry value is of raw type.
+ */
+ public boolean isRaw() {
+ return m_type == NetworkTableType.kRaw;
+ }
+
+ /**
+ * Determine if entry value contains a rpc definition.
+ *
+ * @return True if the entry value is of rpc definition type.
+ */
+ public boolean isRpc() {
+ return m_type == NetworkTableType.kRpc;
+ }
+
+ /**
+ * Determine if entry value contains a boolean array.
+ *
+ * @return True if the entry value is of boolean array type.
+ */
+ public boolean isBooleanArray() {
+ return m_type == NetworkTableType.kBooleanArray;
+ }
+
+ /**
+ * Determine if entry value contains a double array.
+ *
+ * @return True if the entry value is of double array type.
+ */
+ public boolean isDoubleArray() {
+ return m_type == NetworkTableType.kDoubleArray;
+ }
+
+ /**
+ * Determine if entry value contains a string array.
+ *
+ * @return True if the entry value is of string array type.
+ */
+ public boolean isStringArray() {
+ return m_type == NetworkTableType.kStringArray;
+ }
+
+ /*
+ * Type-Safe Getters
+ */
+
+ /**
+ * Get the entry's boolean value.
+ *
+ * @return The boolean value.
+ * @throws ClassCastException if the entry value is not of boolean type.
+ */
+ public boolean getBoolean() {
+ if (m_type != NetworkTableType.kBoolean) {
+ throw new ClassCastException("cannot convert " + m_type + " to boolean");
+ }
+ return ((Boolean) m_value).booleanValue();
+ }
+
+ /**
+ * Get the entry's double value.
+ *
+ * @return The double value.
+ * @throws ClassCastException if the entry value is not of double type.
+ */
+ public double getDouble() {
+ if (m_type != NetworkTableType.kDouble) {
+ throw new ClassCastException("cannot convert " + m_type + " to double");
+ }
+ return ((Number) m_value).doubleValue();
+ }
+
+ /**
+ * Get the entry's string value.
+ *
+ * @return The string value.
+ * @throws ClassCastException if the entry value is not of string type.
+ */
+ public String getString() {
+ if (m_type != NetworkTableType.kString) {
+ throw new ClassCastException("cannot convert " + m_type + " to string");
+ }
+ return (String) m_value;
+ }
+
+ /**
+ * Get the entry's raw value.
+ *
+ * @return The raw value.
+ * @throws ClassCastException if the entry value is not of raw type.
+ */
+ public byte[] getRaw() {
+ if (m_type != NetworkTableType.kRaw) {
+ throw new ClassCastException("cannot convert " + m_type + " to raw");
+ }
+ return (byte[]) m_value;
+ }
+
+ /**
+ * Get the entry's rpc definition value.
+ *
+ * @return The rpc definition value.
+ * @throws ClassCastException if the entry value is not of rpc definition type.
+ */
+ public byte[] getRpc() {
+ if (m_type != NetworkTableType.kRpc) {
+ throw new ClassCastException("cannot convert " + m_type + " to rpc");
+ }
+ return (byte[]) m_value;
+ }
+
+ /**
+ * Get the entry's boolean array value.
+ *
+ * @return The boolean array value.
+ * @throws ClassCastException if the entry value is not of boolean array type.
+ */
+ public boolean[] getBooleanArray() {
+ if (m_type != NetworkTableType.kBooleanArray) {
+ throw new ClassCastException("cannot convert " + m_type + " to boolean array");
+ }
+ return (boolean[]) m_value;
+ }
+
+ /**
+ * Get the entry's double array value.
+ *
+ * @return The double array value.
+ * @throws ClassCastException if the entry value is not of double array type.
+ */
+ public double[] getDoubleArray() {
+ if (m_type != NetworkTableType.kDoubleArray) {
+ throw new ClassCastException("cannot convert " + m_type + " to double array");
+ }
+ return (double[]) m_value;
+ }
+
+ /**
+ * Get the entry's string array value.
+ *
+ * @return The string array value.
+ * @throws ClassCastException if the entry value is not of string array type.
+ */
+ public String[] getStringArray() {
+ if (m_type != NetworkTableType.kStringArray) {
+ throw new ClassCastException("cannot convert " + m_type + " to string array");
+ }
+ return (String[]) m_value;
+ }
+
+ /*
+ * Factory functions.
+ */
+
+ /**
+ * Creates a boolean entry value.
+ *
+ * @param value the value
+ * @return The entry value
+ */
+ public static NetworkTableValue makeBoolean(boolean value) {
+ return new NetworkTableValue(NetworkTableType.kBoolean, Boolean.valueOf(value));
+ }
+
+ /**
+ * Creates a boolean entry value.
+ *
+ * @param value the value
+ * @param time the creation time to use (instead of the current time)
+ * @return The entry value
+ */
+ public static NetworkTableValue makeBoolean(boolean value, long time) {
+ return new NetworkTableValue(NetworkTableType.kBoolean, Boolean.valueOf(value), time);
+ }
+
+ /**
+ * Creates a double entry value.
+ *
+ * @param value the value
+ * @return The entry value
+ */
+ public static NetworkTableValue makeDouble(double value) {
+ return new NetworkTableValue(NetworkTableType.kDouble, Double.valueOf(value));
+ }
+
+ /**
+ * Creates a double entry value.
+ *
+ * @param value the value
+ * @param time the creation time to use (instead of the current time)
+ * @return The entry value
+ */
+ public static NetworkTableValue makeDouble(double value, long time) {
+ return new NetworkTableValue(NetworkTableType.kDouble, Double.valueOf(value), time);
+ }
+
+ /**
+ * Creates a string entry value.
+ *
+ * @param value the value
+ * @return The entry value
+ */
+ public static NetworkTableValue makeString(String value) {
+ return new NetworkTableValue(NetworkTableType.kString, value);
+ }
+
+ /**
+ * Creates a string entry value.
+ *
+ * @param value the value
+ * @param time the creation time to use (instead of the current time)
+ * @return The entry value
+ */
+ public static NetworkTableValue makeString(String value, long time) {
+ return new NetworkTableValue(NetworkTableType.kString, value, time);
+ }
+
+ /**
+ * Creates a raw entry value.
+ *
+ * @param value the value
+ * @return The entry value
+ */
+ public static NetworkTableValue makeRaw(byte[] value) {
+ return new NetworkTableValue(NetworkTableType.kRaw, value);
+ }
+
+ /**
+ * Creates a raw entry value.
+ *
+ * @param value the value
+ * @param time the creation time to use (instead of the current time)
+ * @return The entry value
+ */
+ public static NetworkTableValue makeRaw(byte[] value, long time) {
+ return new NetworkTableValue(NetworkTableType.kRaw, value, time);
+ }
+
+ /**
+ * Creates a rpc entry value.
+ *
+ * @param value the value
+ * @return The entry value
+ */
+ public static NetworkTableValue makeRpc(byte[] value) {
+ return new NetworkTableValue(NetworkTableType.kRpc, value);
+ }
+
+ /**
+ * Creates a rpc entry value.
+ *
+ * @param value the value
+ * @param time the creation time to use (instead of the current time)
+ * @return The entry value
+ */
+ public static NetworkTableValue makeRpc(byte[] value, long time) {
+ return new NetworkTableValue(NetworkTableType.kRpc, value, time);
+ }
+
+ /**
+ * Creates a boolean array entry value.
+ *
+ * @param value the value
+ * @return The entry value
+ */
+ public static NetworkTableValue makeBooleanArray(boolean[] value) {
+ return new NetworkTableValue(NetworkTableType.kBooleanArray, value);
+ }
+
+ /**
+ * Creates a boolean array entry value.
+ *
+ * @param value the value
+ * @param time the creation time to use (instead of the current time)
+ * @return The entry value
+ */
+ public static NetworkTableValue makeBooleanArray(boolean[] value, long time) {
+ return new NetworkTableValue(NetworkTableType.kBooleanArray, value, time);
+ }
+
+ /**
+ * Creates a boolean array entry value.
+ *
+ * @param value the value
+ * @return The entry value
+ */
+ public static NetworkTableValue makeBooleanArray(Boolean[] value) {
+ return new NetworkTableValue(NetworkTableType.kBooleanArray, toNative(value));
+ }
+
+ /**
+ * Creates a boolean array entry value.
+ *
+ * @param value the value
+ * @param time the creation time to use (instead of the current time)
+ * @return The entry value
+ */
+ public static NetworkTableValue makeBooleanArray(Boolean[] value, long time) {
+ return new NetworkTableValue(NetworkTableType.kBooleanArray, toNative(value), time);
+ }
+
+ /**
+ * Creates a double array entry value.
+ *
+ * @param value the value
+ * @return The entry value
+ */
+ public static NetworkTableValue makeDoubleArray(double[] value) {
+ return new NetworkTableValue(NetworkTableType.kDoubleArray, value);
+ }
+
+ /**
+ * Creates a double array entry value.
+ *
+ * @param value the value
+ * @param time the creation time to use (instead of the current time)
+ * @return The entry value
+ */
+ public static NetworkTableValue makeDoubleArray(double[] value, long time) {
+ return new NetworkTableValue(NetworkTableType.kDoubleArray, value, time);
+ }
+
+ /**
+ * Creates a double array entry value.
+ *
+ * @param value the value
+ * @return The entry value
+ */
+ public static NetworkTableValue makeDoubleArray(Number[] value) {
+ return new NetworkTableValue(NetworkTableType.kDoubleArray, toNative(value));
+ }
+
+ /**
+ * Creates a double array entry value.
+ *
+ * @param value the value
+ * @param time the creation time to use (instead of the current time)
+ * @return The entry value
+ */
+ public static NetworkTableValue makeDoubleArray(Number[] value, long time) {
+ return new NetworkTableValue(NetworkTableType.kDoubleArray, toNative(value), time);
+ }
+
+ /**
+ * Creates a string array entry value.
+ *
+ * @param value the value
+ * @return The entry value
+ */
+ public static NetworkTableValue makeStringArray(String[] value) {
+ return new NetworkTableValue(NetworkTableType.kStringArray, value);
+ }
+
+ /**
+ * Creates a string array entry value.
+ *
+ * @param value the value
+ * @param time the creation time to use (instead of the current time)
+ * @return The entry value
+ */
+ public static NetworkTableValue makeStringArray(String[] value, long time) {
+ return new NetworkTableValue(NetworkTableType.kStringArray, value, time);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+ if (!(other instanceof NetworkTableValue)) {
+ return false;
+ }
+ NetworkTableValue ntOther = (NetworkTableValue) other;
+ return m_type == ntOther.m_type && m_value.equals(ntOther.m_value);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(m_type, m_value);
+ }
+
+ static boolean[] toNative(Boolean[] arr) {
+ boolean[] out = new boolean[arr.length];
+ for (int i = 0; i < arr.length; i++) {
+ out[i] = arr[i];
+ }
+ return out;
+ }
+
+ static double[] toNative(Number[] arr) {
+ double[] out = new double[arr.length];
+ for (int i = 0; i < arr.length; i++) {
+ out[i] = arr[i].doubleValue();
+ }
+ return out;
+ }
+
+ static Boolean[] fromNative(boolean[] arr) {
+ Boolean[] out = new Boolean[arr.length];
+ for (int i = 0; i < arr.length; i++) {
+ out[i] = arr[i];
+ }
+ return out;
+ }
+
+ static Double[] fromNative(double[] arr) {
+ Double[] out = new Double[arr.length];
+ for (int i = 0; i < arr.length; i++) {
+ out[i] = arr[i];
+ }
+ return out;
+ }
+
+ private NetworkTableType m_type;
+ private Object m_value;
+ private long m_time;
+}
diff --git a/ntcore/src/main/java/edu/wpi/first/networktables/NetworkTablesJNI.java b/ntcore/src/main/java/edu/wpi/first/networktables/NetworkTablesJNI.java
new file mode 100644
index 0000000..1d736fc
--- /dev/null
+++ b/ntcore/src/main/java/edu/wpi/first/networktables/NetworkTablesJNI.java
@@ -0,0 +1,153 @@
+/*----------------------------------------------------------------------------*/
+/* Copyright (c) 2017-2018 FIRST. All Rights Reserved. */
+/* Open Source Software - may be modified and shared by FRC teams. The code */
+/* must be accompanied by the FIRST BSD license file in the root directory of */
+/* the project. */
+/*----------------------------------------------------------------------------*/
+
+package edu.wpi.first.networktables;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+
+import edu.wpi.first.wpiutil.RuntimeLoader;
+
+public final class NetworkTablesJNI {
+ static boolean libraryLoaded = false;
+ static RuntimeLoader<NetworkTablesJNI> loader = null;
+
+ static {
+ if (!libraryLoaded) {
+ try {
+ loader = new RuntimeLoader<>("ntcorejni", RuntimeLoader.getDefaultExtractionRoot(), NetworkTablesJNI.class);
+ loader.loadLibrary();
+ } catch (IOException ex) {
+ ex.printStackTrace();
+ System.exit(1);
+ }
+ libraryLoaded = true;
+ }
+ }
+
+ public static native int getDefaultInstance();
+ public static native int createInstance();
+ public static native void destroyInstance(int inst);
+ public static native int getInstanceFromHandle(int handle);
+
+ public static native int getEntry(int inst, String key);
+ public static native int[] getEntries(int inst, String prefix, int types);
+ public static native String getEntryName(int entry);
+ public static native long getEntryLastChange(int entry);
+
+ public static native int getType(int entry);
+
+ public static native boolean setBoolean(int entry, long time, boolean value, boolean force);
+ public static native boolean setDouble(int entry, long time, double value, boolean force);
+ public static native boolean setString(int entry, long time, String value, boolean force);
+ public static native boolean setRaw(int entry, long time, byte[] value, boolean force);
+ public static native boolean setRaw(int entry, long time, ByteBuffer value, int len, boolean force);
+ public static native boolean setBooleanArray(int entry, long time, boolean[] value, boolean force);
+ public static native boolean setDoubleArray(int entry, long time, double[] value, boolean force);
+ public static native boolean setStringArray(int entry, long time, String[] value, boolean force);
+
+ public static native NetworkTableValue getValue(int entry);
+
+ public static native boolean getBoolean(int entry, boolean defaultValue);
+ public static native double getDouble(int entry, double defaultValue);
+ public static native String getString(int entry, String defaultValue);
+ public static native byte[] getRaw(int entry, byte[] defaultValue);
+ public static native boolean[] getBooleanArray(int entry, boolean[] defaultValue);
+ public static native double[] getDoubleArray(int entry, double[] defaultValue);
+ public static native String[] getStringArray(int entry, String[] defaultValue);
+ public static native boolean setDefaultBoolean(int entry, long time, boolean defaultValue);
+
+ public static native boolean setDefaultDouble(int entry, long time, double defaultValue);
+ public static native boolean setDefaultString(int entry, long time, String defaultValue);
+ public static native boolean setDefaultRaw(int entry, long time, byte[] defaultValue);
+ public static native boolean setDefaultBooleanArray(int entry, long time, boolean[] defaultValue);
+ public static native boolean setDefaultDoubleArray(int entry, long time, double[] defaultValue);
+ public static native boolean setDefaultStringArray(int entry, long time, String[] defaultValue);
+
+ public static native void setEntryFlags(int entry, int flags);
+ public static native int getEntryFlags(int entry);
+
+ public static native void deleteEntry(int entry);
+
+ public static native void deleteAllEntries(int inst);
+
+ public static native EntryInfo getEntryInfoHandle(NetworkTableInstance inst, int entry);
+ public static native EntryInfo[] getEntryInfo(NetworkTableInstance instObject, int inst, String prefix, int types);
+
+ public static native int createEntryListenerPoller(int inst);
+ public static native void destroyEntryListenerPoller(int poller);
+ public static native int addPolledEntryListener(int poller, String prefix, int flags);
+ public static native int addPolledEntryListener(int poller, int entry, int flags);
+ public static native EntryNotification[] pollEntryListener(NetworkTableInstance inst, int poller) throws InterruptedException;
+ public static native EntryNotification[] pollEntryListenerTimeout(NetworkTableInstance inst, int poller, double timeout) throws InterruptedException;
+ public static native void cancelPollEntryListener(int poller);
+ public static native void removeEntryListener(int entryListener);
+ public static native boolean waitForEntryListenerQueue(int inst, double timeout);
+
+ public static native int createConnectionListenerPoller(int inst);
+ public static native void destroyConnectionListenerPoller(int poller);
+ public static native int addPolledConnectionListener(int poller, boolean immediateNotify);
+ public static native ConnectionNotification[] pollConnectionListener(NetworkTableInstance inst, int poller) throws InterruptedException;
+ public static native ConnectionNotification[] pollConnectionListenerTimeout(NetworkTableInstance inst, int poller, double timeout) throws InterruptedException;
+ public static native void cancelPollConnectionListener(int poller);
+ public static native void removeConnectionListener(int connListener);
+ public static native boolean waitForConnectionListenerQueue(int inst, double timeout);
+
+ public static native int createRpcCallPoller(int inst);
+ public static native void destroyRpcCallPoller(int poller);
+ public static native void createPolledRpc(int entry, byte[] def, int poller);
+ public static native RpcAnswer[] pollRpc(NetworkTableInstance inst, int poller) throws InterruptedException;
+ public static native RpcAnswer[] pollRpcTimeout(NetworkTableInstance inst, int poller, double timeout) throws InterruptedException;
+ public static native void cancelPollRpc(int poller);
+ public static native boolean waitForRpcCallQueue(int inst, double timeout);
+ public static native boolean postRpcResponse(int entry, int call, byte[] result);
+ public static native int callRpc(int entry, byte[] params);
+ public static native byte[] getRpcResult(int entry, int call);
+ public static native byte[] getRpcResult(int entry, int call, double timeout);
+ public static native void cancelRpcResult(int entry, int call);
+
+ public static native byte[] getRpc(int entry, byte[] defaultValue);
+
+ public static native void setNetworkIdentity(int inst, String name);
+ public static native int getNetworkMode(int inst);
+ public static native void startServer(int inst, String persistFilename, String listenAddress, int port);
+ public static native void stopServer(int inst);
+ public static native void startClient(int inst);
+ public static native void startClient(int inst, String serverName, int port);
+ public static native void startClient(int inst, String[] serverNames, int[] ports);
+ public static native void startClientTeam(int inst, int team, int port);
+ public static native void stopClient(int inst);
+ public static native void setServer(int inst, String serverName, int port);
+ public static native void setServer(int inst, String[] serverNames, int[] ports);
+ public static native void setServerTeam(int inst, int team, int port);
+ public static native void startDSClient(int inst, int port);
+ public static native void stopDSClient(int inst);
+ public static native void setUpdateRate(int inst, double interval);
+
+ public static native void flush(int inst);
+
+ public static native ConnectionInfo[] getConnections(int inst);
+
+ public static native boolean isConnected(int inst);
+
+ public static native void savePersistent(int inst, String filename) throws PersistentException;
+ public static native String[] loadPersistent(int inst, String filename) throws PersistentException; // returns warnings
+
+ public static native void saveEntries(int inst, String filename, String prefix) throws PersistentException;
+ public static native String[] loadEntries(int inst, String filename, String prefix) throws PersistentException; // returns warnings
+
+ public static native long now();
+
+ public static native int createLoggerPoller(int inst);
+ public static native void destroyLoggerPoller(int poller);
+ public static native int addPolledLogger(int poller, int minLevel, int maxLevel);
+ public static native LogMessage[] pollLogger(NetworkTableInstance inst, int poller) throws InterruptedException;
+ public static native LogMessage[] pollLoggerTimeout(NetworkTableInstance inst, int poller, double timeout) throws InterruptedException;
+ public static native void cancelPollLogger(int poller);
+ public static native void removeLogger(int logger);
+ public static native boolean waitForLoggerQueue(int inst, double timeout);
+}
diff --git a/ntcore/src/main/java/edu/wpi/first/networktables/PersistentException.java b/ntcore/src/main/java/edu/wpi/first/networktables/PersistentException.java
new file mode 100644
index 0000000..205b015
--- /dev/null
+++ b/ntcore/src/main/java/edu/wpi/first/networktables/PersistentException.java
@@ -0,0 +1,21 @@
+/*----------------------------------------------------------------------------*/
+/* Copyright (c) 2017-2018 FIRST. All Rights Reserved. */
+/* Open Source Software - may be modified and shared by FRC teams. The code */
+/* must be accompanied by the FIRST BSD license file in the root directory of */
+/* the project. */
+/*----------------------------------------------------------------------------*/
+
+package edu.wpi.first.networktables;
+
+import java.io.IOException;
+
+/**
+ * An exception thrown when persistent load/save fails in a {@link NetworkTable}.
+ */
+public final class PersistentException extends IOException {
+ public static final long serialVersionUID = 0;
+
+ public PersistentException(String message) {
+ super(message);
+ }
+}
diff --git a/ntcore/src/main/java/edu/wpi/first/networktables/RpcAnswer.java b/ntcore/src/main/java/edu/wpi/first/networktables/RpcAnswer.java
new file mode 100644
index 0000000..91e1aa4
--- /dev/null
+++ b/ntcore/src/main/java/edu/wpi/first/networktables/RpcAnswer.java
@@ -0,0 +1,105 @@
+/*----------------------------------------------------------------------------*/
+/* Copyright (c) 2017-2018 FIRST. All Rights Reserved. */
+/* Open Source Software - may be modified and shared by FRC teams. The code */
+/* must be accompanied by the FIRST BSD license file in the root directory of */
+/* the project. */
+/*----------------------------------------------------------------------------*/
+
+package edu.wpi.first.networktables;
+
+/**
+ * NetworkTables Remote Procedure Call (Server Side).
+ */
+public final class RpcAnswer {
+ /** Entry handle. */
+ @SuppressWarnings("MemberName")
+ public final int entry;
+
+ /** Call handle. */
+ @SuppressWarnings("MemberName")
+ public int call;
+
+ /** Entry name. */
+ @SuppressWarnings("MemberName")
+ public final String name;
+
+ /** Call raw parameters. */
+ @SuppressWarnings("MemberName")
+ public final byte[] params;
+
+ /** Connection that called the RPC. */
+ @SuppressWarnings("MemberName")
+ public final ConnectionInfo conn;
+
+ /** Constructor.
+ * This should generally only be used internally to NetworkTables.
+ *
+ * @param inst Instance
+ * @param entry Entry handle
+ * @param call Call handle
+ * @param name Entry name
+ * @param params Call raw parameters
+ * @param conn Connection info
+ */
+ public RpcAnswer(NetworkTableInstance inst, int entry, int call, String name, byte[] params,
+ ConnectionInfo conn) {
+ this.m_inst = inst;
+ this.entry = entry;
+ this.call = call;
+ this.name = name;
+ this.params = params;
+ this.conn = conn;
+ }
+
+ static final byte[] emptyResponse = new byte[] {};
+
+ /*
+ * Finishes an RPC answer by replying empty if the user did not respond.
+ * Called internally by the callback thread.
+ */
+ void finish() {
+ if (call != 0) {
+ NetworkTablesJNI.postRpcResponse(entry, call, emptyResponse);
+ call = 0;
+ }
+ }
+
+ /**
+ * Determines if the native handle is valid.
+ *
+ * @return True if the native handle is valid, false otherwise.
+ */
+ public boolean isValid() {
+ return call != 0;
+ }
+
+ /**
+ * Post RPC response (return value) for a polled RPC.
+ *
+ * @param result result raw data that will be provided to remote caller
+ * @return true if the response was posted, otherwise false
+ */
+ public boolean postResponse(byte[] result) {
+ boolean ret = NetworkTablesJNI.postRpcResponse(entry, call, result);
+ call = 0;
+ return ret;
+ }
+
+ /* Network table instance. */
+ private final NetworkTableInstance m_inst;
+
+ /* Cached entry object. */
+ NetworkTableEntry m_entryObject;
+
+ /**
+ * Get the entry as an object.
+ *
+ * @return NetworkTableEntry for the RPC.
+ */
+ NetworkTableEntry getEntry() {
+ if (m_entryObject == null) {
+ m_entryObject = new NetworkTableEntry(m_inst, entry);
+ }
+ return m_entryObject;
+ }
+}
diff --git a/ntcore/src/main/java/edu/wpi/first/networktables/RpcCall.java b/ntcore/src/main/java/edu/wpi/first/networktables/RpcCall.java
new file mode 100644
index 0000000..1b377f6
--- /dev/null
+++ b/ntcore/src/main/java/edu/wpi/first/networktables/RpcCall.java
@@ -0,0 +1,105 @@
+/*----------------------------------------------------------------------------*/
+/* Copyright (c) 2017-2018 FIRST. All Rights Reserved. */
+/* Open Source Software - may be modified and shared by FRC teams. The code */
+/* must be accompanied by the FIRST BSD license file in the root directory of */
+/* the project. */
+/*----------------------------------------------------------------------------*/
+
+package edu.wpi.first.networktables;
+
+/**
+ * NetworkTables Remote Procedure Call.
+ */
+public final class RpcCall implements AutoCloseable {
+ /** Constructor.
+ * This should generally only be used internally to NetworkTables.
+ *
+ * @param entry Entry
+ * @param call Call handle
+ */
+ public RpcCall(NetworkTableEntry entry, int call) {
+ m_entry = entry;
+ m_call = call;
+ }
+
+ @Deprecated
+ public void free() {
+ close();
+ }
+
+ /**
+ * Cancels the result if no other action taken.
+ */
+ @Override
+ public synchronized void close() {
+ if (m_call != 0) {
+ cancelResult();
+ }
+ }
+
+ /**
+ * Determines if the native handle is valid.
+ *
+ * @return True if the native handle is valid, false otherwise.
+ */
+ public boolean isValid() {
+ return m_call != 0;
+ }
+
+ /**
+ * Get the RPC entry.
+ *
+ * @return NetworkTableEntry for the RPC.
+ */
+ public NetworkTableEntry getEntry() {
+ return m_entry;
+ }
+
+ /**
+ * Get the call native handle.
+ *
+ * @return Native handle.
+ */
+ public int getCall() {
+ return m_call;
+ }
+
+ /**
+ * Get the result (return value). This function blocks until
+ * the result is received.
+ *
+ * @return Received result (output)
+ */
+ public byte[] getResult() {
+ byte[] result = NetworkTablesJNI.getRpcResult(m_entry.getHandle(), m_call);
+ if (result.length != 0) {
+ m_call = 0;
+ }
+ return result;
+ }
+
+ /**
+ * Get the result (return value). This function blocks until
+ * the result is received or it times out.
+ *
+ * @param timeout timeout, in seconds
+ * @return Received result (output)
+ */
+ public byte[] getResult(double timeout) {
+ byte[] result = NetworkTablesJNI.getRpcResult(m_entry.getHandle(), m_call, timeout);
+ if (result.length != 0) {
+ m_call = 0;
+ }
+ return result;
+ }
+
+ /**
+ * Ignore the result. This function is non-blocking.
+ */
+ public void cancelResult() {
+ NetworkTablesJNI.cancelRpcResult(m_entry.getHandle(), m_call);
+ }
+
+ private final NetworkTableEntry m_entry;
+ private int m_call;
+}
diff --git a/ntcore/src/main/java/edu/wpi/first/networktables/TableEntryListener.java b/ntcore/src/main/java/edu/wpi/first/networktables/TableEntryListener.java
new file mode 100644
index 0000000..676e57e
--- /dev/null
+++ b/ntcore/src/main/java/edu/wpi/first/networktables/TableEntryListener.java
@@ -0,0 +1,27 @@
+/*----------------------------------------------------------------------------*/
+/* Copyright (c) 2017-2018 FIRST. All Rights Reserved. */
+/* Open Source Software - may be modified and shared by FRC teams. The code */
+/* must be accompanied by the FIRST BSD license file in the root directory of */
+/* the project. */
+/*----------------------------------------------------------------------------*/
+
+package edu.wpi.first.networktables;
+
+/**
+ * A listener that listens to changes in values in a {@link NetworkTable}.
+ */
+@FunctionalInterface
+public interface TableEntryListener extends EntryListenerFlags {
+ /**
+ * Called when a key-value pair is changed in a {@link NetworkTable}.
+ *
+ * @param table the table the key-value pair exists in
+ * @param key the key associated with the value that changed
+ * @param entry the entry associated with the value that changed
+ * @param value the new value
+ * @param flags update flags; for example, EntryListenerFlags.kNew if the key
+ * did not previously exist in the table
+ */
+ void valueChanged(NetworkTable table, String key, NetworkTableEntry entry,
+ NetworkTableValue value, int flags);
+}
diff --git a/ntcore/src/main/java/edu/wpi/first/networktables/TableListener.java b/ntcore/src/main/java/edu/wpi/first/networktables/TableListener.java
new file mode 100644
index 0000000..3c686e5
--- /dev/null
+++ b/ntcore/src/main/java/edu/wpi/first/networktables/TableListener.java
@@ -0,0 +1,23 @@
+/*----------------------------------------------------------------------------*/
+/* Copyright (c) 2017-2018 FIRST. All Rights Reserved. */
+/* Open Source Software - may be modified and shared by FRC teams. The code */
+/* must be accompanied by the FIRST BSD license file in the root directory of */
+/* the project. */
+/*----------------------------------------------------------------------------*/
+
+package edu.wpi.first.networktables;
+
+/**
+ * A listener that listens to new tables in a {@link NetworkTable}.
+ */
+@FunctionalInterface
+public interface TableListener {
+ /**
+ * Called when a new table is created within a {@link NetworkTable}.
+ *
+ * @param parent the parent of the table
+ * @param name the name of the new table
+ * @param table the new table
+ */
+ void tableCreated(NetworkTable parent, String name, NetworkTable table);
+}
diff --git a/ntcore/src/main/java/edu/wpi/first/wpilibj/networktables/NetworkTable.java b/ntcore/src/main/java/edu/wpi/first/wpilibj/networktables/NetworkTable.java
new file mode 100644
index 0000000..f360a19
--- /dev/null
+++ b/ntcore/src/main/java/edu/wpi/first/wpilibj/networktables/NetworkTable.java
@@ -0,0 +1,1189 @@
+/*----------------------------------------------------------------------------*/
+/* Copyright (c) 2017-2018 FIRST. All Rights Reserved. */
+/* Open Source Software - may be modified and shared by FRC teams. The code */
+/* must be accompanied by the FIRST BSD license file in the root directory of */
+/* the project. */
+/*----------------------------------------------------------------------------*/
+
+package edu.wpi.first.wpilibj.networktables;
+
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.function.Consumer;
+
+import edu.wpi.first.networktables.ConnectionInfo;
+import edu.wpi.first.networktables.ConnectionNotification;
+import edu.wpi.first.networktables.EntryInfo;
+import edu.wpi.first.networktables.EntryNotification;
+import edu.wpi.first.networktables.NetworkTableEntry;
+import edu.wpi.first.networktables.NetworkTableInstance;
+import edu.wpi.first.networktables.NetworkTableType;
+import edu.wpi.first.networktables.NetworkTableValue;
+import edu.wpi.first.networktables.NetworkTablesJNI;
+import edu.wpi.first.networktables.PersistentException;
+import edu.wpi.first.wpilibj.tables.IRemote;
+import edu.wpi.first.wpilibj.tables.IRemoteConnectionListener;
+import edu.wpi.first.wpilibj.tables.ITable;
+import edu.wpi.first.wpilibj.tables.ITableListener;
+
+/**
+ * A network table that knows its subtable path.
+ * @deprecated Use {@link edu.wpi.first.networktables.NetworkTable} instead.
+ */
+@Deprecated
+@SuppressWarnings("checkstyle:all")
+public class NetworkTable implements ITable, IRemote {
+ /**
+ * The path separator for sub-tables and keys
+ *
+ */
+ public static final char PATH_SEPARATOR = '/';
+ /**
+ * The default port that network tables operates on
+ */
+ public static final int DEFAULT_PORT = 1735;
+
+ private static boolean client = false;
+ private static boolean enableDS = true;
+ private static boolean running = false;
+ private static int port = DEFAULT_PORT;
+ private static String persistentFilename = "networktables.ini";
+
+ private synchronized static void checkInit() {
+ if (running)
+ throw new IllegalStateException(
+ "Network tables has already been initialized");
+ }
+
+ /**
+ * initializes network tables
+ * @deprecated Use {@link NetworkTableInstance#startServer()} or
+ * {@link NetworkTableInstance#startClient()} instead.
+ */
+ @Deprecated
+ public synchronized static void initialize() {
+ if (running)
+ shutdown();
+ NetworkTableInstance inst = NetworkTableInstance.getDefault();
+ if (client) {
+ inst.startClient();
+ if (enableDS)
+ inst.startDSClient(port);
+ } else
+ inst.startServer(persistentFilename, "", port);
+ running = true;
+ }
+
+ /**
+ * shuts down network tables
+ * @deprecated Use {@link NetworkTableInstance#stopServer()} or
+ * {@link NetworkTableInstance#stopClient()} instead.
+ */
+ @Deprecated
+ public synchronized static void shutdown() {
+ if (!running)
+ return;
+ NetworkTableInstance inst = NetworkTableInstance.getDefault();
+ if (client) {
+ inst.stopDSClient();
+ inst.stopClient();
+ } else
+ inst.stopServer();
+ running = false;
+ }
+
+ /**
+ * set that network tables should be a server
+ * This must be called before initialize or getTable
+ * @deprecated Use {@link NetworkTableInstance#startServer()} instead.
+ */
+ @Deprecated
+ public synchronized static void setServerMode() {
+ if (!client)
+ return;
+ checkInit();
+ client = false;
+ }
+
+ /**
+ * set that network tables should be a client
+ * This must be called before initialize or getTable
+ * @deprecated Use {@link NetworkTableInstance#startClient()} instead.
+ */
+ @Deprecated
+ public synchronized static void setClientMode() {
+ if (client)
+ return;
+ checkInit();
+ client = true;
+ }
+
+ /**
+ * set the team the robot is configured for (this will set the mdns address that
+ * network tables will connect to in client mode)
+ * This must be called before initialize or getTable
+ * @param team the team number
+ * @deprecated Use {@link NetworkTableInstance#setServerTeam(int)} or
+ * {@link NetworkTableInstance#startClientTeam(int)} instead.
+ */
+ @Deprecated
+ public synchronized static void setTeam(int team) {
+ NetworkTableInstance inst = NetworkTableInstance.getDefault();
+ inst.setServerTeam(team, port);
+ if (enableDS)
+ inst.startDSClient(port);
+ }
+
+ /**
+ * @param address the address that network tables will connect to in client
+ * mode
+ * @deprecated Use {@link NetworkTableInstance#setServer(String)} or
+ * {@link NetworkTableInstance#startClient(String)} instead.
+ */
+ @Deprecated
+ public synchronized static void setIPAddress(final String address) {
+ String[] addresses = new String[1];
+ addresses[0] = address;
+ setIPAddress(addresses);
+ }
+
+ /**
+ * @param addresses the adresses that network tables will connect to in
+ * client mode (in round robin order)
+ * @deprecated Use {@link NetworkTableInstance#setServer(String[])} or
+ * {@link NetworkTableInstance#startClient(String[])} instead.
+ */
+ @Deprecated
+ public synchronized static void setIPAddress(final String[] addresses) {
+ NetworkTableInstance inst = NetworkTableInstance.getDefault();
+ inst.setServer(addresses, port);
+
+ // Stop the DS client if we're explicitly connecting to localhost
+ if (addresses.length > 0 &&
+ (addresses[0].equals("localhost") || addresses[0].equals("127.0.0.1")))
+ inst.stopDSClient();
+ else if (enableDS)
+ inst.startDSClient(port);
+ }
+
+ /**
+ * Set the port number that network tables will connect to in client
+ * mode or listen to in server mode.
+ * @param aport the port number
+ * @deprecated Use the appropriate parameters to
+ * {@link NetworkTableInstance#setServer(String, int)},
+ * {@link NetworkTableInstance#startClient(String, int)},
+ * {@link NetworkTableInstance#startServer(String, String, int)}, and
+ * {@link NetworkTableInstance#startDSClient(int)} instead.
+ */
+ @Deprecated
+ public synchronized static void setPort(int aport) {
+ if (port == aport)
+ return;
+ checkInit();
+ port = aport;
+ }
+
+ /**
+ * Enable requesting the server address from the Driver Station.
+ * @param enabled whether to enable the connection to the local DS
+ * @deprecated Use {@link NetworkTableInstance#startDSClient()} and
+ * {@link NetworkTableInstance#stopDSClient()} instead.
+ */
+ @Deprecated
+ public synchronized static void setDSClientEnabled(boolean enabled) {
+ NetworkTableInstance inst = NetworkTableInstance.getDefault();
+ enableDS = enabled;
+ if (enableDS)
+ inst.startDSClient(port);
+ else
+ inst.stopDSClient();
+ }
+
+ /**
+ * Sets the persistent filename.
+ * @param filename the filename that the network tables server uses for
+ * automatic loading and saving of persistent values
+ * @deprecated Use the appropriate parameter to
+ * {@link NetworkTableInstance#startServer()} instead.
+ */
+ @Deprecated
+ public synchronized static void setPersistentFilename(final String filename) {
+ if (persistentFilename.equals(filename))
+ return;
+ checkInit();
+ persistentFilename = filename;
+ }
+
+ /**
+ * Sets the network identity.
+ * This is provided in the connection info on the remote end.
+ * @param name identity
+ * @deprecated Use {@link NetworkTableInstance#setNetworkIdentity(String)}
+ * instead.
+ */
+ @Deprecated
+ public static void setNetworkIdentity(String name) {
+ NetworkTableInstance.getDefault().setNetworkIdentity(name);
+ }
+
+ public static boolean[] toNative(Boolean[] arr) {
+ boolean[] out = new boolean[arr.length];
+ for (int i = 0; i < arr.length; i++)
+ out[i] = arr[i];
+ return out;
+ }
+
+ public static double[] toNative(Number[] arr) {
+ double[] out = new double[arr.length];
+ for (int i = 0; i < arr.length; i++)
+ out[i] = arr[i].doubleValue();
+ return out;
+ }
+
+ public static Boolean[] fromNative(boolean[] arr) {
+ Boolean[] out = new Boolean[arr.length];
+ for (int i = 0; i < arr.length; i++)
+ out[i] = arr[i];
+ return out;
+ }
+
+ public static Double[] fromNative(double[] arr) {
+ Double[] out = new Double[arr.length];
+ for (int i = 0; i < arr.length; i++)
+ out[i] = arr[i];
+ return out;
+ }
+
+ /**
+ * Gets the table with the specified key. If the table does not exist, a new
+ * table will be created.<br>
+ * This will automatically initialize network tables if it has not been
+ * already
+ *
+ * @deprecated Use {@link NetworkTableInstance#getTable(String)} instead.
+ *
+ * @param key the key name
+ * @return the network table requested
+ */
+ @Deprecated
+ public synchronized static NetworkTable getTable(String key) {
+ if (!running)
+ initialize();
+ String theKey;
+ if (key.isEmpty() || key.equals("/")) {
+ theKey = "";
+ } else if (key.charAt(0) == NetworkTable.PATH_SEPARATOR) {
+ theKey = key;
+ } else {
+ theKey = NetworkTable.PATH_SEPARATOR + key;
+ }
+ return new NetworkTable(NetworkTableInstance.getDefault(), theKey);
+ }
+
+ private final String path;
+ private final String pathWithSep;
+ private final NetworkTableInstance inst;
+
+ NetworkTable(NetworkTableInstance inst, String path) {
+ this.path = path;
+ this.pathWithSep = path + PATH_SEPARATOR;
+ this.inst = inst;
+ }
+
+ @Override
+ public String toString() { return "NetworkTable: " + path; }
+
+ private final ConcurrentMap<String, NetworkTableEntry> entries = new ConcurrentHashMap<String, NetworkTableEntry>();
+
+ /**
+ * Gets the entry for a subkey.
+ * @param key the key name
+ * @return Network table entry.
+ */
+ private NetworkTableEntry getEntry(String key) {
+ NetworkTableEntry entry = entries.get(key);
+ if (entry == null) {
+ entry = inst.getEntry(pathWithSep + key);
+ entries.putIfAbsent(key, entry);
+ }
+ return entry;
+ }
+
+ /**
+ * Gets the current network connections.
+ * @return An array of connection information.
+ * @deprecated Use {@link NetworkTableInstance#getConnections()} instead.
+ */
+ @Deprecated
+ public static ConnectionInfo[] connections() {
+ return NetworkTableInstance.getDefault().getConnections();
+ }
+
+ /**
+ * Determine whether or not a network connection is active.
+ * @return True if connected, false if not connected.
+ * @deprecated Use {@link NetworkTableInstance#isConnected()} instead.
+ */
+ @Override
+ @Deprecated
+ public boolean isConnected() {
+ return inst.isConnected();
+ }
+
+ /**
+ * Determine whether NetworkTables is operating as a server or as a client.
+ * @return True if operating as a server, false otherwise.
+ * @deprecated Use {@link NetworkTableInstance#getNetworkMode()} instead.
+ */
+ @Override
+ @Deprecated
+ public boolean isServer() {
+ return (inst.getNetworkMode() & NetworkTableInstance.kNetModeServer) != 0;
+ }
+
+ /* Backwards compatibility shims for IRemoteConnectionListener */
+ private static class ConnectionListenerAdapter implements Consumer<ConnectionNotification> {
+ public int uid;
+ private final IRemote targetSource;
+ private final IRemoteConnectionListener targetListener;
+
+ public ConnectionListenerAdapter(IRemote targetSource, IRemoteConnectionListener targetListener) {
+ this.targetSource = targetSource;
+ this.targetListener = targetListener;
+ }
+
+ @Override
+ public void accept(ConnectionNotification event) {
+ if (event.connected)
+ targetListener.connectedEx(targetSource, event.conn);
+ else
+ targetListener.disconnectedEx(targetSource, event.conn);
+ }
+ }
+
+ private static final HashMap<IRemoteConnectionListener,ConnectionListenerAdapter> globalConnectionListenerMap = new HashMap<IRemoteConnectionListener,ConnectionListenerAdapter>();
+
+ private static IRemote staticRemote = new IRemote() {
+ @Override
+ public void addConnectionListener(IRemoteConnectionListener listener, boolean immediateNotify) {
+ NetworkTable.addGlobalConnectionListener(listener, immediateNotify);
+ }
+ @Override
+ public void removeConnectionListener(IRemoteConnectionListener listener) {
+ NetworkTable.removeGlobalConnectionListener(listener);
+ }
+ @Override
+ public boolean isConnected() {
+ ConnectionInfo[] conns = NetworkTableInstance.getDefault().getConnections();
+ return conns.length > 0;
+ }
+ @Override
+ public boolean isServer() {
+ return (NetworkTableInstance.getDefault().getNetworkMode() & NetworkTableInstance.kNetModeServer) != 0;
+ }
+ };
+
+ private final HashMap<IRemoteConnectionListener,ConnectionListenerAdapter> connectionListenerMap = new HashMap<IRemoteConnectionListener,ConnectionListenerAdapter>();
+
+ /**
+ * Add a connection listener.
+ * @param listener connection listener
+ * @param immediateNotify call listener immediately for all existing connections
+ * @deprecated Use {@link NetworkTableInstance#addConnectionListener(Consumer, boolean)} instead.
+ */
+ @Deprecated
+ public static synchronized void addGlobalConnectionListener(IRemoteConnectionListener listener, boolean immediateNotify) {
+ ConnectionListenerAdapter adapter = new ConnectionListenerAdapter(staticRemote, listener);
+ if (globalConnectionListenerMap.putIfAbsent(listener, adapter) != null) {
+ throw new IllegalStateException("Cannot add the same listener twice");
+ }
+ adapter.uid = NetworkTableInstance.getDefault().addConnectionListener(adapter, immediateNotify);
+ }
+
+ /**
+ * Remove a connection listener.
+ * @param listener connection listener
+ * @deprecated Use {@link NetworkTableInstance#removeConnectionListener(int)} instead.
+ */
+ @Deprecated
+ public static synchronized void removeGlobalConnectionListener(IRemoteConnectionListener listener) {
+ ConnectionListenerAdapter adapter = globalConnectionListenerMap.remove(listener);
+ if (adapter != null) {
+ NetworkTableInstance.getDefault().removeConnectionListener(adapter.uid);
+ }
+ }
+
+ /**
+ * Add a connection listener.
+ * @param listener connection listener
+ * @param immediateNotify call listener immediately for all existing connections
+ * @deprecated Use {@link NetworkTableInstance#addConnectionListener(Consumer, boolean)} instead.
+ */
+ @Override
+ @Deprecated
+ public synchronized void addConnectionListener(IRemoteConnectionListener listener,
+ boolean immediateNotify) {
+ ConnectionListenerAdapter adapter = new ConnectionListenerAdapter(this, listener);
+ if (connectionListenerMap.putIfAbsent(listener, adapter) != null) {
+ throw new IllegalStateException("Cannot add the same listener twice");
+ }
+ adapter.uid = inst.addConnectionListener(adapter, immediateNotify);
+ }
+
+ /**
+ * Remove a connection listener.
+ * @param listener connection listener
+ * @deprecated Use {@link NetworkTableInstance#removeConnectionListener(int)} instead.
+ */
+ @Override
+ @Deprecated
+ public synchronized void removeConnectionListener(IRemoteConnectionListener listener) {
+ ConnectionListenerAdapter adapter = connectionListenerMap.get(listener);
+ if (adapter != null && connectionListenerMap.remove(listener, adapter)) {
+ inst.removeConnectionListener(adapter.uid);
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ * @deprecated Use {@link edu.wpi.first.networktables.NetworkTable#addEntryListener(TableEntryListener, int)} instead
+ * (with flags value of NOTIFY_NEW | NOTIFY_UPDATE).
+ */
+ @Override
+ @Deprecated
+ public void addTableListener(ITableListener listener) {
+ addTableListenerEx(listener, NOTIFY_NEW | NOTIFY_UPDATE);
+ }
+
+ /**
+ * {@inheritDoc}
+ * @deprecated Use {@link edu.wpi.first.networktables.NetworkTable#addEntryListener(TableEntryListener, int)} instead
+ * (with flags value of NOTIFY_NEW | NOTIFY_UPDATE | NOTIFY_IMMEDIATE).
+ */
+ @Override
+ @Deprecated
+ public void addTableListener(ITableListener listener,
+ boolean immediateNotify) {
+ int flags = NOTIFY_NEW | NOTIFY_UPDATE;
+ if (immediateNotify)
+ flags |= NOTIFY_IMMEDIATE;
+ addTableListenerEx(listener, flags);
+ }
+
+ /* Base class for listeners; stores uid to implement remove functions */
+ private static class ListenerBase {
+ public int uid;
+ }
+
+ private static class OldTableListenerAdapter extends ListenerBase implements Consumer<EntryNotification> {
+ private final int prefixLen;
+ private final ITable targetSource;
+ private final ITableListener targetListener;
+
+ public OldTableListenerAdapter(int prefixLen, ITable targetSource, ITableListener targetListener) {
+ this.prefixLen = prefixLen;
+ this.targetSource = targetSource;
+ this.targetListener = targetListener;
+ }
+
+ @Override
+ public void accept(EntryNotification event) {
+ String relativeKey = event.name.substring(prefixLen);
+ if (relativeKey.indexOf(PATH_SEPARATOR) != -1)
+ return;
+ targetListener.valueChangedEx(targetSource, relativeKey, event.value.getValue(), event.flags);
+ }
+ }
+
+ private final HashMap<ITableListener,List<ListenerBase>> oldListenerMap = new HashMap<ITableListener,List<ListenerBase>>();
+
+ /**
+ * {@inheritDoc}
+ * @deprecated Use {@link edu.wpi.first.networktables.NetworkTable#addEntryListener(TableEntryListener, int)} instead.
+ */
+ @Override
+ @Deprecated
+ public synchronized void addTableListenerEx(ITableListener listener,
+ int flags) {
+ List<ListenerBase> adapters = oldListenerMap.get(listener);
+ if (adapters == null) {
+ adapters = new ArrayList<ListenerBase>();
+ oldListenerMap.put(listener, adapters);
+ }
+ OldTableListenerAdapter adapter =
+ new OldTableListenerAdapter(path.length() + 1, this, listener);
+ adapter.uid = inst.addEntryListener(pathWithSep, adapter, flags);
+ adapters.add(adapter);
+ }
+
+ /**
+ * {@inheritDoc}
+ * @deprecated Use {@link edu.wpi.first.networktables.NetworkTable#addEntryListener(String, TableEntryListener, int)}
+ * or {@link NetworkTableEntry#addListener(Consumer, int)} instead.
+ */
+ @Override
+ @Deprecated
+ public void addTableListener(String key, ITableListener listener,
+ boolean immediateNotify) {
+ int flags = NOTIFY_NEW | NOTIFY_UPDATE;
+ if (immediateNotify)
+ flags |= NOTIFY_IMMEDIATE;
+ addTableListenerEx(key, listener, flags);
+ }
+
+ private static class OldKeyListenerAdapter extends ListenerBase implements Consumer<EntryNotification> {
+ private final String relativeKey;
+ private final ITable targetSource;
+ private final ITableListener targetListener;
+
+ public OldKeyListenerAdapter(String relativeKey, ITable targetSource, ITableListener targetListener) {
+ this.relativeKey = relativeKey;
+ this.targetSource = targetSource;
+ this.targetListener = targetListener;
+ }
+
+ @Override
+ public void accept(EntryNotification event) {
+ targetListener.valueChangedEx(targetSource, relativeKey, event.value.getValue(), event.flags);
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ * @deprecated Use {@link edu.wpi.first.networktables.NetworkTable#addEntryListener(String, TableEntryListener, int)}
+ * or {@link NetworkTableEntry#addListener(Consumer, int)} instead.
+ */
+ @Override
+ @Deprecated
+ public synchronized void addTableListenerEx(String key,
+ ITableListener listener,
+ int flags) {
+ List<ListenerBase> adapters = oldListenerMap.get(listener);
+ if (adapters == null) {
+ adapters = new ArrayList<ListenerBase>();
+ oldListenerMap.put(listener, adapters);
+ }
+ OldKeyListenerAdapter adapter = new OldKeyListenerAdapter(key, this, listener);
+ adapter.uid = inst.addEntryListener(getEntry(key), adapter, flags);
+ adapters.add(adapter);
+ }
+
+ /**
+ * {@inheritDoc}
+ * @deprecated Use {@link edu.wpi.first.networktables.NetworkTable#addSubTableListener(TableListener, boolean)}
+ * instead.
+ */
+ @Override
+ @Deprecated
+ public void addSubTableListener(final ITableListener listener) {
+ addSubTableListener(listener, false);
+ }
+
+ private static class OldSubListenerAdapter extends ListenerBase implements Consumer<EntryNotification> {
+ private final int prefixLen;
+ private final ITable targetSource;
+ private final ITableListener targetListener;
+ private final Set<String> notifiedTables = new HashSet<String>();
+
+ public OldSubListenerAdapter(int prefixLen, ITable targetSource, ITableListener targetListener) {
+ this.prefixLen = prefixLen;
+ this.targetSource = targetSource;
+ this.targetListener = targetListener;
+ }
+
+ @Override
+ public void accept(EntryNotification event) {
+ String relativeKey = event.name.substring(prefixLen);
+ int endSubTable = relativeKey.indexOf(PATH_SEPARATOR);
+ if (endSubTable == -1)
+ return;
+ String subTableKey = relativeKey.substring(0, endSubTable);
+ if (notifiedTables.contains(subTableKey))
+ return;
+ notifiedTables.add(subTableKey);
+ targetListener.valueChangedEx(targetSource, subTableKey, targetSource.getSubTable(subTableKey), event.flags);
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ * @deprecated Use {@link edu.wpi.first.networktables.NetworkTable#addSubTableListener(TableListener, boolean)}
+ * instead.
+ */
+ @Override
+ @Deprecated
+ public synchronized void addSubTableListener(final ITableListener listener,
+ boolean localNotify) {
+ List<ListenerBase> adapters = oldListenerMap.get(listener);
+ if (adapters == null) {
+ adapters = new ArrayList<ListenerBase>();
+ oldListenerMap.put(listener, adapters);
+ }
+ OldSubListenerAdapter adapter =
+ new OldSubListenerAdapter(path.length() + 1, this, listener);
+ int flags = NOTIFY_NEW | NOTIFY_IMMEDIATE;
+ if (localNotify)
+ flags |= NOTIFY_LOCAL;
+ adapter.uid = inst.addEntryListener(pathWithSep, adapter, flags);
+ adapters.add(adapter);
+ }
+
+ /**
+ * {@inheritDoc}
+ * @deprecated Use {@link edu.wpi.first.networktables.NetworkTable#removeTableListener(int)} instead.
+ */
+ @Override
+ @Deprecated
+ public synchronized void removeTableListener(ITableListener listener) {
+ List<ListenerBase> adapters = oldListenerMap.remove(listener);
+ if (adapters != null) {
+ for (ListenerBase adapter : adapters)
+ inst.removeEntryListener(adapter.uid);
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public ITable getSubTable(String key) {
+ return new NetworkTable(inst, pathWithSep + key);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean containsKey(String key) {
+ return getEntry(key).exists();
+ }
+
+ @Override
+ public boolean containsSubTable(String key) {
+ int[] handles = NetworkTablesJNI.getEntries(inst.getHandle(), pathWithSep + key + PATH_SEPARATOR, 0);
+ return handles.length != 0;
+ }
+
+ /**
+ * @param types bitmask of types; 0 is treated as a "don't care".
+ * @return keys currently in the table
+ */
+ @Override
+ public Set<String> getKeys(int types) {
+ Set<String> keys = new HashSet<String>();
+ int prefixLen = path.length() + 1;
+ for (EntryInfo info : inst.getEntryInfo(pathWithSep, types)) {
+ String relativeKey = info.name.substring(prefixLen);
+ if (relativeKey.indexOf(PATH_SEPARATOR) != -1)
+ continue;
+ keys.add(relativeKey);
+ // populate entries as we go
+ if (entries.get(relativeKey) == null) {
+ entries.putIfAbsent(relativeKey, new NetworkTableEntry(inst, info.entry));
+ }
+ }
+ return keys;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Set<String> getKeys() {
+ return getKeys(0);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Set<String> getSubTables() {
+ Set<String> keys = new HashSet<String>();
+ int prefixLen = path.length() + 1;
+ for (EntryInfo info : inst.getEntryInfo(pathWithSep, 0)) {
+ String relativeKey = info.name.substring(prefixLen);
+ int endSubTable = relativeKey.indexOf(PATH_SEPARATOR);
+ if (endSubTable == -1)
+ continue;
+ keys.add(relativeKey.substring(0, endSubTable));
+ }
+ return keys;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean putNumber(String key, double value) {
+ return getEntry(key).setNumber(value);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean setDefaultNumber(String key, double defaultValue) {
+ return getEntry(key).setDefaultDouble(defaultValue);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public double getNumber(String key, double defaultValue) {
+ return getEntry(key).getDouble(defaultValue);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean putString(String key, String value) {
+ return getEntry(key).setString(value);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean setDefaultString(String key, String defaultValue) {
+ return getEntry(key).setDefaultString(defaultValue);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String getString(String key, String defaultValue) {
+ return getEntry(key).getString(defaultValue);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean putBoolean(String key, boolean value) {
+ return getEntry(key).setBoolean(value);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean setDefaultBoolean(String key, boolean defaultValue) {
+ return getEntry(key).setDefaultBoolean(defaultValue);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean getBoolean(String key, boolean defaultValue) {
+ return getEntry(key).getBoolean(defaultValue);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean putBooleanArray(String key, boolean[] value) {
+ return getEntry(key).setBooleanArray(value);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean putBooleanArray(String key, Boolean[] value) {
+ return getEntry(key).setBooleanArray(value);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean setDefaultBooleanArray(String key, boolean[] defaultValue) {
+ return getEntry(key).setDefaultBooleanArray(defaultValue);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean setDefaultBooleanArray(String key, Boolean[] defaultValue) {
+ return getEntry(key).setDefaultBooleanArray(defaultValue);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean[] getBooleanArray(String key, boolean[] defaultValue) {
+ return getEntry(key).getBooleanArray(defaultValue);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Boolean[] getBooleanArray(String key, Boolean[] defaultValue) {
+ return getEntry(key).getBooleanArray(defaultValue);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean putNumberArray(String key, double[] value) {
+ return getEntry(key).setDoubleArray(value);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean putNumberArray(String key, Double[] value) {
+ return getEntry(key).setNumberArray(value);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean setDefaultNumberArray(String key, double[] defaultValue) {
+ return getEntry(key).setDefaultDoubleArray(defaultValue);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean setDefaultNumberArray(String key, Double[] defaultValue) {
+ return getEntry(key).setDefaultNumberArray(defaultValue);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public double[] getNumberArray(String key, double[] defaultValue) {
+ return getEntry(key).getDoubleArray(defaultValue);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Double[] getNumberArray(String key, Double[] defaultValue) {
+ return getEntry(key).getDoubleArray(defaultValue);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean putStringArray(String key, String[] value) {
+ return getEntry(key).setStringArray(value);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean setDefaultStringArray(String key, String[] defaultValue) {
+ return getEntry(key).setDefaultStringArray(defaultValue);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String[] getStringArray(String key, String[] defaultValue) {
+ return getEntry(key).getStringArray(defaultValue);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean putRaw(String key, byte[] value) {
+ return getEntry(key).setRaw(value);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean setDefaultRaw(String key, byte[] defaultValue) {
+ return getEntry(key).setDefaultRaw(defaultValue);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean putRaw(String key, ByteBuffer value, int len) {
+ return getEntry(key).setRaw(value, len);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public byte[] getRaw(String key, byte[] defaultValue) {
+ return getEntry(key).getRaw(defaultValue);
+ }
+
+ /**
+ * Put a value in the table
+ * @param key the key to be assigned to
+ * @param value the value that will be assigned
+ * @return False if the table key already exists with a different type
+ */
+ public boolean putValue(String key, NetworkTableValue value) {
+ return getEntry(key).setValue(value);
+ }
+
+ /**
+ * Sets the current value in the table if it does not exist.
+ * @param key the key
+ * @param defaultValue the default value to set if key doens't exist.
+ * @return False if the table key exists with a different type
+ */
+ public boolean setDefaultValue(String key, NetworkTableValue defaultValue) {
+ return getEntry(key).setDefaultValue(defaultValue);
+ }
+
+ /**
+ * Gets the value associated with a key as a NetworkTableValue object.
+ * @param key the key of the value to look up
+ * @return the value associated with the given key
+ */
+ public NetworkTableValue getValue(String key) {
+ return getEntry(key).getValue();
+ }
+
+ /**
+ * {@inheritDoc}
+ * @deprecated Use {@link edu.wpi.first.networktables.NetworkTableEntry#setValue(Object)}
+ * instead, e.g. `NetworkTable.getEntry(key).setValue(NetworkTableEntry.makeBoolean(false));`
+ * or `NetworkTable.getEntry(key).setValue(new Boolean(false));`
+ */
+ @Override
+ @Deprecated
+ public boolean putValue(String key, Object value) throws IllegalArgumentException {
+ if (value instanceof Boolean)
+ return putBoolean(key, ((Boolean)value).booleanValue());
+ else if (value instanceof Number)
+ return putDouble(key, ((Number)value).doubleValue());
+ else if (value instanceof String)
+ return putString(key, (String)value);
+ else if (value instanceof byte[])
+ return putRaw(key, (byte[])value);
+ else if (value instanceof boolean[])
+ return putBooleanArray(key, (boolean[])value);
+ else if (value instanceof double[])
+ return putNumberArray(key, (double[])value);
+ else if (value instanceof Boolean[])
+ return putBooleanArray(key, toNative((Boolean[])value));
+ else if (value instanceof Number[])
+ return putNumberArray(key, toNative((Number[])value));
+ else if (value instanceof String[])
+ return putStringArray(key, (String[])value);
+ else if (value instanceof NetworkTableValue)
+ return getEntry(key).setValue((NetworkTableValue)value);
+ else
+ throw new IllegalArgumentException("Value of type " + value.getClass().getName() + " cannot be put into a table");
+ }
+
+ /**
+ * {@inheritDoc}
+ * @deprecated Use {@link edu.wpi.first.networktables.NetworkTableEntry#getValue()}
+ * instead, e.g. `NetworkTable.getEntry(key).getValue();`
+ */
+ @Override
+ @Deprecated
+ public Object getValue(String key, Object defaultValue) {
+ NetworkTableValue value = getValue(key);
+ if (value.getType() == NetworkTableType.kUnassigned) {
+ return defaultValue;
+ }
+ return value.getValue();
+ }
+
+ /** The persistent flag value. */
+ public static final int PERSISTENT = 1;
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void setPersistent(String key) {
+ getEntry(key).setPersistent();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void clearPersistent(String key) {
+ getEntry(key).clearPersistent();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean isPersistent(String key) {
+ return getEntry(key).isPersistent();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void setFlags(String key, int flags) {
+ getEntry(key).setFlags(flags);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void clearFlags(String key, int flags) {
+ getEntry(key).clearFlags(flags);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public int getFlags(String key) {
+ return getEntry(key).getFlags();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void delete(String key) {
+ getEntry(key).delete();
+ }
+
+ /**
+ * Deletes ALL keys in ALL subtables. Use with caution!
+ * @deprecated Use {@link NetworkTableInstance#deleteAllEntries()} instead.
+ */
+ @Deprecated
+ public static void globalDeleteAll() {
+ NetworkTableInstance.getDefault().deleteAllEntries();
+ }
+
+ /**
+ * Flushes all updated values immediately to the network.
+ * Note: This is rate-limited to protect the network from flooding.
+ * This is primarily useful for synchronizing network updates with
+ * user code.
+ * @deprecated Use {@link NetworkTableInstance#flush()} instead.
+ */
+ @Deprecated
+ public static void flush() {
+ NetworkTableInstance.getDefault().flush();
+ }
+
+ /**
+ * Set the periodic update rate.
+ *
+ * @param interval update interval in seconds (range 0.01 to 1.0)
+ * @deprecated Use {@link NetworkTableInstance#setUpdateRate(double)}
+ * instead.
+ */
+ @Deprecated
+ public static void setUpdateRate(double interval) {
+ NetworkTableInstance.getDefault().setUpdateRate(interval);
+ }
+
+ /**
+ * Saves persistent keys to a file. The server does this automatically.
+ *
+ * @param filename file name
+ * @throws PersistentException if error saving file
+ * @deprecated Use {@link NetworkTableInstance#savePersistent(String)}
+ * instead.
+ */
+ @Deprecated
+ public static void savePersistent(String filename) throws PersistentException {
+ NetworkTableInstance.getDefault().savePersistent(filename);
+ }
+
+ /**
+ * Loads persistent keys from a file. The server does this automatically.
+ *
+ * @param filename file name
+ * @return List of warnings (errors result in an exception instead)
+ * @throws PersistentException if error reading file
+ * @deprecated Use {@link NetworkTableInstance#loadPersistent(String)}
+ * instead.
+ */
+ @Deprecated
+ public static String[] loadPersistent(String filename) throws PersistentException {
+ return NetworkTableInstance.getDefault().loadPersistent(filename);
+ }
+
+ /*
+ * Deprecated Methods
+ */
+
+ /**
+ * {@inheritDoc}
+ * @deprecated Use {@link #putNumber(String, double)} instead.
+ */
+ @Override
+ @Deprecated
+ public boolean putDouble(String key, double value) {
+ return putNumber(key, value);
+ }
+
+ /**
+ * {@inheritDoc}
+ * @deprecated Use {@link #getNumber(String, double)} instead.
+ */
+ @Override
+ @Deprecated
+ public double getDouble(String key, double defaultValue) {
+ return getNumber(key, defaultValue);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String getPath() {
+ return path;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o == this) {
+ return true;
+ }
+ if (!(o instanceof NetworkTable)) {
+ return false;
+ }
+ NetworkTable other = (NetworkTable) o;
+ return inst.equals(other.inst) && path.equals(other.path);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(inst, path);
+ }
+}
diff --git a/ntcore/src/main/java/edu/wpi/first/wpilibj/tables/IRemote.java b/ntcore/src/main/java/edu/wpi/first/wpilibj/tables/IRemote.java
new file mode 100644
index 0000000..22b6f96
--- /dev/null
+++ b/ntcore/src/main/java/edu/wpi/first/wpilibj/tables/IRemote.java
@@ -0,0 +1,44 @@
+/*----------------------------------------------------------------------------*/
+/* Copyright (c) 2017-2018 FIRST. All Rights Reserved. */
+/* Open Source Software - may be modified and shared by FRC teams. The code */
+/* must be accompanied by the FIRST BSD license file in the root directory of */
+/* the project. */
+/*----------------------------------------------------------------------------*/
+
+package edu.wpi.first.wpilibj.tables;
+
+
+/**
+ * Represents an object that has a remote connection.
+ * @deprecated Use {@link edu.wpi.first.networktables.NetworkTableInstance}.
+ */
+@Deprecated
+@SuppressWarnings("checkstyle:all")
+public interface IRemote {
+ /**
+ * Register an object to listen for connection and disconnection events
+ *
+ * @param listener the listener to be register
+ * @param immediateNotify if the listener object should be notified of the current connection state
+ */
+ public void addConnectionListener(IRemoteConnectionListener listener, boolean immediateNotify);
+
+ /**
+ * Unregister a listener from connection events
+ *
+ * @param listener the listener to be unregistered
+ */
+ public void removeConnectionListener(IRemoteConnectionListener listener);
+
+ /**
+ * Get the current state of the objects connection
+ * @return the current connection state
+ */
+ public boolean isConnected();
+
+ /**
+ * If the object is acting as a server
+ * @return if the object is a server
+ */
+ public boolean isServer();
+}
diff --git a/ntcore/src/main/java/edu/wpi/first/wpilibj/tables/IRemoteConnectionListener.java b/ntcore/src/main/java/edu/wpi/first/wpilibj/tables/IRemoteConnectionListener.java
new file mode 100644
index 0000000..a3ff118
--- /dev/null
+++ b/ntcore/src/main/java/edu/wpi/first/wpilibj/tables/IRemoteConnectionListener.java
@@ -0,0 +1,47 @@
+/*----------------------------------------------------------------------------*/
+/* Copyright (c) 2017-2018 FIRST. All Rights Reserved. */
+/* Open Source Software - may be modified and shared by FRC teams. The code */
+/* must be accompanied by the FIRST BSD license file in the root directory of */
+/* the project. */
+/*----------------------------------------------------------------------------*/
+
+package edu.wpi.first.wpilibj.tables;
+
+import edu.wpi.first.networktables.ConnectionInfo;
+
+/**
+ * A listener that listens for connection changes in a {@link IRemote} object.
+ * @deprecated Use Consumer<{@link edu.wpi.first.networktables.ConnectionNotification}>.
+ */
+@Deprecated
+@SuppressWarnings("checkstyle:all")
+public interface IRemoteConnectionListener {
+ /**
+ * Called when an IRemote is connected
+ * @param remote the object that connected
+ */
+ public void connected(IRemote remote);
+ /**
+ * Called when an IRemote is disconnected
+ * @param remote the object that disconnected
+ */
+ public void disconnected(IRemote remote);
+ /**
+ * Extended version of connected called when an IRemote is connected.
+ * Contains the connection info of the connected remote
+ * @param remote the object that connected
+ * @param info the connection info for the connected remote
+ */
+ default public void connectedEx(IRemote remote, ConnectionInfo info) {
+ connected(remote);
+ }
+ /**
+ * Extended version of connected called when an IRemote is disconnected.
+ * Contains the connection info of the disconnected remote
+ * @param remote the object that disconnected
+ * @param info the connection info for the disconnected remote
+ */
+ default public void disconnectedEx(IRemote remote, ConnectionInfo info) {
+ disconnected(remote);
+ }
+}
diff --git a/ntcore/src/main/java/edu/wpi/first/wpilibj/tables/ITable.java b/ntcore/src/main/java/edu/wpi/first/wpilibj/tables/ITable.java
new file mode 100644
index 0000000..2d0d5d6
--- /dev/null
+++ b/ntcore/src/main/java/edu/wpi/first/wpilibj/tables/ITable.java
@@ -0,0 +1,488 @@
+/*----------------------------------------------------------------------------*/
+/* Copyright (c) 2017-2018 FIRST. All Rights Reserved. */
+/* Open Source Software - may be modified and shared by FRC teams. The code */
+/* must be accompanied by the FIRST BSD license file in the root directory of */
+/* the project. */
+/*----------------------------------------------------------------------------*/
+
+package edu.wpi.first.wpilibj.tables;
+
+import java.nio.ByteBuffer;
+import java.util.Set;
+
+
+/**
+ * A table whose values can be read and written to.
+ * @deprecated Use {@link edu.wpi.first.networktables.NetworkTable}.
+ */
+@Deprecated
+@SuppressWarnings("checkstyle:all")
+public interface ITable {
+
+ /**
+ * Checks the table and tells if it contains the specified key
+ *
+ * @param key the key to search for
+ * @return true if the table as a value assigned to the given key
+ */
+ public boolean containsKey(String key);
+
+ /**
+ * @param key the key to search for
+ * @return true if there is a subtable with the key which contains at least
+ * one key/subtable of its own
+ */
+ public boolean containsSubTable(String key);
+
+ /**
+ * Returns the table at the specified key. If there is no table at the
+ * specified key, it will create a new table
+ *
+ * @param key the name of the table relative to this one
+ * @return a sub table relative to this one
+ */
+ public ITable getSubTable(String key);
+
+ /**
+ * Gets all keys in the table (not including sub-tables).
+ * @param types bitmask of types; 0 is treated as a "don't care".
+ * @return keys currently in the table
+ */
+ public Set<String> getKeys(int types);
+
+ /**
+ * Gets all keys in the table (not including sub-tables).
+ * @return keys currently in the table
+ */
+ public Set<String> getKeys();
+
+ /**
+ * Gets the names of all subtables in the table.
+ * @return subtables currently in the table
+ */
+ public Set<String> getSubTables();
+
+ /**
+ * Makes a key's value persistent through program restarts.
+ * The key cannot be null.
+ *
+ * @param key the key name
+ */
+ public void setPersistent(String key);
+
+ /**
+ * Stop making a key's value persistent through program restarts.
+ * The key cannot be null.
+ *
+ * @param key the key name
+ */
+ public void clearPersistent(String key);
+
+ /**
+ * Returns whether the value is persistent through program restarts.
+ * The key cannot be null.
+ *
+ * @param key the key name
+ * @return True if the value is persistent.
+ */
+ public boolean isPersistent(String key);
+
+ /**
+ * Sets flags on the specified key in this table. The key can
+ * not be null.
+ *
+ * @param key the key name
+ * @param flags the flags to set (bitmask)
+ */
+ public void setFlags(String key, int flags);
+
+ /**
+ * Clears flags on the specified key in this table. The key can
+ * not be null.
+ *
+ * @param key the key name
+ * @param flags the flags to clear (bitmask)
+ */
+ public void clearFlags(String key, int flags);
+
+ /**
+ * Returns the flags for the specified key.
+ *
+ * @param key the key name
+ * @return the flags, or 0 if the key is not defined
+ */
+ public int getFlags(String key);
+
+ /**
+ * Deletes the specified key in this table. The key can
+ * not be null.
+ *
+ * @param key the key name
+ */
+ public void delete(String key);
+
+ /**
+ * Gets the value associated with a key as an object.
+ * NOTE: If the value is a double, it will return a Double object,
+ * not a primitive. To get the primitive, use
+ * {@link #getDouble(String, double)}.
+ * @param key the key of the value to look up
+ * @param defaultValue the default value if the key is null
+ * @return the value associated with the given key
+ */
+ public Object getValue(String key, Object defaultValue);
+
+ /**
+ * Put a value in the table
+ * @param key the key to be assigned to
+ * @param value the value that will be assigned
+ * @return False if the table key already exists with a different type
+ * @throws IllegalArgumentException when the value is not supported by the
+ * table
+ */
+ public boolean putValue(String key, Object value)
+ throws IllegalArgumentException;
+
+ /**
+ * Put a number in the table
+ * @param key the key to be assigned to
+ * @param value the value that will be assigned
+ * @return False if the table key already exists with a different type
+ */
+ public boolean putNumber(String key, double value);
+
+ /**
+ * Gets the current value in the table, setting it if it does not exist.
+ * @param key the key
+ * @param defaultValue the default value to set if key doens't exist.
+ * @return False if the table key exists with a different type
+ */
+ public boolean setDefaultNumber(String key, double defaultValue);
+
+ /**
+ * Returns the number the key maps to. If the key does not exist or is of
+ * different type, it will return the default value.
+ * @param key the key to look up
+ * @param defaultValue the value to be returned if no value is found
+ * @return the value associated with the given key or the given default value
+ * if there is no value associated with the key
+ */
+ public double getNumber(String key, double defaultValue);
+
+ /**
+ * Put a string in the table
+ * @param key the key to be assigned to
+ * @param value the value that will be assigned
+ * @return False if the table key already exists with a different type
+ */
+ public boolean putString(String key, String value);
+
+ /**
+ * Gets the current value in the table, setting it if it does not exist.
+ * @param key the key
+ * @param defaultValue the default value to set if key doens't exist.
+ * @return False if the table key exists with a different type
+ */
+ public boolean setDefaultString(String key, String defaultValue);
+
+ /**
+ * Returns the string the key maps to. If the key does not exist or is of
+ * different type, it will return the default value.
+ * @param key the key to look up
+ * @param defaultValue the value to be returned if no value is found
+ * @return the value associated with the given key or the given default value
+ * if there is no value associated with the key
+ */
+ public String getString(String key, String defaultValue);
+
+ /**
+ * Put a boolean in the table
+ * @param key the key to be assigned to
+ * @param value the value that will be assigned
+ * @return False if the table key already exists with a different type
+ */
+ public boolean putBoolean(String key, boolean value);
+
+ /**
+ * Gets the current value in the table, setting it if it does not exist.
+ * @param key the key
+ * @param defaultValue the default value to set if key doens't exist.
+ * @return False if the table key exists with a different type
+ */
+ public boolean setDefaultBoolean(String key, boolean defaultValue);
+
+ /**
+ * Returns the boolean the key maps to. If the key does not exist or is of
+ * different type, it will return the default value.
+ * @param key the key to look up
+ * @param defaultValue the value to be returned if no value is found
+ * @return the value associated with the given key or the given default value
+ * if there is no value associated with the key
+ */
+ public boolean getBoolean(String key, boolean defaultValue);
+
+ /**
+ * Put a boolean array in the table
+ * @param key the key to be assigned to
+ * @param value the value that will be assigned
+ * @return False if the table key already exists with a different type
+ */
+ public boolean putBooleanArray(String key, boolean[] value);
+
+ /**
+ * Gets the current value in the table, setting it if it does not exist.
+ * @param key the key
+ * @param defaultValue the default value to set if key doens't exist.
+ * @return False if the table key exists with a different type
+ */
+ public boolean setDefaultBooleanArray(String key, boolean[] defaultValue);
+
+ /**
+ * Put a boolean array in the table
+ * @param key the key to be assigned to
+ * @param value the value that will be assigned
+ * @return False if the table key already exists with a different type
+ */
+ public boolean putBooleanArray(String key, Boolean[] value);
+
+ /**
+ * Gets the current value in the table, setting it if it does not exist.
+ * @param key the key
+ * @param defaultValue the default value to set if key doens't exist.
+ * @return False if the table key exists with a different type
+ */
+ public boolean setDefaultBooleanArray(String key, Boolean[] defaultValue);
+
+ /**
+ * Returns the boolean array the key maps to. If the key does not exist or is
+ * of different type, it will return the default value.
+ * @param key the key to look up
+ * @param defaultValue the value to be returned if no value is found
+ * @return the value associated with the given key or the given default value
+ * if there is no value associated with the key
+ */
+ public boolean[] getBooleanArray(String key, boolean[] defaultValue);
+ /**
+ * Returns the boolean array the key maps to. If the key does not exist or is
+ * of different type, it will return the default value.
+ * @param key the key to look up
+ * @param defaultValue the value to be returned if no value is found
+ * @return the value associated with the given key or the given default value
+ * if there is no value associated with the key
+ */
+ public Boolean[] getBooleanArray(String key, Boolean[] defaultValue);
+
+ /**
+ * Put a number array in the table
+ * @param key the key to be assigned to
+ * @param value the value that will be assigned
+ * @return False if the table key already exists with a different type
+ */
+ public boolean putNumberArray(String key, double[] value);
+
+ /**
+ * Gets the current value in the table, setting it if it does not exist.
+ * @param key the key
+ * @param defaultValue the default value to set if key doens't exist.
+ * @return False if the table key exists with a different type
+ */
+ public boolean setDefaultNumberArray(String key, double[] defaultValue);
+
+ /**
+ * Put a number array in the table
+ * @param key the key to be assigned to
+ * @param value the value that will be assigned
+ * @return False if the table key already exists with a different type
+ */
+ public boolean putNumberArray(String key, Double[] value);
+
+ /**
+ * Gets the current value in the table, setting it if it does not exist.
+ * @param key the key
+ * @param defaultValue the default value to set if key doens't exist.
+ * @return False if the table key exists with a different type
+ */
+ public boolean setDefaultNumberArray(String key, Double[] defaultValue);
+
+ /**
+ * Returns the number array the key maps to. If the key does not exist or is
+ * of different type, it will return the default value.
+ * @param key the key to look up
+ * @param defaultValue the value to be returned if no value is found
+ * @return the value associated with the given key or the given default value
+ * if there is no value associated with the key
+ */
+ public double[] getNumberArray(String key, double[] defaultValue);
+ /**
+ * Returns the number array the key maps to. If the key does not exist or is
+ * of different type, it will return the default value.
+ * @param key the key to look up
+ * @param defaultValue the value to be returned if no value is found
+ * @return the value associated with the given key or the given default value
+ * if there is no value associated with the key
+ */
+ public Double[] getNumberArray(String key, Double[] defaultValue);
+
+ /**
+ * Put a string array in the table
+ * @param key the key to be assigned to
+ * @param value the value that will be assigned
+ * @return False if the table key already exists with a different type
+ */
+ public boolean putStringArray(String key, String[] value);
+
+ /**
+ * Gets the current value in the table, setting it if it does not exist.
+ * @param key the key
+ * @param defaultValue the default value to set if key doens't exist.
+ * @return False if the table key exists with a different type
+ */
+ public boolean setDefaultStringArray(String key, String[] defaultValue);
+
+ /**
+ * Returns the string array the key maps to. If the key does not exist or is
+ * of different type, it will return the default value.
+ * @param key the key to look up
+ * @param defaultValue the value to be returned if no value is found
+ * @return the value associated with the given key or the given default value
+ * if there is no value associated with the key
+ */
+ public String[] getStringArray(String key, String[] defaultValue);
+
+ /**
+ * Put a raw value (byte array) in the table
+ * @param key the key to be assigned to
+ * @param value the value that will be assigned
+ * @return False if the table key already exists with a different type
+ */
+ public boolean putRaw(String key, byte[] value);
+
+ /**
+ * Gets the current value in the table, setting it if it does not exist.
+ * @param key the key
+ * @param defaultValue the default value to set if key doens't exist.
+ * @return False if the table key exists with a different type
+ */
+ public boolean setDefaultRaw(String key, byte[] defaultValue);
+
+ /**
+ * Put a raw value (bytes from a byte buffer) in the table
+ * @param key the key to be assigned to
+ * @param value the value that will be assigned
+ * @param len the length of the value
+ * @return False if the table key already exists with a different type
+ */
+ public boolean putRaw(String key, ByteBuffer value, int len);
+
+ /**
+ * Returns the raw value (byte array) the key maps to. If the key does not
+ * exist or is of different type, it will return the default value.
+ * @param key the key to look up
+ * @param defaultValue the value to be returned if no value is found
+ * @return the value associated with the given key or the given default value
+ * if there is no value associated with the key
+ */
+ public byte[] getRaw(String key, byte[] defaultValue);
+
+ /** Notifier flag values. */
+ public static final int NOTIFY_IMMEDIATE = 0x01;
+ public static final int NOTIFY_LOCAL = 0x02;
+ public static final int NOTIFY_NEW = 0x04;
+ public static final int NOTIFY_DELETE = 0x08;
+ public static final int NOTIFY_UPDATE = 0x10;
+ public static final int NOTIFY_FLAGS = 0x20;
+
+ /**
+ * Add a listener for changes to the table
+ * @param listener the listener to add
+ */
+ public void addTableListener(ITableListener listener);
+ /**
+ * Add a listener for changes to the table
+ * @param listener the listener to add
+ * @param immediateNotify if true then this listener will be notified of all
+ * current entries (marked as new)
+ */
+ public void addTableListener(ITableListener listener,
+ boolean immediateNotify);
+ /**
+ * Add a listener for changes to the table
+ * @param listener the listener to add
+ * @param flags bitmask specifying desired notifications
+ */
+ public void addTableListenerEx(ITableListener listener, int flags);
+
+ /**
+ * Add a listener for changes to a specific key the table
+ * @param key the key to listen for
+ * @param listener the listener to add
+ * @param immediateNotify if true then this listener will be notified of all
+ * current entries (marked as new)
+ */
+ public void addTableListener(String key, ITableListener listener,
+ boolean immediateNotify);
+ /**
+ * Add a listener for changes to a specific key the table
+ * @param key the key to listen for
+ * @param listener the listener to add
+ * @param flags bitmask specifying desired notifications
+ */
+ public void addTableListenerEx(String key, ITableListener listener,
+ int flags);
+ /**
+ * This will immediately notify the listener of all current sub tables
+ * @param listener the listener to notify
+ */
+ public void addSubTableListener(final ITableListener listener);
+ /**
+ * This will immediately notify the listener of all current sub tables
+ * @param listener the listener to notify
+ * @param localNotify if true then this listener will be notified of all
+ * local changes in addition to all remote changes
+ */
+ public void addSubTableListener(final ITableListener listener,
+ boolean localNotify);
+ /**
+ * Remove a listener from receiving table events
+ * @param listener the listener to be removed
+ */
+ public void removeTableListener(ITableListener listener);
+
+ /*
+ * Deprecated Methods
+ */
+
+ /**
+ * Maps the specified key to the specified value in this table.
+ * The key can not be null.
+ * The value can be retrieved by calling the get method with a key that is
+ * equal to the original key.
+ * @param key the key
+ * @param value the value
+ * @return False if the table key already exists with a different type
+ * @throws IllegalArgumentException if key is null
+ * @deprecated Use {@link #putNumber(String, double)} instead.
+ */
+ @Deprecated
+ public boolean putDouble(String key, double value);
+
+ /**
+ * Returns the value at the specified key.
+ * @param key the key
+ * @param defaultValue the value returned if the key is undefined
+ * @return the value
+ * @throws IllegalArgumentException if the value mapped to by the key is not a
+ * double
+ * @throws IllegalArgumentException if the key is null
+ * @deprecated Use {@link #getNumber(String, double)} instead.
+ */
+ @Deprecated
+ public double getDouble(String key, double defaultValue);
+
+ /**
+ * Gets the full path of this table. Does not include the trailing "/".
+ * @return The path to this table (e.g. "", "/foo").
+ */
+ public String getPath();
+
+}
diff --git a/ntcore/src/main/java/edu/wpi/first/wpilibj/tables/ITableListener.java b/ntcore/src/main/java/edu/wpi/first/wpilibj/tables/ITableListener.java
new file mode 100644
index 0000000..b08312b
--- /dev/null
+++ b/ntcore/src/main/java/edu/wpi/first/wpilibj/tables/ITableListener.java
@@ -0,0 +1,44 @@
+/*----------------------------------------------------------------------------*/
+/* Copyright (c) 2017-2018 FIRST. All Rights Reserved. */
+/* Open Source Software - may be modified and shared by FRC teams. The code */
+/* must be accompanied by the FIRST BSD license file in the root directory of */
+/* the project. */
+/*----------------------------------------------------------------------------*/
+
+package edu.wpi.first.wpilibj.tables;
+
+/**
+ * A listener that listens to changes in values in a {@link ITable}.
+ * @deprecated Use Consumer<{@link edu.wpi.first.networktables.EntryNotification}>,
+ * {@link edu.wpi.first.networktables.TableEntryListener}, or
+ * {@link edu.wpi.first.networktables.TableListener} as appropriate.
+ */
+@FunctionalInterface
+@Deprecated
+@SuppressWarnings("checkstyle:all")
+public interface ITableListener {
+ /**
+ * Called when a key-value pair is changed in a {@link ITable}
+ * @param source the table the key-value pair exists in
+ * @param key the key associated with the value that changed
+ * @param value the new value
+ * @param isNew true if the key did not previously exist in the table, otherwise it is false
+ */
+ public void valueChanged(ITable source, String key, Object value, boolean isNew);
+
+ /**
+ * Extended version of valueChanged. Called when a key-value pair is
+ * changed in a {@link ITable}. The default implementation simply calls
+ * valueChanged(). If this is overridden, valueChanged() will not be
+ * called.
+ * @param source the table the key-value pair exists in
+ * @param key the key associated with the value that changed
+ * @param value the new value
+ * @param flags update flags; for example, NOTIFY_NEW if the key did not
+ * previously exist in the table
+ */
+ default public void valueChangedEx(ITable source, String key, Object value, int flags) {
+ // NOTIFY_NEW = 0x04
+ valueChanged(source, key, value, (flags & 0x04) != 0);
+ }
+}
diff --git a/ntcore/src/main/native/cpp/CallbackManager.h b/ntcore/src/main/native/cpp/CallbackManager.h
new file mode 100644
index 0000000..0fd9617
--- /dev/null
+++ b/ntcore/src/main/native/cpp/CallbackManager.h
@@ -0,0 +1,327 @@
+/*----------------------------------------------------------------------------*/
+/* Copyright (c) 2017-2018 FIRST. All Rights Reserved. */
+/* Open Source Software - may be modified and shared by FRC teams. The code */
+/* must be accompanied by the FIRST BSD license file in the root directory of */
+/* the project. */
+/*----------------------------------------------------------------------------*/
+
+#ifndef NTCORE_CALLBACKMANAGER_H_
+#define NTCORE_CALLBACKMANAGER_H_
+
+#include <atomic>
+#include <climits>
+#include <functional>
+#include <memory>
+#include <queue>
+#include <utility>
+#include <vector>
+
+#include <wpi/SafeThread.h>
+#include <wpi/UidVector.h>
+#include <wpi/condition_variable.h>
+#include <wpi/mutex.h>
+#include <wpi/raw_ostream.h>
+
+namespace nt {
+
+namespace impl {
+
+template <typename Callback>
+class ListenerData {
+ public:
+ ListenerData() = default;
+ explicit ListenerData(Callback callback_) : callback(callback_) {}
+ explicit ListenerData(unsigned int poller_uid_) : poller_uid(poller_uid_) {}
+
+ explicit operator bool() const { return callback || poller_uid != UINT_MAX; }
+
+ Callback callback;
+ unsigned int poller_uid = UINT_MAX;
+};
+
+// CRTP callback manager thread
+// @tparam Derived derived class
+// @tparam NotifierData data buffered for each callback
+// @tparam ListenerData data stored for each listener
+// Derived must define the following functions:
+// bool Matches(const ListenerData& listener, const NotifierData& data);
+// void SetListener(NotifierData* data, unsigned int listener_uid);
+// void DoCallback(Callback callback, const NotifierData& data);
+template <typename Derived, typename TUserInfo,
+ typename TListenerData =
+ ListenerData<std::function<void(const TUserInfo& info)>>,
+ typename TNotifierData = TUserInfo>
+class CallbackThread : public wpi::SafeThread {
+ public:
+ typedef TUserInfo UserInfo;
+ typedef TNotifierData NotifierData;
+ typedef TListenerData ListenerData;
+
+ ~CallbackThread() {
+ // Wake up any blocked pollers
+ for (size_t i = 0; i < m_pollers.size(); ++i) {
+ if (auto poller = m_pollers[i]) poller->Terminate();
+ }
+ }
+
+ void Main() override;
+
+ wpi::UidVector<ListenerData, 64> m_listeners;
+
+ std::queue<std::pair<unsigned int, NotifierData>> m_queue;
+ wpi::condition_variable m_queue_empty;
+
+ struct Poller {
+ void Terminate() {
+ {
+ std::lock_guard<wpi::mutex> lock(poll_mutex);
+ terminating = true;
+ }
+ poll_cond.notify_all();
+ }
+ std::queue<NotifierData> poll_queue;
+ wpi::mutex poll_mutex;
+ wpi::condition_variable poll_cond;
+ bool terminating = false;
+ bool cancelling = false;
+ };
+ wpi::UidVector<std::shared_ptr<Poller>, 64> m_pollers;
+
+ // Must be called with m_mutex held
+ template <typename... Args>
+ void SendPoller(unsigned int poller_uid, Args&&... args) {
+ if (poller_uid > m_pollers.size()) return;
+ auto poller = m_pollers[poller_uid];
+ if (!poller) return;
+ {
+ std::lock_guard<wpi::mutex> lock(poller->poll_mutex);
+ poller->poll_queue.emplace(std::forward<Args>(args)...);
+ }
+ poller->poll_cond.notify_one();
+ }
+};
+
+template <typename Derived, typename TUserInfo, typename TListenerData,
+ typename TNotifierData>
+void CallbackThread<Derived, TUserInfo, TListenerData, TNotifierData>::Main() {
+ std::unique_lock<wpi::mutex> lock(m_mutex);
+ while (m_active) {
+ while (m_queue.empty()) {
+ m_cond.wait(lock);
+ if (!m_active) return;
+ }
+
+ while (!m_queue.empty()) {
+ if (!m_active) return;
+ auto item = std::move(m_queue.front());
+
+ if (item.first != UINT_MAX) {
+ if (item.first < m_listeners.size()) {
+ auto& listener = m_listeners[item.first];
+ if (listener &&
+ static_cast<Derived*>(this)->Matches(listener, item.second)) {
+ static_cast<Derived*>(this)->SetListener(&item.second, item.first);
+ if (listener.callback) {
+ lock.unlock();
+ static_cast<Derived*>(this)->DoCallback(listener.callback,
+ item.second);
+ lock.lock();
+ } else if (listener.poller_uid != UINT_MAX) {
+ SendPoller(listener.poller_uid, std::move(item.second));
+ }
+ }
+ }
+ } else {
+ // Use index because iterator might get invalidated.
+ for (size_t i = 0; i < m_listeners.size(); ++i) {
+ auto& listener = m_listeners[i];
+ if (!listener) continue;
+ if (!static_cast<Derived*>(this)->Matches(listener, item.second))
+ continue;
+ static_cast<Derived*>(this)->SetListener(&item.second, i);
+ if (listener.callback) {
+ lock.unlock();
+ static_cast<Derived*>(this)->DoCallback(listener.callback,
+ item.second);
+ lock.lock();
+ } else if (listener.poller_uid != UINT_MAX) {
+ SendPoller(listener.poller_uid, item.second);
+ }
+ }
+ }
+ m_queue.pop();
+ }
+
+ m_queue_empty.notify_all();
+ }
+}
+
+} // namespace impl
+
+// CRTP callback manager
+// @tparam Derived derived class
+// @tparam Thread custom thread (must be derived from impl::CallbackThread)
+//
+// Derived must define the following functions:
+// void Start();
+template <typename Derived, typename Thread>
+class CallbackManager {
+ friend class RpcServerTest;
+
+ public:
+ void Stop() { m_owner.Stop(); }
+
+ void Remove(unsigned int listener_uid) {
+ auto thr = m_owner.GetThread();
+ if (!thr) return;
+ thr->m_listeners.erase(listener_uid);
+ }
+
+ unsigned int CreatePoller() {
+ static_cast<Derived*>(this)->Start();
+ auto thr = m_owner.GetThread();
+ return thr->m_pollers.emplace_back(
+ std::make_shared<typename Thread::Poller>());
+ }
+
+ void RemovePoller(unsigned int poller_uid) {
+ auto thr = m_owner.GetThread();
+ if (!thr) return;
+
+ // Remove any listeners that are associated with this poller
+ for (size_t i = 0; i < thr->m_listeners.size(); ++i) {
+ if (thr->m_listeners[i].poller_uid == poller_uid)
+ thr->m_listeners.erase(i);
+ }
+
+ // Wake up any blocked pollers
+ if (poller_uid >= thr->m_pollers.size()) return;
+ auto poller = thr->m_pollers[poller_uid];
+ if (!poller) return;
+ poller->Terminate();
+ return thr->m_pollers.erase(poller_uid);
+ }
+
+ bool WaitForQueue(double timeout) {
+ auto thr = m_owner.GetThread();
+ if (!thr) return true;
+
+ auto& lock = thr.GetLock();
+ auto timeout_time = std::chrono::steady_clock::now() +
+ std::chrono::duration<double>(timeout);
+ while (!thr->m_queue.empty()) {
+ if (!thr->m_active) return true;
+ if (timeout == 0) return false;
+ if (timeout < 0) {
+ thr->m_queue_empty.wait(lock);
+ } else {
+ auto cond_timed_out = thr->m_queue_empty.wait_until(lock, timeout_time);
+ if (cond_timed_out == std::cv_status::timeout) return false;
+ }
+ }
+
+ return true;
+ }
+
+ std::vector<typename Thread::UserInfo> Poll(unsigned int poller_uid) {
+ bool timed_out = false;
+ return Poll(poller_uid, -1, &timed_out);
+ }
+
+ std::vector<typename Thread::UserInfo> Poll(unsigned int poller_uid,
+ double timeout, bool* timed_out) {
+ std::vector<typename Thread::UserInfo> infos;
+ std::shared_ptr<typename Thread::Poller> poller;
+ {
+ auto thr = m_owner.GetThread();
+ if (!thr) return infos;
+ if (poller_uid > thr->m_pollers.size()) return infos;
+ poller = thr->m_pollers[poller_uid];
+ if (!poller) return infos;
+ }
+
+ std::unique_lock<wpi::mutex> lock(poller->poll_mutex);
+ auto timeout_time = std::chrono::steady_clock::now() +
+ std::chrono::duration<double>(timeout);
+ *timed_out = false;
+ while (poller->poll_queue.empty()) {
+ if (poller->terminating) return infos;
+ if (poller->cancelling) {
+ // Note: this only works if there's a single thread calling this
+ // function for any particular poller, but that's the intended use.
+ poller->cancelling = false;
+ return infos;
+ }
+ if (timeout == 0) {
+ *timed_out = true;
+ return infos;
+ }
+ if (timeout < 0) {
+ poller->poll_cond.wait(lock);
+ } else {
+ auto cond_timed_out = poller->poll_cond.wait_until(lock, timeout_time);
+ if (cond_timed_out == std::cv_status::timeout) {
+ *timed_out = true;
+ return infos;
+ }
+ }
+ }
+
+ while (!poller->poll_queue.empty()) {
+ infos.emplace_back(std::move(poller->poll_queue.front()));
+ poller->poll_queue.pop();
+ }
+ return infos;
+ }
+
+ void CancelPoll(unsigned int poller_uid) {
+ std::shared_ptr<typename Thread::Poller> poller;
+ {
+ auto thr = m_owner.GetThread();
+ if (!thr) return;
+ if (poller_uid > thr->m_pollers.size()) return;
+ poller = thr->m_pollers[poller_uid];
+ if (!poller) return;
+ }
+
+ {
+ std::lock_guard<wpi::mutex> lock(poller->poll_mutex);
+ poller->cancelling = true;
+ }
+ poller->poll_cond.notify_one();
+ }
+
+ protected:
+ template <typename... Args>
+ void DoStart(Args&&... args) {
+ m_owner.Start(std::forward<Args>(args)...);
+ }
+
+ template <typename... Args>
+ unsigned int DoAdd(Args&&... args) {
+ static_cast<Derived*>(this)->Start();
+ auto thr = m_owner.GetThread();
+ return thr->m_listeners.emplace_back(std::forward<Args>(args)...);
+ }
+
+ template <typename... Args>
+ void Send(unsigned int only_listener, Args&&... args) {
+ auto thr = m_owner.GetThread();
+ if (!thr || thr->m_listeners.empty()) return;
+ thr->m_queue.emplace(std::piecewise_construct,
+ std::make_tuple(only_listener),
+ std::forward_as_tuple(std::forward<Args>(args)...));
+ thr->m_cond.notify_one();
+ }
+
+ typename wpi::SafeThreadOwner<Thread>::Proxy GetThread() const {
+ return m_owner.GetThread();
+ }
+
+ private:
+ wpi::SafeThreadOwner<Thread> m_owner;
+};
+
+} // namespace nt
+
+#endif // NTCORE_CALLBACKMANAGER_H_
diff --git a/ntcore/src/main/native/cpp/ConnectionNotifier.cpp b/ntcore/src/main/native/cpp/ConnectionNotifier.cpp
new file mode 100644
index 0000000..340aad0
--- /dev/null
+++ b/ntcore/src/main/native/cpp/ConnectionNotifier.cpp
@@ -0,0 +1,29 @@
+/*----------------------------------------------------------------------------*/
+/* Copyright (c) 2015-2018 FIRST. All Rights Reserved. */
+/* Open Source Software - may be modified and shared by FRC teams. The code */
+/* must be accompanied by the FIRST BSD license file in the root directory of */
+/* the project. */
+/*----------------------------------------------------------------------------*/
+
+#include "ConnectionNotifier.h"
+
+using namespace nt;
+
+ConnectionNotifier::ConnectionNotifier(int inst) : m_inst(inst) {}
+
+void ConnectionNotifier::Start() { DoStart(m_inst); }
+
+unsigned int ConnectionNotifier::Add(
+ std::function<void(const ConnectionNotification& event)> callback) {
+ return DoAdd(callback);
+}
+
+unsigned int ConnectionNotifier::AddPolled(unsigned int poller_uid) {
+ return DoAdd(poller_uid);
+}
+
+void ConnectionNotifier::NotifyConnection(bool connected,
+ const ConnectionInfo& conn_info,
+ unsigned int only_listener) {
+ Send(only_listener, 0, connected, conn_info);
+}
diff --git a/ntcore/src/main/native/cpp/ConnectionNotifier.h b/ntcore/src/main/native/cpp/ConnectionNotifier.h
new file mode 100644
index 0000000..65eec06
--- /dev/null
+++ b/ntcore/src/main/native/cpp/ConnectionNotifier.h
@@ -0,0 +1,72 @@
+/*----------------------------------------------------------------------------*/
+/* Copyright (c) 2015-2018 FIRST. All Rights Reserved. */
+/* Open Source Software - may be modified and shared by FRC teams. The code */
+/* must be accompanied by the FIRST BSD license file in the root directory of */
+/* the project. */
+/*----------------------------------------------------------------------------*/
+
+#ifndef NTCORE_CONNECTIONNOTIFIER_H_
+#define NTCORE_CONNECTIONNOTIFIER_H_
+
+#include "CallbackManager.h"
+#include "Handle.h"
+#include "IConnectionNotifier.h"
+#include "ntcore_cpp.h"
+
+namespace nt {
+
+namespace impl {
+
+class ConnectionNotifierThread
+ : public CallbackThread<ConnectionNotifierThread, ConnectionNotification> {
+ public:
+ explicit ConnectionNotifierThread(int inst) : m_inst(inst) {}
+
+ bool Matches(const ListenerData& /*listener*/,
+ const ConnectionNotification& /*data*/) {
+ return true;
+ }
+
+ void SetListener(ConnectionNotification* data, unsigned int listener_uid) {
+ data->listener =
+ Handle(m_inst, listener_uid, Handle::kConnectionListener).handle();
+ }
+
+ void DoCallback(
+ std::function<void(const ConnectionNotification& event)> callback,
+ const ConnectionNotification& data) {
+ callback(data);
+ }
+
+ int m_inst;
+};
+
+} // namespace impl
+
+class ConnectionNotifier
+ : public IConnectionNotifier,
+ public CallbackManager<ConnectionNotifier,
+ impl::ConnectionNotifierThread> {
+ friend class ConnectionNotifierTest;
+ friend class CallbackManager<ConnectionNotifier,
+ impl::ConnectionNotifierThread>;
+
+ public:
+ explicit ConnectionNotifier(int inst);
+
+ void Start();
+
+ unsigned int Add(std::function<void(const ConnectionNotification& event)>
+ callback) override;
+ unsigned int AddPolled(unsigned int poller_uid) override;
+
+ void NotifyConnection(bool connected, const ConnectionInfo& conn_info,
+ unsigned int only_listener = UINT_MAX) override;
+
+ private:
+ int m_inst;
+};
+
+} // namespace nt
+
+#endif // NTCORE_CONNECTIONNOTIFIER_H_
diff --git a/ntcore/src/main/native/cpp/Dispatcher.cpp b/ntcore/src/main/native/cpp/Dispatcher.cpp
new file mode 100644
index 0000000..ea54fe4
--- /dev/null
+++ b/ntcore/src/main/native/cpp/Dispatcher.cpp
@@ -0,0 +1,641 @@
+/*----------------------------------------------------------------------------*/
+/* Copyright (c) 2015-2018 FIRST. All Rights Reserved. */
+/* Open Source Software - may be modified and shared by FRC teams. The code */
+/* must be accompanied by the FIRST BSD license file in the root directory of */
+/* the project. */
+/*----------------------------------------------------------------------------*/
+
+#include "Dispatcher.h"
+
+#include <algorithm>
+#include <iterator>
+
+#include <wpi/TCPAcceptor.h>
+#include <wpi/TCPConnector.h>
+
+#include "IConnectionNotifier.h"
+#include "IStorage.h"
+#include "Log.h"
+#include "NetworkConnection.h"
+
+using namespace nt;
+
+void Dispatcher::StartServer(const Twine& persist_filename,
+ const char* listen_address, unsigned int port) {
+ std::string listen_address_copy(StringRef(listen_address).trim());
+ DispatcherBase::StartServer(
+ persist_filename,
+ std::unique_ptr<wpi::NetworkAcceptor>(new wpi::TCPAcceptor(
+ static_cast<int>(port), listen_address_copy.c_str(), m_logger)));
+}
+
+void Dispatcher::SetServer(const char* server_name, unsigned int port) {
+ std::string server_name_copy(StringRef(server_name).trim());
+ SetConnector([=]() -> std::unique_ptr<wpi::NetworkStream> {
+ return wpi::TCPConnector::connect(server_name_copy.c_str(),
+ static_cast<int>(port), m_logger, 1);
+ });
+}
+
+void Dispatcher::SetServer(
+ ArrayRef<std::pair<StringRef, unsigned int>> servers) {
+ wpi::SmallVector<std::pair<std::string, int>, 16> servers_copy;
+ for (const auto& server : servers)
+ servers_copy.emplace_back(std::string{server.first.trim()},
+ static_cast<int>(server.second));
+
+ SetConnector([=]() -> std::unique_ptr<wpi::NetworkStream> {
+ wpi::SmallVector<std::pair<const char*, int>, 16> servers_copy2;
+ for (const auto& server : servers_copy)
+ servers_copy2.emplace_back(server.first.c_str(), server.second);
+ return wpi::TCPConnector::connect_parallel(servers_copy2, m_logger, 1);
+ });
+}
+
+void Dispatcher::SetServerTeam(unsigned int team, unsigned int port) {
+ std::pair<StringRef, unsigned int> servers[5];
+
+ // 10.te.am.2
+ wpi::SmallString<32> fixed;
+ {
+ wpi::raw_svector_ostream oss{fixed};
+ oss << "10." << static_cast<int>(team / 100) << '.'
+ << static_cast<int>(team % 100) << ".2";
+ servers[0] = std::make_pair(oss.str(), port);
+ }
+
+ // 172.22.11.2
+ servers[1] = std::make_pair("172.22.11.2", port);
+
+ // roboRIO-<team>-FRC.local
+ wpi::SmallString<32> mdns;
+ {
+ wpi::raw_svector_ostream oss{mdns};
+ oss << "roboRIO-" << team << "-FRC.local";
+ servers[2] = std::make_pair(oss.str(), port);
+ }
+
+ // roboRIO-<team>-FRC.lan
+ wpi::SmallString<32> mdns_lan;
+ {
+ wpi::raw_svector_ostream oss{mdns_lan};
+ oss << "roboRIO-" << team << "-FRC.lan";
+ servers[3] = std::make_pair(oss.str(), port);
+ }
+
+ // roboRIO-<team>-FRC.frc-field.local
+ wpi::SmallString<64> field_local;
+ {
+ wpi::raw_svector_ostream oss{field_local};
+ oss << "roboRIO-" << team << "-FRC.frc-field.local";
+ servers[4] = std::make_pair(oss.str(), port);
+ }
+
+ SetServer(servers);
+}
+
+void Dispatcher::SetServerOverride(const char* server_name, unsigned int port) {
+ std::string server_name_copy(StringRef(server_name).trim());
+ SetConnectorOverride([=]() -> std::unique_ptr<wpi::NetworkStream> {
+ return wpi::TCPConnector::connect(server_name_copy.c_str(),
+ static_cast<int>(port), m_logger, 1);
+ });
+}
+
+void Dispatcher::ClearServerOverride() { ClearConnectorOverride(); }
+
+DispatcherBase::DispatcherBase(IStorage& storage, IConnectionNotifier& notifier,
+ wpi::Logger& logger)
+ : m_storage(storage), m_notifier(notifier), m_logger(logger) {
+ m_active = false;
+ m_update_rate = 100;
+}
+
+DispatcherBase::~DispatcherBase() { Stop(); }
+
+unsigned int DispatcherBase::GetNetworkMode() const { return m_networkMode; }
+
+void DispatcherBase::StartServer(
+ const Twine& persist_filename,
+ std::unique_ptr<wpi::NetworkAcceptor> acceptor) {
+ {
+ std::lock_guard<wpi::mutex> lock(m_user_mutex);
+ if (m_active) return;
+ m_active = true;
+ }
+ m_networkMode = NT_NET_MODE_SERVER | NT_NET_MODE_STARTING;
+ m_persist_filename = persist_filename.str();
+ m_server_acceptor = std::move(acceptor);
+
+ // Load persistent file. Ignore errors, but pass along warnings.
+ if (!persist_filename.isTriviallyEmpty() &&
+ (!persist_filename.isSingleStringRef() ||
+ !persist_filename.getSingleStringRef().empty())) {
+ bool first = true;
+ m_storage.LoadPersistent(
+ persist_filename, [&](size_t line, const char* msg) {
+ if (first) {
+ first = false;
+ WARNING("When reading initial persistent values from '"
+ << persist_filename << "':");
+ }
+ WARNING(persist_filename << ":" << line << ": " << msg);
+ });
+ }
+
+ m_storage.SetDispatcher(this, true);
+
+ m_dispatch_thread = std::thread(&Dispatcher::DispatchThreadMain, this);
+ m_clientserver_thread = std::thread(&Dispatcher::ServerThreadMain, this);
+}
+
+void DispatcherBase::StartClient() {
+ {
+ std::lock_guard<wpi::mutex> lock(m_user_mutex);
+ if (m_active) return;
+ m_active = true;
+ }
+ m_networkMode = NT_NET_MODE_CLIENT | NT_NET_MODE_STARTING;
+ m_storage.SetDispatcher(this, false);
+
+ m_dispatch_thread = std::thread(&Dispatcher::DispatchThreadMain, this);
+ m_clientserver_thread = std::thread(&Dispatcher::ClientThreadMain, this);
+}
+
+void DispatcherBase::Stop() {
+ m_active = false;
+
+ // wake up dispatch thread with a flush
+ m_flush_cv.notify_one();
+
+ // wake up client thread with a reconnect
+ {
+ std::lock_guard<wpi::mutex> lock(m_user_mutex);
+ m_client_connector = nullptr;
+ }
+ ClientReconnect();
+
+ // wake up server thread by shutting down the socket
+ if (m_server_acceptor) m_server_acceptor->shutdown();
+
+ // join threads, with timeout
+ if (m_dispatch_thread.joinable()) m_dispatch_thread.join();
+ if (m_clientserver_thread.joinable()) m_clientserver_thread.join();
+
+ std::vector<std::shared_ptr<INetworkConnection>> conns;
+ {
+ std::lock_guard<wpi::mutex> lock(m_user_mutex);
+ conns.swap(m_connections);
+ }
+
+ // close all connections
+ conns.resize(0);
+}
+
+void DispatcherBase::SetUpdateRate(double interval) {
+ // don't allow update rates faster than 10 ms or slower than 1 second
+ if (interval < 0.01)
+ interval = 0.01;
+ else if (interval > 1.0)
+ interval = 1.0;
+ m_update_rate = static_cast<unsigned int>(interval * 1000);
+}
+
+void DispatcherBase::SetIdentity(const Twine& name) {
+ std::lock_guard<wpi::mutex> lock(m_user_mutex);
+ m_identity = name.str();
+}
+
+void DispatcherBase::Flush() {
+ auto now = std::chrono::steady_clock::now();
+ {
+ std::lock_guard<wpi::mutex> lock(m_flush_mutex);
+ // don't allow flushes more often than every 10 ms
+ if ((now - m_last_flush) < std::chrono::milliseconds(10)) return;
+ m_last_flush = now;
+ m_do_flush = true;
+ }
+ m_flush_cv.notify_one();
+}
+
+std::vector<ConnectionInfo> DispatcherBase::GetConnections() const {
+ std::vector<ConnectionInfo> conns;
+ if (!m_active) return conns;
+
+ std::lock_guard<wpi::mutex> lock(m_user_mutex);
+ for (auto& conn : m_connections) {
+ if (conn->state() != NetworkConnection::kActive) continue;
+ conns.emplace_back(conn->info());
+ }
+
+ return conns;
+}
+
+bool DispatcherBase::IsConnected() const {
+ if (!m_active) return false;
+
+ std::lock_guard<wpi::mutex> lock(m_user_mutex);
+ for (auto& conn : m_connections) {
+ if (conn->state() == NetworkConnection::kActive) return true;
+ }
+
+ return false;
+}
+
+unsigned int DispatcherBase::AddListener(
+ std::function<void(const ConnectionNotification& event)> callback,
+ bool immediate_notify) const {
+ std::lock_guard<wpi::mutex> lock(m_user_mutex);
+ unsigned int uid = m_notifier.Add(callback);
+ // perform immediate notifications
+ if (immediate_notify) {
+ for (auto& conn : m_connections) {
+ if (conn->state() != NetworkConnection::kActive) continue;
+ m_notifier.NotifyConnection(true, conn->info(), uid);
+ }
+ }
+ return uid;
+}
+
+unsigned int DispatcherBase::AddPolledListener(unsigned int poller_uid,
+ bool immediate_notify) const {
+ std::lock_guard<wpi::mutex> lock(m_user_mutex);
+ unsigned int uid = m_notifier.AddPolled(poller_uid);
+ // perform immediate notifications
+ if (immediate_notify) {
+ for (auto& conn : m_connections) {
+ if (conn->state() != NetworkConnection::kActive) continue;
+ m_notifier.NotifyConnection(true, conn->info(), uid);
+ }
+ }
+ return uid;
+}
+
+void DispatcherBase::SetConnector(Connector connector) {
+ std::lock_guard<wpi::mutex> lock(m_user_mutex);
+ m_client_connector = std::move(connector);
+}
+
+void DispatcherBase::SetConnectorOverride(Connector connector) {
+ std::lock_guard<wpi::mutex> lock(m_user_mutex);
+ m_client_connector_override = std::move(connector);
+}
+
+void DispatcherBase::ClearConnectorOverride() {
+ std::lock_guard<wpi::mutex> lock(m_user_mutex);
+ m_client_connector_override = nullptr;
+}
+
+void DispatcherBase::DispatchThreadMain() {
+ auto timeout_time = std::chrono::steady_clock::now();
+
+ static const auto save_delta_time = std::chrono::seconds(1);
+ auto next_save_time = timeout_time + save_delta_time;
+
+ int count = 0;
+
+ while (m_active) {
+ // handle loop taking too long
+ auto start = std::chrono::steady_clock::now();
+ if (start > timeout_time) timeout_time = start;
+
+ // wait for periodic or when flushed
+ timeout_time += std::chrono::milliseconds(m_update_rate);
+ std::unique_lock<wpi::mutex> flush_lock(m_flush_mutex);
+ m_flush_cv.wait_until(flush_lock, timeout_time,
+ [&] { return !m_active || m_do_flush; });
+ m_do_flush = false;
+ flush_lock.unlock();
+ if (!m_active) break; // in case we were woken up to terminate
+
+ // perform periodic persistent save
+ if ((m_networkMode & NT_NET_MODE_SERVER) != 0 &&
+ !m_persist_filename.empty() && start > next_save_time) {
+ next_save_time += save_delta_time;
+ // handle loop taking too long
+ if (start > next_save_time) next_save_time = start + save_delta_time;
+ const char* err = m_storage.SavePersistent(m_persist_filename, true);
+ if (err) WARNING("periodic persistent save: " << err);
+ }
+
+ {
+ std::lock_guard<wpi::mutex> user_lock(m_user_mutex);
+ bool reconnect = false;
+
+ if (++count > 10) {
+ DEBUG("dispatch running " << m_connections.size() << " connections");
+ count = 0;
+ }
+
+ for (auto& conn : m_connections) {
+ // post outgoing messages if connection is active
+ // only send keep-alives on client
+ if (conn->state() == NetworkConnection::kActive)
+ conn->PostOutgoing((m_networkMode & NT_NET_MODE_CLIENT) != 0);
+
+ // if client, reconnect if connection died
+ if ((m_networkMode & NT_NET_MODE_CLIENT) != 0 &&
+ conn->state() == NetworkConnection::kDead)
+ reconnect = true;
+ }
+ // reconnect if we disconnected (and a reconnect is not in progress)
+ if (reconnect && !m_do_reconnect) {
+ m_do_reconnect = true;
+ m_reconnect_cv.notify_one();
+ }
+ }
+ }
+}
+
+void DispatcherBase::QueueOutgoing(std::shared_ptr<Message> msg,
+ INetworkConnection* only,
+ INetworkConnection* except) {
+ std::lock_guard<wpi::mutex> user_lock(m_user_mutex);
+ for (auto& conn : m_connections) {
+ if (conn.get() == except) continue;
+ if (only && conn.get() != only) continue;
+ auto state = conn->state();
+ if (state != NetworkConnection::kSynchronized &&
+ state != NetworkConnection::kActive)
+ continue;
+ conn->QueueOutgoing(msg);
+ }
+}
+
+void DispatcherBase::ServerThreadMain() {
+ if (m_server_acceptor->start() != 0) {
+ m_active = false;
+ m_networkMode = NT_NET_MODE_SERVER | NT_NET_MODE_FAILURE;
+ return;
+ }
+ m_networkMode = NT_NET_MODE_SERVER;
+ while (m_active) {
+ auto stream = m_server_acceptor->accept();
+ if (!stream) {
+ m_active = false;
+ return;
+ }
+ if (!m_active) {
+ m_networkMode = NT_NET_MODE_NONE;
+ return;
+ }
+ DEBUG("server: client connection from " << stream->getPeerIP() << " port "
+ << stream->getPeerPort());
+
+ // add to connections list
+ using namespace std::placeholders;
+ auto conn = std::make_shared<NetworkConnection>(
+ ++m_connections_uid, std::move(stream), m_notifier, m_logger,
+ std::bind(&Dispatcher::ServerHandshake, this, _1, _2, _3),
+ std::bind(&IStorage::GetMessageEntryType, &m_storage, _1));
+ conn->set_process_incoming(
+ std::bind(&IStorage::ProcessIncoming, &m_storage, _1, _2,
+ std::weak_ptr<NetworkConnection>(conn)));
+ {
+ std::lock_guard<wpi::mutex> lock(m_user_mutex);
+ // reuse dead connection slots
+ bool placed = false;
+ for (auto& c : m_connections) {
+ if (c->state() == NetworkConnection::kDead) {
+ c = conn;
+ placed = true;
+ break;
+ }
+ }
+ if (!placed) m_connections.emplace_back(conn);
+ conn->Start();
+ }
+ }
+ m_networkMode = NT_NET_MODE_NONE;
+}
+
+void DispatcherBase::ClientThreadMain() {
+ while (m_active) {
+ // sleep between retries
+ std::this_thread::sleep_for(std::chrono::milliseconds(250));
+ Connector connect;
+
+ // get next server to connect to
+ {
+ std::lock_guard<wpi::mutex> lock(m_user_mutex);
+ if (m_client_connector_override) {
+ connect = m_client_connector_override;
+ } else {
+ if (!m_client_connector) {
+ m_networkMode = NT_NET_MODE_CLIENT | NT_NET_MODE_FAILURE;
+ continue;
+ }
+ connect = m_client_connector;
+ }
+ }
+
+ // try to connect (with timeout)
+ DEBUG("client trying to connect");
+ auto stream = connect();
+ if (!stream) {
+ m_networkMode = NT_NET_MODE_CLIENT | NT_NET_MODE_FAILURE;
+ continue; // keep retrying
+ }
+ DEBUG("client connected");
+ m_networkMode = NT_NET_MODE_CLIENT;
+
+ std::unique_lock<wpi::mutex> lock(m_user_mutex);
+ using namespace std::placeholders;
+ auto conn = std::make_shared<NetworkConnection>(
+ ++m_connections_uid, std::move(stream), m_notifier, m_logger,
+ std::bind(&Dispatcher::ClientHandshake, this, _1, _2, _3),
+ std::bind(&IStorage::GetMessageEntryType, &m_storage, _1));
+ conn->set_process_incoming(
+ std::bind(&IStorage::ProcessIncoming, &m_storage, _1, _2,
+ std::weak_ptr<NetworkConnection>(conn)));
+ m_connections.resize(0); // disconnect any current
+ m_connections.emplace_back(conn);
+ conn->set_proto_rev(m_reconnect_proto_rev);
+ conn->Start();
+
+ // reconnect the next time starting with latest protocol revision
+ m_reconnect_proto_rev = 0x0300;
+
+ // block until told to reconnect
+ m_do_reconnect = false;
+ m_reconnect_cv.wait(lock, [&] { return !m_active || m_do_reconnect; });
+ }
+ m_networkMode = NT_NET_MODE_NONE;
+}
+
+bool DispatcherBase::ClientHandshake(
+ NetworkConnection& conn, std::function<std::shared_ptr<Message>()> get_msg,
+ std::function<void(wpi::ArrayRef<std::shared_ptr<Message>>)> send_msgs) {
+ // get identity
+ std::string self_id;
+ {
+ std::lock_guard<wpi::mutex> lock(m_user_mutex);
+ self_id = m_identity;
+ }
+
+ // send client hello
+ DEBUG("client: sending hello");
+ send_msgs(Message::ClientHello(self_id));
+
+ // wait for response
+ auto msg = get_msg();
+ if (!msg) {
+ // disconnected, retry
+ DEBUG("client: server disconnected before first response");
+ return false;
+ }
+
+ if (msg->Is(Message::kProtoUnsup)) {
+ if (msg->id() == 0x0200) ClientReconnect(0x0200);
+ return false;
+ }
+
+ bool new_server = true;
+ if (conn.proto_rev() >= 0x0300) {
+ // should be server hello; if not, disconnect.
+ if (!msg->Is(Message::kServerHello)) return false;
+ conn.set_remote_id(msg->str());
+ if ((msg->flags() & 1) != 0) new_server = false;
+ // get the next message
+ msg = get_msg();
+ }
+
+ // receive initial assignments
+ std::vector<std::shared_ptr<Message>> incoming;
+ for (;;) {
+ if (!msg) {
+ // disconnected, retry
+ DEBUG("client: server disconnected during initial entries");
+ return false;
+ }
+ DEBUG4("received init str=" << msg->str() << " id=" << msg->id()
+ << " seq_num=" << msg->seq_num_uid());
+ if (msg->Is(Message::kServerHelloDone)) break;
+ // shouldn't receive a keep alive, but handle gracefully
+ if (msg->Is(Message::kKeepAlive)) {
+ msg = get_msg();
+ continue;
+ }
+ if (!msg->Is(Message::kEntryAssign)) {
+ // unexpected message
+ DEBUG("client: received message ("
+ << msg->type()
+ << ") other than entry assignment during initial handshake");
+ return false;
+ }
+ incoming.emplace_back(std::move(msg));
+ // get the next message
+ msg = get_msg();
+ }
+
+ // generate outgoing assignments
+ NetworkConnection::Outgoing outgoing;
+
+ m_storage.ApplyInitialAssignments(conn, incoming, new_server, &outgoing);
+
+ if (conn.proto_rev() >= 0x0300)
+ outgoing.emplace_back(Message::ClientHelloDone());
+
+ if (!outgoing.empty()) send_msgs(outgoing);
+
+ INFO("client: CONNECTED to server " << conn.stream().getPeerIP() << " port "
+ << conn.stream().getPeerPort());
+ return true;
+}
+
+bool DispatcherBase::ServerHandshake(
+ NetworkConnection& conn, std::function<std::shared_ptr<Message>()> get_msg,
+ std::function<void(wpi::ArrayRef<std::shared_ptr<Message>>)> send_msgs) {
+ // Wait for the client to send us a hello.
+ auto msg = get_msg();
+ if (!msg) {
+ DEBUG("server: client disconnected before sending hello");
+ return false;
+ }
+ if (!msg->Is(Message::kClientHello)) {
+ DEBUG("server: client initial message was not client hello");
+ return false;
+ }
+
+ // Check that the client requested version is not too high.
+ unsigned int proto_rev = msg->id();
+ if (proto_rev > 0x0300) {
+ DEBUG("server: client requested proto > 0x0300");
+ send_msgs(Message::ProtoUnsup());
+ return false;
+ }
+
+ if (proto_rev >= 0x0300) conn.set_remote_id(msg->str());
+
+ // Set the proto version to the client requested version
+ DEBUG("server: client protocol " << proto_rev);
+ conn.set_proto_rev(proto_rev);
+
+ // Send initial set of assignments
+ NetworkConnection::Outgoing outgoing;
+
+ // Start with server hello. TODO: initial connection flag
+ if (proto_rev >= 0x0300) {
+ std::lock_guard<wpi::mutex> lock(m_user_mutex);
+ outgoing.emplace_back(Message::ServerHello(0u, m_identity));
+ }
+
+ // Get snapshot of initial assignments
+ m_storage.GetInitialAssignments(conn, &outgoing);
+
+ // Finish with server hello done
+ outgoing.emplace_back(Message::ServerHelloDone());
+
+ // Batch transmit
+ DEBUG("server: sending initial assignments");
+ send_msgs(outgoing);
+
+ // In proto rev 3.0 and later, the handshake concludes with a client hello
+ // done message, so we can batch the assigns before marking the connection
+ // active. In pre-3.0, we need to just immediately mark it active and hand
+ // off control to the dispatcher to assign them as they arrive.
+ if (proto_rev >= 0x0300) {
+ // receive client initial assignments
+ std::vector<std::shared_ptr<Message>> incoming;
+ msg = get_msg();
+ for (;;) {
+ if (!msg) {
+ // disconnected, retry
+ DEBUG("server: disconnected waiting for initial entries");
+ return false;
+ }
+ if (msg->Is(Message::kClientHelloDone)) break;
+ // shouldn't receive a keep alive, but handle gracefully
+ if (msg->Is(Message::kKeepAlive)) {
+ msg = get_msg();
+ continue;
+ }
+ if (!msg->Is(Message::kEntryAssign)) {
+ // unexpected message
+ DEBUG("server: received message ("
+ << msg->type()
+ << ") other than entry assignment during initial handshake");
+ return false;
+ }
+ incoming.push_back(msg);
+ // get the next message (blocks)
+ msg = get_msg();
+ }
+ for (auto& msg : incoming)
+ m_storage.ProcessIncoming(msg, &conn, std::weak_ptr<NetworkConnection>());
+ }
+
+ INFO("server: client CONNECTED: " << conn.stream().getPeerIP() << " port "
+ << conn.stream().getPeerPort());
+ return true;
+}
+
+void DispatcherBase::ClientReconnect(unsigned int proto_rev) {
+ if ((m_networkMode & NT_NET_MODE_SERVER) != 0) return;
+ {
+ std::lock_guard<wpi::mutex> lock(m_user_mutex);
+ m_reconnect_proto_rev = proto_rev;
+ m_do_reconnect = true;
+ }
+ m_reconnect_cv.notify_one();
+}
diff --git a/ntcore/src/main/native/cpp/Dispatcher.h b/ntcore/src/main/native/cpp/Dispatcher.h
new file mode 100644
index 0000000..dfaf4d5
--- /dev/null
+++ b/ntcore/src/main/native/cpp/Dispatcher.h
@@ -0,0 +1,151 @@
+/*----------------------------------------------------------------------------*/
+/* Copyright (c) 2015-2018 FIRST. All Rights Reserved. */
+/* Open Source Software - may be modified and shared by FRC teams. The code */
+/* must be accompanied by the FIRST BSD license file in the root directory of */
+/* the project. */
+/*----------------------------------------------------------------------------*/
+
+#ifndef NTCORE_DISPATCHER_H_
+#define NTCORE_DISPATCHER_H_
+
+#include <atomic>
+#include <chrono>
+#include <functional>
+#include <memory>
+#include <string>
+#include <thread>
+#include <utility>
+#include <vector>
+
+#include <wpi/StringRef.h>
+#include <wpi/Twine.h>
+#include <wpi/condition_variable.h>
+#include <wpi/mutex.h>
+
+#include "IDispatcher.h"
+#include "INetworkConnection.h"
+
+namespace wpi {
+class Logger;
+class NetworkAcceptor;
+class NetworkStream;
+} // namespace wpi
+
+namespace nt {
+
+class IConnectionNotifier;
+class IStorage;
+class NetworkConnection;
+
+class DispatcherBase : public IDispatcher {
+ friend class DispatcherTest;
+
+ public:
+ typedef std::function<std::unique_ptr<wpi::NetworkStream>()> Connector;
+
+ DispatcherBase(IStorage& storage, IConnectionNotifier& notifier,
+ wpi::Logger& logger);
+ virtual ~DispatcherBase();
+
+ unsigned int GetNetworkMode() const;
+ void StartServer(const Twine& persist_filename,
+ std::unique_ptr<wpi::NetworkAcceptor> acceptor);
+ void StartClient();
+ void Stop();
+ void SetUpdateRate(double interval);
+ void SetIdentity(const Twine& name);
+ void Flush();
+ std::vector<ConnectionInfo> GetConnections() const;
+ bool IsConnected() const;
+
+ unsigned int AddListener(
+ std::function<void(const ConnectionNotification& event)> callback,
+ bool immediate_notify) const;
+ unsigned int AddPolledListener(unsigned int poller_uid,
+ bool immediate_notify) const;
+
+ void SetConnector(Connector connector);
+ void SetConnectorOverride(Connector connector);
+ void ClearConnectorOverride();
+
+ bool active() const { return m_active; }
+
+ DispatcherBase(const DispatcherBase&) = delete;
+ DispatcherBase& operator=(const DispatcherBase&) = delete;
+
+ private:
+ void DispatchThreadMain();
+ void ServerThreadMain();
+ void ClientThreadMain();
+
+ bool ClientHandshake(
+ NetworkConnection& conn,
+ std::function<std::shared_ptr<Message>()> get_msg,
+ std::function<void(wpi::ArrayRef<std::shared_ptr<Message>>)> send_msgs);
+ bool ServerHandshake(
+ NetworkConnection& conn,
+ std::function<std::shared_ptr<Message>()> get_msg,
+ std::function<void(wpi::ArrayRef<std::shared_ptr<Message>>)> send_msgs);
+
+ void ClientReconnect(unsigned int proto_rev = 0x0300);
+
+ void QueueOutgoing(std::shared_ptr<Message> msg, INetworkConnection* only,
+ INetworkConnection* except) override;
+
+ IStorage& m_storage;
+ IConnectionNotifier& m_notifier;
+ unsigned int m_networkMode = NT_NET_MODE_NONE;
+ std::string m_persist_filename;
+ std::thread m_dispatch_thread;
+ std::thread m_clientserver_thread;
+
+ std::unique_ptr<wpi::NetworkAcceptor> m_server_acceptor;
+ Connector m_client_connector_override;
+ Connector m_client_connector;
+ uint8_t m_connections_uid = 0;
+
+ // Mutex for user-accessible items
+ mutable wpi::mutex m_user_mutex;
+ std::vector<std::shared_ptr<INetworkConnection>> m_connections;
+ std::string m_identity;
+
+ std::atomic_bool m_active; // set to false to terminate threads
+ std::atomic_uint m_update_rate; // periodic dispatch update rate, in ms
+
+ // Condition variable for forced dispatch wakeup (flush)
+ wpi::mutex m_flush_mutex;
+ wpi::condition_variable m_flush_cv;
+ std::chrono::steady_clock::time_point m_last_flush;
+ bool m_do_flush = false;
+
+ // Condition variable for client reconnect (uses user mutex)
+ wpi::condition_variable m_reconnect_cv;
+ unsigned int m_reconnect_proto_rev = 0x0300;
+ bool m_do_reconnect = true;
+
+ protected:
+ wpi::Logger& m_logger;
+};
+
+class Dispatcher : public DispatcherBase {
+ friend class DispatcherTest;
+
+ public:
+ Dispatcher(IStorage& storage, IConnectionNotifier& notifier,
+ wpi::Logger& logger)
+ : DispatcherBase(storage, notifier, logger) {}
+
+ void StartServer(const Twine& persist_filename, const char* listen_address,
+ unsigned int port);
+
+ void SetServer(const char* server_name, unsigned int port);
+ void SetServer(ArrayRef<std::pair<StringRef, unsigned int>> servers);
+ void SetServerTeam(unsigned int team, unsigned int port);
+
+ void SetServerOverride(const char* server_name, unsigned int port);
+ void ClearServerOverride();
+};
+
+} // namespace nt
+
+#endif // NTCORE_DISPATCHER_H_
diff --git a/ntcore/src/main/native/cpp/DsClient.cpp b/ntcore/src/main/native/cpp/DsClient.cpp
new file mode 100644
index 0000000..8b97be7
--- /dev/null
+++ b/ntcore/src/main/native/cpp/DsClient.cpp
@@ -0,0 +1,153 @@
+/*----------------------------------------------------------------------------*/
+/* Copyright (c) 2015-2018 FIRST. All Rights Reserved. */
+/* Open Source Software - may be modified and shared by FRC teams. The code */
+/* must be accompanied by the FIRST BSD license file in the root directory of */
+/* the project. */
+/*----------------------------------------------------------------------------*/
+
+#include "DsClient.h"
+
+#include <wpi/SmallString.h>
+#include <wpi/TCPConnector.h>
+#include <wpi/raw_ostream.h>
+#include <wpi/raw_socket_istream.h>
+
+#include "Dispatcher.h"
+#include "Log.h"
+
+using namespace nt;
+
+class DsClient::Thread : public wpi::SafeThread {
+ public:
+ Thread(Dispatcher& dispatcher, wpi::Logger& logger, unsigned int port)
+ : m_dispatcher(dispatcher), m_logger(logger), m_port(port) {}
+
+ void Main();
+
+ Dispatcher& m_dispatcher;
+ wpi::Logger& m_logger;
+ unsigned int m_port;
+ std::unique_ptr<wpi::NetworkStream> m_stream;
+};
+
+DsClient::DsClient(Dispatcher& dispatcher, wpi::Logger& logger)
+ : m_dispatcher(dispatcher), m_logger(logger) {}
+
+void DsClient::Start(unsigned int port) {
+ auto thr = m_owner.GetThread();
+ if (!thr)
+ m_owner.Start(m_dispatcher, m_logger, port);
+ else
+ thr->m_port = port;
+}
+
+void DsClient::Stop() {
+ {
+ // Close the stream so the read (if any) terminates.
+ auto thr = m_owner.GetThread();
+ if (thr) {
+ thr->m_active = false;
+ if (thr->m_stream) thr->m_stream->close();
+ }
+ }
+ m_owner.Stop();
+}
+
+void DsClient::Thread::Main() {
+ unsigned int oldip = 0;
+ wpi::Logger nolog; // to silence log messages from TCPConnector
+
+ while (m_active) {
+ // wait for periodic reconnect or termination
+ auto timeout_time =
+ std::chrono::steady_clock::now() + std::chrono::milliseconds(500);
+ unsigned int port;
+ {
+ std::unique_lock<wpi::mutex> lock(m_mutex);
+ m_cond.wait_until(lock, timeout_time, [&] { return !m_active; });
+ port = m_port;
+ }
+ if (!m_active) goto done;
+
+ // Try to connect to DS on the local machine
+ m_stream = wpi::TCPConnector::connect("127.0.0.1", 1742, nolog, 1);
+ if (!m_active) goto done;
+ if (!m_stream) continue;
+
+ DEBUG3("connected to DS");
+ wpi::raw_socket_istream is(*m_stream);
+
+ while (m_active && !is.has_error()) {
+ // Read JSON "{...}". This is very limited, does not handle quoted "}" or
+ // nested {}, but is sufficient for this purpose.
+ wpi::SmallString<128> json;
+ char ch;
+
+ // Throw away characters until {
+ do {
+ is.read(ch);
+ if (is.has_error()) break;
+ if (!m_active) goto done;
+ } while (ch != '{');
+ json += '{';
+
+ if (is.has_error()) {
+ m_stream = nullptr;
+ break;
+ }
+
+ // Read characters until }
+ do {
+ is.read(ch);
+ if (is.has_error()) break;
+ if (!m_active) goto done;
+ json += ch;
+ } while (ch != '}');
+
+ if (is.has_error()) {
+ m_stream = nullptr;
+ break;
+ }
+ DEBUG3("json=" << json);
+
+ // Look for "robotIP":12345, and get 12345 portion
+ size_t pos = json.find("\"robotIP\"");
+ if (pos == wpi::StringRef::npos) continue; // could not find?
+ pos += 9;
+ pos = json.find(':', pos);
+ if (pos == wpi::StringRef::npos) continue; // could not find?
+ size_t endpos = json.find_first_not_of("0123456789", pos + 1);
+ DEBUG3("found robotIP=" << json.slice(pos + 1, endpos));
+
+ // Parse into number
+ unsigned int ip = 0;
+ if (json.slice(pos + 1, endpos).getAsInteger(10, ip)) continue; // error
+
+ // If zero, clear the server override
+ if (ip == 0) {
+ m_dispatcher.ClearServerOverride();
+ oldip = 0;
+ continue;
+ }
+
+ // If unchanged, don't reconnect
+ if (ip == oldip) continue;
+ oldip = ip;
+
+ // Convert number into dotted quad
+ json.clear();
+ wpi::raw_svector_ostream os{json};
+ os << ((ip >> 24) & 0xff) << "." << ((ip >> 16) & 0xff) << "."
+ << ((ip >> 8) & 0xff) << "." << (ip & 0xff);
+ INFO("client: DS overriding server IP to " << os.str());
+ m_dispatcher.SetServerOverride(json.c_str(), port);
+ }
+
+ // We disconnected from the DS, clear the server override
+ m_dispatcher.ClearServerOverride();
+ oldip = 0;
+ }
+
+done:
+ m_dispatcher.ClearServerOverride();
+}
diff --git a/ntcore/src/main/native/cpp/DsClient.h b/ntcore/src/main/native/cpp/DsClient.h
new file mode 100644
index 0000000..ad13d25
--- /dev/null
+++ b/ntcore/src/main/native/cpp/DsClient.h
@@ -0,0 +1,36 @@
+/*----------------------------------------------------------------------------*/
+/* Copyright (c) 2016-2018 FIRST. All Rights Reserved. */
+/* Open Source Software - may be modified and shared by FRC teams. The code */
+/* must be accompanied by the FIRST BSD license file in the root directory of */
+/* the project. */
+/*----------------------------------------------------------------------------*/
+
+#ifndef NTCORE_DSCLIENT_H_
+#define NTCORE_DSCLIENT_H_
+
+#include <wpi/SafeThread.h>
+
+#include "Log.h"
+
+namespace nt {
+
+class Dispatcher;
+
+class DsClient {
+ public:
+ DsClient(Dispatcher& dispatcher, wpi::Logger& logger);
+ ~DsClient() = default;
+
+ void Start(unsigned int port);
+ void Stop();
+
+ private:
+ class Thread;
+ wpi::SafeThreadOwner<Thread> m_owner;
+ Dispatcher& m_dispatcher;
+ wpi::Logger& m_logger;
+};
+
+} // namespace nt
+
+#endif // NTCORE_DSCLIENT_H_
diff --git a/ntcore/src/main/native/cpp/EntryNotifier.cpp b/ntcore/src/main/native/cpp/EntryNotifier.cpp
new file mode 100644
index 0000000..5fde687
--- /dev/null
+++ b/ntcore/src/main/native/cpp/EntryNotifier.cpp
@@ -0,0 +1,89 @@
+/*----------------------------------------------------------------------------*/
+/* Copyright (c) 2015-2018 FIRST. All Rights Reserved. */
+/* Open Source Software - may be modified and shared by FRC teams. The code */
+/* must be accompanied by the FIRST BSD license file in the root directory of */
+/* the project. */
+/*----------------------------------------------------------------------------*/
+
+#include "EntryNotifier.h"
+
+#include "Log.h"
+
+using namespace nt;
+
+EntryNotifier::EntryNotifier(int inst, wpi::Logger& logger)
+ : m_inst(inst), m_logger(logger) {
+ m_local_notifiers = false;
+}
+
+void EntryNotifier::Start() { DoStart(m_inst); }
+
+bool EntryNotifier::local_notifiers() const { return m_local_notifiers; }
+
+bool impl::EntryNotifierThread::Matches(const EntryListenerData& listener,
+ const EntryNotification& data) {
+ if (!data.value) return false;
+
+ // Flags must be within requested flag set for this listener.
+ // Because assign messages can result in both a value and flags update,
+ // we handle that case specially.
+ unsigned int listen_flags =
+ listener.flags & ~(NT_NOTIFY_IMMEDIATE | NT_NOTIFY_LOCAL);
+ unsigned int flags = data.flags & ~(NT_NOTIFY_IMMEDIATE | NT_NOTIFY_LOCAL);
+ unsigned int assign_both = NT_NOTIFY_UPDATE | NT_NOTIFY_FLAGS;
+ if ((flags & assign_both) == assign_both) {
+ if ((listen_flags & assign_both) == 0) return false;
+ listen_flags &= ~assign_both;
+ flags &= ~assign_both;
+ }
+ if ((flags & ~listen_flags) != 0) return false;
+
+ // must match local id or prefix
+ if (listener.entry != 0 && data.entry != listener.entry) return false;
+ if (listener.entry == 0 &&
+ !wpi::StringRef(data.name).startswith(listener.prefix))
+ return false;
+
+ return true;
+}
+
+unsigned int EntryNotifier::Add(
+ std::function<void(const EntryNotification& event)> callback,
+ StringRef prefix, unsigned int flags) {
+ if ((flags & NT_NOTIFY_LOCAL) != 0) m_local_notifiers = true;
+ return DoAdd(callback, prefix, flags);
+}
+
+unsigned int EntryNotifier::Add(
+ std::function<void(const EntryNotification& event)> callback,
+ unsigned int local_id, unsigned int flags) {
+ if ((flags & NT_NOTIFY_LOCAL) != 0) m_local_notifiers = true;
+ return DoAdd(callback, Handle(m_inst, local_id, Handle::kEntry), flags);
+}
+
+unsigned int EntryNotifier::AddPolled(unsigned int poller_uid,
+ wpi::StringRef prefix,
+ unsigned int flags) {
+ if ((flags & NT_NOTIFY_LOCAL) != 0) m_local_notifiers = true;
+ return DoAdd(poller_uid, prefix, flags);
+}
+
+unsigned int EntryNotifier::AddPolled(unsigned int poller_uid,
+ unsigned int local_id,
+ unsigned int flags) {
+ if ((flags & NT_NOTIFY_LOCAL) != 0) m_local_notifiers = true;
+ return DoAdd(poller_uid, Handle(m_inst, local_id, Handle::kEntry), flags);
+}
+
+void EntryNotifier::NotifyEntry(unsigned int local_id, StringRef name,
+ std::shared_ptr<Value> value,
+ unsigned int flags,
+ unsigned int only_listener) {
+ // optimization: don't generate needless local queue entries if we have
+ // no local listeners (as this is a common case on the server side)
+ if ((flags & NT_NOTIFY_LOCAL) != 0 && !m_local_notifiers) return;
+ DEBUG("notifying '" << name << "' (local=" << local_id
+ << "), flags=" << flags);
+ Send(only_listener, 0, Handle(m_inst, local_id, Handle::kEntry).handle(),
+ name, value, flags);
+}
diff --git a/ntcore/src/main/native/cpp/EntryNotifier.h b/ntcore/src/main/native/cpp/EntryNotifier.h
new file mode 100644
index 0000000..3ccf9ff
--- /dev/null
+++ b/ntcore/src/main/native/cpp/EntryNotifier.h
@@ -0,0 +1,109 @@
+/*----------------------------------------------------------------------------*/
+/* Copyright (c) 2015-2018 FIRST. All Rights Reserved. */
+/* Open Source Software - may be modified and shared by FRC teams. The code */
+/* must be accompanied by the FIRST BSD license file in the root directory of */
+/* the project. */
+/*----------------------------------------------------------------------------*/
+
+#ifndef NTCORE_ENTRYNOTIFIER_H_
+#define NTCORE_ENTRYNOTIFIER_H_
+
+#include <atomic>
+#include <memory>
+#include <string>
+
+#include "CallbackManager.h"
+#include "Handle.h"
+#include "IEntryNotifier.h"
+#include "ntcore_cpp.h"
+
+namespace wpi {
+class Logger;
+} // namespace wpi
+
+namespace nt {
+
+namespace impl {
+
+struct EntryListenerData
+ : public ListenerData<std::function<void(const EntryNotification& event)>> {
+ EntryListenerData() = default;
+ EntryListenerData(
+ std::function<void(const EntryNotification& event)> callback_,
+ StringRef prefix_, unsigned int flags_)
+ : ListenerData(callback_), prefix(prefix_), flags(flags_) {}
+ EntryListenerData(
+ std::function<void(const EntryNotification& event)> callback_,
+ NT_Entry entry_, unsigned int flags_)
+ : ListenerData(callback_), entry(entry_), flags(flags_) {}
+ EntryListenerData(unsigned int poller_uid_, StringRef prefix_,
+ unsigned int flags_)
+ : ListenerData(poller_uid_), prefix(prefix_), flags(flags_) {}
+ EntryListenerData(unsigned int poller_uid_, NT_Entry entry_,
+ unsigned int flags_)
+ : ListenerData(poller_uid_), entry(entry_), flags(flags_) {}
+
+ std::string prefix;
+ NT_Entry entry = 0;
+ unsigned int flags;
+};
+
+class EntryNotifierThread
+ : public CallbackThread<EntryNotifierThread, EntryNotification,
+ EntryListenerData> {
+ public:
+ explicit EntryNotifierThread(int inst) : m_inst(inst) {}
+
+ bool Matches(const EntryListenerData& listener,
+ const EntryNotification& data);
+
+ void SetListener(EntryNotification* data, unsigned int listener_uid) {
+ data->listener =
+ Handle(m_inst, listener_uid, Handle::kEntryListener).handle();
+ }
+
+ void DoCallback(std::function<void(const EntryNotification& event)> callback,
+ const EntryNotification& data) {
+ callback(data);
+ }
+
+ int m_inst;
+};
+
+} // namespace impl
+
+class EntryNotifier
+ : public IEntryNotifier,
+ public CallbackManager<EntryNotifier, impl::EntryNotifierThread> {
+ friend class EntryNotifierTest;
+ friend class CallbackManager<EntryNotifier, impl::EntryNotifierThread>;
+
+ public:
+ explicit EntryNotifier(int inst, wpi::Logger& logger);
+
+ void Start();
+
+ bool local_notifiers() const override;
+
+ unsigned int Add(std::function<void(const EntryNotification& event)> callback,
+ wpi::StringRef prefix, unsigned int flags) override;
+ unsigned int Add(std::function<void(const EntryNotification& event)> callback,
+ unsigned int local_id, unsigned int flags) override;
+ unsigned int AddPolled(unsigned int poller_uid, wpi::StringRef prefix,
+ unsigned int flags) override;
+ unsigned int AddPolled(unsigned int poller_uid, unsigned int local_id,
+ unsigned int flags) override;
+
+ void NotifyEntry(unsigned int local_id, StringRef name,
+ std::shared_ptr<Value> value, unsigned int flags,
+ unsigned int only_listener = UINT_MAX) override;
+
+ private:
+ int m_inst;
+ wpi::Logger& m_logger;
+ std::atomic_bool m_local_notifiers;
+};
+
+} // namespace nt
+
+#endif // NTCORE_ENTRYNOTIFIER_H_
diff --git a/ntcore/src/main/native/cpp/Handle.h b/ntcore/src/main/native/cpp/Handle.h
new file mode 100644
index 0000000..47b5edf
--- /dev/null
+++ b/ntcore/src/main/native/cpp/Handle.h
@@ -0,0 +1,65 @@
+/*----------------------------------------------------------------------------*/
+/* Copyright (c) 2016-2018 FIRST. All Rights Reserved. */
+/* Open Source Software - may be modified and shared by FRC teams. The code */
+/* must be accompanied by the FIRST BSD license file in the root directory of */
+/* the project. */
+/*----------------------------------------------------------------------------*/
+
+#ifndef NTCORE_HANDLE_H_
+#define NTCORE_HANDLE_H_
+
+#include "ntcore_c.h"
+
+namespace nt {
+
+// Handle data layout:
+// Bits 30-28: Type
+// Bits 27-20: Instance index
+// Bits 19-0: Handle index (0/unused for instance handles)
+
+class Handle {
+ public:
+ enum Type {
+ kConnectionListener = 1,
+ kConnectionListenerPoller,
+ kEntry,
+ kEntryListener,
+ kEntryListenerPoller,
+ kInstance,
+ kLogger,
+ kLoggerPoller,
+ kRpcCall,
+ kRpcCallPoller
+ };
+ enum { kIndexMax = 0xfffff };
+
+ explicit Handle(NT_Handle handle) : m_handle(handle) {}
+ operator NT_Handle() const { return m_handle; }
+
+ NT_Handle handle() const { return m_handle; }
+
+ Handle(int inst, int index, Type type) {
+ if (inst < 0 || index < 0) {
+ m_handle = 0;
+ return;
+ }
+ m_handle = ((static_cast<int>(type) & 0xf) << 27) | ((inst & 0x7f) << 20) |
+ (index & 0xfffff);
+ }
+
+ int GetIndex() const { return static_cast<int>(m_handle) & 0xfffff; }
+ Type GetType() const {
+ return static_cast<Type>((static_cast<int>(m_handle) >> 27) & 0xf);
+ }
+ int GetInst() const { return (static_cast<int>(m_handle) >> 20) & 0x7f; }
+ bool IsType(Type type) const { return type == GetType(); }
+ int GetTypedIndex(Type type) const { return IsType(type) ? GetIndex() : -1; }
+ int GetTypedInst(Type type) const { return IsType(type) ? GetInst() : -1; }
+
+ private:
+ NT_Handle m_handle;
+};
+
+} // namespace nt
+
+#endif // NTCORE_HANDLE_H_
diff --git a/ntcore/src/main/native/cpp/IConnectionNotifier.h b/ntcore/src/main/native/cpp/IConnectionNotifier.h
new file mode 100644
index 0000000..7a165f2
--- /dev/null
+++ b/ntcore/src/main/native/cpp/IConnectionNotifier.h
@@ -0,0 +1,32 @@
+/*----------------------------------------------------------------------------*/
+/* Copyright (c) 2017-2018 FIRST. All Rights Reserved. */
+/* Open Source Software - may be modified and shared by FRC teams. The code */
+/* must be accompanied by the FIRST BSD license file in the root directory of */
+/* the project. */
+/*----------------------------------------------------------------------------*/
+
+#ifndef NTCORE_ICONNECTIONNOTIFIER_H_
+#define NTCORE_ICONNECTIONNOTIFIER_H_
+
+#include <climits>
+
+#include "ntcore_cpp.h"
+
+namespace nt {
+
+class IConnectionNotifier {
+ public:
+ IConnectionNotifier() = default;
+ IConnectionNotifier(const IConnectionNotifier&) = delete;
+ IConnectionNotifier& operator=(const IConnectionNotifier&) = delete;
+ virtual ~IConnectionNotifier() = default;
+ virtual unsigned int Add(
+ std::function<void(const ConnectionNotification& event)> callback) = 0;
+ virtual unsigned int AddPolled(unsigned int poller_uid) = 0;
+ virtual void NotifyConnection(bool connected, const ConnectionInfo& conn_info,
+ unsigned int only_listener = UINT_MAX) = 0;
+};
+
+} // namespace nt
+
+#endif // NTCORE_ICONNECTIONNOTIFIER_H_
diff --git a/ntcore/src/main/native/cpp/IDispatcher.h b/ntcore/src/main/native/cpp/IDispatcher.h
new file mode 100644
index 0000000..aace766
--- /dev/null
+++ b/ntcore/src/main/native/cpp/IDispatcher.h
@@ -0,0 +1,34 @@
+/*----------------------------------------------------------------------------*/
+/* Copyright (c) 2017-2018 FIRST. All Rights Reserved. */
+/* Open Source Software - may be modified and shared by FRC teams. The code */
+/* must be accompanied by the FIRST BSD license file in the root directory of */
+/* the project. */
+/*----------------------------------------------------------------------------*/
+
+#ifndef NTCORE_IDISPATCHER_H_
+#define NTCORE_IDISPATCHER_H_
+
+#include <memory>
+
+#include "Message.h"
+
+namespace nt {
+
+class INetworkConnection;
+
+// Interface for generation of outgoing messages to break a dependency loop
+// between Storage and Dispatcher.
+class IDispatcher {
+ public:
+ IDispatcher() = default;
+ IDispatcher(const IDispatcher&) = delete;
+ IDispatcher& operator=(const IDispatcher&) = delete;
+ virtual ~IDispatcher() = default;
+ virtual void QueueOutgoing(std::shared_ptr<Message> msg,
+ INetworkConnection* only,
+ INetworkConnection* except) = 0;
+};
+
+} // namespace nt
+
+#endif // NTCORE_IDISPATCHER_H_
diff --git a/ntcore/src/main/native/cpp/IEntryNotifier.h b/ntcore/src/main/native/cpp/IEntryNotifier.h
new file mode 100644
index 0000000..80ed97e
--- /dev/null
+++ b/ntcore/src/main/native/cpp/IEntryNotifier.h
@@ -0,0 +1,44 @@
+/*----------------------------------------------------------------------------*/
+/* Copyright (c) 2017-2018 FIRST. All Rights Reserved. */
+/* Open Source Software - may be modified and shared by FRC teams. The code */
+/* must be accompanied by the FIRST BSD license file in the root directory of */
+/* the project. */
+/*----------------------------------------------------------------------------*/
+
+#ifndef NTCORE_IENTRYNOTIFIER_H_
+#define NTCORE_IENTRYNOTIFIER_H_
+
+#include <climits>
+#include <memory>
+
+#include "ntcore_cpp.h"
+
+namespace nt {
+
+class IEntryNotifier {
+ public:
+ IEntryNotifier() = default;
+ IEntryNotifier(const IEntryNotifier&) = delete;
+ IEntryNotifier& operator=(const IEntryNotifier&) = delete;
+ virtual ~IEntryNotifier() = default;
+ virtual bool local_notifiers() const = 0;
+
+ virtual unsigned int Add(
+ std::function<void(const EntryNotification& event)> callback,
+ wpi::StringRef prefix, unsigned int flags) = 0;
+ virtual unsigned int Add(
+ std::function<void(const EntryNotification& event)> callback,
+ unsigned int local_id, unsigned int flags) = 0;
+ virtual unsigned int AddPolled(unsigned int poller_uid, wpi::StringRef prefix,
+ unsigned int flags) = 0;
+ virtual unsigned int AddPolled(unsigned int poller_uid, unsigned int local_id,
+ unsigned int flags) = 0;
+
+ virtual void NotifyEntry(unsigned int local_id, StringRef name,
+ std::shared_ptr<Value> value, unsigned int flags,
+ unsigned int only_listener = UINT_MAX) = 0;
+};
+
+} // namespace nt
+
+#endif // NTCORE_IENTRYNOTIFIER_H_
diff --git a/ntcore/src/main/native/cpp/INetworkConnection.h b/ntcore/src/main/native/cpp/INetworkConnection.h
new file mode 100644
index 0000000..0387cc9
--- /dev/null
+++ b/ntcore/src/main/native/cpp/INetworkConnection.h
@@ -0,0 +1,41 @@
+/*----------------------------------------------------------------------------*/
+/* Copyright (c) 2017-2018 FIRST. All Rights Reserved. */
+/* Open Source Software - may be modified and shared by FRC teams. The code */
+/* must be accompanied by the FIRST BSD license file in the root directory of */
+/* the project. */
+/*----------------------------------------------------------------------------*/
+
+#ifndef NTCORE_INETWORKCONNECTION_H_
+#define NTCORE_INETWORKCONNECTION_H_
+
+#include <memory>
+
+#include "Message.h"
+#include "ntcore_cpp.h"
+
+namespace nt {
+
+class INetworkConnection {
+ public:
+ enum State { kCreated, kInit, kHandshake, kSynchronized, kActive, kDead };
+
+ INetworkConnection() = default;
+ INetworkConnection(const INetworkConnection&) = delete;
+ INetworkConnection& operator=(const INetworkConnection&) = delete;
+ virtual ~INetworkConnection() = default;
+
+ virtual ConnectionInfo info() const = 0;
+
+ virtual void QueueOutgoing(std::shared_ptr<Message> msg) = 0;
+ virtual void PostOutgoing(bool keep_alive) = 0;
+
+ virtual unsigned int proto_rev() const = 0;
+ virtual void set_proto_rev(unsigned int proto_rev) = 0;
+
+ virtual State state() const = 0;
+ virtual void set_state(State state) = 0;
+};
+
+} // namespace nt
+
+#endif // NTCORE_INETWORKCONNECTION_H_
diff --git a/ntcore/src/main/native/cpp/IRpcServer.h b/ntcore/src/main/native/cpp/IRpcServer.h
new file mode 100644
index 0000000..dc8b0a6
--- /dev/null
+++ b/ntcore/src/main/native/cpp/IRpcServer.h
@@ -0,0 +1,38 @@
+/*----------------------------------------------------------------------------*/
+/* Copyright (c) 2017-2018 FIRST. All Rights Reserved. */
+/* Open Source Software - may be modified and shared by FRC teams. The code */
+/* must be accompanied by the FIRST BSD license file in the root directory of */
+/* the project. */
+/*----------------------------------------------------------------------------*/
+
+#ifndef NTCORE_IRPCSERVER_H_
+#define NTCORE_IRPCSERVER_H_
+
+#include <memory>
+
+#include "Message.h"
+#include "ntcore_cpp.h"
+
+namespace nt {
+
+class IRpcServer {
+ public:
+ typedef std::function<void(StringRef result)> SendResponseFunc;
+
+ IRpcServer() = default;
+ IRpcServer(const IRpcServer&) = delete;
+ IRpcServer& operator=(const IRpcServer&) = delete;
+ virtual ~IRpcServer() = default;
+
+ virtual void RemoveRpc(unsigned int rpc_uid) = 0;
+
+ virtual void ProcessRpc(unsigned int local_id, unsigned int call_uid,
+ StringRef name, StringRef params,
+ const ConnectionInfo& conn,
+ SendResponseFunc send_response,
+ unsigned int rpc_uid) = 0;
+};
+
+} // namespace nt
+
+#endif // NTCORE_IRPCSERVER_H_
diff --git a/ntcore/src/main/native/cpp/IStorage.h b/ntcore/src/main/native/cpp/IStorage.h
new file mode 100644
index 0000000..0fb3a0b
--- /dev/null
+++ b/ntcore/src/main/native/cpp/IStorage.h
@@ -0,0 +1,65 @@
+/*----------------------------------------------------------------------------*/
+/* Copyright (c) 2015-2018 FIRST. All Rights Reserved. */
+/* Open Source Software - may be modified and shared by FRC teams. The code */
+/* must be accompanied by the FIRST BSD license file in the root directory of */
+/* the project. */
+/*----------------------------------------------------------------------------*/
+
+#ifndef NTCORE_ISTORAGE_H_
+#define NTCORE_ISTORAGE_H_
+
+#include <functional>
+#include <memory>
+#include <vector>
+
+#include <wpi/ArrayRef.h>
+#include <wpi/Twine.h>
+
+#include "Message.h"
+#include "ntcore_cpp.h"
+
+namespace nt {
+
+class IDispatcher;
+class INetworkConnection;
+
+class IStorage {
+ public:
+ IStorage() = default;
+ IStorage(const IStorage&) = delete;
+ IStorage& operator=(const IStorage&) = delete;
+ virtual ~IStorage() = default;
+
+ // Accessors required by Dispatcher. An interface is used for
+ // generation of outgoing messages to break a dependency loop between
+ // Storage and Dispatcher.
+ virtual void SetDispatcher(IDispatcher* dispatcher, bool server) = 0;
+ virtual void ClearDispatcher() = 0;
+
+ // Required for wire protocol 2.0 to get the entry type of an entry when
+ // receiving entry updates (because the length/type is not provided in the
+ // message itself). Not used in wire protocol 3.0.
+ virtual NT_Type GetMessageEntryType(unsigned int id) const = 0;
+
+ virtual void ProcessIncoming(std::shared_ptr<Message> msg,
+ INetworkConnection* conn,
+ std::weak_ptr<INetworkConnection> conn_weak) = 0;
+ virtual void GetInitialAssignments(
+ INetworkConnection& conn,
+ std::vector<std::shared_ptr<Message>>* msgs) = 0;
+ virtual void ApplyInitialAssignments(
+ INetworkConnection& conn, wpi::ArrayRef<std::shared_ptr<Message>> msgs,
+ bool new_server, std::vector<std::shared_ptr<Message>>* out_msgs) = 0;
+
+ // Filename-based save/load functions. Used both by periodic saves and
+ // accessible directly via the user API.
+ virtual const char* SavePersistent(const Twine& filename,
+ bool periodic) const = 0;
+ virtual const char* LoadPersistent(
+ const Twine& filename,
+ std::function<void(size_t line, const char* msg)> warn) = 0;
+};
+
+} // namespace nt
+
+#endif // NTCORE_ISTORAGE_H_
diff --git a/ntcore/src/main/native/cpp/InstanceImpl.cpp b/ntcore/src/main/native/cpp/InstanceImpl.cpp
new file mode 100644
index 0000000..3b34292
--- /dev/null
+++ b/ntcore/src/main/native/cpp/InstanceImpl.cpp
@@ -0,0 +1,109 @@
+/*----------------------------------------------------------------------------*/
+/* Copyright (c) 2015-2018 FIRST. All Rights Reserved. */
+/* Open Source Software - may be modified and shared by FRC teams. The code */
+/* must be accompanied by the FIRST BSD license file in the root directory of */
+/* the project. */
+/*----------------------------------------------------------------------------*/
+
+#include "InstanceImpl.h"
+
+using namespace nt;
+
+std::atomic<int> InstanceImpl::s_default{-1};
+std::atomic<InstanceImpl*> InstanceImpl::s_fast_instances[10];
+wpi::UidVector<InstanceImpl*, 10> InstanceImpl::s_instances;
+wpi::mutex InstanceImpl::s_mutex;
+
+using namespace std::placeholders;
+
+InstanceImpl::InstanceImpl(int inst)
+ : logger_impl(inst),
+ logger(std::bind(&LoggerImpl::Log, &logger_impl, _1, _2, _3, _4)),
+ connection_notifier(inst),
+ entry_notifier(inst, logger),
+ rpc_server(inst, logger),
+ storage(entry_notifier, rpc_server, logger),
+ dispatcher(storage, connection_notifier, logger),
+ ds_client(dispatcher, logger) {
+ logger.set_min_level(logger_impl.GetMinLevel());
+}
+
+InstanceImpl::~InstanceImpl() { logger.SetLogger(nullptr); }
+
+InstanceImpl* InstanceImpl::GetDefault() { return Get(GetDefaultIndex()); }
+
+InstanceImpl* InstanceImpl::Get(int inst) {
+ if (inst < 0) return nullptr;
+
+ // fast path, just an atomic read
+ if (static_cast<unsigned int>(inst) <
+ (sizeof(s_fast_instances) / sizeof(s_fast_instances[0]))) {
+ InstanceImpl* ptr = s_fast_instances[inst];
+ if (ptr) return ptr;
+ }
+
+ // slow path
+ std::lock_guard<wpi::mutex> lock(s_mutex);
+
+ // static fast-path block
+ if (static_cast<unsigned int>(inst) <
+ (sizeof(s_fast_instances) / sizeof(s_fast_instances[0]))) {
+ // double-check
+ return s_fast_instances[inst];
+ }
+
+ // vector
+ if (static_cast<unsigned int>(inst) < s_instances.size()) {
+ return s_instances[inst];
+ }
+
+ // doesn't exist
+ return nullptr;
+}
+
+int InstanceImpl::GetDefaultIndex() {
+ int inst = s_default;
+ if (inst >= 0) return inst;
+
+ // slow path
+ std::lock_guard<wpi::mutex> lock(s_mutex);
+
+ // double-check
+ inst = s_default;
+ if (inst >= 0) return inst;
+
+ // alloc and save
+ inst = AllocImpl();
+ s_default = inst;
+ return inst;
+}
+
+int InstanceImpl::Alloc() {
+ std::lock_guard<wpi::mutex> lock(s_mutex);
+ return AllocImpl();
+}
+
+int InstanceImpl::AllocImpl() {
+ unsigned int inst = s_instances.emplace_back(nullptr);
+ InstanceImpl* ptr = new InstanceImpl(inst);
+ s_instances[inst] = ptr;
+
+ if (inst < (sizeof(s_fast_instances) / sizeof(s_fast_instances[0]))) {
+ s_fast_instances[inst] = ptr;
+ }
+
+ return static_cast<int>(inst);
+}
+
+void InstanceImpl::Destroy(int inst) {
+ std::lock_guard<wpi::mutex> lock(s_mutex);
+ if (inst < 0 || static_cast<unsigned int>(inst) >= s_instances.size()) return;
+
+ if (static_cast<unsigned int>(inst) <
+ (sizeof(s_fast_instances) / sizeof(s_fast_instances[0]))) {
+ s_fast_instances[inst] = nullptr;
+ }
+
+ delete s_instances[inst];
+ s_instances.erase(inst);
+}
diff --git a/ntcore/src/main/native/cpp/InstanceImpl.h b/ntcore/src/main/native/cpp/InstanceImpl.h
new file mode 100644
index 0000000..6e732d8
--- /dev/null
+++ b/ntcore/src/main/native/cpp/InstanceImpl.h
@@ -0,0 +1,60 @@
+/*----------------------------------------------------------------------------*/
+/* Copyright (c) 2016-2018 FIRST. All Rights Reserved. */
+/* Open Source Software - may be modified and shared by FRC teams. The code */
+/* must be accompanied by the FIRST BSD license file in the root directory of */
+/* the project. */
+/*----------------------------------------------------------------------------*/
+
+#ifndef NTCORE_INSTANCEIMPL_H_
+#define NTCORE_INSTANCEIMPL_H_
+
+#include <atomic>
+#include <memory>
+
+#include <wpi/UidVector.h>
+#include <wpi/mutex.h>
+
+#include "ConnectionNotifier.h"
+#include "Dispatcher.h"
+#include "DsClient.h"
+#include "EntryNotifier.h"
+#include "Log.h"
+#include "LoggerImpl.h"
+#include "RpcServer.h"
+#include "Storage.h"
+
+namespace nt {
+
+class InstanceImpl {
+ public:
+ explicit InstanceImpl(int inst);
+ ~InstanceImpl();
+
+ // Instance repository
+ static InstanceImpl* GetDefault();
+ static InstanceImpl* Get(int inst);
+ static int GetDefaultIndex();
+ static int Alloc();
+ static void Destroy(int inst);
+
+ LoggerImpl logger_impl;
+ wpi::Logger logger;
+ ConnectionNotifier connection_notifier;
+ EntryNotifier entry_notifier;
+ RpcServer rpc_server;
+ Storage storage;
+ Dispatcher dispatcher;
+ DsClient ds_client;
+
+ private:
+ static int AllocImpl();
+
+ static std::atomic<int> s_default;
+ static std::atomic<InstanceImpl*> s_fast_instances[10];
+ static wpi::UidVector<InstanceImpl*, 10> s_instances;
+ static wpi::mutex s_mutex;
+};
+
+} // namespace nt
+
+#endif // NTCORE_INSTANCEIMPL_H_
diff --git a/ntcore/src/main/native/cpp/Log.h b/ntcore/src/main/native/cpp/Log.h
new file mode 100644
index 0000000..b8a3daf
--- /dev/null
+++ b/ntcore/src/main/native/cpp/Log.h
@@ -0,0 +1,26 @@
+/*----------------------------------------------------------------------------*/
+/* Copyright (c) 2015-2018 FIRST. All Rights Reserved. */
+/* Open Source Software - may be modified and shared by FRC teams. The code */
+/* must be accompanied by the FIRST BSD license file in the root directory of */
+/* the project. */
+/*----------------------------------------------------------------------------*/
+
+#ifndef NTCORE_LOG_H_
+#define NTCORE_LOG_H_
+
+#include <wpi/Logger.h>
+
+#define LOG(level, x) WPI_LOG(m_logger, level, x)
+
+#undef ERROR
+#define ERROR(x) WPI_ERROR(m_logger, x)
+#define WARNING(x) WPI_WARNING(m_logger, x)
+#define INFO(x) WPI_INFO(m_logger, x)
+
+#define DEBUG(x) WPI_DEBUG(m_logger, x)
+#define DEBUG1(x) WPI_DEBUG1(m_logger, x)
+#define DEBUG2(x) WPI_DEBUG2(m_logger, x)
+#define DEBUG3(x) WPI_DEBUG3(m_logger, x)
+#define DEBUG4(x) WPI_DEBUG4(m_logger, x)
+
+#endif // NTCORE_LOG_H_
diff --git a/ntcore/src/main/native/cpp/LoggerImpl.cpp b/ntcore/src/main/native/cpp/LoggerImpl.cpp
new file mode 100644
index 0000000..b2c6786
--- /dev/null
+++ b/ntcore/src/main/native/cpp/LoggerImpl.cpp
@@ -0,0 +1,77 @@
+/*----------------------------------------------------------------------------*/
+/* Copyright (c) 2015-2018 FIRST. All Rights Reserved. */
+/* Open Source Software - may be modified and shared by FRC teams. The code */
+/* must be accompanied by the FIRST BSD license file in the root directory of */
+/* the project. */
+/*----------------------------------------------------------------------------*/
+
+#include "LoggerImpl.h"
+
+#include <wpi/Path.h>
+#include <wpi/SmallString.h>
+#include <wpi/StringRef.h>
+#include <wpi/raw_ostream.h>
+
+using namespace nt;
+
+static void DefaultLogger(unsigned int level, const char* file,
+ unsigned int line, const char* msg) {
+ wpi::SmallString<128> buf;
+ wpi::raw_svector_ostream oss(buf);
+ if (level == 20) {
+ oss << "NT: " << msg << '\n';
+ wpi::errs() << oss.str();
+ return;
+ }
+
+ wpi::StringRef levelmsg;
+ if (level >= 50)
+ levelmsg = "CRITICAL: ";
+ else if (level >= 40)
+ levelmsg = "ERROR: ";
+ else if (level >= 30)
+ levelmsg = "WARNING: ";
+ else
+ return;
+ oss << "NT: " << levelmsg << msg << " (" << file << ':' << line << ")\n";
+ wpi::errs() << oss.str();
+}
+
+LoggerImpl::LoggerImpl(int inst) : m_inst(inst) {}
+
+void LoggerImpl::Start() { DoStart(m_inst); }
+
+unsigned int LoggerImpl::Add(
+ std::function<void(const LogMessage& msg)> callback, unsigned int min_level,
+ unsigned int max_level) {
+ return DoAdd(callback, min_level, max_level);
+}
+
+unsigned int LoggerImpl::AddPolled(unsigned int poller_uid,
+ unsigned int min_level,
+ unsigned int max_level) {
+ return DoAdd(poller_uid, min_level, max_level);
+}
+
+unsigned int LoggerImpl::GetMinLevel() {
+ auto thr = GetThread();
+ if (!thr) return NT_LOG_INFO;
+ unsigned int level = NT_LOG_INFO;
+ for (size_t i = 0; i < thr->m_listeners.size(); ++i) {
+ const auto& listener = thr->m_listeners[i];
+ if (listener && listener.min_level < level) level = listener.min_level;
+ }
+ return level;
+}
+
+void LoggerImpl::Log(unsigned int level, const char* file, unsigned int line,
+ const char* msg) {
+ // this is safe because it's null terminated and always the end
+ const char* filename = wpi::sys::path::filename(file).data();
+ {
+ auto thr = GetThread();
+ if (!thr || thr->m_listeners.empty())
+ DefaultLogger(level, filename, line, msg);
+ }
+ Send(UINT_MAX, 0, level, filename, line, msg);
+}
diff --git a/ntcore/src/main/native/cpp/LoggerImpl.h b/ntcore/src/main/native/cpp/LoggerImpl.h
new file mode 100644
index 0000000..3ac0295
--- /dev/null
+++ b/ntcore/src/main/native/cpp/LoggerImpl.h
@@ -0,0 +1,83 @@
+/*----------------------------------------------------------------------------*/
+/* Copyright (c) 2015-2018 FIRST. All Rights Reserved. */
+/* Open Source Software - may be modified and shared by FRC teams. The code */
+/* must be accompanied by the FIRST BSD license file in the root directory of */
+/* the project. */
+/*----------------------------------------------------------------------------*/
+
+#ifndef NTCORE_LOGGERIMPL_H_
+#define NTCORE_LOGGERIMPL_H_
+
+#include "CallbackManager.h"
+#include "Handle.h"
+#include "ntcore_cpp.h"
+
+namespace nt {
+
+namespace impl {
+
+struct LoggerListenerData
+ : public ListenerData<std::function<void(const LogMessage& msg)>> {
+ LoggerListenerData() = default;
+ LoggerListenerData(std::function<void(const LogMessage& msg)> callback_,
+ unsigned int min_level_, unsigned int max_level_)
+ : ListenerData(callback_), min_level(min_level_), max_level(max_level_) {}
+ LoggerListenerData(unsigned int poller_uid_, unsigned int min_level_,
+ unsigned int max_level_)
+ : ListenerData(poller_uid_),
+ min_level(min_level_),
+ max_level(max_level_) {}
+
+ unsigned int min_level;
+ unsigned int max_level;
+};
+
+class LoggerThread
+ : public CallbackThread<LoggerThread, LogMessage, LoggerListenerData> {
+ public:
+ explicit LoggerThread(int inst) : m_inst(inst) {}
+
+ bool Matches(const LoggerListenerData& listener, const LogMessage& data) {
+ return data.level >= listener.min_level && data.level <= listener.max_level;
+ }
+
+ void SetListener(LogMessage* data, unsigned int listener_uid) {
+ data->logger = Handle(m_inst, listener_uid, Handle::kLogger).handle();
+ }
+
+ void DoCallback(std::function<void(const LogMessage& msg)> callback,
+ const LogMessage& data) {
+ callback(data);
+ }
+
+ int m_inst;
+};
+
+} // namespace impl
+
+class LoggerImpl : public CallbackManager<LoggerImpl, impl::LoggerThread> {
+ friend class LoggerTest;
+ friend class CallbackManager<LoggerImpl, impl::LoggerThread>;
+
+ public:
+ explicit LoggerImpl(int inst);
+
+ void Start();
+
+ unsigned int Add(std::function<void(const LogMessage& msg)> callback,
+ unsigned int min_level, unsigned int max_level);
+ unsigned int AddPolled(unsigned int poller_uid, unsigned int min_level,
+ unsigned int max_level);
+
+ unsigned int GetMinLevel();
+
+ void Log(unsigned int level, const char* file, unsigned int line,
+ const char* msg);
+
+ private:
+ int m_inst;
+};
+
+} // namespace nt
+
+#endif // NTCORE_LOGGERIMPL_H_
diff --git a/ntcore/src/main/native/cpp/Message.cpp b/ntcore/src/main/native/cpp/Message.cpp
new file mode 100644
index 0000000..576e444
--- /dev/null
+++ b/ntcore/src/main/native/cpp/Message.cpp
@@ -0,0 +1,301 @@
+/*----------------------------------------------------------------------------*/
+/* Copyright (c) 2015-2018 FIRST. All Rights Reserved. */
+/* Open Source Software - may be modified and shared by FRC teams. The code */
+/* must be accompanied by the FIRST BSD license file in the root directory of */
+/* the project. */
+/*----------------------------------------------------------------------------*/
+
+#include "Message.h"
+
+#include <stdint.h>
+
+#include "Log.h"
+#include "WireDecoder.h"
+#include "WireEncoder.h"
+
+#define kClearAllMagic 0xD06CB27Aul
+
+using namespace nt;
+
+std::shared_ptr<Message> Message::Read(WireDecoder& decoder,
+ GetEntryTypeFunc get_entry_type) {
+ unsigned int msg_type = 0;
+ if (!decoder.Read8(&msg_type)) return nullptr;
+ auto msg =
+ std::make_shared<Message>(static_cast<MsgType>(msg_type), private_init());
+ switch (msg_type) {
+ case kKeepAlive:
+ break;
+ case kClientHello: {
+ unsigned int proto_rev;
+ if (!decoder.Read16(&proto_rev)) return nullptr;
+ msg->m_id = proto_rev;
+ // This intentionally uses the provided proto_rev instead of
+ // decoder.proto_rev().
+ if (proto_rev >= 0x0300u) {
+ if (!decoder.ReadString(&msg->m_str)) return nullptr;
+ }
+ break;
+ }
+ case kProtoUnsup: {
+ if (!decoder.Read16(&msg->m_id)) return nullptr; // proto rev
+ break;
+ }
+ case kServerHelloDone:
+ break;
+ case kServerHello:
+ if (decoder.proto_rev() < 0x0300u) {
+ decoder.set_error("received SERVER_HELLO in protocol < 3.0");
+ return nullptr;
+ }
+ if (!decoder.Read8(&msg->m_flags)) return nullptr;
+ if (!decoder.ReadString(&msg->m_str)) return nullptr;
+ break;
+ case kClientHelloDone:
+ if (decoder.proto_rev() < 0x0300u) {
+ decoder.set_error("received CLIENT_HELLO_DONE in protocol < 3.0");
+ return nullptr;
+ }
+ break;
+ case kEntryAssign: {
+ if (!decoder.ReadString(&msg->m_str)) return nullptr; // name
+ NT_Type type;
+ if (!decoder.ReadType(&type)) return nullptr; // entry type
+ if (!decoder.Read16(&msg->m_id)) return nullptr; // id
+ if (!decoder.Read16(&msg->m_seq_num_uid)) return nullptr; // seq num
+ if (decoder.proto_rev() >= 0x0300u) {
+ if (!decoder.Read8(&msg->m_flags)) return nullptr; // flags
+ }
+ msg->m_value = decoder.ReadValue(type);
+ if (!msg->m_value) return nullptr;
+ break;
+ }
+ case kEntryUpdate: {
+ if (!decoder.Read16(&msg->m_id)) return nullptr; // id
+ if (!decoder.Read16(&msg->m_seq_num_uid)) return nullptr; // seq num
+ NT_Type type;
+ if (decoder.proto_rev() >= 0x0300u) {
+ if (!decoder.ReadType(&type)) return nullptr;
+ } else {
+ type = get_entry_type(msg->m_id);
+ }
+ WPI_DEBUG4(decoder.logger(), "update message data type: " << type);
+ msg->m_value = decoder.ReadValue(type);
+ if (!msg->m_value) return nullptr;
+ break;
+ }
+ case kFlagsUpdate: {
+ if (decoder.proto_rev() < 0x0300u) {
+ decoder.set_error("received FLAGS_UPDATE in protocol < 3.0");
+ return nullptr;
+ }
+ if (!decoder.Read16(&msg->m_id)) return nullptr;
+ if (!decoder.Read8(&msg->m_flags)) return nullptr;
+ break;
+ }
+ case kEntryDelete: {
+ if (decoder.proto_rev() < 0x0300u) {
+ decoder.set_error("received ENTRY_DELETE in protocol < 3.0");
+ return nullptr;
+ }
+ if (!decoder.Read16(&msg->m_id)) return nullptr;
+ break;
+ }
+ case kClearEntries: {
+ if (decoder.proto_rev() < 0x0300u) {
+ decoder.set_error("received CLEAR_ENTRIES in protocol < 3.0");
+ return nullptr;
+ }
+ uint32_t magic;
+ if (!decoder.Read32(&magic)) return nullptr;
+ if (magic != kClearAllMagic) {
+ decoder.set_error(
+ "received incorrect CLEAR_ENTRIES magic value, ignoring");
+ return nullptr;
+ }
+ break;
+ }
+ case kExecuteRpc: {
+ if (decoder.proto_rev() < 0x0300u) {
+ decoder.set_error("received EXECUTE_RPC in protocol < 3.0");
+ return nullptr;
+ }
+ if (!decoder.Read16(&msg->m_id)) return nullptr;
+ if (!decoder.Read16(&msg->m_seq_num_uid)) return nullptr; // uid
+ uint64_t size;
+ if (!decoder.ReadUleb128(&size)) return nullptr;
+ const char* params;
+ if (!decoder.Read(¶ms, size)) return nullptr;
+ msg->m_str = wpi::StringRef(params, size);
+ break;
+ }
+ case kRpcResponse: {
+ if (decoder.proto_rev() < 0x0300u) {
+ decoder.set_error("received RPC_RESPONSE in protocol < 3.0");
+ return nullptr;
+ }
+ if (!decoder.Read16(&msg->m_id)) return nullptr;
+ if (!decoder.Read16(&msg->m_seq_num_uid)) return nullptr; // uid
+ uint64_t size;
+ if (!decoder.ReadUleb128(&size)) return nullptr;
+ const char* results;
+ if (!decoder.Read(&results, size)) return nullptr;
+ msg->m_str = wpi::StringRef(results, size);
+ break;
+ }
+ default:
+ decoder.set_error("unrecognized message type");
+ WPI_INFO(decoder.logger(), "unrecognized message type: " << msg_type);
+ return nullptr;
+ }
+ return msg;
+}
+
+std::shared_ptr<Message> Message::ClientHello(wpi::StringRef self_id) {
+ auto msg = std::make_shared<Message>(kClientHello, private_init());
+ msg->m_str = self_id;
+ return msg;
+}
+
+std::shared_ptr<Message> Message::ServerHello(unsigned int flags,
+ wpi::StringRef self_id) {
+ auto msg = std::make_shared<Message>(kServerHello, private_init());
+ msg->m_str = self_id;
+ msg->m_flags = flags;
+ return msg;
+}
+
+std::shared_ptr<Message> Message::EntryAssign(wpi::StringRef name,
+ unsigned int id,
+ unsigned int seq_num,
+ std::shared_ptr<Value> value,
+ unsigned int flags) {
+ auto msg = std::make_shared<Message>(kEntryAssign, private_init());
+ msg->m_str = name;
+ msg->m_value = value;
+ msg->m_id = id;
+ msg->m_flags = flags;
+ msg->m_seq_num_uid = seq_num;
+ return msg;
+}
+
+std::shared_ptr<Message> Message::EntryUpdate(unsigned int id,
+ unsigned int seq_num,
+ std::shared_ptr<Value> value) {
+ auto msg = std::make_shared<Message>(kEntryUpdate, private_init());
+ msg->m_value = value;
+ msg->m_id = id;
+ msg->m_seq_num_uid = seq_num;
+ return msg;
+}
+
+std::shared_ptr<Message> Message::FlagsUpdate(unsigned int id,
+ unsigned int flags) {
+ auto msg = std::make_shared<Message>(kFlagsUpdate, private_init());
+ msg->m_id = id;
+ msg->m_flags = flags;
+ return msg;
+}
+
+std::shared_ptr<Message> Message::EntryDelete(unsigned int id) {
+ auto msg = std::make_shared<Message>(kEntryDelete, private_init());
+ msg->m_id = id;
+ return msg;
+}
+
+std::shared_ptr<Message> Message::ExecuteRpc(unsigned int id, unsigned int uid,
+ wpi::StringRef params) {
+ auto msg = std::make_shared<Message>(kExecuteRpc, private_init());
+ msg->m_str = params;
+ msg->m_id = id;
+ msg->m_seq_num_uid = uid;
+ return msg;
+}
+
+std::shared_ptr<Message> Message::RpcResponse(unsigned int id, unsigned int uid,
+ wpi::StringRef result) {
+ auto msg = std::make_shared<Message>(kRpcResponse, private_init());
+ msg->m_str = result;
+ msg->m_id = id;
+ msg->m_seq_num_uid = uid;
+ return msg;
+}
+
+void Message::Write(WireEncoder& encoder) const {
+ switch (m_type) {
+ case kKeepAlive:
+ encoder.Write8(kKeepAlive);
+ break;
+ case kClientHello:
+ encoder.Write8(kClientHello);
+ encoder.Write16(encoder.proto_rev());
+ if (encoder.proto_rev() < 0x0300u) return;
+ encoder.WriteString(m_str);
+ break;
+ case kProtoUnsup:
+ encoder.Write8(kProtoUnsup);
+ encoder.Write16(encoder.proto_rev());
+ break;
+ case kServerHelloDone:
+ encoder.Write8(kServerHelloDone);
+ break;
+ case kServerHello:
+ if (encoder.proto_rev() < 0x0300u) return; // new message in version 3.0
+ encoder.Write8(kServerHello);
+ encoder.Write8(m_flags);
+ encoder.WriteString(m_str);
+ break;
+ case kClientHelloDone:
+ if (encoder.proto_rev() < 0x0300u) return; // new message in version 3.0
+ encoder.Write8(kClientHelloDone);
+ break;
+ case kEntryAssign:
+ encoder.Write8(kEntryAssign);
+ encoder.WriteString(m_str);
+ encoder.WriteType(m_value->type());
+ encoder.Write16(m_id);
+ encoder.Write16(m_seq_num_uid);
+ if (encoder.proto_rev() >= 0x0300u) encoder.Write8(m_flags);
+ encoder.WriteValue(*m_value);
+ break;
+ case kEntryUpdate:
+ encoder.Write8(kEntryUpdate);
+ encoder.Write16(m_id);
+ encoder.Write16(m_seq_num_uid);
+ if (encoder.proto_rev() >= 0x0300u) encoder.WriteType(m_value->type());
+ encoder.WriteValue(*m_value);
+ break;
+ case kFlagsUpdate:
+ if (encoder.proto_rev() < 0x0300u) return; // new message in version 3.0
+ encoder.Write8(kFlagsUpdate);
+ encoder.Write16(m_id);
+ encoder.Write8(m_flags);
+ break;
+ case kEntryDelete:
+ if (encoder.proto_rev() < 0x0300u) return; // new message in version 3.0
+ encoder.Write8(kEntryDelete);
+ encoder.Write16(m_id);
+ break;
+ case kClearEntries:
+ if (encoder.proto_rev() < 0x0300u) return; // new message in version 3.0
+ encoder.Write8(kClearEntries);
+ encoder.Write32(kClearAllMagic);
+ break;
+ case kExecuteRpc:
+ if (encoder.proto_rev() < 0x0300u) return; // new message in version 3.0
+ encoder.Write8(kExecuteRpc);
+ encoder.Write16(m_id);
+ encoder.Write16(m_seq_num_uid);
+ encoder.WriteString(m_str);
+ break;
+ case kRpcResponse:
+ if (encoder.proto_rev() < 0x0300u) return; // new message in version 3.0
+ encoder.Write8(kRpcResponse);
+ encoder.Write16(m_id);
+ encoder.Write16(m_seq_num_uid);
+ encoder.WriteString(m_str);
+ break;
+ default:
+ break;
+ }
+}
diff --git a/ntcore/src/main/native/cpp/Message.h b/ntcore/src/main/native/cpp/Message.h
new file mode 100644
index 0000000..9b25abf
--- /dev/null
+++ b/ntcore/src/main/native/cpp/Message.h
@@ -0,0 +1,117 @@
+/*----------------------------------------------------------------------------*/
+/* Copyright (c) 2015-2018 FIRST. All Rights Reserved. */
+/* Open Source Software - may be modified and shared by FRC teams. The code */
+/* must be accompanied by the FIRST BSD license file in the root directory of */
+/* the project. */
+/*----------------------------------------------------------------------------*/
+
+#ifndef NTCORE_MESSAGE_H_
+#define NTCORE_MESSAGE_H_
+
+#include <functional>
+#include <memory>
+#include <string>
+
+#include "networktables/NetworkTableValue.h"
+
+namespace nt {
+
+class WireDecoder;
+class WireEncoder;
+
+class Message {
+ struct private_init {};
+
+ public:
+ enum MsgType {
+ kUnknown = -1,
+ kKeepAlive = 0x00,
+ kClientHello = 0x01,
+ kProtoUnsup = 0x02,
+ kServerHelloDone = 0x03,
+ kServerHello = 0x04,
+ kClientHelloDone = 0x05,
+ kEntryAssign = 0x10,
+ kEntryUpdate = 0x11,
+ kFlagsUpdate = 0x12,
+ kEntryDelete = 0x13,
+ kClearEntries = 0x14,
+ kExecuteRpc = 0x20,
+ kRpcResponse = 0x21
+ };
+ typedef std::function<NT_Type(unsigned int id)> GetEntryTypeFunc;
+
+ Message() : m_type(kUnknown), m_id(0), m_flags(0), m_seq_num_uid(0) {}
+ Message(MsgType type, const private_init&)
+ : m_type(type), m_id(0), m_flags(0), m_seq_num_uid(0) {}
+
+ MsgType type() const { return m_type; }
+ bool Is(MsgType type) const { return type == m_type; }
+
+ // Message data accessors. Callers are responsible for knowing what data is
+ // actually provided for a particular message.
+ wpi::StringRef str() const { return m_str; }
+ std::shared_ptr<Value> value() const { return m_value; }
+ unsigned int id() const { return m_id; }
+ unsigned int flags() const { return m_flags; }
+ unsigned int seq_num_uid() const { return m_seq_num_uid; }
+
+ // Read and write from wire representation
+ void Write(WireEncoder& encoder) const;
+ static std::shared_ptr<Message> Read(WireDecoder& decoder,
+ GetEntryTypeFunc get_entry_type);
+
+ // Create messages without data
+ static std::shared_ptr<Message> KeepAlive() {
+ return std::make_shared<Message>(kKeepAlive, private_init());
+ }
+ static std::shared_ptr<Message> ProtoUnsup() {
+ return std::make_shared<Message>(kProtoUnsup, private_init());
+ }
+ static std::shared_ptr<Message> ServerHelloDone() {
+ return std::make_shared<Message>(kServerHelloDone, private_init());
+ }
+ static std::shared_ptr<Message> ClientHelloDone() {
+ return std::make_shared<Message>(kClientHelloDone, private_init());
+ }
+ static std::shared_ptr<Message> ClearEntries() {
+ return std::make_shared<Message>(kClearEntries, private_init());
+ }
+
+ // Create messages with data
+ static std::shared_ptr<Message> ClientHello(wpi::StringRef self_id);
+ static std::shared_ptr<Message> ServerHello(unsigned int flags,
+ wpi::StringRef self_id);
+ static std::shared_ptr<Message> EntryAssign(wpi::StringRef name,
+ unsigned int id,
+ unsigned int seq_num,
+ std::shared_ptr<Value> value,
+ unsigned int flags);
+ static std::shared_ptr<Message> EntryUpdate(unsigned int id,
+ unsigned int seq_num,
+ std::shared_ptr<Value> value);
+ static std::shared_ptr<Message> FlagsUpdate(unsigned int id,
+ unsigned int flags);
+ static std::shared_ptr<Message> EntryDelete(unsigned int id);
+ static std::shared_ptr<Message> ExecuteRpc(unsigned int id, unsigned int uid,
+ wpi::StringRef params);
+ static std::shared_ptr<Message> RpcResponse(unsigned int id, unsigned int uid,
+ wpi::StringRef result);
+
+ Message(const Message&) = delete;
+ Message& operator=(const Message&) = delete;
+
+ private:
+ MsgType m_type;
+
+ // Message data. Use varies by message type.
+ std::string m_str;
+ std::shared_ptr<Value> m_value;
+ unsigned int m_id; // also used for proto_rev
+ unsigned int m_flags;
+ unsigned int m_seq_num_uid;
+};
+
+} // namespace nt
+
+#endif // NTCORE_MESSAGE_H_
diff --git a/ntcore/src/main/native/cpp/NetworkConnection.cpp b/ntcore/src/main/native/cpp/NetworkConnection.cpp
new file mode 100644
index 0000000..cb1e333
--- /dev/null
+++ b/ntcore/src/main/native/cpp/NetworkConnection.cpp
@@ -0,0 +1,333 @@
+/*----------------------------------------------------------------------------*/
+/* Copyright (c) 2015-2018 FIRST. All Rights Reserved. */
+/* Open Source Software - may be modified and shared by FRC teams. The code */
+/* must be accompanied by the FIRST BSD license file in the root directory of */
+/* the project. */
+/*----------------------------------------------------------------------------*/
+
+#include "NetworkConnection.h"
+
+#include <wpi/NetworkStream.h>
+#include <wpi/raw_socket_istream.h>
+#include <wpi/timestamp.h>
+
+#include "IConnectionNotifier.h"
+#include "Log.h"
+#include "WireDecoder.h"
+#include "WireEncoder.h"
+
+using namespace nt;
+
+NetworkConnection::NetworkConnection(unsigned int uid,
+ std::unique_ptr<wpi::NetworkStream> stream,
+ IConnectionNotifier& notifier,
+ wpi::Logger& logger,
+ HandshakeFunc handshake,
+ Message::GetEntryTypeFunc get_entry_type)
+ : m_uid(uid),
+ m_stream(std::move(stream)),
+ m_notifier(notifier),
+ m_logger(logger),
+ m_handshake(handshake),
+ m_get_entry_type(get_entry_type),
+ m_state(kCreated) {
+ m_active = false;
+ m_proto_rev = 0x0300;
+ m_last_update = 0;
+
+ // turn off Nagle algorithm; we bundle packets for transmission
+ m_stream->setNoDelay();
+}
+
+NetworkConnection::~NetworkConnection() { Stop(); }
+
+void NetworkConnection::Start() {
+ if (m_active) return;
+ m_active = true;
+ set_state(kInit);
+ // clear queue
+ while (!m_outgoing.empty()) m_outgoing.pop();
+ // reset shutdown flags
+ {
+ std::lock_guard<wpi::mutex> lock(m_shutdown_mutex);
+ m_read_shutdown = false;
+ m_write_shutdown = false;
+ }
+ // start threads
+ m_write_thread = std::thread(&NetworkConnection::WriteThreadMain, this);
+ m_read_thread = std::thread(&NetworkConnection::ReadThreadMain, this);
+}
+
+void NetworkConnection::Stop() {
+ DEBUG2("NetworkConnection stopping (" << this << ")");
+ set_state(kDead);
+ m_active = false;
+ // closing the stream so the read thread terminates
+ if (m_stream) m_stream->close();
+ // send an empty outgoing message set so the write thread terminates
+ m_outgoing.push(Outgoing());
+ // wait for threads to terminate, with timeout
+ if (m_write_thread.joinable()) {
+ std::unique_lock<wpi::mutex> lock(m_shutdown_mutex);
+ auto timeout_time =
+ std::chrono::steady_clock::now() + std::chrono::milliseconds(200);
+ if (m_write_shutdown_cv.wait_until(lock, timeout_time,
+ [&] { return m_write_shutdown; }))
+ m_write_thread.join();
+ else
+ m_write_thread.detach(); // timed out, detach it
+ }
+ if (m_read_thread.joinable()) {
+ std::unique_lock<wpi::mutex> lock(m_shutdown_mutex);
+ auto timeout_time =
+ std::chrono::steady_clock::now() + std::chrono::milliseconds(200);
+ if (m_read_shutdown_cv.wait_until(lock, timeout_time,
+ [&] { return m_read_shutdown; }))
+ m_read_thread.join();
+ else
+ m_read_thread.detach(); // timed out, detach it
+ }
+ // clear queue
+ while (!m_outgoing.empty()) m_outgoing.pop();
+}
+
+ConnectionInfo NetworkConnection::info() const {
+ return ConnectionInfo{remote_id(), m_stream->getPeerIP(),
+ static_cast<unsigned int>(m_stream->getPeerPort()),
+ m_last_update, m_proto_rev};
+}
+
+unsigned int NetworkConnection::proto_rev() const { return m_proto_rev; }
+
+void NetworkConnection::set_proto_rev(unsigned int proto_rev) {
+ m_proto_rev = proto_rev;
+}
+
+NetworkConnection::State NetworkConnection::state() const {
+ std::lock_guard<wpi::mutex> lock(m_state_mutex);
+ return m_state;
+}
+
+void NetworkConnection::set_state(State state) {
+ std::lock_guard<wpi::mutex> lock(m_state_mutex);
+ // Don't update state any more once we've died
+ if (m_state == kDead) return;
+ // One-shot notify state changes
+ if (m_state != kActive && state == kActive)
+ m_notifier.NotifyConnection(true, info());
+ if (m_state != kDead && state == kDead)
+ m_notifier.NotifyConnection(false, info());
+ m_state = state;
+}
+
+std::string NetworkConnection::remote_id() const {
+ std::lock_guard<wpi::mutex> lock(m_remote_id_mutex);
+ return m_remote_id;
+}
+
+void NetworkConnection::set_remote_id(StringRef remote_id) {
+ std::lock_guard<wpi::mutex> lock(m_remote_id_mutex);
+ m_remote_id = remote_id;
+}
+
+void NetworkConnection::ReadThreadMain() {
+ wpi::raw_socket_istream is(*m_stream);
+ WireDecoder decoder(is, m_proto_rev, m_logger);
+
+ set_state(kHandshake);
+ if (!m_handshake(*this,
+ [&] {
+ decoder.set_proto_rev(m_proto_rev);
+ auto msg = Message::Read(decoder, m_get_entry_type);
+ if (!msg && decoder.error())
+ DEBUG("error reading in handshake: " << decoder.error());
+ return msg;
+ },
+ [&](wpi::ArrayRef<std::shared_ptr<Message>> msgs) {
+ m_outgoing.emplace(msgs);
+ })) {
+ set_state(kDead);
+ m_active = false;
+ goto done;
+ }
+
+ set_state(kActive);
+ while (m_active) {
+ if (!m_stream) break;
+ decoder.set_proto_rev(m_proto_rev);
+ decoder.Reset();
+ auto msg = Message::Read(decoder, m_get_entry_type);
+ if (!msg) {
+ if (decoder.error()) INFO("read error: " << decoder.error());
+ // terminate connection on bad message
+ if (m_stream) m_stream->close();
+ break;
+ }
+ DEBUG3("received type=" << msg->type() << " with str=" << msg->str()
+ << " id=" << msg->id()
+ << " seq_num=" << msg->seq_num_uid());
+ m_last_update = Now();
+ m_process_incoming(std::move(msg), this);
+ }
+ DEBUG2("read thread died (" << this << ")");
+ set_state(kDead);
+ m_active = false;
+ m_outgoing.push(Outgoing()); // also kill write thread
+
+done:
+ // use condition variable to signal thread shutdown
+ {
+ std::lock_guard<wpi::mutex> lock(m_shutdown_mutex);
+ m_read_shutdown = true;
+ m_read_shutdown_cv.notify_one();
+ }
+}
+
+void NetworkConnection::WriteThreadMain() {
+ WireEncoder encoder(m_proto_rev);
+
+ while (m_active) {
+ auto msgs = m_outgoing.pop();
+ DEBUG4("write thread woke up");
+ if (msgs.empty()) continue;
+ encoder.set_proto_rev(m_proto_rev);
+ encoder.Reset();
+ DEBUG3("sending " << msgs.size() << " messages");
+ for (auto& msg : msgs) {
+ if (msg) {
+ DEBUG3("sending type=" << msg->type() << " with str=" << msg->str()
+ << " id=" << msg->id()
+ << " seq_num=" << msg->seq_num_uid());
+ msg->Write(encoder);
+ }
+ }
+ wpi::NetworkStream::Error err;
+ if (!m_stream) break;
+ if (encoder.size() == 0) continue;
+ if (m_stream->send(encoder.data(), encoder.size(), &err) == 0) break;
+ DEBUG4("sent " << encoder.size() << " bytes");
+ }
+ DEBUG2("write thread died (" << this << ")");
+ set_state(kDead);
+ m_active = false;
+ if (m_stream) m_stream->close(); // also kill read thread
+
+ // use condition variable to signal thread shutdown
+ {
+ std::lock_guard<wpi::mutex> lock(m_shutdown_mutex);
+ m_write_shutdown = true;
+ m_write_shutdown_cv.notify_one();
+ }
+}
+
+void NetworkConnection::QueueOutgoing(std::shared_ptr<Message> msg) {
+ std::lock_guard<wpi::mutex> lock(m_pending_mutex);
+
+ // Merge with previous. One case we don't combine: delete/assign loop.
+ switch (msg->type()) {
+ case Message::kEntryAssign:
+ case Message::kEntryUpdate: {
+ // don't do this for unassigned id's
+ unsigned int id = msg->id();
+ if (id == 0xffff) {
+ m_pending_outgoing.push_back(msg);
+ break;
+ }
+ if (id < m_pending_update.size() && m_pending_update[id].first != 0) {
+ // overwrite the previous one for this id
+ auto& oldmsg = m_pending_outgoing[m_pending_update[id].first - 1];
+ if (oldmsg && oldmsg->Is(Message::kEntryAssign) &&
+ msg->Is(Message::kEntryUpdate)) {
+ // need to update assignment with new seq_num and value
+ oldmsg = Message::EntryAssign(oldmsg->str(), id, msg->seq_num_uid(),
+ msg->value(), oldmsg->flags());
+ } else {
+ oldmsg = msg; // easy update
+ }
+ } else {
+ // new, but remember it
+ size_t pos = m_pending_outgoing.size();
+ m_pending_outgoing.push_back(msg);
+ if (id >= m_pending_update.size()) m_pending_update.resize(id + 1);
+ m_pending_update[id].first = pos + 1;
+ }
+ break;
+ }
+ case Message::kEntryDelete: {
+ // don't do this for unassigned id's
+ unsigned int id = msg->id();
+ if (id == 0xffff) {
+ m_pending_outgoing.push_back(msg);
+ break;
+ }
+
+ // clear previous updates
+ if (id < m_pending_update.size()) {
+ if (m_pending_update[id].first != 0) {
+ m_pending_outgoing[m_pending_update[id].first - 1].reset();
+ m_pending_update[id].first = 0;
+ }
+ if (m_pending_update[id].second != 0) {
+ m_pending_outgoing[m_pending_update[id].second - 1].reset();
+ m_pending_update[id].second = 0;
+ }
+ }
+
+ // add deletion
+ m_pending_outgoing.push_back(msg);
+ break;
+ }
+ case Message::kFlagsUpdate: {
+ // don't do this for unassigned id's
+ unsigned int id = msg->id();
+ if (id == 0xffff) {
+ m_pending_outgoing.push_back(msg);
+ break;
+ }
+ if (id < m_pending_update.size() && m_pending_update[id].second != 0) {
+ // overwrite the previous one for this id
+ m_pending_outgoing[m_pending_update[id].second - 1] = msg;
+ } else {
+ // new, but remember it
+ size_t pos = m_pending_outgoing.size();
+ m_pending_outgoing.push_back(msg);
+ if (id >= m_pending_update.size()) m_pending_update.resize(id + 1);
+ m_pending_update[id].second = pos + 1;
+ }
+ break;
+ }
+ case Message::kClearEntries: {
+ // knock out all previous assigns/updates!
+ for (auto& i : m_pending_outgoing) {
+ if (!i) continue;
+ auto t = i->type();
+ if (t == Message::kEntryAssign || t == Message::kEntryUpdate ||
+ t == Message::kFlagsUpdate || t == Message::kEntryDelete ||
+ t == Message::kClearEntries)
+ i.reset();
+ }
+ m_pending_update.resize(0);
+ m_pending_outgoing.push_back(msg);
+ break;
+ }
+ default:
+ m_pending_outgoing.push_back(msg);
+ break;
+ }
+}
+
+void NetworkConnection::PostOutgoing(bool keep_alive) {
+ std::lock_guard<wpi::mutex> lock(m_pending_mutex);
+ auto now = std::chrono::steady_clock::now();
+ if (m_pending_outgoing.empty()) {
+ if (!keep_alive) return;
+ // send keep-alives once a second (if no other messages have been sent)
+ if ((now - m_last_post) < std::chrono::seconds(1)) return;
+ m_outgoing.emplace(Outgoing{Message::KeepAlive()});
+ } else {
+ m_outgoing.emplace(std::move(m_pending_outgoing));
+ m_pending_outgoing.resize(0);
+ m_pending_update.resize(0);
+ }
+ m_last_post = now;
+}
diff --git a/ntcore/src/main/native/cpp/NetworkConnection.h b/ntcore/src/main/native/cpp/NetworkConnection.h
new file mode 100644
index 0000000..91ad64e
--- /dev/null
+++ b/ntcore/src/main/native/cpp/NetworkConnection.h
@@ -0,0 +1,127 @@
+/*----------------------------------------------------------------------------*/
+/* Copyright (c) 2015-2018 FIRST. All Rights Reserved. */
+/* Open Source Software - may be modified and shared by FRC teams. The code */
+/* must be accompanied by the FIRST BSD license file in the root directory of */
+/* the project. */
+/*----------------------------------------------------------------------------*/
+
+#ifndef NTCORE_NETWORKCONNECTION_H_
+#define NTCORE_NETWORKCONNECTION_H_
+
+#include <stdint.h>
+
+#include <atomic>
+#include <chrono>
+#include <memory>
+#include <string>
+#include <thread>
+#include <utility>
+#include <vector>
+
+#include <wpi/ConcurrentQueue.h>
+#include <wpi/condition_variable.h>
+#include <wpi/mutex.h>
+
+#include "INetworkConnection.h"
+#include "Message.h"
+#include "ntcore_cpp.h"
+
+namespace wpi {
+class Logger;
+class NetworkStream;
+} // namespace wpi
+
+namespace nt {
+
+class IConnectionNotifier;
+
+class NetworkConnection : public INetworkConnection {
+ public:
+ typedef std::function<bool(
+ NetworkConnection& conn,
+ std::function<std::shared_ptr<Message>()> get_msg,
+ std::function<void(wpi::ArrayRef<std::shared_ptr<Message>>)> send_msgs)>
+ HandshakeFunc;
+ typedef std::function<void(std::shared_ptr<Message> msg,
+ NetworkConnection* conn)>
+ ProcessIncomingFunc;
+ typedef std::vector<std::shared_ptr<Message>> Outgoing;
+ typedef wpi::ConcurrentQueue<Outgoing> OutgoingQueue;
+
+ NetworkConnection(unsigned int uid,
+ std::unique_ptr<wpi::NetworkStream> stream,
+ IConnectionNotifier& notifier, wpi::Logger& logger,
+ HandshakeFunc handshake,
+ Message::GetEntryTypeFunc get_entry_type);
+ ~NetworkConnection();
+
+ // Set the input processor function. This must be called before Start().
+ void set_process_incoming(ProcessIncomingFunc func) {
+ m_process_incoming = func;
+ }
+
+ void Start();
+ void Stop();
+
+ ConnectionInfo info() const override;
+
+ bool active() const { return m_active; }
+ wpi::NetworkStream& stream() { return *m_stream; }
+
+ void QueueOutgoing(std::shared_ptr<Message> msg) override;
+ void PostOutgoing(bool keep_alive) override;
+
+ unsigned int uid() const { return m_uid; }
+
+ unsigned int proto_rev() const override;
+ void set_proto_rev(unsigned int proto_rev) override;
+
+ State state() const override;
+ void set_state(State state) override;
+
+ std::string remote_id() const;
+ void set_remote_id(StringRef remote_id);
+
+ uint64_t last_update() const { return m_last_update; }
+
+ NetworkConnection(const NetworkConnection&) = delete;
+ NetworkConnection& operator=(const NetworkConnection&) = delete;
+
+ private:
+ void ReadThreadMain();
+ void WriteThreadMain();
+
+ unsigned int m_uid;
+ std::unique_ptr<wpi::NetworkStream> m_stream;
+ IConnectionNotifier& m_notifier;
+ wpi::Logger& m_logger;
+ OutgoingQueue m_outgoing;
+ HandshakeFunc m_handshake;
+ Message::GetEntryTypeFunc m_get_entry_type;
+ ProcessIncomingFunc m_process_incoming;
+ std::thread m_read_thread;
+ std::thread m_write_thread;
+ std::atomic_bool m_active;
+ std::atomic_uint m_proto_rev;
+ mutable wpi::mutex m_state_mutex;
+ State m_state;
+ mutable wpi::mutex m_remote_id_mutex;
+ std::string m_remote_id;
+ std::atomic_ullong m_last_update;
+ std::chrono::steady_clock::time_point m_last_post;
+
+ wpi::mutex m_pending_mutex;
+ Outgoing m_pending_outgoing;
+ std::vector<std::pair<size_t, size_t>> m_pending_update;
+
+ // Condition variables for shutdown
+ wpi::mutex m_shutdown_mutex;
+ wpi::condition_variable m_read_shutdown_cv;
+ wpi::condition_variable m_write_shutdown_cv;
+ bool m_read_shutdown = false;
+ bool m_write_shutdown = false;
+};
+
+} // namespace nt
+
+#endif // NTCORE_NETWORKCONNECTION_H_
diff --git a/ntcore/src/main/native/cpp/RpcServer.cpp b/ntcore/src/main/native/cpp/RpcServer.cpp
new file mode 100644
index 0000000..0c6811c
--- /dev/null
+++ b/ntcore/src/main/native/cpp/RpcServer.cpp
@@ -0,0 +1,49 @@
+/*----------------------------------------------------------------------------*/
+/* Copyright (c) 2015-2018 FIRST. All Rights Reserved. */
+/* Open Source Software - may be modified and shared by FRC teams. The code */
+/* must be accompanied by the FIRST BSD license file in the root directory of */
+/* the project. */
+/*----------------------------------------------------------------------------*/
+
+#include "RpcServer.h"
+
+using namespace nt;
+
+RpcServer::RpcServer(int inst, wpi::Logger& logger)
+ : m_inst(inst), m_logger(logger) {}
+
+void RpcServer::Start() { DoStart(m_inst, m_logger); }
+
+unsigned int RpcServer::Add(
+ std::function<void(const RpcAnswer& answer)> callback) {
+ return DoAdd(callback);
+}
+
+unsigned int RpcServer::AddPolled(unsigned int poller_uid) {
+ return DoAdd(poller_uid);
+}
+
+void RpcServer::RemoveRpc(unsigned int rpc_uid) { Remove(rpc_uid); }
+
+void RpcServer::ProcessRpc(unsigned int local_id, unsigned int call_uid,
+ StringRef name, StringRef params,
+ const ConnectionInfo& conn,
+ SendResponseFunc send_response,
+ unsigned int rpc_uid) {
+ Send(rpc_uid, Handle(m_inst, local_id, Handle::kEntry).handle(),
+ Handle(m_inst, call_uid, Handle::kRpcCall).handle(), name, params, conn,
+ send_response);
+}
+
+bool RpcServer::PostRpcResponse(unsigned int local_id, unsigned int call_uid,
+ wpi::StringRef result) {
+ auto thr = GetThread();
+ auto i = thr->m_response_map.find(impl::RpcIdPair{local_id, call_uid});
+ if (i == thr->m_response_map.end()) {
+ WARNING("posting RPC response to nonexistent call (or duplicate response)");
+ return false;
+ }
+ (i->getSecond())(result);
+ thr->m_response_map.erase(i);
+ return true;
+}
diff --git a/ntcore/src/main/native/cpp/RpcServer.h b/ntcore/src/main/native/cpp/RpcServer.h
new file mode 100644
index 0000000..5afe62a
--- /dev/null
+++ b/ntcore/src/main/native/cpp/RpcServer.h
@@ -0,0 +1,113 @@
+/*----------------------------------------------------------------------------*/
+/* Copyright (c) 2015-2018 FIRST. All Rights Reserved. */
+/* Open Source Software - may be modified and shared by FRC teams. The code */
+/* must be accompanied by the FIRST BSD license file in the root directory of */
+/* the project. */
+/*----------------------------------------------------------------------------*/
+
+#ifndef NTCORE_RPCSERVER_H_
+#define NTCORE_RPCSERVER_H_
+
+#include <utility>
+
+#include <wpi/DenseMap.h>
+#include <wpi/mutex.h>
+
+#include "CallbackManager.h"
+#include "Handle.h"
+#include "IRpcServer.h"
+#include "Log.h"
+
+namespace nt {
+
+namespace impl {
+
+typedef std::pair<unsigned int, unsigned int> RpcIdPair;
+
+struct RpcNotifierData : public RpcAnswer {
+ RpcNotifierData(NT_Entry entry_, NT_RpcCall call_, StringRef name_,
+ StringRef params_, const ConnectionInfo& conn_,
+ IRpcServer::SendResponseFunc send_response_)
+ : RpcAnswer{entry_, call_, name_, params_, conn_},
+ send_response{send_response_} {}
+
+ IRpcServer::SendResponseFunc send_response;
+};
+
+using RpcListenerData =
+ ListenerData<std::function<void(const RpcAnswer& answer)>>;
+
+class RpcServerThread
+ : public CallbackThread<RpcServerThread, RpcAnswer, RpcListenerData,
+ RpcNotifierData> {
+ public:
+ RpcServerThread(int inst, wpi::Logger& logger)
+ : m_inst(inst), m_logger(logger) {}
+
+ bool Matches(const RpcListenerData& /*listener*/,
+ const RpcNotifierData& data) {
+ return !data.name.empty() && data.send_response;
+ }
+
+ void SetListener(RpcNotifierData* data, unsigned int /*listener_uid*/) {
+ unsigned int local_id = Handle{data->entry}.GetIndex();
+ unsigned int call_uid = Handle{data->call}.GetIndex();
+ RpcIdPair lookup_uid{local_id, call_uid};
+ m_response_map.insert(std::make_pair(lookup_uid, data->send_response));
+ }
+
+ void DoCallback(std::function<void(const RpcAnswer& call)> callback,
+ const RpcNotifierData& data) {
+ DEBUG4("rpc calling " << data.name);
+ unsigned int local_id = Handle{data.entry}.GetIndex();
+ unsigned int call_uid = Handle{data.call}.GetIndex();
+ RpcIdPair lookup_uid{local_id, call_uid};
+ callback(data);
+ {
+ std::lock_guard<wpi::mutex> lock(m_mutex);
+ auto i = m_response_map.find(lookup_uid);
+ if (i != m_response_map.end()) {
+ // post an empty response and erase it
+ (i->getSecond())("");
+ m_response_map.erase(i);
+ }
+ }
+ }
+
+ int m_inst;
+ wpi::Logger& m_logger;
+ wpi::DenseMap<RpcIdPair, IRpcServer::SendResponseFunc> m_response_map;
+};
+
+} // namespace impl
+
+class RpcServer : public IRpcServer,
+ public CallbackManager<RpcServer, impl::RpcServerThread> {
+ friend class RpcServerTest;
+ friend class CallbackManager<RpcServer, impl::RpcServerThread>;
+
+ public:
+ RpcServer(int inst, wpi::Logger& logger);
+
+ void Start();
+
+ unsigned int Add(std::function<void(const RpcAnswer& answer)> callback);
+ unsigned int AddPolled(unsigned int poller_uid);
+ void RemoveRpc(unsigned int rpc_uid) override;
+
+ void ProcessRpc(unsigned int local_id, unsigned int call_uid, StringRef name,
+ StringRef params, const ConnectionInfo& conn,
+ SendResponseFunc send_response,
+ unsigned int rpc_uid) override;
+
+ bool PostRpcResponse(unsigned int local_id, unsigned int call_uid,
+ wpi::StringRef result);
+
+ private:
+ int m_inst;
+ wpi::Logger& m_logger;
+};
+
+} // namespace nt
+
+#endif // NTCORE_RPCSERVER_H_
diff --git a/ntcore/src/main/native/cpp/SequenceNumber.cpp b/ntcore/src/main/native/cpp/SequenceNumber.cpp
new file mode 100644
index 0000000..6d61331
--- /dev/null
+++ b/ntcore/src/main/native/cpp/SequenceNumber.cpp
@@ -0,0 +1,30 @@
+/*----------------------------------------------------------------------------*/
+/* Copyright (c) 2015-2018 FIRST. All Rights Reserved. */
+/* Open Source Software - may be modified and shared by FRC teams. The code */
+/* must be accompanied by the FIRST BSD license file in the root directory of */
+/* the project. */
+/*----------------------------------------------------------------------------*/
+
+#include "SequenceNumber.h"
+
+namespace nt {
+
+bool operator<(const SequenceNumber& lhs, const SequenceNumber& rhs) {
+ if (lhs.m_value < rhs.m_value)
+ return (rhs.m_value - lhs.m_value) < (1u << 15);
+ else if (lhs.m_value > rhs.m_value)
+ return (lhs.m_value - rhs.m_value) > (1u << 15);
+ else
+ return false;
+}
+
+bool operator>(const SequenceNumber& lhs, const SequenceNumber& rhs) {
+ if (lhs.m_value < rhs.m_value)
+ return (rhs.m_value - lhs.m_value) > (1u << 15);
+ else if (lhs.m_value > rhs.m_value)
+ return (lhs.m_value - rhs.m_value) < (1u << 15);
+ else
+ return false;
+}
+
+} // namespace nt
diff --git a/ntcore/src/main/native/cpp/SequenceNumber.h b/ntcore/src/main/native/cpp/SequenceNumber.h
new file mode 100644
index 0000000..11d9953
--- /dev/null
+++ b/ntcore/src/main/native/cpp/SequenceNumber.h
@@ -0,0 +1,63 @@
+/*----------------------------------------------------------------------------*/
+/* Copyright (c) 2015-2018 FIRST. All Rights Reserved. */
+/* Open Source Software - may be modified and shared by FRC teams. The code */
+/* must be accompanied by the FIRST BSD license file in the root directory of */
+/* the project. */
+/*----------------------------------------------------------------------------*/
+
+#ifndef NTCORE_SEQUENCENUMBER_H_
+#define NTCORE_SEQUENCENUMBER_H_
+
+namespace nt {
+
+/* A sequence number per RFC 1982 */
+class SequenceNumber {
+ public:
+ SequenceNumber() : m_value(0) {}
+ explicit SequenceNumber(unsigned int value) : m_value(value) {}
+ unsigned int value() const { return m_value; }
+
+ SequenceNumber& operator++() {
+ ++m_value;
+ if (m_value > 0xffff) m_value = 0;
+ return *this;
+ }
+ SequenceNumber operator++(int) {
+ SequenceNumber tmp(*this);
+ operator++();
+ return tmp;
+ }
+
+ friend bool operator<(const SequenceNumber& lhs, const SequenceNumber& rhs);
+ friend bool operator>(const SequenceNumber& lhs, const SequenceNumber& rhs);
+ friend bool operator<=(const SequenceNumber& lhs, const SequenceNumber& rhs);
+ friend bool operator>=(const SequenceNumber& lhs, const SequenceNumber& rhs);
+ friend bool operator==(const SequenceNumber& lhs, const SequenceNumber& rhs);
+ friend bool operator!=(const SequenceNumber& lhs, const SequenceNumber& rhs);
+
+ private:
+ unsigned int m_value;
+};
+
+bool operator<(const SequenceNumber& lhs, const SequenceNumber& rhs);
+bool operator>(const SequenceNumber& lhs, const SequenceNumber& rhs);
+
+inline bool operator<=(const SequenceNumber& lhs, const SequenceNumber& rhs) {
+ return lhs == rhs || lhs < rhs;
+}
+
+inline bool operator>=(const SequenceNumber& lhs, const SequenceNumber& rhs) {
+ return lhs == rhs || lhs > rhs;
+}
+
+inline bool operator==(const SequenceNumber& lhs, const SequenceNumber& rhs) {
+ return lhs.m_value == rhs.m_value;
+}
+
+inline bool operator!=(const SequenceNumber& lhs, const SequenceNumber& rhs) {
+ return lhs.m_value != rhs.m_value;
+}
+
+} // namespace nt
+
+#endif // NTCORE_SEQUENCENUMBER_H_
diff --git a/ntcore/src/main/native/cpp/Storage.cpp b/ntcore/src/main/native/cpp/Storage.cpp
new file mode 100644
index 0000000..0d8d467
--- /dev/null
+++ b/ntcore/src/main/native/cpp/Storage.cpp
@@ -0,0 +1,1131 @@
+/*----------------------------------------------------------------------------*/
+/* Copyright (c) 2015-2018 FIRST. All Rights Reserved. */
+/* Open Source Software - may be modified and shared by FRC teams. The code */
+/* must be accompanied by the FIRST BSD license file in the root directory of */
+/* the project. */
+/*----------------------------------------------------------------------------*/
+
+#include "Storage.h"
+
+#include <wpi/timestamp.h>
+
+#include "Handle.h"
+#include "IDispatcher.h"
+#include "IEntryNotifier.h"
+#include "INetworkConnection.h"
+#include "IRpcServer.h"
+#include "Log.h"
+
+using namespace nt;
+
+Storage::Storage(IEntryNotifier& notifier, IRpcServer& rpc_server,
+ wpi::Logger& logger)
+ : m_notifier(notifier), m_rpc_server(rpc_server), m_logger(logger) {
+ m_terminating = false;
+}
+
+Storage::~Storage() {
+ m_terminating = true;
+ m_rpc_results_cond.notify_all();
+}
+
+void Storage::SetDispatcher(IDispatcher* dispatcher, bool server) {
+ std::lock_guard<wpi::mutex> lock(m_mutex);
+ m_dispatcher = dispatcher;
+ m_server = server;
+}
+
+void Storage::ClearDispatcher() { m_dispatcher = nullptr; }
+
+NT_Type Storage::GetMessageEntryType(unsigned int id) const {
+ std::lock_guard<wpi::mutex> lock(m_mutex);
+ if (id >= m_idmap.size()) return NT_UNASSIGNED;
+ Entry* entry = m_idmap[id];
+ if (!entry || !entry->value) return NT_UNASSIGNED;
+ return entry->value->type();
+}
+
+void Storage::ProcessIncoming(std::shared_ptr<Message> msg,
+ INetworkConnection* conn,
+ std::weak_ptr<INetworkConnection> conn_weak) {
+ switch (msg->type()) {
+ case Message::kKeepAlive:
+ break; // ignore
+ case Message::kClientHello:
+ case Message::kProtoUnsup:
+ case Message::kServerHelloDone:
+ case Message::kServerHello:
+ case Message::kClientHelloDone:
+ // shouldn't get these, but ignore if we do
+ break;
+ case Message::kEntryAssign:
+ ProcessIncomingEntryAssign(std::move(msg), conn);
+ break;
+ case Message::kEntryUpdate:
+ ProcessIncomingEntryUpdate(std::move(msg), conn);
+ break;
+ case Message::kFlagsUpdate:
+ ProcessIncomingFlagsUpdate(std::move(msg), conn);
+ break;
+ case Message::kEntryDelete:
+ ProcessIncomingEntryDelete(std::move(msg), conn);
+ break;
+ case Message::kClearEntries:
+ ProcessIncomingClearEntries(std::move(msg), conn);
+ break;
+ case Message::kExecuteRpc:
+ ProcessIncomingExecuteRpc(std::move(msg), conn, std::move(conn_weak));
+ break;
+ case Message::kRpcResponse:
+ ProcessIncomingRpcResponse(std::move(msg), conn);
+ break;
+ default:
+ break;
+ }
+}
+
+void Storage::ProcessIncomingEntryAssign(std::shared_ptr<Message> msg,
+ INetworkConnection* conn) {
+ std::unique_lock<wpi::mutex> lock(m_mutex);
+ unsigned int id = msg->id();
+ StringRef name = msg->str();
+ Entry* entry;
+ bool may_need_update = false;
+ SequenceNumber seq_num(msg->seq_num_uid());
+ if (m_server) {
+ // if we're a server, id=0xffff requests are requests for an id
+ // to be assigned, and we need to send the new assignment back to
+ // the sender as well as all other connections.
+ if (id == 0xffff) {
+ entry = GetOrNew(name);
+ // see if it was already assigned; ignore if so.
+ if (entry->id != 0xffff) return;
+
+ entry->flags = msg->flags();
+ entry->seq_num = seq_num;
+ SetEntryValueImpl(entry, msg->value(), lock, false);
+ return;
+ }
+ if (id >= m_idmap.size() || !m_idmap[id]) {
+ // ignore arbitrary entry assignments
+ // this can happen due to e.g. assignment to deleted entry
+ lock.unlock();
+ DEBUG("server: received assignment to unknown entry");
+ return;
+ }
+ entry = m_idmap[id];
+ } else {
+ // clients simply accept new assignments
+ if (id == 0xffff) {
+ lock.unlock();
+ DEBUG("client: received entry assignment request?");
+ return;
+ }
+ if (id >= m_idmap.size()) m_idmap.resize(id + 1);
+ entry = m_idmap[id];
+ if (!entry) {
+ // create local
+ entry = GetOrNew(name);
+ entry->id = id;
+ m_idmap[id] = entry;
+ if (!entry->value) {
+ // didn't exist at all (rather than just being a response to a
+ // id assignment request)
+ entry->value = msg->value();
+ entry->flags = msg->flags();
+ entry->seq_num = seq_num;
+
+ // notify
+ m_notifier.NotifyEntry(entry->local_id, name, entry->value,
+ NT_NOTIFY_NEW);
+ return;
+ }
+ may_need_update = true; // we may need to send an update message
+
+ // if the received flags don't match what we sent, we most likely
+ // updated flags locally in the interim; send flags update message.
+ if (msg->flags() != entry->flags) {
+ auto dispatcher = m_dispatcher;
+ auto outmsg = Message::FlagsUpdate(id, entry->flags);
+ lock.unlock();
+ dispatcher->QueueOutgoing(outmsg, nullptr, nullptr);
+ lock.lock();
+ }
+ }
+ }
+
+ // common client and server handling
+
+ // already exists; ignore if sequence number not higher than local
+ if (seq_num < entry->seq_num) {
+ if (may_need_update) {
+ auto dispatcher = m_dispatcher;
+ auto outmsg =
+ Message::EntryUpdate(entry->id, entry->seq_num.value(), entry->value);
+ lock.unlock();
+ dispatcher->QueueOutgoing(outmsg, nullptr, nullptr);
+ }
+ return;
+ }
+
+ // sanity check: name should match id
+ if (msg->str() != entry->name) {
+ lock.unlock();
+ DEBUG("entry assignment for same id with different name?");
+ return;
+ }
+
+ unsigned int notify_flags = NT_NOTIFY_UPDATE;
+
+ // don't update flags from a <3.0 remote (not part of message)
+ // don't update flags if this is a server response to a client id request
+ if (!may_need_update && conn->proto_rev() >= 0x0300) {
+ // update persistent dirty flag if persistent flag changed
+ if ((entry->flags & NT_PERSISTENT) != (msg->flags() & NT_PERSISTENT))
+ m_persistent_dirty = true;
+ if (entry->flags != msg->flags()) notify_flags |= NT_NOTIFY_FLAGS;
+ entry->flags = msg->flags();
+ }
+
+ // update persistent dirty flag if the value changed and it's persistent
+ if (entry->IsPersistent() && *entry->value != *msg->value())
+ m_persistent_dirty = true;
+
+ // update local
+ entry->value = msg->value();
+ entry->seq_num = seq_num;
+
+ // notify
+ m_notifier.NotifyEntry(entry->local_id, name, entry->value, notify_flags);
+
+ // broadcast to all other connections (note for client there won't
+ // be any other connections, so don't bother)
+ if (m_server && m_dispatcher) {
+ auto dispatcher = m_dispatcher;
+ auto outmsg = Message::EntryAssign(entry->name, id, msg->seq_num_uid(),
+ msg->value(), entry->flags);
+ lock.unlock();
+ dispatcher->QueueOutgoing(outmsg, nullptr, conn);
+ }
+}
+
+void Storage::ProcessIncomingEntryUpdate(std::shared_ptr<Message> msg,
+ INetworkConnection* conn) {
+ std::unique_lock<wpi::mutex> lock(m_mutex);
+ unsigned int id = msg->id();
+ if (id >= m_idmap.size() || !m_idmap[id]) {
+ // ignore arbitrary entry updates;
+ // this can happen due to deleted entries
+ lock.unlock();
+ DEBUG("received update to unknown entry");
+ return;
+ }
+ Entry* entry = m_idmap[id];
+
+ // ignore if sequence number not higher than local
+ SequenceNumber seq_num(msg->seq_num_uid());
+ if (seq_num <= entry->seq_num) return;
+
+ // update local
+ entry->value = msg->value();
+ entry->seq_num = seq_num;
+
+ // update persistent dirty flag if it's a persistent value
+ if (entry->IsPersistent()) m_persistent_dirty = true;
+
+ // notify
+ m_notifier.NotifyEntry(entry->local_id, entry->name, entry->value,
+ NT_NOTIFY_UPDATE);
+
+ // broadcast to all other connections (note for client there won't
+ // be any other connections, so don't bother)
+ if (m_server && m_dispatcher) {
+ auto dispatcher = m_dispatcher;
+ lock.unlock();
+ dispatcher->QueueOutgoing(msg, nullptr, conn);
+ }
+}
+
+void Storage::ProcessIncomingFlagsUpdate(std::shared_ptr<Message> msg,
+ INetworkConnection* conn) {
+ std::unique_lock<wpi::mutex> lock(m_mutex);
+ unsigned int id = msg->id();
+ if (id >= m_idmap.size() || !m_idmap[id]) {
+ // ignore arbitrary entry updates;
+ // this can happen due to deleted entries
+ lock.unlock();
+ DEBUG("received flags update to unknown entry");
+ return;
+ }
+
+ // update local
+ SetEntryFlagsImpl(m_idmap[id], msg->flags(), lock, false);
+
+ // broadcast to all other connections (note for client there won't
+ // be any other connections, so don't bother)
+ if (m_server && m_dispatcher) {
+ auto dispatcher = m_dispatcher;
+ lock.unlock();
+ dispatcher->QueueOutgoing(msg, nullptr, conn);
+ }
+}
+
+void Storage::ProcessIncomingEntryDelete(std::shared_ptr<Message> msg,
+ INetworkConnection* conn) {
+ std::unique_lock<wpi::mutex> lock(m_mutex);
+ unsigned int id = msg->id();
+ if (id >= m_idmap.size() || !m_idmap[id]) {
+ // ignore arbitrary entry updates;
+ // this can happen due to deleted entries
+ lock.unlock();
+ DEBUG("received delete to unknown entry");
+ return;
+ }
+
+ // update local
+ DeleteEntryImpl(m_idmap[id], lock, false);
+
+ // broadcast to all other connections (note for client there won't
+ // be any other connections, so don't bother)
+ if (m_server && m_dispatcher) {
+ auto dispatcher = m_dispatcher;
+ lock.unlock();
+ dispatcher->QueueOutgoing(msg, nullptr, conn);
+ }
+}
+
+void Storage::ProcessIncomingClearEntries(std::shared_ptr<Message> msg,
+ INetworkConnection* conn) {
+ std::unique_lock<wpi::mutex> lock(m_mutex);
+ // update local
+ DeleteAllEntriesImpl(false);
+
+ // broadcast to all other connections (note for client there won't
+ // be any other connections, so don't bother)
+ if (m_server && m_dispatcher) {
+ auto dispatcher = m_dispatcher;
+ lock.unlock();
+ dispatcher->QueueOutgoing(msg, nullptr, conn);
+ }
+}
+
+void Storage::ProcessIncomingExecuteRpc(
+ std::shared_ptr<Message> msg, INetworkConnection* /*conn*/,
+ std::weak_ptr<INetworkConnection> conn_weak) {
+ std::unique_lock<wpi::mutex> lock(m_mutex);
+ if (!m_server) return; // only process on server
+ unsigned int id = msg->id();
+ if (id >= m_idmap.size() || !m_idmap[id]) {
+ // ignore call to non-existent RPC
+ // this can happen due to deleted entries
+ lock.unlock();
+ DEBUG("received RPC call to unknown entry");
+ return;
+ }
+ Entry* entry = m_idmap[id];
+ if (!entry->value || !entry->value->IsRpc()) {
+ lock.unlock();
+ DEBUG("received RPC call to non-RPC entry");
+ return;
+ }
+ ConnectionInfo conn_info;
+ auto c = conn_weak.lock();
+ if (c) {
+ conn_info = c->info();
+ } else {
+ conn_info.remote_id = "";
+ conn_info.remote_ip = "";
+ conn_info.remote_port = 0;
+ conn_info.last_update = 0;
+ conn_info.protocol_version = 0;
+ }
+ unsigned int call_uid = msg->seq_num_uid();
+ m_rpc_server.ProcessRpc(
+ entry->local_id, call_uid, entry->name, msg->str(), conn_info,
+ [=](StringRef result) {
+ auto c = conn_weak.lock();
+ if (c) c->QueueOutgoing(Message::RpcResponse(id, call_uid, result));
+ },
+ entry->rpc_uid);
+}
+
+void Storage::ProcessIncomingRpcResponse(std::shared_ptr<Message> msg,
+ INetworkConnection* /*conn*/) {
+ std::unique_lock<wpi::mutex> lock(m_mutex);
+ if (m_server) return; // only process on client
+ unsigned int id = msg->id();
+ if (id >= m_idmap.size() || !m_idmap[id]) {
+ // ignore response to non-existent RPC
+ // this can happen due to deleted entries
+ lock.unlock();
+ DEBUG("received rpc response to unknown entry");
+ return;
+ }
+ Entry* entry = m_idmap[id];
+ if (!entry->value || !entry->value->IsRpc()) {
+ lock.unlock();
+ DEBUG("received RPC response to non-RPC entry");
+ return;
+ }
+ m_rpc_results.insert(std::make_pair(
+ RpcIdPair{entry->local_id, msg->seq_num_uid()}, msg->str()));
+ m_rpc_results_cond.notify_all();
+}
+
+void Storage::GetInitialAssignments(
+ INetworkConnection& conn, std::vector<std::shared_ptr<Message>>* msgs) {
+ std::lock_guard<wpi::mutex> lock(m_mutex);
+ conn.set_state(INetworkConnection::kSynchronized);
+ for (auto& i : m_entries) {
+ Entry* entry = i.getValue();
+ if (!entry->value) continue;
+ msgs->emplace_back(Message::EntryAssign(i.getKey(), entry->id,
+ entry->seq_num.value(),
+ entry->value, entry->flags));
+ }
+}
+
+void Storage::ApplyInitialAssignments(
+ INetworkConnection& conn, wpi::ArrayRef<std::shared_ptr<Message>> msgs,
+ bool /*new_server*/, std::vector<std::shared_ptr<Message>>* out_msgs) {
+ std::unique_lock<wpi::mutex> lock(m_mutex);
+ if (m_server) return; // should not do this on server
+
+ conn.set_state(INetworkConnection::kSynchronized);
+
+ std::vector<std::shared_ptr<Message>> update_msgs;
+
+ // clear existing id's
+ for (auto& i : m_entries) i.getValue()->id = 0xffff;
+
+ // clear existing idmap
+ m_idmap.resize(0);
+
+ // apply assignments
+ for (auto& msg : msgs) {
+ if (!msg->Is(Message::kEntryAssign)) {
+ DEBUG("client: received non-entry assignment request?");
+ continue;
+ }
+
+ unsigned int id = msg->id();
+ if (id == 0xffff) {
+ DEBUG("client: received entry assignment request?");
+ continue;
+ }
+
+ SequenceNumber seq_num(msg->seq_num_uid());
+ StringRef name = msg->str();
+
+ Entry* entry = GetOrNew(name);
+ entry->seq_num = seq_num;
+ entry->id = id;
+ if (!entry->value) {
+ // doesn't currently exist
+ entry->value = msg->value();
+ entry->flags = msg->flags();
+ // notify
+ m_notifier.NotifyEntry(entry->local_id, name, entry->value,
+ NT_NOTIFY_NEW);
+ } else {
+ // if we have written the value locally and the value is not persistent,
+ // then we don't update the local value and instead send it back to the
+ // server as an update message
+ if (entry->local_write && !entry->IsPersistent()) {
+ ++entry->seq_num;
+ update_msgs.emplace_back(Message::EntryUpdate(
+ entry->id, entry->seq_num.value(), entry->value));
+ } else {
+ entry->value = msg->value();
+ unsigned int notify_flags = NT_NOTIFY_UPDATE;
+ // don't update flags from a <3.0 remote (not part of message)
+ if (conn.proto_rev() >= 0x0300) {
+ if (entry->flags != msg->flags()) notify_flags |= NT_NOTIFY_FLAGS;
+ entry->flags = msg->flags();
+ }
+ // notify
+ m_notifier.NotifyEntry(entry->local_id, name, entry->value,
+ notify_flags);
+ }
+ }
+
+ // save to idmap
+ if (id >= m_idmap.size()) m_idmap.resize(id + 1);
+ m_idmap[id] = entry;
+ }
+
+ // delete or generate assign messages for unassigned local entries
+ DeleteAllEntriesImpl(false, [&](Entry* entry) -> bool {
+ // was assigned by the server, don't delete
+ if (entry->id != 0xffff) return false;
+ // if we have written the value locally, we send an assign message to the
+ // server instead of deleting
+ if (entry->local_write) {
+ out_msgs->emplace_back(Message::EntryAssign(entry->name, entry->id,
+ entry->seq_num.value(),
+ entry->value, entry->flags));
+ return false;
+ }
+ // otherwise delete
+ return true;
+ });
+ auto dispatcher = m_dispatcher;
+ lock.unlock();
+ for (auto& msg : update_msgs)
+ dispatcher->QueueOutgoing(msg, nullptr, nullptr);
+}
+
+std::shared_ptr<Value> Storage::GetEntryValue(StringRef name) const {
+ std::lock_guard<wpi::mutex> lock(m_mutex);
+ auto i = m_entries.find(name);
+ if (i == m_entries.end()) return nullptr;
+ return i->getValue()->value;
+}
+
+std::shared_ptr<Value> Storage::GetEntryValue(unsigned int local_id) const {
+ std::lock_guard<wpi::mutex> lock(m_mutex);
+ if (local_id >= m_localmap.size()) return nullptr;
+ return m_localmap[local_id]->value;
+}
+
+bool Storage::SetDefaultEntryValue(StringRef name,
+ std::shared_ptr<Value> value) {
+ if (name.empty()) return false;
+ if (!value) return false;
+ std::unique_lock<wpi::mutex> lock(m_mutex);
+ Entry* entry = GetOrNew(name);
+
+ // we return early if value already exists; if types match return true
+ if (entry->value) return entry->value->type() == value->type();
+
+ SetEntryValueImpl(entry, value, lock, true);
+ return true;
+}
+
+bool Storage::SetDefaultEntryValue(unsigned int local_id,
+ std::shared_ptr<Value> value) {
+ if (!value) return false;
+ std::unique_lock<wpi::mutex> lock(m_mutex);
+ if (local_id >= m_localmap.size()) return false;
+ Entry* entry = m_localmap[local_id].get();
+
+ // we return early if value already exists; if types match return true
+ if (entry->value) return entry->value->type() == value->type();
+
+ SetEntryValueImpl(entry, value, lock, true);
+ return true;
+}
+
+bool Storage::SetEntryValue(StringRef name, std::shared_ptr<Value> value) {
+ if (name.empty()) return true;
+ if (!value) return true;
+ std::unique_lock<wpi::mutex> lock(m_mutex);
+ Entry* entry = GetOrNew(name);
+
+ if (entry->value && entry->value->type() != value->type())
+ return false; // error on type mismatch
+
+ SetEntryValueImpl(entry, value, lock, true);
+ return true;
+}
+
+bool Storage::SetEntryValue(unsigned int local_id,
+ std::shared_ptr<Value> value) {
+ if (!value) return true;
+ std::unique_lock<wpi::mutex> lock(m_mutex);
+ if (local_id >= m_localmap.size()) return true;
+ Entry* entry = m_localmap[local_id].get();
+
+ if (entry->value && entry->value->type() != value->type())
+ return false; // error on type mismatch
+
+ SetEntryValueImpl(entry, value, lock, true);
+ return true;
+}
+
+void Storage::SetEntryValueImpl(Entry* entry, std::shared_ptr<Value> value,
+ std::unique_lock<wpi::mutex>& lock,
+ bool local) {
+ if (!value) return;
+ auto old_value = entry->value;
+ entry->value = value;
+
+ // if we're the server, assign an id if it doesn't have one
+ if (m_server && entry->id == 0xffff) {
+ unsigned int id = m_idmap.size();
+ entry->id = id;
+ m_idmap.push_back(entry);
+ }
+
+ // update persistent dirty flag if value changed and it's persistent
+ if (entry->IsPersistent() && (!old_value || *old_value != *value))
+ m_persistent_dirty = true;
+
+ // notify
+ if (!old_value)
+ m_notifier.NotifyEntry(entry->local_id, entry->name, value,
+ NT_NOTIFY_NEW | (local ? NT_NOTIFY_LOCAL : 0));
+ else if (*old_value != *value)
+ m_notifier.NotifyEntry(entry->local_id, entry->name, value,
+ NT_NOTIFY_UPDATE | (local ? NT_NOTIFY_LOCAL : 0));
+
+ // remember local changes
+ if (local) entry->local_write = true;
+
+ // generate message
+ if (!m_dispatcher || (!local && !m_server)) return;
+ auto dispatcher = m_dispatcher;
+ if (!old_value || old_value->type() != value->type()) {
+ if (local) ++entry->seq_num;
+ auto msg = Message::EntryAssign(
+ entry->name, entry->id, entry->seq_num.value(), value, entry->flags);
+ lock.unlock();
+ dispatcher->QueueOutgoing(msg, nullptr, nullptr);
+ } else if (*old_value != *value) {
+ if (local) ++entry->seq_num;
+ // don't send an update if we don't have an assigned id yet
+ if (entry->id != 0xffff) {
+ auto msg = Message::EntryUpdate(entry->id, entry->seq_num.value(), value);
+ lock.unlock();
+ dispatcher->QueueOutgoing(msg, nullptr, nullptr);
+ }
+ }
+}
+
+void Storage::SetEntryTypeValue(StringRef name, std::shared_ptr<Value> value) {
+ if (name.empty()) return;
+ if (!value) return;
+ std::unique_lock<wpi::mutex> lock(m_mutex);
+ Entry* entry = GetOrNew(name);
+
+ SetEntryValueImpl(entry, value, lock, true);
+}
+
+void Storage::SetEntryTypeValue(unsigned int local_id,
+ std::shared_ptr<Value> value) {
+ if (!value) return;
+ std::unique_lock<wpi::mutex> lock(m_mutex);
+ if (local_id >= m_localmap.size()) return;
+ Entry* entry = m_localmap[local_id].get();
+ if (!entry) return;
+
+ SetEntryValueImpl(entry, value, lock, true);
+}
+
+void Storage::SetEntryFlags(StringRef name, unsigned int flags) {
+ if (name.empty()) return;
+ std::unique_lock<wpi::mutex> lock(m_mutex);
+ auto i = m_entries.find(name);
+ if (i == m_entries.end()) return;
+ SetEntryFlagsImpl(i->getValue(), flags, lock, true);
+}
+
+void Storage::SetEntryFlags(unsigned int id_local, unsigned int flags) {
+ std::unique_lock<wpi::mutex> lock(m_mutex);
+ if (id_local >= m_localmap.size()) return;
+ SetEntryFlagsImpl(m_localmap[id_local].get(), flags, lock, true);
+}
+
+void Storage::SetEntryFlagsImpl(Entry* entry, unsigned int flags,
+ std::unique_lock<wpi::mutex>& lock,
+ bool local) {
+ if (!entry->value || entry->flags == flags) return;
+
+ // update persistent dirty flag if persistent flag changed
+ if ((entry->flags & NT_PERSISTENT) != (flags & NT_PERSISTENT))
+ m_persistent_dirty = true;
+
+ entry->flags = flags;
+
+ // notify
+ m_notifier.NotifyEntry(entry->local_id, entry->name, entry->value,
+ NT_NOTIFY_FLAGS | (local ? NT_NOTIFY_LOCAL : 0));
+
+ // generate message
+ if (!local || !m_dispatcher) return;
+ auto dispatcher = m_dispatcher;
+ unsigned int id = entry->id;
+ // don't send an update if we don't have an assigned id yet
+ if (id != 0xffff) {
+ lock.unlock();
+ dispatcher->QueueOutgoing(Message::FlagsUpdate(id, flags), nullptr,
+ nullptr);
+ }
+}
+
+unsigned int Storage::GetEntryFlags(StringRef name) const {
+ std::lock_guard<wpi::mutex> lock(m_mutex);
+ auto i = m_entries.find(name);
+ if (i == m_entries.end()) return 0;
+ return i->getValue()->flags;
+}
+
+unsigned int Storage::GetEntryFlags(unsigned int local_id) const {
+ std::lock_guard<wpi::mutex> lock(m_mutex);
+ if (local_id >= m_localmap.size()) return 0;
+ return m_localmap[local_id]->flags;
+}
+
+void Storage::DeleteEntry(StringRef name) {
+ std::unique_lock<wpi::mutex> lock(m_mutex);
+ auto i = m_entries.find(name);
+ if (i == m_entries.end()) return;
+ DeleteEntryImpl(i->getValue(), lock, true);
+}
+
+void Storage::DeleteEntry(unsigned int local_id) {
+ std::unique_lock<wpi::mutex> lock(m_mutex);
+ if (local_id >= m_localmap.size()) return;
+ DeleteEntryImpl(m_localmap[local_id].get(), lock, true);
+}
+
+void Storage::DeleteEntryImpl(Entry* entry, std::unique_lock<wpi::mutex>& lock,
+ bool local) {
+ unsigned int id = entry->id;
+
+ // Erase entry from id mapping.
+ if (id < m_idmap.size()) m_idmap[id] = nullptr;
+
+ // empty the value and reset id and local_write flag
+ std::shared_ptr<Value> old_value;
+ old_value.swap(entry->value);
+ entry->id = 0xffff;
+ entry->local_write = false;
+
+ // remove RPC if there was one
+ if (entry->rpc_uid != UINT_MAX) {
+ m_rpc_server.RemoveRpc(entry->rpc_uid);
+ entry->rpc_uid = UINT_MAX;
+ }
+
+ // update persistent dirty flag if it's a persistent value
+ if (entry->IsPersistent()) m_persistent_dirty = true;
+
+ // reset flags
+ entry->flags = 0;
+
+ if (!old_value) return; // was not previously assigned
+
+ // notify
+ m_notifier.NotifyEntry(entry->local_id, entry->name, old_value,
+ NT_NOTIFY_DELETE | (local ? NT_NOTIFY_LOCAL : 0));
+
+ // if it had a value, generate message
+ // don't send an update if we don't have an assigned id yet
+ if (local && id != 0xffff) {
+ if (!m_dispatcher) return;
+ auto dispatcher = m_dispatcher;
+ lock.unlock();
+ dispatcher->QueueOutgoing(Message::EntryDelete(id), nullptr, nullptr);
+ }
+}
+
+template <typename F>
+void Storage::DeleteAllEntriesImpl(bool local, F should_delete) {
+ for (auto& i : m_entries) {
+ Entry* entry = i.getValue();
+ if (entry->value && should_delete(entry)) {
+ // notify it's being deleted
+ m_notifier.NotifyEntry(entry->local_id, i.getKey(), entry->value,
+ NT_NOTIFY_DELETE | (local ? NT_NOTIFY_LOCAL : 0));
+ // remove it from idmap
+ if (entry->id < m_idmap.size()) m_idmap[entry->id] = nullptr;
+ entry->id = 0xffff;
+ entry->local_write = false;
+ entry->value.reset();
+ continue;
+ }
+ }
+}
+
+void Storage::DeleteAllEntriesImpl(bool local) {
+ // only delete non-persistent values
+ DeleteAllEntriesImpl(local,
+ [](Entry* entry) { return !entry->IsPersistent(); });
+}
+
+void Storage::DeleteAllEntries() {
+ std::unique_lock<wpi::mutex> lock(m_mutex);
+ if (m_entries.empty()) return;
+
+ DeleteAllEntriesImpl(true);
+
+ // generate message
+ if (!m_dispatcher) return;
+ auto dispatcher = m_dispatcher;
+ lock.unlock();
+ dispatcher->QueueOutgoing(Message::ClearEntries(), nullptr, nullptr);
+}
+
+Storage::Entry* Storage::GetOrNew(const Twine& name) {
+ wpi::SmallString<128> nameBuf;
+ StringRef nameStr = name.toStringRef(nameBuf);
+ auto& entry = m_entries[nameStr];
+ if (!entry) {
+ m_localmap.emplace_back(new Entry(nameStr));
+ entry = m_localmap.back().get();
+ entry->local_id = m_localmap.size() - 1;
+ }
+ return entry;
+}
+
+unsigned int Storage::GetEntry(const Twine& name) {
+ if (name.isTriviallyEmpty() ||
+ (name.isSingleStringRef() && name.getSingleStringRef().empty()))
+ return UINT_MAX;
+ std::unique_lock<wpi::mutex> lock(m_mutex);
+ return GetOrNew(name)->local_id;
+}
+
+std::vector<unsigned int> Storage::GetEntries(const Twine& prefix,
+ unsigned int types) {
+ wpi::SmallString<128> prefixBuf;
+ StringRef prefixStr = prefix.toStringRef(prefixBuf);
+ std::lock_guard<wpi::mutex> lock(m_mutex);
+ std::vector<unsigned int> ids;
+ for (auto& i : m_entries) {
+ Entry* entry = i.getValue();
+ auto value = entry->value.get();
+ if (!value || !i.getKey().startswith(prefixStr)) continue;
+ if (types != 0 && (types & value->type()) == 0) continue;
+ ids.push_back(entry->local_id);
+ }
+ return ids;
+}
+
+EntryInfo Storage::GetEntryInfo(int inst, unsigned int local_id) const {
+ EntryInfo info;
+ info.entry = 0;
+ info.type = NT_UNASSIGNED;
+ info.flags = 0;
+ info.last_change = 0;
+
+ std::unique_lock<wpi::mutex> lock(m_mutex);
+ if (local_id >= m_localmap.size()) return info;
+ Entry* entry = m_localmap[local_id].get();
+ if (!entry->value) return info;
+
+ info.entry = Handle(inst, local_id, Handle::kEntry);
+ info.name = entry->name;
+ info.type = entry->value->type();
+ info.flags = entry->flags;
+ info.last_change = entry->value->last_change();
+ return info;
+}
+
+std::string Storage::GetEntryName(unsigned int local_id) const {
+ std::unique_lock<wpi::mutex> lock(m_mutex);
+ if (local_id >= m_localmap.size()) return std::string{};
+ return m_localmap[local_id]->name;
+}
+
+NT_Type Storage::GetEntryType(unsigned int local_id) const {
+ std::unique_lock<wpi::mutex> lock(m_mutex);
+ if (local_id >= m_localmap.size()) return NT_UNASSIGNED;
+ Entry* entry = m_localmap[local_id].get();
+ if (!entry->value) return NT_UNASSIGNED;
+ return entry->value->type();
+}
+
+uint64_t Storage::GetEntryLastChange(unsigned int local_id) const {
+ std::unique_lock<wpi::mutex> lock(m_mutex);
+ if (local_id >= m_localmap.size()) return 0;
+ Entry* entry = m_localmap[local_id].get();
+ if (!entry->value) return 0;
+ return entry->value->last_change();
+}
+
+std::vector<EntryInfo> Storage::GetEntryInfo(int inst, const Twine& prefix,
+ unsigned int types) {
+ wpi::SmallString<128> prefixBuf;
+ StringRef prefixStr = prefix.toStringRef(prefixBuf);
+ std::lock_guard<wpi::mutex> lock(m_mutex);
+ std::vector<EntryInfo> infos;
+ for (auto& i : m_entries) {
+ Entry* entry = i.getValue();
+ auto value = entry->value.get();
+ if (!value || !i.getKey().startswith(prefixStr)) continue;
+ if (types != 0 && (types & value->type()) == 0) continue;
+ EntryInfo info;
+ info.entry = Handle(inst, entry->local_id, Handle::kEntry);
+ info.name = i.getKey();
+ info.type = value->type();
+ info.flags = entry->flags;
+ info.last_change = value->last_change();
+ infos.push_back(std::move(info));
+ }
+ return infos;
+}
+
+unsigned int Storage::AddListener(
+ const Twine& prefix,
+ std::function<void(const EntryNotification& event)> callback,
+ unsigned int flags) const {
+ wpi::SmallString<128> prefixBuf;
+ StringRef prefixStr = prefix.toStringRef(prefixBuf);
+ std::lock_guard<wpi::mutex> lock(m_mutex);
+ unsigned int uid = m_notifier.Add(callback, prefixStr, flags);
+ // perform immediate notifications
+ if ((flags & NT_NOTIFY_IMMEDIATE) != 0 && (flags & NT_NOTIFY_NEW) != 0) {
+ for (auto& i : m_entries) {
+ Entry* entry = i.getValue();
+ if (!entry->value || !i.getKey().startswith(prefixStr)) continue;
+ m_notifier.NotifyEntry(entry->local_id, i.getKey(), entry->value,
+ NT_NOTIFY_IMMEDIATE | NT_NOTIFY_NEW, uid);
+ }
+ }
+ return uid;
+}
+
+unsigned int Storage::AddListener(
+ unsigned int local_id,
+ std::function<void(const EntryNotification& event)> callback,
+ unsigned int flags) const {
+ std::lock_guard<wpi::mutex> lock(m_mutex);
+ unsigned int uid = m_notifier.Add(callback, local_id, flags);
+ // perform immediate notifications
+ if ((flags & NT_NOTIFY_IMMEDIATE) != 0 && (flags & NT_NOTIFY_NEW) != 0 &&
+ local_id < m_localmap.size()) {
+ Entry* entry = m_localmap[local_id].get();
+ if (entry->value) {
+ m_notifier.NotifyEntry(local_id, entry->name, entry->value,
+ NT_NOTIFY_IMMEDIATE | NT_NOTIFY_NEW, uid);
+ }
+ }
+ return uid;
+}
+
+unsigned int Storage::AddPolledListener(unsigned int poller,
+ const Twine& prefix,
+ unsigned int flags) const {
+ wpi::SmallString<128> prefixBuf;
+ StringRef prefixStr = prefix.toStringRef(prefixBuf);
+ std::lock_guard<wpi::mutex> lock(m_mutex);
+ unsigned int uid = m_notifier.AddPolled(poller, prefixStr, flags);
+ // perform immediate notifications
+ if ((flags & NT_NOTIFY_IMMEDIATE) != 0 && (flags & NT_NOTIFY_NEW) != 0) {
+ for (auto& i : m_entries) {
+ if (!i.getKey().startswith(prefixStr)) continue;
+ Entry* entry = i.getValue();
+ if (!entry->value) continue;
+ m_notifier.NotifyEntry(entry->local_id, i.getKey(), entry->value,
+ NT_NOTIFY_IMMEDIATE | NT_NOTIFY_NEW, uid);
+ }
+ }
+ return uid;
+}
+
+unsigned int Storage::AddPolledListener(unsigned int poller,
+ unsigned int local_id,
+ unsigned int flags) const {
+ std::lock_guard<wpi::mutex> lock(m_mutex);
+ unsigned int uid = m_notifier.AddPolled(poller, local_id, flags);
+ // perform immediate notifications
+ if ((flags & NT_NOTIFY_IMMEDIATE) != 0 && (flags & NT_NOTIFY_NEW) != 0 &&
+ local_id < m_localmap.size()) {
+ Entry* entry = m_localmap[local_id].get();
+ // if no value, don't notify
+ if (entry->value) {
+ m_notifier.NotifyEntry(local_id, entry->name, entry->value,
+ NT_NOTIFY_IMMEDIATE | NT_NOTIFY_NEW, uid);
+ }
+ }
+ return uid;
+}
+
+bool Storage::GetPersistentEntries(
+ bool periodic,
+ std::vector<std::pair<std::string, std::shared_ptr<Value>>>* entries)
+ const {
+ // copy values out of storage as quickly as possible so lock isn't held
+ {
+ std::lock_guard<wpi::mutex> lock(m_mutex);
+ // for periodic, don't re-save unless something has changed
+ if (periodic && !m_persistent_dirty) return false;
+ m_persistent_dirty = false;
+ entries->reserve(m_entries.size());
+ for (auto& i : m_entries) {
+ Entry* entry = i.getValue();
+ // only write persistent-flagged values
+ if (!entry->value || !entry->IsPersistent()) continue;
+ entries->emplace_back(i.getKey(), entry->value);
+ }
+ }
+
+ // sort in name order
+ std::sort(entries->begin(), entries->end(),
+ [](const std::pair<std::string, std::shared_ptr<Value>>& a,
+ const std::pair<std::string, std::shared_ptr<Value>>& b) {
+ return a.first < b.first;
+ });
+ return true;
+}
+
+bool Storage::GetEntries(
+ const Twine& prefix,
+ std::vector<std::pair<std::string, std::shared_ptr<Value>>>* entries)
+ const {
+ wpi::SmallString<128> prefixBuf;
+ StringRef prefixStr = prefix.toStringRef(prefixBuf);
+ // copy values out of storage as quickly as possible so lock isn't held
+ {
+ std::lock_guard<wpi::mutex> lock(m_mutex);
+ entries->reserve(m_entries.size());
+ for (auto& i : m_entries) {
+ Entry* entry = i.getValue();
+ // only write values with given prefix
+ if (!entry->value || !i.getKey().startswith(prefixStr)) continue;
+ entries->emplace_back(i.getKey(), entry->value);
+ }
+ }
+
+ // sort in name order
+ std::sort(entries->begin(), entries->end(),
+ [](const std::pair<std::string, std::shared_ptr<Value>>& a,
+ const std::pair<std::string, std::shared_ptr<Value>>& b) {
+ return a.first < b.first;
+ });
+ return true;
+}
+
+void Storage::CreateRpc(unsigned int local_id, StringRef def,
+ unsigned int rpc_uid) {
+ std::unique_lock<wpi::mutex> lock(m_mutex);
+ if (local_id >= m_localmap.size()) return;
+ Entry* entry = m_localmap[local_id].get();
+
+ auto old_value = entry->value;
+ auto value = Value::MakeRpc(def);
+ entry->value = value;
+
+ // set up the RPC info
+ entry->rpc_uid = rpc_uid;
+
+ if (old_value && *old_value == *value) return;
+
+ // assign an id if it doesn't have one
+ if (entry->id == 0xffff) {
+ unsigned int id = m_idmap.size();
+ entry->id = id;
+ m_idmap.push_back(entry);
+ }
+
+ // generate message
+ if (!m_dispatcher) return;
+ auto dispatcher = m_dispatcher;
+ if (!old_value || old_value->type() != value->type()) {
+ ++entry->seq_num;
+ auto msg = Message::EntryAssign(
+ entry->name, entry->id, entry->seq_num.value(), value, entry->flags);
+ lock.unlock();
+ dispatcher->QueueOutgoing(msg, nullptr, nullptr);
+ } else {
+ ++entry->seq_num;
+ auto msg = Message::EntryUpdate(entry->id, entry->seq_num.value(), value);
+ lock.unlock();
+ dispatcher->QueueOutgoing(msg, nullptr, nullptr);
+ }
+}
+
+unsigned int Storage::CallRpc(unsigned int local_id, StringRef params) {
+ std::unique_lock<wpi::mutex> lock(m_mutex);
+ if (local_id >= m_localmap.size()) return 0;
+ Entry* entry = m_localmap[local_id].get();
+
+ if (!entry->value || !entry->value->IsRpc()) return 0;
+
+ ++entry->rpc_call_uid;
+ if (entry->rpc_call_uid > 0xffff) entry->rpc_call_uid = 0;
+ unsigned int call_uid = entry->rpc_call_uid;
+
+ auto msg = Message::ExecuteRpc(entry->id, call_uid, params);
+ StringRef name{entry->name};
+
+ if (m_server) {
+ // RPCs are unlikely to be used locally on the server, but handle it
+ // gracefully anyway.
+ auto rpc_uid = entry->rpc_uid;
+ lock.unlock();
+ ConnectionInfo conn_info;
+ conn_info.remote_id = "Server";
+ conn_info.remote_ip = "localhost";
+ conn_info.remote_port = 0;
+ conn_info.last_update = wpi::Now();
+ conn_info.protocol_version = 0x0300;
+ unsigned int call_uid = msg->seq_num_uid();
+ m_rpc_server.ProcessRpc(local_id, call_uid, name, msg->str(), conn_info,
+ [=](StringRef result) {
+ std::lock_guard<wpi::mutex> lock(m_mutex);
+ m_rpc_results.insert(std::make_pair(
+ RpcIdPair{local_id, call_uid}, result));
+ m_rpc_results_cond.notify_all();
+ },
+ rpc_uid);
+ } else {
+ auto dispatcher = m_dispatcher;
+ lock.unlock();
+ dispatcher->QueueOutgoing(msg, nullptr, nullptr);
+ }
+ return call_uid;
+}
+
+bool Storage::GetRpcResult(unsigned int local_id, unsigned int call_uid,
+ std::string* result) {
+ bool timed_out = false;
+ return GetRpcResult(local_id, call_uid, result, -1, &timed_out);
+}
+
+bool Storage::GetRpcResult(unsigned int local_id, unsigned int call_uid,
+ std::string* result, double timeout,
+ bool* timed_out) {
+ std::unique_lock<wpi::mutex> lock(m_mutex);
+
+ RpcIdPair call_pair{local_id, call_uid};
+
+ // only allow one blocking call per rpc call uid
+ if (!m_rpc_blocking_calls.insert(call_pair).second) return false;
+
+ auto timeout_time =
+ std::chrono::steady_clock::now() + std::chrono::duration<double>(timeout);
+ *timed_out = false;
+ for (;;) {
+ auto i = m_rpc_results.find(call_pair);
+ if (i == m_rpc_results.end()) {
+ if (timeout == 0 || m_terminating) {
+ m_rpc_blocking_calls.erase(call_pair);
+ return false;
+ }
+ if (timeout < 0) {
+ m_rpc_results_cond.wait(lock);
+ } else {
+ auto cond_timed_out = m_rpc_results_cond.wait_until(lock, timeout_time);
+ if (cond_timed_out == std::cv_status::timeout) {
+ m_rpc_blocking_calls.erase(call_pair);
+ *timed_out = true;
+ return false;
+ }
+ }
+ // if element does not exist, we have been canceled
+ if (m_rpc_blocking_calls.count(call_pair) == 0) {
+ return false;
+ }
+ if (m_terminating) {
+ m_rpc_blocking_calls.erase(call_pair);
+ return false;
+ }
+ continue;
+ }
+ result->swap(i->getSecond());
+ // safe to erase even if id does not exist
+ m_rpc_blocking_calls.erase(call_pair);
+ m_rpc_results.erase(i);
+ return true;
+ }
+}
+
+void Storage::CancelRpcResult(unsigned int local_id, unsigned int call_uid) {
+ std::unique_lock<wpi::mutex> lock(m_mutex);
+ // safe to erase even if id does not exist
+ m_rpc_blocking_calls.erase(RpcIdPair{local_id, call_uid});
+ m_rpc_results_cond.notify_all();
+}
diff --git a/ntcore/src/main/native/cpp/Storage.h b/ntcore/src/main/native/cpp/Storage.h
new file mode 100644
index 0000000..fa9b2bf
--- /dev/null
+++ b/ntcore/src/main/native/cpp/Storage.h
@@ -0,0 +1,262 @@
+/*----------------------------------------------------------------------------*/
+/* Copyright (c) 2015-2018 FIRST. All Rights Reserved. */
+/* Open Source Software - may be modified and shared by FRC teams. The code */
+/* must be accompanied by the FIRST BSD license file in the root directory of */
+/* the project. */
+/*----------------------------------------------------------------------------*/
+
+#ifndef NTCORE_STORAGE_H_
+#define NTCORE_STORAGE_H_
+
+#include <stdint.h>
+
+#include <atomic>
+#include <cstddef>
+#include <functional>
+#include <memory>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include <wpi/DenseMap.h>
+#include <wpi/SmallSet.h>
+#include <wpi/StringMap.h>
+#include <wpi/condition_variable.h>
+#include <wpi/mutex.h>
+
+#include "IStorage.h"
+#include "Message.h"
+#include "SequenceNumber.h"
+#include "ntcore_cpp.h"
+
+namespace wpi {
+class Logger;
+class raw_istream;
+class raw_ostream;
+} // namespace wpi
+
+namespace nt {
+
+class IEntryNotifier;
+class INetworkConnection;
+class IRpcServer;
+class IStorageTest;
+
+class Storage : public IStorage {
+ friend class StorageTest;
+
+ public:
+ Storage(IEntryNotifier& notifier, IRpcServer& rpcserver, wpi::Logger& logger);
+ Storage(const Storage&) = delete;
+ Storage& operator=(const Storage&) = delete;
+
+ ~Storage();
+
+ // Accessors required by Dispatcher. An interface is used for
+ // generation of outgoing messages to break a dependency loop between
+ // Storage and Dispatcher.
+ void SetDispatcher(IDispatcher* dispatcher, bool server) override;
+ void ClearDispatcher() override;
+
+ // Required for wire protocol 2.0 to get the entry type of an entry when
+ // receiving entry updates (because the length/type is not provided in the
+ // message itself). Not used in wire protocol 3.0.
+ NT_Type GetMessageEntryType(unsigned int id) const override;
+
+ void ProcessIncoming(std::shared_ptr<Message> msg, INetworkConnection* conn,
+ std::weak_ptr<INetworkConnection> conn_weak) override;
+ void GetInitialAssignments(
+ INetworkConnection& conn,
+ std::vector<std::shared_ptr<Message>>* msgs) override;
+ void ApplyInitialAssignments(
+ INetworkConnection& conn, wpi::ArrayRef<std::shared_ptr<Message>> msgs,
+ bool new_server,
+ std::vector<std::shared_ptr<Message>>* out_msgs) override;
+
+ // User functions. These are the actual implementations of the corresponding
+ // user API functions in ntcore_cpp.
+ std::shared_ptr<Value> GetEntryValue(StringRef name) const;
+ std::shared_ptr<Value> GetEntryValue(unsigned int local_id) const;
+
+ bool SetDefaultEntryValue(StringRef name, std::shared_ptr<Value> value);
+ bool SetDefaultEntryValue(unsigned int local_id,
+ std::shared_ptr<Value> value);
+
+ bool SetEntryValue(StringRef name, std::shared_ptr<Value> value);
+ bool SetEntryValue(unsigned int local_id, std::shared_ptr<Value> value);
+
+ void SetEntryTypeValue(StringRef name, std::shared_ptr<Value> value);
+ void SetEntryTypeValue(unsigned int local_id, std::shared_ptr<Value> value);
+
+ void SetEntryFlags(StringRef name, unsigned int flags);
+ void SetEntryFlags(unsigned int local_id, unsigned int flags);
+
+ unsigned int GetEntryFlags(StringRef name) const;
+ unsigned int GetEntryFlags(unsigned int local_id) const;
+
+ void DeleteEntry(StringRef name);
+ void DeleteEntry(unsigned int local_id);
+
+ void DeleteAllEntries();
+
+ std::vector<EntryInfo> GetEntryInfo(int inst, const Twine& prefix,
+ unsigned int types);
+
+ unsigned int AddListener(
+ const Twine& prefix,
+ std::function<void(const EntryNotification& event)> callback,
+ unsigned int flags) const;
+ unsigned int AddListener(
+ unsigned int local_id,
+ std::function<void(const EntryNotification& event)> callback,
+ unsigned int flags) const;
+
+ unsigned int AddPolledListener(unsigned int poller_uid, const Twine& prefix,
+ unsigned int flags) const;
+ unsigned int AddPolledListener(unsigned int poller_uid, unsigned int local_id,
+ unsigned int flags) const;
+
+ // Index-only
+ unsigned int GetEntry(const Twine& name);
+ std::vector<unsigned int> GetEntries(const Twine& prefix, unsigned int types);
+ EntryInfo GetEntryInfo(int inst, unsigned int local_id) const;
+ std::string GetEntryName(unsigned int local_id) const;
+ NT_Type GetEntryType(unsigned int local_id) const;
+ uint64_t GetEntryLastChange(unsigned int local_id) const;
+
+ // Filename-based save/load functions. Used both by periodic saves and
+ // accessible directly via the user API.
+ const char* SavePersistent(const Twine& filename,
+ bool periodic) const override;
+ const char* LoadPersistent(
+ const Twine& filename,
+ std::function<void(size_t line, const char* msg)> warn) override;
+
+ const char* SaveEntries(const Twine& filename, const Twine& prefix) const;
+ const char* LoadEntries(
+ const Twine& filename, const Twine& prefix,
+ std::function<void(size_t line, const char* msg)> warn);
+
+ // Stream-based save/load functions (exposed for testing purposes). These
+ // implement the guts of the filename-based functions.
+ void SavePersistent(wpi::raw_ostream& os, bool periodic) const;
+ bool LoadEntries(wpi::raw_istream& is, const Twine& prefix, bool persistent,
+ std::function<void(size_t line, const char* msg)> warn);
+
+ void SaveEntries(wpi::raw_ostream& os, const Twine& prefix) const;
+
+ // RPC configuration needs to come through here as RPC definitions are
+ // actually special Storage value types.
+ void CreateRpc(unsigned int local_id, StringRef def, unsigned int rpc_uid);
+ unsigned int CallRpc(unsigned int local_id, StringRef params);
+ bool GetRpcResult(unsigned int local_id, unsigned int call_uid,
+ std::string* result);
+ bool GetRpcResult(unsigned int local_id, unsigned int call_uid,
+ std::string* result, double timeout, bool* timed_out);
+ void CancelRpcResult(unsigned int local_id, unsigned int call_uid);
+
+ private:
+ // Data for each table entry.
+ struct Entry {
+ explicit Entry(wpi::StringRef name_) : name(name_) {}
+ bool IsPersistent() const { return (flags & NT_PERSISTENT) != 0; }
+
+ // We redundantly store the name so that it's available when accessing the
+ // raw Entry* via the ID map.
+ std::string name;
+
+ // The current value and flags.
+ std::shared_ptr<Value> value;
+ unsigned int flags{0};
+
+ // Unique ID for this entry as used in network messages. The value is
+ // assigned by the server, so on the client this is 0xffff until an
+ // entry assignment is received back from the server.
+ unsigned int id{0xffff};
+
+ // Local ID.
+ unsigned int local_id{UINT_MAX};
+
+ // Sequence number for update resolution.
+ SequenceNumber seq_num;
+
+ // If value has been written locally. Used during initial handshake
+ // on client to determine whether or not to accept remote changes.
+ bool local_write{false};
+
+ // RPC handle.
+ unsigned int rpc_uid{UINT_MAX};
+
+ // Last UID used when calling this RPC (primarily for client use). This
+ // is incremented for each call.
+ unsigned int rpc_call_uid{0};
+ };
+
+ typedef wpi::StringMap<Entry*> EntriesMap;
+ typedef std::vector<Entry*> IdMap;
+ typedef std::vector<std::unique_ptr<Entry>> LocalMap;
+ typedef std::pair<unsigned int, unsigned int> RpcIdPair;
+ typedef wpi::DenseMap<RpcIdPair, std::string> RpcResultMap;
+ typedef wpi::SmallSet<RpcIdPair, 12> RpcBlockingCallSet;
+
+ mutable wpi::mutex m_mutex;
+ EntriesMap m_entries;
+ IdMap m_idmap;
+ LocalMap m_localmap;
+ RpcResultMap m_rpc_results;
+ RpcBlockingCallSet m_rpc_blocking_calls;
+ // If any persistent values have changed
+ mutable bool m_persistent_dirty = false;
+
+ // condition variable and termination flag for blocking on a RPC result
+ std::atomic_bool m_terminating;
+ wpi::condition_variable m_rpc_results_cond;
+
+ // configured by dispatcher at startup
+ IDispatcher* m_dispatcher = nullptr;
+ bool m_server = true;
+
+ IEntryNotifier& m_notifier;
+ IRpcServer& m_rpc_server;
+ wpi::Logger& m_logger;
+
+ void ProcessIncomingEntryAssign(std::shared_ptr<Message> msg,
+ INetworkConnection* conn);
+ void ProcessIncomingEntryUpdate(std::shared_ptr<Message> msg,
+ INetworkConnection* conn);
+ void ProcessIncomingFlagsUpdate(std::shared_ptr<Message> msg,
+ INetworkConnection* conn);
+ void ProcessIncomingEntryDelete(std::shared_ptr<Message> msg,
+ INetworkConnection* conn);
+ void ProcessIncomingClearEntries(std::shared_ptr<Message> msg,
+ INetworkConnection* conn);
+ void ProcessIncomingExecuteRpc(std::shared_ptr<Message> msg,
+ INetworkConnection* conn,
+ std::weak_ptr<INetworkConnection> conn_weak);
+ void ProcessIncomingRpcResponse(std::shared_ptr<Message> msg,
+ INetworkConnection* conn);
+
+ bool GetPersistentEntries(
+ bool periodic,
+ std::vector<std::pair<std::string, std::shared_ptr<Value>>>* entries)
+ const;
+ bool GetEntries(const Twine& prefix,
+ std::vector<std::pair<std::string, std::shared_ptr<Value>>>*
+ entries) const;
+ void SetEntryValueImpl(Entry* entry, std::shared_ptr<Value> value,
+ std::unique_lock<wpi::mutex>& lock, bool local);
+ void SetEntryFlagsImpl(Entry* entry, unsigned int flags,
+ std::unique_lock<wpi::mutex>& lock, bool local);
+ void DeleteEntryImpl(Entry* entry, std::unique_lock<wpi::mutex>& lock,
+ bool local);
+
+ // Must be called with m_mutex held
+ template <typename F>
+ void DeleteAllEntriesImpl(bool local, F should_delete);
+ void DeleteAllEntriesImpl(bool local);
+ Entry* GetOrNew(const Twine& name);
+};
+
+} // namespace nt
+
+#endif // NTCORE_STORAGE_H_
diff --git a/ntcore/src/main/native/cpp/Storage_load.cpp b/ntcore/src/main/native/cpp/Storage_load.cpp
new file mode 100644
index 0000000..c05e029
--- /dev/null
+++ b/ntcore/src/main/native/cpp/Storage_load.cpp
@@ -0,0 +1,453 @@
+/*----------------------------------------------------------------------------*/
+/* Copyright (c) 2015-2018 FIRST. All Rights Reserved. */
+/* Open Source Software - may be modified and shared by FRC teams. The code */
+/* must be accompanied by the FIRST BSD license file in the root directory of */
+/* the project. */
+/*----------------------------------------------------------------------------*/
+
+#include <cctype>
+#include <string>
+
+#include <wpi/Base64.h>
+#include <wpi/SmallString.h>
+#include <wpi/StringExtras.h>
+#include <wpi/raw_istream.h>
+
+#include "IDispatcher.h"
+#include "IEntryNotifier.h"
+#include "Storage.h"
+
+using namespace nt;
+
+namespace {
+
+class LoadPersistentImpl {
+ public:
+ typedef std::pair<std::string, std::shared_ptr<Value>> Entry;
+ typedef std::function<void(size_t line, const char* msg)> WarnFunc;
+
+ LoadPersistentImpl(wpi::raw_istream& is, WarnFunc warn)
+ : m_is(is), m_warn(warn) {}
+
+ bool Load(StringRef prefix, std::vector<Entry>* entries);
+
+ private:
+ bool ReadLine();
+ bool ReadHeader();
+ NT_Type ReadType();
+ wpi::StringRef ReadName(wpi::SmallVectorImpl<char>& buf);
+ std::shared_ptr<Value> ReadValue(NT_Type type);
+ std::shared_ptr<Value> ReadBooleanValue();
+ std::shared_ptr<Value> ReadDoubleValue();
+ std::shared_ptr<Value> ReadStringValue();
+ std::shared_ptr<Value> ReadRawValue();
+ std::shared_ptr<Value> ReadBooleanArrayValue();
+ std::shared_ptr<Value> ReadDoubleArrayValue();
+ std::shared_ptr<Value> ReadStringArrayValue();
+
+ void Warn(const char* msg) {
+ if (m_warn) m_warn(m_line_num, msg);
+ }
+
+ wpi::raw_istream& m_is;
+ WarnFunc m_warn;
+
+ wpi::StringRef m_line;
+ wpi::SmallString<128> m_line_buf;
+ size_t m_line_num = 0;
+
+ std::vector<int> m_buf_boolean_array;
+ std::vector<double> m_buf_double_array;
+ std::vector<std::string> m_buf_string_array;
+};
+
+} // namespace
+
+/* Extracts an escaped string token. Does not unescape the string.
+ * If a string cannot be matched, an empty string is returned.
+ * If the string is unterminated, an empty tail string is returned.
+ * The returned token includes the starting and trailing quotes (unless the
+ * string is unterminated).
+ * Returns a pair containing the extracted token (if any) and the remaining
+ * tail string.
+ */
+static std::pair<wpi::StringRef, wpi::StringRef> ReadStringToken(
+ wpi::StringRef source) {
+ // Match opening quote
+ if (source.empty() || source.front() != '"')
+ return std::make_pair(wpi::StringRef(), source);
+
+ // Scan for ending double quote, checking for escaped as we go.
+ size_t size = source.size();
+ size_t pos;
+ for (pos = 1; pos < size; ++pos) {
+ if (source[pos] == '"' && source[pos - 1] != '\\') {
+ ++pos; // we want to include the trailing quote in the result
+ break;
+ }
+ }
+ return std::make_pair(source.slice(0, pos), source.substr(pos));
+}
+
+static int fromxdigit(char ch) {
+ if (ch >= 'a' && ch <= 'f')
+ return (ch - 'a' + 10);
+ else if (ch >= 'A' && ch <= 'F')
+ return (ch - 'A' + 10);
+ else
+ return ch - '0';
+}
+
+static wpi::StringRef UnescapeString(wpi::StringRef source,
+ wpi::SmallVectorImpl<char>& buf) {
+ assert(source.size() >= 2 && source.front() == '"' && source.back() == '"');
+ buf.clear();
+ buf.reserve(source.size() - 2);
+ for (auto s = source.begin() + 1, end = source.end() - 1; s != end; ++s) {
+ if (*s != '\\') {
+ buf.push_back(*s);
+ continue;
+ }
+ switch (*++s) {
+ case '\\':
+ case '"':
+ buf.push_back(s[-1]);
+ break;
+ case 't':
+ buf.push_back('\t');
+ break;
+ case 'n':
+ buf.push_back('\n');
+ break;
+ case 'x': {
+ if (!isxdigit(*(s + 1))) {
+ buf.push_back('x'); // treat it like a unknown escape
+ break;
+ }
+ int ch = fromxdigit(*++s);
+ if (std::isxdigit(*(s + 1))) {
+ ch <<= 4;
+ ch |= fromxdigit(*++s);
+ }
+ buf.push_back(static_cast<char>(ch));
+ break;
+ }
+ default:
+ buf.push_back(s[-1]);
+ break;
+ }
+ }
+ return wpi::StringRef{buf.data(), buf.size()};
+}
+
+bool LoadPersistentImpl::Load(StringRef prefix, std::vector<Entry>* entries) {
+ if (!ReadHeader()) return false; // header
+
+ while (ReadLine()) {
+ // type
+ NT_Type type = ReadType();
+ if (type == NT_UNASSIGNED) {
+ Warn("unrecognized type");
+ continue;
+ }
+
+ // name
+ wpi::SmallString<128> buf;
+ wpi::StringRef name = ReadName(buf);
+ if (name.empty() || !name.startswith(prefix)) continue;
+
+ // =
+ m_line = m_line.ltrim(" \t");
+ if (m_line.empty() || m_line.front() != '=') {
+ Warn("expected = after name");
+ continue;
+ }
+ m_line = m_line.drop_front().ltrim(" \t");
+
+ // value
+ auto value = ReadValue(type);
+
+ // move to entries
+ if (value) entries->emplace_back(name, std::move(value));
+ }
+ return true;
+}
+
+bool LoadPersistentImpl::ReadLine() {
+ // ignore blank lines and lines that start with ; or # (comments)
+ while (!m_is.has_error()) {
+ ++m_line_num;
+ m_line = m_is.getline(m_line_buf, INT_MAX).trim();
+ if (!m_line.empty() && m_line.front() != ';' && m_line.front() != '#')
+ return true;
+ }
+ return false;
+}
+
+bool LoadPersistentImpl::ReadHeader() {
+ // header
+ if (!ReadLine() || m_line != "[NetworkTables Storage 3.0]") {
+ Warn("header line mismatch, ignoring rest of file");
+ return false;
+ }
+ return true;
+}
+
+NT_Type LoadPersistentImpl::ReadType() {
+ wpi::StringRef tok;
+ std::tie(tok, m_line) = m_line.split(' ');
+ if (tok == "boolean") {
+ return NT_BOOLEAN;
+ } else if (tok == "double") {
+ return NT_DOUBLE;
+ } else if (tok == "string") {
+ return NT_STRING;
+ } else if (tok == "raw") {
+ return NT_RAW;
+ } else if (tok == "array") {
+ wpi::StringRef array_tok;
+ std::tie(array_tok, m_line) = m_line.split(' ');
+ if (array_tok == "boolean")
+ return NT_BOOLEAN_ARRAY;
+ else if (array_tok == "double")
+ return NT_DOUBLE_ARRAY;
+ else if (array_tok == "string")
+ return NT_STRING_ARRAY;
+ }
+ return NT_UNASSIGNED;
+}
+
+wpi::StringRef LoadPersistentImpl::ReadName(wpi::SmallVectorImpl<char>& buf) {
+ wpi::StringRef tok;
+ std::tie(tok, m_line) = ReadStringToken(m_line);
+ if (tok.empty()) {
+ Warn("missing name");
+ return wpi::StringRef{};
+ }
+ if (tok.back() != '"') {
+ Warn("unterminated name string");
+ return wpi::StringRef{};
+ }
+ return UnescapeString(tok, buf);
+}
+
+std::shared_ptr<Value> LoadPersistentImpl::ReadValue(NT_Type type) {
+ switch (type) {
+ case NT_BOOLEAN:
+ return ReadBooleanValue();
+ case NT_DOUBLE:
+ return ReadDoubleValue();
+ case NT_STRING:
+ return ReadStringValue();
+ case NT_RAW:
+ return ReadRawValue();
+ case NT_BOOLEAN_ARRAY:
+ return ReadBooleanArrayValue();
+ case NT_DOUBLE_ARRAY:
+ return ReadDoubleArrayValue();
+ case NT_STRING_ARRAY:
+ return ReadStringArrayValue();
+ default:
+ return nullptr;
+ }
+}
+
+std::shared_ptr<Value> LoadPersistentImpl::ReadBooleanValue() {
+ // only true or false is accepted
+ if (m_line == "true") return Value::MakeBoolean(true);
+ if (m_line == "false") return Value::MakeBoolean(false);
+ Warn("unrecognized boolean value, not 'true' or 'false'");
+ return nullptr;
+}
+
+std::shared_ptr<Value> LoadPersistentImpl::ReadDoubleValue() {
+ // need to convert to null-terminated string for std::strtod()
+ wpi::SmallString<64> buf = m_line;
+ char* end;
+ double v = std::strtod(buf.c_str(), &end);
+ if (*end != '\0') {
+ Warn("invalid double value");
+ return nullptr;
+ }
+ return Value::MakeDouble(v);
+}
+
+std::shared_ptr<Value> LoadPersistentImpl::ReadStringValue() {
+ wpi::StringRef tok;
+ std::tie(tok, m_line) = ReadStringToken(m_line);
+ if (tok.empty()) {
+ Warn("missing string value");
+ return nullptr;
+ }
+ if (tok.back() != '"') {
+ Warn("unterminated string value");
+ return nullptr;
+ }
+ wpi::SmallString<128> buf;
+ return Value::MakeString(UnescapeString(tok, buf));
+}
+
+std::shared_ptr<Value> LoadPersistentImpl::ReadRawValue() {
+ wpi::SmallString<128> buf;
+ size_t nr;
+ return Value::MakeRaw(wpi::Base64Decode(m_line, &nr, buf));
+}
+
+std::shared_ptr<Value> LoadPersistentImpl::ReadBooleanArrayValue() {
+ m_buf_boolean_array.clear();
+ while (!m_line.empty()) {
+ wpi::StringRef tok;
+ std::tie(tok, m_line) = m_line.split(',');
+ tok = tok.trim(" \t");
+ if (tok == "true") {
+ m_buf_boolean_array.push_back(1);
+ } else if (tok == "false") {
+ m_buf_boolean_array.push_back(0);
+ } else {
+ Warn("unrecognized boolean value, not 'true' or 'false'");
+ return nullptr;
+ }
+ }
+ return Value::MakeBooleanArray(std::move(m_buf_boolean_array));
+}
+
+std::shared_ptr<Value> LoadPersistentImpl::ReadDoubleArrayValue() {
+ m_buf_double_array.clear();
+ while (!m_line.empty()) {
+ wpi::StringRef tok;
+ std::tie(tok, m_line) = m_line.split(',');
+ tok = tok.trim(" \t");
+ // need to convert to null-terminated string for std::strtod()
+ wpi::SmallString<64> buf = tok;
+ char* end;
+ double v = std::strtod(buf.c_str(), &end);
+ if (*end != '\0') {
+ Warn("invalid double value");
+ return nullptr;
+ }
+ m_buf_double_array.push_back(v);
+ }
+
+ return Value::MakeDoubleArray(std::move(m_buf_double_array));
+}
+
+std::shared_ptr<Value> LoadPersistentImpl::ReadStringArrayValue() {
+ m_buf_string_array.clear();
+ while (!m_line.empty()) {
+ wpi::StringRef tok;
+ std::tie(tok, m_line) = ReadStringToken(m_line);
+ if (tok.empty()) {
+ Warn("missing string value");
+ return nullptr;
+ }
+ if (tok.back() != '"') {
+ Warn("unterminated string value");
+ return nullptr;
+ }
+
+ wpi::SmallString<128> buf;
+ m_buf_string_array.push_back(UnescapeString(tok, buf));
+
+ m_line = m_line.ltrim(" \t");
+ if (m_line.empty()) break;
+ if (m_line.front() != ',') {
+ Warn("expected comma between strings");
+ return nullptr;
+ }
+ m_line = m_line.drop_front().ltrim(" \t");
+ }
+
+ return Value::MakeStringArray(std::move(m_buf_string_array));
+}
+
+bool Storage::LoadEntries(
+ wpi::raw_istream& is, const Twine& prefix, bool persistent,
+ std::function<void(size_t line, const char* msg)> warn) {
+ wpi::SmallString<128> prefixBuf;
+ StringRef prefixStr = prefix.toStringRef(prefixBuf);
+
+ // entries to add
+ std::vector<LoadPersistentImpl::Entry> entries;
+
+ // load file
+ if (!LoadPersistentImpl(is, warn).Load(prefixStr, &entries)) return false;
+
+ // copy values into storage as quickly as possible so lock isn't held
+ std::vector<std::shared_ptr<Message>> msgs;
+ std::unique_lock<wpi::mutex> lock(m_mutex);
+ for (auto& i : entries) {
+ Entry* entry = GetOrNew(i.first);
+ auto old_value = entry->value;
+ entry->value = i.second;
+ bool was_persist = entry->IsPersistent();
+ if (!was_persist && persistent) entry->flags |= NT_PERSISTENT;
+
+ // if we're the server, assign an id if it doesn't have one
+ if (m_server && entry->id == 0xffff) {
+ unsigned int id = m_idmap.size();
+ entry->id = id;
+ m_idmap.push_back(entry);
+ }
+
+ // notify (for local listeners)
+ if (m_notifier.local_notifiers()) {
+ if (!old_value) {
+ m_notifier.NotifyEntry(entry->local_id, i.first, i.second,
+ NT_NOTIFY_NEW | NT_NOTIFY_LOCAL);
+ } else if (*old_value != *i.second) {
+ unsigned int notify_flags = NT_NOTIFY_UPDATE | NT_NOTIFY_LOCAL;
+ if (!was_persist && persistent) notify_flags |= NT_NOTIFY_FLAGS;
+ m_notifier.NotifyEntry(entry->local_id, i.first, i.second,
+ notify_flags);
+ } else if (!was_persist && persistent) {
+ m_notifier.NotifyEntry(entry->local_id, i.first, i.second,
+ NT_NOTIFY_FLAGS | NT_NOTIFY_LOCAL);
+ }
+ }
+
+ if (!m_dispatcher) continue; // shortcut
+ ++entry->seq_num;
+
+ // put on update queue
+ if (!old_value || old_value->type() != i.second->type()) {
+ msgs.emplace_back(Message::EntryAssign(
+ i.first, entry->id, entry->seq_num.value(), i.second, entry->flags));
+ } else if (entry->id != 0xffff) {
+ // don't send an update if we don't have an assigned id yet
+ if (*old_value != *i.second)
+ msgs.emplace_back(
+ Message::EntryUpdate(entry->id, entry->seq_num.value(), i.second));
+ if (!was_persist)
+ msgs.emplace_back(Message::FlagsUpdate(entry->id, entry->flags));
+ }
+ }
+
+ if (m_dispatcher) {
+ auto dispatcher = m_dispatcher;
+ lock.unlock();
+ for (auto& msg : msgs)
+ dispatcher->QueueOutgoing(std::move(msg), nullptr, nullptr);
+ }
+
+ return true;
+}
+
+const char* Storage::LoadPersistent(
+ const Twine& filename,
+ std::function<void(size_t line, const char* msg)> warn) {
+ std::error_code ec;
+ wpi::raw_fd_istream is(filename, ec);
+ if (ec.value() != 0) return "could not open file";
+ if (!LoadEntries(is, "", true, warn)) return "error reading file";
+ return nullptr;
+}
+
+const char* Storage::LoadEntries(
+ const Twine& filename, const Twine& prefix,
+ std::function<void(size_t line, const char* msg)> warn) {
+ std::error_code ec;
+ wpi::raw_fd_istream is(filename, ec);
+ if (ec.value() != 0) return "could not open file";
+ if (!LoadEntries(is, prefix, false, warn)) return "error reading file";
+ return nullptr;
+}
diff --git a/ntcore/src/main/native/cpp/Storage_save.cpp b/ntcore/src/main/native/cpp/Storage_save.cpp
new file mode 100644
index 0000000..3f352da
--- /dev/null
+++ b/ntcore/src/main/native/cpp/Storage_save.cpp
@@ -0,0 +1,272 @@
+/*----------------------------------------------------------------------------*/
+/* Copyright (c) 2015-2018 FIRST. All Rights Reserved. */
+/* Open Source Software - may be modified and shared by FRC teams. The code */
+/* must be accompanied by the FIRST BSD license file in the root directory of */
+/* the project. */
+/*----------------------------------------------------------------------------*/
+
+#include <cctype>
+#include <string>
+
+#include <wpi/Base64.h>
+#include <wpi/FileSystem.h>
+#include <wpi/Format.h>
+#include <wpi/SmallString.h>
+#include <wpi/StringExtras.h>
+#include <wpi/raw_ostream.h>
+
+#include "Log.h"
+#include "Storage.h"
+
+using namespace nt;
+
+namespace {
+
+class SavePersistentImpl {
+ public:
+ typedef std::pair<std::string, std::shared_ptr<Value>> Entry;
+
+ explicit SavePersistentImpl(wpi::raw_ostream& os) : m_os(os) {}
+
+ void Save(wpi::ArrayRef<Entry> entries);
+
+ private:
+ void WriteString(wpi::StringRef str);
+ void WriteHeader();
+ void WriteEntries(wpi::ArrayRef<Entry> entries);
+ void WriteEntry(wpi::StringRef name, const Value& value);
+ bool WriteType(NT_Type type);
+ void WriteValue(const Value& value);
+
+ wpi::raw_ostream& m_os;
+};
+
+} // namespace
+
+/* Escapes and writes a string, including start and end double quotes */
+void SavePersistentImpl::WriteString(wpi::StringRef str) {
+ m_os << '"';
+ for (auto c : str) {
+ switch (c) {
+ case '\\':
+ m_os << "\\\\";
+ break;
+ case '\t':
+ m_os << "\\t";
+ break;
+ case '\n':
+ m_os << "\\n";
+ break;
+ case '"':
+ m_os << "\\\"";
+ break;
+ default:
+ if (std::isprint(c) && c != '=') {
+ m_os << c;
+ break;
+ }
+
+ // Write out the escaped representation.
+ m_os << "\\x";
+ m_os << wpi::hexdigit((c >> 4) & 0xF);
+ m_os << wpi::hexdigit((c >> 0) & 0xF);
+ }
+ }
+ m_os << '"';
+}
+
+void SavePersistentImpl::Save(wpi::ArrayRef<Entry> entries) {
+ WriteHeader();
+ WriteEntries(entries);
+}
+
+void SavePersistentImpl::WriteHeader() {
+ m_os << "[NetworkTables Storage 3.0]\n";
+}
+
+void SavePersistentImpl::WriteEntries(wpi::ArrayRef<Entry> entries) {
+ for (auto& i : entries) {
+ if (!i.second) continue;
+ WriteEntry(i.first, *i.second);
+ }
+}
+
+void SavePersistentImpl::WriteEntry(wpi::StringRef name, const Value& value) {
+ if (!WriteType(value.type())) return; // type
+ WriteString(name); // name
+ m_os << '='; // '='
+ WriteValue(value); // value
+ m_os << '\n'; // eol
+}
+
+bool SavePersistentImpl::WriteType(NT_Type type) {
+ switch (type) {
+ case NT_BOOLEAN:
+ m_os << "boolean ";
+ break;
+ case NT_DOUBLE:
+ m_os << "double ";
+ break;
+ case NT_STRING:
+ m_os << "string ";
+ break;
+ case NT_RAW:
+ m_os << "raw ";
+ break;
+ case NT_BOOLEAN_ARRAY:
+ m_os << "array boolean ";
+ break;
+ case NT_DOUBLE_ARRAY:
+ m_os << "array double ";
+ break;
+ case NT_STRING_ARRAY:
+ m_os << "array string ";
+ break;
+ default:
+ return false;
+ }
+ return true;
+}
+
+void SavePersistentImpl::WriteValue(const Value& value) {
+ switch (value.type()) {
+ case NT_BOOLEAN:
+ m_os << (value.GetBoolean() ? "true" : "false");
+ break;
+ case NT_DOUBLE:
+ m_os << wpi::format("%g", value.GetDouble());
+ break;
+ case NT_STRING:
+ WriteString(value.GetString());
+ break;
+ case NT_RAW: {
+ wpi::Base64Encode(m_os, value.GetRaw());
+ break;
+ }
+ case NT_BOOLEAN_ARRAY: {
+ bool first = true;
+ for (auto elem : value.GetBooleanArray()) {
+ if (!first) m_os << ',';
+ first = false;
+ m_os << (elem ? "true" : "false");
+ }
+ break;
+ }
+ case NT_DOUBLE_ARRAY: {
+ bool first = true;
+ for (auto elem : value.GetDoubleArray()) {
+ if (!first) m_os << ',';
+ first = false;
+ m_os << wpi::format("%g", elem);
+ }
+ break;
+ }
+ case NT_STRING_ARRAY: {
+ bool first = true;
+ for (auto& elem : value.GetStringArray()) {
+ if (!first) m_os << ',';
+ first = false;
+ WriteString(elem);
+ }
+ break;
+ }
+ default:
+ break;
+ }
+}
+
+void Storage::SavePersistent(wpi::raw_ostream& os, bool periodic) const {
+ std::vector<SavePersistentImpl::Entry> entries;
+ if (!GetPersistentEntries(periodic, &entries)) return;
+ SavePersistentImpl(os).Save(entries);
+}
+
+const char* Storage::SavePersistent(const Twine& filename,
+ bool periodic) const {
+ wpi::SmallString<128> fn;
+ filename.toVector(fn);
+ wpi::SmallString<128> tmp = fn;
+ tmp += ".tmp";
+ wpi::SmallString<128> bak = fn;
+ bak += ".bak";
+
+ // Get entries before creating file
+ std::vector<SavePersistentImpl::Entry> entries;
+ if (!GetPersistentEntries(periodic, &entries)) return nullptr;
+
+ const char* err = nullptr;
+
+ // start by writing to temporary file
+ std::error_code ec;
+ wpi::raw_fd_ostream os(tmp, ec, wpi::sys::fs::F_Text);
+ if (ec.value() != 0) {
+ err = "could not open file";
+ goto done;
+ }
+ DEBUG("saving persistent file '" << filename << "'");
+ SavePersistentImpl(os).Save(entries);
+ os.close();
+ if (os.has_error()) {
+ std::remove(tmp.c_str());
+ err = "error saving file";
+ goto done;
+ }
+
+ // Safely move to real file. We ignore any failures related to the backup.
+ std::remove(bak.c_str());
+ std::rename(fn.c_str(), bak.c_str());
+ if (std::rename(tmp.c_str(), fn.c_str()) != 0) {
+ std::rename(bak.c_str(), fn.c_str()); // attempt to restore backup
+ err = "could not rename temp file to real file";
+ goto done;
+ }
+
+done:
+ // try again if there was an error
+ if (err && periodic) m_persistent_dirty = true;
+ return err;
+}
+
+void Storage::SaveEntries(wpi::raw_ostream& os, const Twine& prefix) const {
+ std::vector<SavePersistentImpl::Entry> entries;
+ if (!GetEntries(prefix, &entries)) return;
+ SavePersistentImpl(os).Save(entries);
+}
+
+const char* Storage::SaveEntries(const Twine& filename,
+ const Twine& prefix) const {
+ wpi::SmallString<128> fn;
+ filename.toVector(fn);
+ wpi::SmallString<128> tmp = fn;
+ tmp += ".tmp";
+ wpi::SmallString<128> bak = fn;
+ bak += ".bak";
+
+ // Get entries before creating file
+ std::vector<SavePersistentImpl::Entry> entries;
+ if (!GetEntries(prefix, &entries)) return nullptr;
+
+ // start by writing to temporary file
+ std::error_code ec;
+ wpi::raw_fd_ostream os(tmp, ec, wpi::sys::fs::F_Text);
+ if (ec.value() != 0) {
+ return "could not open file";
+ }
+ DEBUG("saving file '" << filename << "'");
+ SavePersistentImpl(os).Save(entries);
+ os.close();
+ if (os.has_error()) {
+ std::remove(tmp.c_str());
+ return "error saving file";
+ }
+
+ // Safely move to real file. We ignore any failures related to the backup.
+ std::remove(bak.c_str());
+ std::rename(fn.c_str(), bak.c_str());
+ if (std::rename(tmp.c_str(), fn.c_str()) != 0) {
+ std::rename(bak.c_str(), fn.c_str()); // attempt to restore backup
+ return "could not rename temp file to real file";
+ }
+
+ return nullptr;
+}
diff --git a/ntcore/src/main/native/cpp/Value.cpp b/ntcore/src/main/native/cpp/Value.cpp
new file mode 100644
index 0000000..61390f0
--- /dev/null
+++ b/ntcore/src/main/native/cpp/Value.cpp
@@ -0,0 +1,228 @@
+/*----------------------------------------------------------------------------*/
+/* Copyright (c) 2015-2018 FIRST. All Rights Reserved. */
+/* Open Source Software - may be modified and shared by FRC teams. The code */
+/* must be accompanied by the FIRST BSD license file in the root directory of */
+/* the project. */
+/*----------------------------------------------------------------------------*/
+
+#include <stdint.h>
+
+#include <wpi/memory.h>
+#include <wpi/timestamp.h>
+
+#include "Value_internal.h"
+#include "networktables/NetworkTableValue.h"
+
+using namespace nt;
+
+Value::Value() {
+ m_val.type = NT_UNASSIGNED;
+ m_val.last_change = wpi::Now();
+}
+
+Value::Value(NT_Type type, uint64_t time, const private_init&) {
+ m_val.type = type;
+ if (time == 0)
+ m_val.last_change = wpi::Now();
+ else
+ m_val.last_change = time;
+ if (m_val.type == NT_BOOLEAN_ARRAY)
+ m_val.data.arr_boolean.arr = nullptr;
+ else if (m_val.type == NT_DOUBLE_ARRAY)
+ m_val.data.arr_double.arr = nullptr;
+ else if (m_val.type == NT_STRING_ARRAY)
+ m_val.data.arr_string.arr = nullptr;
+}
+
+Value::~Value() {
+ if (m_val.type == NT_BOOLEAN_ARRAY)
+ delete[] m_val.data.arr_boolean.arr;
+ else if (m_val.type == NT_DOUBLE_ARRAY)
+ delete[] m_val.data.arr_double.arr;
+ else if (m_val.type == NT_STRING_ARRAY)
+ delete[] m_val.data.arr_string.arr;
+}
+
+std::shared_ptr<Value> Value::MakeBooleanArray(wpi::ArrayRef<bool> value,
+ uint64_t time) {
+ auto val = std::make_shared<Value>(NT_BOOLEAN_ARRAY, time, private_init());
+ val->m_val.data.arr_boolean.arr = new int[value.size()];
+ val->m_val.data.arr_boolean.size = value.size();
+ std::copy(value.begin(), value.end(), val->m_val.data.arr_boolean.arr);
+ return val;
+}
+
+std::shared_ptr<Value> Value::MakeBooleanArray(wpi::ArrayRef<int> value,
+ uint64_t time) {
+ auto val = std::make_shared<Value>(NT_BOOLEAN_ARRAY, time, private_init());
+ val->m_val.data.arr_boolean.arr = new int[value.size()];
+ val->m_val.data.arr_boolean.size = value.size();
+ std::copy(value.begin(), value.end(), val->m_val.data.arr_boolean.arr);
+ return val;
+}
+
+std::shared_ptr<Value> Value::MakeDoubleArray(wpi::ArrayRef<double> value,
+ uint64_t time) {
+ auto val = std::make_shared<Value>(NT_DOUBLE_ARRAY, time, private_init());
+ val->m_val.data.arr_double.arr = new double[value.size()];
+ val->m_val.data.arr_double.size = value.size();
+ std::copy(value.begin(), value.end(), val->m_val.data.arr_double.arr);
+ return val;
+}
+
+std::shared_ptr<Value> Value::MakeStringArray(wpi::ArrayRef<std::string> value,
+ uint64_t time) {
+ auto val = std::make_shared<Value>(NT_STRING_ARRAY, time, private_init());
+ val->m_string_array = value;
+ // point NT_Value to the contents in the vector.
+ val->m_val.data.arr_string.arr = new NT_String[value.size()];
+ val->m_val.data.arr_string.size = val->m_string_array.size();
+ for (size_t i = 0; i < value.size(); ++i) {
+ val->m_val.data.arr_string.arr[i].str = const_cast<char*>(value[i].c_str());
+ val->m_val.data.arr_string.arr[i].len = value[i].size();
+ }
+ return val;
+}
+
+std::shared_ptr<Value> Value::MakeStringArray(std::vector<std::string>&& value,
+ uint64_t time) {
+ auto val = std::make_shared<Value>(NT_STRING_ARRAY, time, private_init());
+ val->m_string_array = std::move(value);
+ value.clear();
+ // point NT_Value to the contents in the vector.
+ val->m_val.data.arr_string.arr = new NT_String[val->m_string_array.size()];
+ val->m_val.data.arr_string.size = val->m_string_array.size();
+ for (size_t i = 0; i < val->m_string_array.size(); ++i) {
+ val->m_val.data.arr_string.arr[i].str =
+ const_cast<char*>(val->m_string_array[i].c_str());
+ val->m_val.data.arr_string.arr[i].len = val->m_string_array[i].size();
+ }
+ return val;
+}
+
+void nt::ConvertToC(const Value& in, NT_Value* out) {
+ out->type = NT_UNASSIGNED;
+ switch (in.type()) {
+ case NT_UNASSIGNED:
+ return;
+ case NT_BOOLEAN:
+ out->data.v_boolean = in.GetBoolean() ? 1 : 0;
+ break;
+ case NT_DOUBLE:
+ out->data.v_double = in.GetDouble();
+ break;
+ case NT_STRING:
+ ConvertToC(in.GetString(), &out->data.v_string);
+ break;
+ case NT_RAW:
+ ConvertToC(in.GetRaw(), &out->data.v_raw);
+ break;
+ case NT_RPC:
+ ConvertToC(in.GetRpc(), &out->data.v_raw);
+ break;
+ case NT_BOOLEAN_ARRAY: {
+ auto v = in.GetBooleanArray();
+ out->data.arr_boolean.arr =
+ static_cast<int*>(wpi::CheckedMalloc(v.size() * sizeof(int)));
+ out->data.arr_boolean.size = v.size();
+ std::copy(v.begin(), v.end(), out->data.arr_boolean.arr);
+ break;
+ }
+ case NT_DOUBLE_ARRAY: {
+ auto v = in.GetDoubleArray();
+ out->data.arr_double.arr =
+ static_cast<double*>(wpi::CheckedMalloc(v.size() * sizeof(double)));
+ out->data.arr_double.size = v.size();
+ std::copy(v.begin(), v.end(), out->data.arr_double.arr);
+ break;
+ }
+ case NT_STRING_ARRAY: {
+ auto v = in.GetStringArray();
+ out->data.arr_string.arr = static_cast<NT_String*>(
+ wpi::CheckedMalloc(v.size() * sizeof(NT_String)));
+ for (size_t i = 0; i < v.size(); ++i)
+ ConvertToC(v[i], &out->data.arr_string.arr[i]);
+ out->data.arr_string.size = v.size();
+ break;
+ }
+ default:
+ // assert(false && "unknown value type");
+ return;
+ }
+ out->type = in.type();
+}
+
+void nt::ConvertToC(wpi::StringRef in, NT_String* out) {
+ out->len = in.size();
+ out->str = static_cast<char*>(wpi::CheckedMalloc(in.size() + 1));
+ std::memcpy(out->str, in.data(), in.size());
+ out->str[in.size()] = '\0';
+}
+
+std::shared_ptr<Value> nt::ConvertFromC(const NT_Value& value) {
+ switch (value.type) {
+ case NT_UNASSIGNED:
+ return nullptr;
+ case NT_BOOLEAN:
+ return Value::MakeBoolean(value.data.v_boolean != 0);
+ case NT_DOUBLE:
+ return Value::MakeDouble(value.data.v_double);
+ case NT_STRING:
+ return Value::MakeString(ConvertFromC(value.data.v_string));
+ case NT_RAW:
+ return Value::MakeRaw(ConvertFromC(value.data.v_raw));
+ case NT_RPC:
+ return Value::MakeRpc(ConvertFromC(value.data.v_raw));
+ case NT_BOOLEAN_ARRAY:
+ return Value::MakeBooleanArray(wpi::ArrayRef<int>(
+ value.data.arr_boolean.arr, value.data.arr_boolean.size));
+ case NT_DOUBLE_ARRAY:
+ return Value::MakeDoubleArray(wpi::ArrayRef<double>(
+ value.data.arr_double.arr, value.data.arr_double.size));
+ case NT_STRING_ARRAY: {
+ std::vector<std::string> v;
+ v.reserve(value.data.arr_string.size);
+ for (size_t i = 0; i < value.data.arr_string.size; ++i)
+ v.push_back(ConvertFromC(value.data.arr_string.arr[i]));
+ return Value::MakeStringArray(std::move(v));
+ }
+ default:
+ // assert(false && "unknown value type");
+ return nullptr;
+ }
+}
+
+bool nt::operator==(const Value& lhs, const Value& rhs) {
+ if (lhs.type() != rhs.type()) return false;
+ switch (lhs.type()) {
+ case NT_UNASSIGNED:
+ return true; // XXX: is this better being false instead?
+ case NT_BOOLEAN:
+ return lhs.m_val.data.v_boolean == rhs.m_val.data.v_boolean;
+ case NT_DOUBLE:
+ return lhs.m_val.data.v_double == rhs.m_val.data.v_double;
+ case NT_STRING:
+ case NT_RAW:
+ case NT_RPC:
+ return lhs.m_string == rhs.m_string;
+ case NT_BOOLEAN_ARRAY:
+ if (lhs.m_val.data.arr_boolean.size != rhs.m_val.data.arr_boolean.size)
+ return false;
+ return std::memcmp(lhs.m_val.data.arr_boolean.arr,
+ rhs.m_val.data.arr_boolean.arr,
+ lhs.m_val.data.arr_boolean.size *
+ sizeof(lhs.m_val.data.arr_boolean.arr[0])) == 0;
+ case NT_DOUBLE_ARRAY:
+ if (lhs.m_val.data.arr_double.size != rhs.m_val.data.arr_double.size)
+ return false;
+ return std::memcmp(lhs.m_val.data.arr_double.arr,
+ rhs.m_val.data.arr_double.arr,
+ lhs.m_val.data.arr_double.size *
+ sizeof(lhs.m_val.data.arr_double.arr[0])) == 0;
+ case NT_STRING_ARRAY:
+ return lhs.m_string_array == rhs.m_string_array;
+ default:
+ // assert(false && "unknown value type");
+ return false;
+ }
+}
diff --git a/ntcore/src/main/native/cpp/Value_internal.h b/ntcore/src/main/native/cpp/Value_internal.h
new file mode 100644
index 0000000..ea25777
--- /dev/null
+++ b/ntcore/src/main/native/cpp/Value_internal.h
@@ -0,0 +1,31 @@
+/*----------------------------------------------------------------------------*/
+/* Copyright (c) 2015-2018 FIRST. All Rights Reserved. */
+/* Open Source Software - may be modified and shared by FRC teams. The code */
+/* must be accompanied by the FIRST BSD license file in the root directory of */
+/* the project. */
+/*----------------------------------------------------------------------------*/
+
+#ifndef NTCORE_VALUE_INTERNAL_H_
+#define NTCORE_VALUE_INTERNAL_H_
+
+#include <memory>
+#include <string>
+
+#include <wpi/StringRef.h>
+
+#include "ntcore_c.h"
+
+namespace nt {
+
+class Value;
+
+void ConvertToC(const Value& in, NT_Value* out);
+std::shared_ptr<Value> ConvertFromC(const NT_Value& value);
+void ConvertToC(wpi::StringRef in, NT_String* out);
+inline wpi::StringRef ConvertFromC(const NT_String& str) {
+ return wpi::StringRef(str.str, str.len);
+}
+
+} // namespace nt
+
+#endif // NTCORE_VALUE_INTERNAL_H_
diff --git a/ntcore/src/main/native/cpp/WireDecoder.cpp b/ntcore/src/main/native/cpp/WireDecoder.cpp
new file mode 100644
index 0000000..132e8a2
--- /dev/null
+++ b/ntcore/src/main/native/cpp/WireDecoder.cpp
@@ -0,0 +1,208 @@
+/*----------------------------------------------------------------------------*/
+/* Copyright (c) 2015-2018 FIRST. All Rights Reserved. */
+/* Open Source Software - may be modified and shared by FRC teams. The code */
+/* must be accompanied by the FIRST BSD license file in the root directory of */
+/* the project. */
+/*----------------------------------------------------------------------------*/
+
+#include "WireDecoder.h"
+
+#include <stdint.h>
+
+#include <cassert>
+#include <cstdlib>
+#include <cstring>
+
+#include <wpi/MathExtras.h>
+#include <wpi/leb128.h>
+#include <wpi/memory.h>
+
+using namespace nt;
+
+static double ReadDouble(const char*& buf) {
+ // Fast but non-portable!
+ uint64_t val = (*reinterpret_cast<const unsigned char*>(buf)) & 0xff;
+ ++buf;
+ val <<= 8;
+ val |= (*reinterpret_cast<const unsigned char*>(buf)) & 0xff;
+ ++buf;
+ val <<= 8;
+ val |= (*reinterpret_cast<const unsigned char*>(buf)) & 0xff;
+ ++buf;
+ val <<= 8;
+ val |= (*reinterpret_cast<const unsigned char*>(buf)) & 0xff;
+ ++buf;
+ val <<= 8;
+ val |= (*reinterpret_cast<const unsigned char*>(buf)) & 0xff;
+ ++buf;
+ val <<= 8;
+ val |= (*reinterpret_cast<const unsigned char*>(buf)) & 0xff;
+ ++buf;
+ val <<= 8;
+ val |= (*reinterpret_cast<const unsigned char*>(buf)) & 0xff;
+ ++buf;
+ val <<= 8;
+ val |= (*reinterpret_cast<const unsigned char*>(buf)) & 0xff;
+ ++buf;
+ return wpi::BitsToDouble(val);
+}
+
+WireDecoder::WireDecoder(wpi::raw_istream& is, unsigned int proto_rev,
+ wpi::Logger& logger)
+ : m_is(is), m_logger(logger) {
+ // Start with a 1K temporary buffer. Use malloc instead of new so we can
+ // realloc.
+ m_allocated = 1024;
+ m_buf = static_cast<char*>(wpi::CheckedMalloc(m_allocated));
+ m_proto_rev = proto_rev;
+ m_error = nullptr;
+}
+
+WireDecoder::~WireDecoder() { std::free(m_buf); }
+
+bool WireDecoder::ReadDouble(double* val) {
+ const char* buf;
+ if (!Read(&buf, 8)) return false;
+ *val = ::ReadDouble(buf);
+ return true;
+}
+
+void WireDecoder::Realloc(size_t len) {
+ // Double current buffer size until we have enough space.
+ if (m_allocated >= len) return;
+ size_t newlen = m_allocated * 2;
+ while (newlen < len) newlen *= 2;
+ m_buf = static_cast<char*>(wpi::CheckedRealloc(m_buf, newlen));
+ m_allocated = newlen;
+}
+
+bool WireDecoder::ReadType(NT_Type* type) {
+ unsigned int itype;
+ if (!Read8(&itype)) return false;
+ // Convert from byte value to enum
+ switch (itype) {
+ case 0x00:
+ *type = NT_BOOLEAN;
+ break;
+ case 0x01:
+ *type = NT_DOUBLE;
+ break;
+ case 0x02:
+ *type = NT_STRING;
+ break;
+ case 0x03:
+ *type = NT_RAW;
+ break;
+ case 0x10:
+ *type = NT_BOOLEAN_ARRAY;
+ break;
+ case 0x11:
+ *type = NT_DOUBLE_ARRAY;
+ break;
+ case 0x12:
+ *type = NT_STRING_ARRAY;
+ break;
+ case 0x20:
+ *type = NT_RPC;
+ break;
+ default:
+ *type = NT_UNASSIGNED;
+ m_error = "unrecognized value type";
+ return false;
+ }
+ return true;
+}
+
+std::shared_ptr<Value> WireDecoder::ReadValue(NT_Type type) {
+ switch (type) {
+ case NT_BOOLEAN: {
+ unsigned int v;
+ if (!Read8(&v)) return nullptr;
+ return Value::MakeBoolean(v != 0);
+ }
+ case NT_DOUBLE: {
+ double v;
+ if (!ReadDouble(&v)) return nullptr;
+ return Value::MakeDouble(v);
+ }
+ case NT_STRING: {
+ std::string v;
+ if (!ReadString(&v)) return nullptr;
+ return Value::MakeString(std::move(v));
+ }
+ case NT_RAW: {
+ if (m_proto_rev < 0x0300u) {
+ m_error = "received raw value in protocol < 3.0";
+ return nullptr;
+ }
+ std::string v;
+ if (!ReadString(&v)) return nullptr;
+ return Value::MakeRaw(std::move(v));
+ }
+ case NT_RPC: {
+ if (m_proto_rev < 0x0300u) {
+ m_error = "received RPC value in protocol < 3.0";
+ return nullptr;
+ }
+ std::string v;
+ if (!ReadString(&v)) return nullptr;
+ return Value::MakeRpc(std::move(v));
+ }
+ case NT_BOOLEAN_ARRAY: {
+ // size
+ unsigned int size;
+ if (!Read8(&size)) return nullptr;
+
+ // array values
+ const char* buf;
+ if (!Read(&buf, size)) return nullptr;
+ std::vector<int> v(size);
+ for (unsigned int i = 0; i < size; ++i) v[i] = buf[i] ? 1 : 0;
+ return Value::MakeBooleanArray(std::move(v));
+ }
+ case NT_DOUBLE_ARRAY: {
+ // size
+ unsigned int size;
+ if (!Read8(&size)) return nullptr;
+
+ // array values
+ const char* buf;
+ if (!Read(&buf, size * 8)) return nullptr;
+ std::vector<double> v(size);
+ for (unsigned int i = 0; i < size; ++i) v[i] = ::ReadDouble(buf);
+ return Value::MakeDoubleArray(std::move(v));
+ }
+ case NT_STRING_ARRAY: {
+ // size
+ unsigned int size;
+ if (!Read8(&size)) return nullptr;
+
+ // array values
+ std::vector<std::string> v(size);
+ for (unsigned int i = 0; i < size; ++i) {
+ if (!ReadString(&v[i])) return nullptr;
+ }
+ return Value::MakeStringArray(std::move(v));
+ }
+ default:
+ m_error = "invalid type when trying to read value";
+ return nullptr;
+ }
+}
+
+bool WireDecoder::ReadString(std::string* str) {
+ size_t len;
+ if (m_proto_rev < 0x0300u) {
+ unsigned int v;
+ if (!Read16(&v)) return false;
+ len = v;
+ } else {
+ uint64_t v;
+ if (!ReadUleb128(&v)) return false;
+ len = v;
+ }
+ const char* buf;
+ if (!Read(&buf, len)) return false;
+ *str = wpi::StringRef(buf, len);
+ return true;
+}
diff --git a/ntcore/src/main/native/cpp/WireDecoder.h b/ntcore/src/main/native/cpp/WireDecoder.h
new file mode 100644
index 0000000..6b4483b
--- /dev/null
+++ b/ntcore/src/main/native/cpp/WireDecoder.h
@@ -0,0 +1,158 @@
+/*----------------------------------------------------------------------------*/
+/* Copyright (c) 2015-2018 FIRST. All Rights Reserved. */
+/* Open Source Software - may be modified and shared by FRC teams. The code */
+/* must be accompanied by the FIRST BSD license file in the root directory of */
+/* the project. */
+/*----------------------------------------------------------------------------*/
+
+#ifndef NTCORE_WIREDECODER_H_
+#define NTCORE_WIREDECODER_H_
+
+#include <stdint.h>
+
+#include <cstddef>
+#include <memory>
+#include <string>
+
+#include <wpi/leb128.h>
+#include <wpi/raw_istream.h>
+
+#include "Log.h"
+#include "networktables/NetworkTableValue.h"
+
+namespace nt {
+
+/* Decodes network data into native representation.
+ * This class is designed to read from a raw_istream, which provides a blocking
+ * read interface. There are no provisions in this class for resuming a read
+ * that was interrupted partway. Read functions return false if
+ * raw_istream.read() returned false (indicating the end of the input data
+ * stream).
+ */
+class WireDecoder {
+ public:
+ WireDecoder(wpi::raw_istream& is, unsigned int proto_rev,
+ wpi::Logger& logger);
+ ~WireDecoder();
+
+ void set_proto_rev(unsigned int proto_rev) { m_proto_rev = proto_rev; }
+
+ /* Get the active protocol revision. */
+ unsigned int proto_rev() const { return m_proto_rev; }
+
+ /* Get the logger. */
+ wpi::Logger& logger() const { return m_logger; }
+
+ /* Clears error indicator. */
+ void Reset() { m_error = nullptr; }
+
+ /* Returns error indicator (a string describing the error). Returns nullptr
+ * if no error has occurred.
+ */
+ const char* error() const { return m_error; }
+
+ void set_error(const char* error) { m_error = error; }
+
+ /* Reads the specified number of bytes.
+ * @param buf pointer to read data (output parameter)
+ * @param len number of bytes to read
+ * Caution: the buffer is only temporarily valid.
+ */
+ bool Read(const char** buf, size_t len) {
+ if (len > m_allocated) Realloc(len);
+ *buf = m_buf;
+ m_is.read(m_buf, len);
+#if 0
+ if (m_logger.min_level() <= NT_LOG_DEBUG4 && m_logger.HasLogger()) {
+ std::ostringstream oss;
+ oss << "read " << len << " bytes:" << std::hex;
+ if (!rv) {
+ oss << "error";
+ } else {
+ for (size_t i = 0; i < len; ++i)
+ oss << ' ' << static_cast<unsigned int>((*buf)[i]);
+ }
+ m_logger.Log(NT_LOG_DEBUG4, __FILE__, __LINE__, oss.str().c_str());
+ }
+#endif
+ return !m_is.has_error();
+ }
+
+ /* Reads a single byte. */
+ bool Read8(unsigned int* val) {
+ const char* buf;
+ if (!Read(&buf, 1)) return false;
+ *val = (*reinterpret_cast<const unsigned char*>(buf)) & 0xff;
+ return true;
+ }
+
+ /* Reads a 16-bit word. */
+ bool Read16(unsigned int* val) {
+ const char* buf;
+ if (!Read(&buf, 2)) return false;
+ unsigned int v = (*reinterpret_cast<const unsigned char*>(buf)) & 0xff;
+ ++buf;
+ v <<= 8;
+ v |= (*reinterpret_cast<const unsigned char*>(buf)) & 0xff;
+ *val = v;
+ return true;
+ }
+
+ /* Reads a 32-bit word. */
+ bool Read32(uint32_t* val) {
+ const char* buf;
+ if (!Read(&buf, 4)) return false;
+ unsigned int v = (*reinterpret_cast<const unsigned char*>(buf)) & 0xff;
+ ++buf;
+ v <<= 8;
+ v |= (*reinterpret_cast<const unsigned char*>(buf)) & 0xff;
+ ++buf;
+ v <<= 8;
+ v |= (*reinterpret_cast<const unsigned char*>(buf)) & 0xff;
+ ++buf;
+ v <<= 8;
+ v |= (*reinterpret_cast<const unsigned char*>(buf)) & 0xff;
+ *val = v;
+ return true;
+ }
+
+ /* Reads a double. */
+ bool ReadDouble(double* val);
+
+ /* Reads an ULEB128-encoded unsigned integer. */
+ bool ReadUleb128(uint64_t* val) { return wpi::ReadUleb128(m_is, val); }
+
+ bool ReadType(NT_Type* type);
+ bool ReadString(std::string* str);
+ std::shared_ptr<Value> ReadValue(NT_Type type);
+
+ WireDecoder(const WireDecoder&) = delete;
+ WireDecoder& operator=(const WireDecoder&) = delete;
+
+ protected:
+ /* The protocol revision. E.g. 0x0200 for version 2.0. */
+ unsigned int m_proto_rev;
+
+ /* Error indicator. */
+ const char* m_error;
+
+ private:
+ /* Reallocate temporary buffer to specified length. */
+ void Realloc(size_t len);
+
+ /* input stream */
+ wpi::raw_istream& m_is;
+
+ /* logger */
+ wpi::Logger& m_logger;
+
+ /* temporary buffer */
+ char* m_buf;
+
+ /* allocated size of temporary buffer */
+ size_t m_allocated;
+};
+
+} // namespace nt
+
+#endif // NTCORE_WIREDECODER_H_
diff --git a/ntcore/src/main/native/cpp/WireEncoder.cpp b/ntcore/src/main/native/cpp/WireEncoder.cpp
new file mode 100644
index 0000000..6538349
--- /dev/null
+++ b/ntcore/src/main/native/cpp/WireEncoder.cpp
@@ -0,0 +1,199 @@
+/*----------------------------------------------------------------------------*/
+/* Copyright (c) 2015-2018 FIRST. All Rights Reserved. */
+/* Open Source Software - may be modified and shared by FRC teams. The code */
+/* must be accompanied by the FIRST BSD license file in the root directory of */
+/* the project. */
+/*----------------------------------------------------------------------------*/
+
+#include "WireEncoder.h"
+
+#include <stdint.h>
+
+#include <cassert>
+#include <cstdlib>
+#include <cstring>
+
+#include <wpi/MathExtras.h>
+#include <wpi/leb128.h>
+
+using namespace nt;
+
+WireEncoder::WireEncoder(unsigned int proto_rev) {
+ m_proto_rev = proto_rev;
+ m_error = nullptr;
+}
+
+void WireEncoder::WriteDouble(double val) {
+ // The highest performance way to do this, albeit non-portable.
+ uint64_t v = wpi::DoubleToBits(val);
+ m_data.append(
+ {static_cast<char>((v >> 56) & 0xff), static_cast<char>((v >> 48) & 0xff),
+ static_cast<char>((v >> 40) & 0xff), static_cast<char>((v >> 32) & 0xff),
+ static_cast<char>((v >> 24) & 0xff), static_cast<char>((v >> 16) & 0xff),
+ static_cast<char>((v >> 8) & 0xff), static_cast<char>(v & 0xff)});
+}
+
+void WireEncoder::WriteUleb128(uint32_t val) { wpi::WriteUleb128(m_data, val); }
+
+void WireEncoder::WriteType(NT_Type type) {
+ char ch;
+ // Convert from enum to actual byte value.
+ switch (type) {
+ case NT_BOOLEAN:
+ ch = 0x00;
+ break;
+ case NT_DOUBLE:
+ ch = 0x01;
+ break;
+ case NT_STRING:
+ ch = 0x02;
+ break;
+ case NT_RAW:
+ if (m_proto_rev < 0x0300u) {
+ m_error = "raw type not supported in protocol < 3.0";
+ return;
+ }
+ ch = 0x03;
+ break;
+ case NT_BOOLEAN_ARRAY:
+ ch = 0x10;
+ break;
+ case NT_DOUBLE_ARRAY:
+ ch = 0x11;
+ break;
+ case NT_STRING_ARRAY:
+ ch = 0x12;
+ break;
+ case NT_RPC:
+ if (m_proto_rev < 0x0300u) {
+ m_error = "RPC type not supported in protocol < 3.0";
+ return;
+ }
+ ch = 0x20;
+ break;
+ default:
+ m_error = "unrecognized type";
+ return;
+ }
+ m_data.push_back(ch);
+}
+
+size_t WireEncoder::GetValueSize(const Value& value) const {
+ switch (value.type()) {
+ case NT_BOOLEAN:
+ return 1;
+ case NT_DOUBLE:
+ return 8;
+ case NT_STRING:
+ return GetStringSize(value.GetString());
+ case NT_RAW:
+ if (m_proto_rev < 0x0300u) return 0;
+ return GetStringSize(value.GetRaw());
+ case NT_RPC:
+ if (m_proto_rev < 0x0300u) return 0;
+ return GetStringSize(value.GetRpc());
+ case NT_BOOLEAN_ARRAY: {
+ // 1-byte size, 1 byte per element
+ size_t size = value.GetBooleanArray().size();
+ if (size > 0xff) size = 0xff; // size is only 1 byte, truncate
+ return 1 + size;
+ }
+ case NT_DOUBLE_ARRAY: {
+ // 1-byte size, 8 bytes per element
+ size_t size = value.GetDoubleArray().size();
+ if (size > 0xff) size = 0xff; // size is only 1 byte, truncate
+ return 1 + size * 8;
+ }
+ case NT_STRING_ARRAY: {
+ auto v = value.GetStringArray();
+ size_t size = v.size();
+ if (size > 0xff) size = 0xff; // size is only 1 byte, truncate
+ size_t len = 1; // 1-byte size
+ for (size_t i = 0; i < size; ++i) len += GetStringSize(v[i]);
+ return len;
+ }
+ default:
+ return 0;
+ }
+}
+
+void WireEncoder::WriteValue(const Value& value) {
+ switch (value.type()) {
+ case NT_BOOLEAN:
+ Write8(value.GetBoolean() ? 1 : 0);
+ break;
+ case NT_DOUBLE:
+ WriteDouble(value.GetDouble());
+ break;
+ case NT_STRING:
+ WriteString(value.GetString());
+ break;
+ case NT_RAW:
+ if (m_proto_rev < 0x0300u) {
+ m_error = "raw values not supported in protocol < 3.0";
+ return;
+ }
+ WriteString(value.GetRaw());
+ break;
+ case NT_RPC:
+ if (m_proto_rev < 0x0300u) {
+ m_error = "RPC values not supported in protocol < 3.0";
+ return;
+ }
+ WriteString(value.GetRpc());
+ break;
+ case NT_BOOLEAN_ARRAY: {
+ auto v = value.GetBooleanArray();
+ size_t size = v.size();
+ if (size > 0xff) size = 0xff; // size is only 1 byte, truncate
+ Write8(size);
+
+ for (size_t i = 0; i < size; ++i) Write8(v[i] ? 1 : 0);
+ break;
+ }
+ case NT_DOUBLE_ARRAY: {
+ auto v = value.GetDoubleArray();
+ size_t size = v.size();
+ if (size > 0xff) size = 0xff; // size is only 1 byte, truncate
+ Write8(size);
+
+ for (size_t i = 0; i < size; ++i) WriteDouble(v[i]);
+ break;
+ }
+ case NT_STRING_ARRAY: {
+ auto v = value.GetStringArray();
+ size_t size = v.size();
+ if (size > 0xff) size = 0xff; // size is only 1 byte, truncate
+ Write8(size);
+
+ for (size_t i = 0; i < size; ++i) WriteString(v[i]);
+ break;
+ }
+ default:
+ m_error = "unrecognized type when writing value";
+ return;
+ }
+}
+
+size_t WireEncoder::GetStringSize(wpi::StringRef str) const {
+ if (m_proto_rev < 0x0300u) {
+ size_t len = str.size();
+ if (len > 0xffff) len = 0xffff; // Limited to 64K length; truncate
+ return 2 + len;
+ }
+ return wpi::SizeUleb128(str.size()) + str.size();
+}
+
+void WireEncoder::WriteString(wpi::StringRef str) {
+ // length
+ size_t len = str.size();
+ if (m_proto_rev < 0x0300u) {
+ if (len > 0xffff) len = 0xffff; // Limited to 64K length; truncate
+ Write16(len);
+ } else {
+ WriteUleb128(len);
+ }
+
+ // contents
+ m_data.append(str.data(), str.data() + len);
+}
diff --git a/ntcore/src/main/native/cpp/WireEncoder.h b/ntcore/src/main/native/cpp/WireEncoder.h
new file mode 100644
index 0000000..c4f769c
--- /dev/null
+++ b/ntcore/src/main/native/cpp/WireEncoder.h
@@ -0,0 +1,111 @@
+/*----------------------------------------------------------------------------*/
+/* Copyright (c) 2015-2018 FIRST. All Rights Reserved. */
+/* Open Source Software - may be modified and shared by FRC teams. The code */
+/* must be accompanied by the FIRST BSD license file in the root directory of */
+/* the project. */
+/*----------------------------------------------------------------------------*/
+
+#ifndef NTCORE_WIREENCODER_H_
+#define NTCORE_WIREENCODER_H_
+
+#include <stdint.h>
+
+#include <cassert>
+#include <cstddef>
+
+#include <wpi/SmallVector.h>
+#include <wpi/StringRef.h>
+
+#include "networktables/NetworkTableValue.h"
+
+namespace nt {
+
+/* Encodes native data for network transmission.
+ * This class maintains an internal memory buffer for written data so that
+ * it can be efficiently bursted to the network after a number of writes
+ * have been performed. For this reason, all operations are non-blocking.
+ */
+class WireEncoder {
+ public:
+ explicit WireEncoder(unsigned int proto_rev);
+
+ /* Change the protocol revision (mostly affects value encoding). */
+ void set_proto_rev(unsigned int proto_rev) { m_proto_rev = proto_rev; }
+
+ /* Get the active protocol revision. */
+ unsigned int proto_rev() const { return m_proto_rev; }
+
+ /* Clears buffer and error indicator. */
+ void Reset() {
+ m_data.clear();
+ m_error = nullptr;
+ }
+
+ /* Returns error indicator (a string describing the error). Returns nullptr
+ * if no error has occurred.
+ */
+ const char* error() const { return m_error; }
+
+ /* Returns pointer to start of memory buffer with written data. */
+ const char* data() const { return m_data.data(); }
+
+ /* Returns number of bytes written to memory buffer. */
+ size_t size() const { return m_data.size(); }
+
+ wpi::StringRef ToStringRef() const {
+ return wpi::StringRef(m_data.data(), m_data.size());
+ }
+
+ /* Writes a single byte. */
+ void Write8(unsigned int val) {
+ m_data.push_back(static_cast<char>(val & 0xff));
+ }
+
+ /* Writes a 16-bit word. */
+ void Write16(unsigned int val) {
+ m_data.append(
+ {static_cast<char>((val >> 8) & 0xff), static_cast<char>(val & 0xff)});
+ }
+
+ /* Writes a 32-bit word. */
+ void Write32(uint32_t val) {
+ m_data.append({static_cast<char>((val >> 24) & 0xff),
+ static_cast<char>((val >> 16) & 0xff),
+ static_cast<char>((val >> 8) & 0xff),
+ static_cast<char>(val & 0xff)});
+ }
+
+ /* Writes a double. */
+ void WriteDouble(double val);
+
+ /* Writes an ULEB128-encoded unsigned integer. */
+ void WriteUleb128(uint32_t val);
+
+ void WriteType(NT_Type type);
+ void WriteValue(const Value& value);
+ void WriteString(wpi::StringRef str);
+
+ /* Utility function to get the written size of a value (without actually
+ * writing it).
+ */
+ size_t GetValueSize(const Value& value) const;
+
+ /* Utility function to get the written size of a string (without actually
+ * writing it).
+ */
+ size_t GetStringSize(wpi::StringRef str) const;
+
+ protected:
+ /* The protocol revision. E.g. 0x0200 for version 2.0. */
+ unsigned int m_proto_rev;
+
+ /* Error indicator. */
+ const char* m_error;
+
+ private:
+ wpi::SmallVector<char, 256> m_data;
+};
+
+} // namespace nt
+
+#endif // NTCORE_WIREENCODER_H_
diff --git a/ntcore/src/main/native/cpp/jni/NetworkTablesJNI.cpp b/ntcore/src/main/native/cpp/jni/NetworkTablesJNI.cpp
new file mode 100644
index 0000000..49f34f0
--- /dev/null
+++ b/ntcore/src/main/native/cpp/jni/NetworkTablesJNI.cpp
@@ -0,0 +1,1874 @@
+/*----------------------------------------------------------------------------*/
+/* Copyright (c) 2018 FIRST. All Rights Reserved. */
+/* Open Source Software - may be modified and shared by FRC teams. The code */
+/* must be accompanied by the FIRST BSD license file in the root directory of */
+/* the project. */
+/*----------------------------------------------------------------------------*/
+
+#include <jni.h>
+
+#include <cassert>
+
+#include <wpi/ConvertUTF.h>
+#include <wpi/SmallString.h>
+#include <wpi/jni_util.h>
+#include <wpi/raw_ostream.h>
+
+#include "edu_wpi_first_networktables_NetworkTablesJNI.h"
+#include "ntcore.h"
+
+using namespace wpi::java;
+
+#ifdef __GNUC__
+#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
+#endif
+
+//
+// Globals and load/unload
+//
+
+// Used for callback.
+static JavaVM* jvm = nullptr;
+static JClass booleanCls;
+static JClass connectionInfoCls;
+static JClass connectionNotificationCls;
+static JClass doubleCls;
+static JClass entryInfoCls;
+static JClass entryNotificationCls;
+static JClass logMessageCls;
+static JClass rpcAnswerCls;
+static JClass valueCls;
+static JException illegalArgEx;
+static JException interruptedEx;
+static JException nullPointerEx;
+static JException persistentEx;
+
+static const JClassInit classes[] = {
+ {"java/lang/Boolean", &booleanCls},
+ {"edu/wpi/first/networktables/ConnectionInfo", &connectionInfoCls},
+ {"edu/wpi/first/networktables/ConnectionNotification",
+ &connectionNotificationCls},
+ {"java/lang/Double", &doubleCls},
+ {"edu/wpi/first/networktables/EntryInfo", &entryInfoCls},
+ {"edu/wpi/first/networktables/EntryNotification", &entryNotificationCls},
+ {"edu/wpi/first/networktables/LogMessage", &logMessageCls},
+ {"edu/wpi/first/networktables/RpcAnswer", &rpcAnswerCls},
+ {"edu/wpi/first/networktables/NetworkTableValue", &valueCls}};
+
+static const JExceptionInit exceptions[] = {
+ {"java/lang/IllegalArgumentException", &illegalArgEx},
+ {"java/lang/InterruptedException", &interruptedEx},
+ {"java/lang/NullPointerException", &nullPointerEx},
+ {"edu/wpi/first/networktables/PersistentException", &persistentEx}};
+
+extern "C" {
+
+JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved) {
+ jvm = vm;
+
+ JNIEnv* env;
+ if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK)
+ return JNI_ERR;
+
+ // Cache references to classes
+ for (auto& c : classes) {
+ *c.cls = JClass(env, c.name);
+ if (!*c.cls) return JNI_ERR;
+ }
+
+ for (auto& c : exceptions) {
+ *c.cls = JException(env, c.name);
+ if (!*c.cls) return JNI_ERR;
+ }
+
+ return JNI_VERSION_1_6;
+}
+
+JNIEXPORT void JNICALL JNI_OnUnload(JavaVM* vm, void* reserved) {
+ JNIEnv* env;
+ if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK)
+ return;
+ // Delete global references
+ for (auto& c : classes) {
+ c.cls->free(env);
+ }
+ for (auto& c : exceptions) {
+ c.cls->free(env);
+ }
+ jvm = nullptr;
+}
+
+} // extern "C"
+
+//
+// Conversions from Java objects to C++
+//
+
+inline std::shared_ptr<nt::Value> FromJavaRaw(JNIEnv* env, jbyteArray jarr,
+ jlong time) {
+ CriticalJByteArrayRef ref{env, jarr};
+ if (!ref) return nullptr;
+ return nt::Value::MakeRaw(ref, time);
+}
+
+inline std::shared_ptr<nt::Value> FromJavaRawBB(JNIEnv* env, jobject jbb,
+ int len, jlong time) {
+ JByteArrayRef ref{env, jbb, len};
+ if (!ref) return nullptr;
+ return nt::Value::MakeRaw(ref.str(), time);
+}
+
+inline std::shared_ptr<nt::Value> FromJavaRpc(JNIEnv* env, jbyteArray jarr,
+ jlong time) {
+ CriticalJByteArrayRef ref{env, jarr};
+ if (!ref) return nullptr;
+ return nt::Value::MakeRpc(ref.str(), time);
+}
+
+std::shared_ptr<nt::Value> FromJavaBooleanArray(JNIEnv* env, jbooleanArray jarr,
+ jlong time) {
+ CriticalJBooleanArrayRef ref{env, jarr};
+ if (!ref) return nullptr;
+ wpi::ArrayRef<jboolean> elements{ref};
+ size_t len = elements.size();
+ std::vector<int> arr;
+ arr.reserve(len);
+ for (size_t i = 0; i < len; ++i) arr.push_back(elements[i]);
+ return nt::Value::MakeBooleanArray(arr, time);
+}
+
+std::shared_ptr<nt::Value> FromJavaDoubleArray(JNIEnv* env, jdoubleArray jarr,
+ jlong time) {
+ CriticalJDoubleArrayRef ref{env, jarr};
+ if (!ref) return nullptr;
+ return nt::Value::MakeDoubleArray(ref, time);
+}
+
+std::shared_ptr<nt::Value> FromJavaStringArray(JNIEnv* env, jobjectArray jarr,
+ jlong time) {
+ size_t len = env->GetArrayLength(jarr);
+ std::vector<std::string> arr;
+ arr.reserve(len);
+ for (size_t i = 0; i < len; ++i) {
+ JLocal<jstring> elem{
+ env, static_cast<jstring>(env->GetObjectArrayElement(jarr, i))};
+ if (!elem) return nullptr;
+ arr.push_back(JStringRef{env, elem}.str());
+ }
+ return nt::Value::MakeStringArray(std::move(arr), time);
+}
+
+//
+// Conversions from C++ to Java objects
+//
+
+static jobject MakeJObject(JNIEnv* env, const nt::Value& value) {
+ static jmethodID booleanConstructor = nullptr;
+ static jmethodID doubleConstructor = nullptr;
+ if (!booleanConstructor)
+ booleanConstructor = env->GetMethodID(booleanCls, "<init>", "(Z)V");
+ if (!doubleConstructor)
+ doubleConstructor = env->GetMethodID(doubleCls, "<init>", "(D)V");
+
+ switch (value.type()) {
+ case NT_BOOLEAN:
+ return env->NewObject(booleanCls, booleanConstructor,
+ (jboolean)(value.GetBoolean() ? 1 : 0));
+ case NT_DOUBLE:
+ return env->NewObject(doubleCls, doubleConstructor,
+ (jdouble)value.GetDouble());
+ case NT_STRING:
+ return MakeJString(env, value.GetString());
+ case NT_RAW:
+ return MakeJByteArray(env, value.GetRaw());
+ case NT_BOOLEAN_ARRAY:
+ return MakeJBooleanArray(env, value.GetBooleanArray());
+ case NT_DOUBLE_ARRAY:
+ return MakeJDoubleArray(env, value.GetDoubleArray());
+ case NT_STRING_ARRAY:
+ return MakeJStringArray(env, value.GetStringArray());
+ case NT_RPC:
+ return MakeJByteArray(env, value.GetRpc());
+ default:
+ return nullptr;
+ }
+}
+
+static jobject MakeJValue(JNIEnv* env, const nt::Value* value) {
+ static jmethodID constructor =
+ env->GetMethodID(valueCls, "<init>", "(ILjava/lang/Object;J)V");
+ if (!value)
+ return env->NewObject(valueCls, constructor, (jint)NT_UNASSIGNED, nullptr,
+ (jlong)0);
+ return env->NewObject(valueCls, constructor, (jint)value->type(),
+ MakeJObject(env, *value), (jlong)value->time());
+}
+
+static jobject MakeJObject(JNIEnv* env, const nt::ConnectionInfo& info) {
+ static jmethodID constructor =
+ env->GetMethodID(connectionInfoCls, "<init>",
+ "(Ljava/lang/String;Ljava/lang/String;IJI)V");
+ JLocal<jstring> remote_id{env, MakeJString(env, info.remote_id)};
+ JLocal<jstring> remote_ip{env, MakeJString(env, info.remote_ip)};
+ return env->NewObject(connectionInfoCls, constructor, remote_id.obj(),
+ remote_ip.obj(), (jint)info.remote_port,
+ (jlong)info.last_update, (jint)info.protocol_version);
+}
+
+static jobject MakeJObject(JNIEnv* env, jobject inst,
+ const nt::ConnectionNotification& notification) {
+ static jmethodID constructor = env->GetMethodID(
+ connectionNotificationCls, "<init>",
+ "(Ledu/wpi/first/networktables/NetworkTableInstance;IZLedu/wpi/first/"
+ "networktables/ConnectionInfo;)V");
+ JLocal<jobject> conn{env, MakeJObject(env, notification.conn)};
+ return env->NewObject(connectionNotificationCls, constructor, inst,
+ (jint)notification.listener,
+ (jboolean)notification.connected, conn.obj());
+}
+
+static jobject MakeJObject(JNIEnv* env, jobject inst,
+ const nt::EntryInfo& info) {
+ static jmethodID constructor =
+ env->GetMethodID(entryInfoCls, "<init>",
+ "(Ledu/wpi/first/networktables/"
+ "NetworkTableInstance;ILjava/lang/String;IIJ)V");
+ JLocal<jstring> name{env, MakeJString(env, info.name)};
+ return env->NewObject(entryInfoCls, constructor, inst, (jint)info.entry,
+ name.obj(), (jint)info.type, (jint)info.flags,
+ (jlong)info.last_change);
+}
+
+static jobject MakeJObject(JNIEnv* env, jobject inst,
+ const nt::EntryNotification& notification) {
+ static jmethodID constructor = env->GetMethodID(
+ entryNotificationCls, "<init>",
+ "(Ledu/wpi/first/networktables/NetworkTableInstance;IILjava/lang/"
+ "String;Ledu/wpi/first/networktables/NetworkTableValue;I)V");
+ JLocal<jstring> name{env, MakeJString(env, notification.name)};
+ JLocal<jobject> value{env, MakeJValue(env, notification.value.get())};
+ return env->NewObject(entryNotificationCls, constructor, inst,
+ (jint)notification.listener, (jint)notification.entry,
+ name.obj(), value.obj(), (jint)notification.flags);
+}
+
+static jobject MakeJObject(JNIEnv* env, jobject inst,
+ const nt::LogMessage& msg) {
+ static jmethodID constructor = env->GetMethodID(
+ logMessageCls, "<init>",
+ "(Ledu/wpi/first/networktables/NetworkTableInstance;IILjava/lang/"
+ "String;ILjava/lang/String;)V");
+ JLocal<jstring> filename{env, MakeJString(env, msg.filename)};
+ JLocal<jstring> message{env, MakeJString(env, msg.message)};
+ return env->NewObject(logMessageCls, constructor, inst, (jint)msg.logger,
+ (jint)msg.level, filename.obj(), (jint)msg.line,
+ message.obj());
+}
+
+static jobject MakeJObject(JNIEnv* env, jobject inst,
+ const nt::RpcAnswer& answer) {
+ static jmethodID constructor =
+ env->GetMethodID(rpcAnswerCls, "<init>",
+ "(Ledu/wpi/first/networktables/"
+ "NetworkTableInstance;IILjava/lang/String;[B"
+ "Ledu/wpi/first/networktables/ConnectionInfo;)V");
+ JLocal<jstring> name{env, MakeJString(env, answer.name)};
+ JLocal<jbyteArray> params{env, MakeJByteArray(env, answer.params)};
+ JLocal<jobject> conn{env, MakeJObject(env, answer.conn)};
+ return env->NewObject(rpcAnswerCls, constructor, inst, (jint)answer.entry,
+ (jint)answer.call, name.obj(), params.obj(),
+ conn.obj());
+}
+
+static jobjectArray MakeJObject(JNIEnv* env, jobject inst,
+ wpi::ArrayRef<nt::ConnectionNotification> arr) {
+ jobjectArray jarr =
+ env->NewObjectArray(arr.size(), connectionNotificationCls, nullptr);
+ if (!jarr) return nullptr;
+ for (size_t i = 0; i < arr.size(); ++i) {
+ JLocal<jobject> elem{env, MakeJObject(env, inst, arr[i])};
+ env->SetObjectArrayElement(jarr, i, elem.obj());
+ }
+ return jarr;
+}
+
+static jobjectArray MakeJObject(JNIEnv* env, jobject inst,
+ wpi::ArrayRef<nt::EntryNotification> arr) {
+ jobjectArray jarr =
+ env->NewObjectArray(arr.size(), entryNotificationCls, nullptr);
+ if (!jarr) return nullptr;
+ for (size_t i = 0; i < arr.size(); ++i) {
+ JLocal<jobject> elem{env, MakeJObject(env, inst, arr[i])};
+ env->SetObjectArrayElement(jarr, i, elem.obj());
+ }
+ return jarr;
+}
+
+static jobjectArray MakeJObject(JNIEnv* env, jobject inst,
+ wpi::ArrayRef<nt::LogMessage> arr) {
+ jobjectArray jarr = env->NewObjectArray(arr.size(), logMessageCls, nullptr);
+ if (!jarr) return nullptr;
+ for (size_t i = 0; i < arr.size(); ++i) {
+ JLocal<jobject> elem{env, MakeJObject(env, inst, arr[i])};
+ env->SetObjectArrayElement(jarr, i, elem.obj());
+ }
+ return jarr;
+}
+
+static jobjectArray MakeJObject(JNIEnv* env, jobject inst,
+ wpi::ArrayRef<nt::RpcAnswer> arr) {
+ jobjectArray jarr = env->NewObjectArray(arr.size(), rpcAnswerCls, nullptr);
+ if (!jarr) return nullptr;
+ for (size_t i = 0; i < arr.size(); ++i) {
+ JLocal<jobject> elem{env, MakeJObject(env, inst, arr[i])};
+ env->SetObjectArrayElement(jarr, i, elem.obj());
+ }
+ return jarr;
+}
+
+extern "C" {
+
+/*
+ * Class: edu_wpi_first_networktables_NetworkTablesJNI
+ * Method: getDefaultInstance
+ * Signature: ()I
+ */
+JNIEXPORT jint JNICALL
+Java_edu_wpi_first_networktables_NetworkTablesJNI_getDefaultInstance
+ (JNIEnv*, jclass)
+{
+ return nt::GetDefaultInstance();
+}
+
+/*
+ * Class: edu_wpi_first_networktables_NetworkTablesJNI
+ * Method: createInstance
+ * Signature: ()I
+ */
+JNIEXPORT jint JNICALL
+Java_edu_wpi_first_networktables_NetworkTablesJNI_createInstance
+ (JNIEnv*, jclass)
+{
+ return nt::CreateInstance();
+}
+
+/*
+ * Class: edu_wpi_first_networktables_NetworkTablesJNI
+ * Method: destroyInstance
+ * Signature: (I)V
+ */
+JNIEXPORT void JNICALL
+Java_edu_wpi_first_networktables_NetworkTablesJNI_destroyInstance
+ (JNIEnv*, jclass, jint inst)
+{
+ nt::DestroyInstance(inst);
+}
+
+/*
+ * Class: edu_wpi_first_networktables_NetworkTablesJNI
+ * Method: getInstanceFromHandle
+ * Signature: (I)I
+ */
+JNIEXPORT jint JNICALL
+Java_edu_wpi_first_networktables_NetworkTablesJNI_getInstanceFromHandle
+ (JNIEnv*, jclass, jint handle)
+{
+ return nt::GetInstanceFromHandle(handle);
+}
+
+/*
+ * Class: edu_wpi_first_networktables_NetworkTablesJNI
+ * Method: getEntry
+ * Signature: (ILjava/lang/String;)I
+ */
+JNIEXPORT jint JNICALL
+Java_edu_wpi_first_networktables_NetworkTablesJNI_getEntry
+ (JNIEnv* env, jclass, jint inst, jstring key)
+{
+ if (!key) {
+ nullPointerEx.Throw(env, "key cannot be null");
+ return false;
+ }
+ return nt::GetEntry(inst, JStringRef{env, key}.str());
+}
+
+/*
+ * Class: edu_wpi_first_networktables_NetworkTablesJNI
+ * Method: getEntries
+ * Signature: (ILjava/lang/String;I)[I
+ */
+JNIEXPORT jintArray JNICALL
+Java_edu_wpi_first_networktables_NetworkTablesJNI_getEntries
+ (JNIEnv* env, jclass, jint inst, jstring prefix, jint types)
+{
+ if (!prefix) {
+ nullPointerEx.Throw(env, "prefix cannot be null");
+ return nullptr;
+ }
+ return MakeJIntArray(
+ env, nt::GetEntries(inst, JStringRef{env, prefix}.str(), types));
+}
+
+/*
+ * Class: edu_wpi_first_networktables_NetworkTablesJNI
+ * Method: getEntryName
+ * Signature: (I)Ljava/lang/String;
+ */
+JNIEXPORT jstring JNICALL
+Java_edu_wpi_first_networktables_NetworkTablesJNI_getEntryName
+ (JNIEnv* env, jclass, jint entry)
+{
+ return MakeJString(env, nt::GetEntryName(entry));
+}
+
+/*
+ * Class: edu_wpi_first_networktables_NetworkTablesJNI
+ * Method: getEntryLastChange
+ * Signature: (I)J
+ */
+JNIEXPORT jlong JNICALL
+Java_edu_wpi_first_networktables_NetworkTablesJNI_getEntryLastChange
+ (JNIEnv*, jclass, jint entry)
+{
+ return nt::GetEntryLastChange(entry);
+}
+
+/*
+ * Class: edu_wpi_first_networktables_NetworkTablesJNI
+ * Method: getType
+ * Signature: (I)I
+ */
+JNIEXPORT jint JNICALL
+Java_edu_wpi_first_networktables_NetworkTablesJNI_getType
+ (JNIEnv*, jclass, jint entry)
+{
+ return nt::GetEntryType(entry);
+}
+
+/*
+ * Class: edu_wpi_first_networktables_NetworkTablesJNI
+ * Method: setBoolean
+ * Signature: (IJZZ)Z
+ */
+JNIEXPORT jboolean JNICALL
+Java_edu_wpi_first_networktables_NetworkTablesJNI_setBoolean
+ (JNIEnv*, jclass, jint entry, jlong time, jboolean value, jboolean force)
+{
+ if (force) {
+ nt::SetEntryTypeValue(entry,
+ nt::Value::MakeBoolean(value != JNI_FALSE, time));
+ return JNI_TRUE;
+ }
+ return nt::SetEntryValue(entry,
+ nt::Value::MakeBoolean(value != JNI_FALSE, time));
+}
+
+/*
+ * Class: edu_wpi_first_networktables_NetworkTablesJNI
+ * Method: setDouble
+ * Signature: (IJDZ)Z
+ */
+JNIEXPORT jboolean JNICALL
+Java_edu_wpi_first_networktables_NetworkTablesJNI_setDouble
+ (JNIEnv*, jclass, jint entry, jlong time, jdouble value, jboolean force)
+{
+ if (force) {
+ nt::SetEntryTypeValue(entry, nt::Value::MakeDouble(value, time));
+ return JNI_TRUE;
+ }
+ return nt::SetEntryValue(entry, nt::Value::MakeDouble(value, time));
+}
+
+/*
+ * Class: edu_wpi_first_networktables_NetworkTablesJNI
+ * Method: setString
+ * Signature: (IJLjava/lang/String;Z)Z
+ */
+JNIEXPORT jboolean JNICALL
+Java_edu_wpi_first_networktables_NetworkTablesJNI_setString
+ (JNIEnv* env, jclass, jint entry, jlong time, jstring value, jboolean force)
+{
+ if (!value) {
+ nullPointerEx.Throw(env, "value cannot be null");
+ return false;
+ }
+ if (force) {
+ nt::SetEntryTypeValue(
+ entry, nt::Value::MakeString(JStringRef{env, value}.str(), time));
+ return JNI_TRUE;
+ }
+ return nt::SetEntryValue(
+ entry, nt::Value::MakeString(JStringRef{env, value}.str(), time));
+}
+
+/*
+ * Class: edu_wpi_first_networktables_NetworkTablesJNI
+ * Method: setRaw
+ * Signature: (IJ[BZ)Z
+ */
+JNIEXPORT jboolean JNICALL
+Java_edu_wpi_first_networktables_NetworkTablesJNI_setRaw__IJ_3BZ
+ (JNIEnv* env, jclass, jint entry, jlong time, jbyteArray value,
+ jboolean force)
+{
+ if (!value) {
+ nullPointerEx.Throw(env, "value cannot be null");
+ return false;
+ }
+ auto v = FromJavaRaw(env, value, time);
+ if (!v) return false;
+ if (force) {
+ nt::SetEntryTypeValue(entry, v);
+ return JNI_TRUE;
+ }
+ return nt::SetEntryValue(entry, v);
+}
+
+/*
+ * Class: edu_wpi_first_networktables_NetworkTablesJNI
+ * Method: setRaw
+ * Signature: (IJLjava/lang/Object;IZ)Z
+ */
+JNIEXPORT jboolean JNICALL
+Java_edu_wpi_first_networktables_NetworkTablesJNI_setRaw__IJLjava_nio_ByteBuffer_2IZ
+ (JNIEnv* env, jclass, jint entry, jlong time, jobject value, jint len,
+ jboolean force)
+{
+ if (!value) {
+ nullPointerEx.Throw(env, "value cannot be null");
+ return false;
+ }
+ auto v = FromJavaRawBB(env, value, len, time);
+ if (!v) return false;
+ if (force) {
+ nt::SetEntryTypeValue(entry, v);
+ return JNI_TRUE;
+ }
+ return nt::SetEntryValue(entry, v);
+}
+
+/*
+ * Class: edu_wpi_first_networktables_NetworkTablesJNI
+ * Method: setBooleanArray
+ * Signature: (IJ[ZZ)Z
+ */
+JNIEXPORT jboolean JNICALL
+Java_edu_wpi_first_networktables_NetworkTablesJNI_setBooleanArray
+ (JNIEnv* env, jclass, jint entry, jlong time, jbooleanArray value,
+ jboolean force)
+{
+ if (!value) {
+ nullPointerEx.Throw(env, "value cannot be null");
+ return false;
+ }
+ auto v = FromJavaBooleanArray(env, value, time);
+ if (!v) return false;
+ if (force) {
+ nt::SetEntryTypeValue(entry, v);
+ return JNI_TRUE;
+ }
+ return nt::SetEntryValue(entry, v);
+}
+
+/*
+ * Class: edu_wpi_first_networktables_NetworkTablesJNI
+ * Method: setDoubleArray
+ * Signature: (IJ[DZ)Z
+ */
+JNIEXPORT jboolean JNICALL
+Java_edu_wpi_first_networktables_NetworkTablesJNI_setDoubleArray
+ (JNIEnv* env, jclass, jint entry, jlong time, jdoubleArray value,
+ jboolean force)
+{
+ if (!value) {
+ nullPointerEx.Throw(env, "value cannot be null");
+ return false;
+ }
+ auto v = FromJavaDoubleArray(env, value, time);
+ if (!v) return false;
+ if (force) {
+ nt::SetEntryTypeValue(entry, v);
+ return JNI_TRUE;
+ }
+ return nt::SetEntryValue(entry, v);
+}
+
+/*
+ * Class: edu_wpi_first_networktables_NetworkTablesJNI
+ * Method: setStringArray
+ * Signature: (IJ[Ljava/lang/Object;Z)Z
+ */
+JNIEXPORT jboolean JNICALL
+Java_edu_wpi_first_networktables_NetworkTablesJNI_setStringArray
+ (JNIEnv* env, jclass, jint entry, jlong time, jobjectArray value,
+ jboolean force)
+{
+ if (!value) {
+ nullPointerEx.Throw(env, "value cannot be null");
+ return false;
+ }
+ auto v = FromJavaStringArray(env, value, time);
+ if (!v) return false;
+ if (force) {
+ nt::SetEntryTypeValue(entry, v);
+ return JNI_TRUE;
+ }
+ return nt::SetEntryValue(entry, v);
+}
+
+/*
+ * Class: edu_wpi_first_networktables_NetworkTablesJNI
+ * Method: getValue
+ * Signature: (I)Ljava/lang/Object;
+ */
+JNIEXPORT jobject JNICALL
+Java_edu_wpi_first_networktables_NetworkTablesJNI_getValue
+ (JNIEnv* env, jclass, jint entry)
+{
+ auto val = nt::GetEntryValue(entry);
+ return MakeJValue(env, val.get());
+}
+
+/*
+ * Class: edu_wpi_first_networktables_NetworkTablesJNI
+ * Method: getBoolean
+ * Signature: (IZ)Z
+ */
+JNIEXPORT jboolean JNICALL
+Java_edu_wpi_first_networktables_NetworkTablesJNI_getBoolean
+ (JNIEnv*, jclass, jint entry, jboolean defaultValue)
+{
+ auto val = nt::GetEntryValue(entry);
+ if (!val || !val->IsBoolean()) return defaultValue;
+ return val->GetBoolean();
+}
+
+/*
+ * Class: edu_wpi_first_networktables_NetworkTablesJNI
+ * Method: getDouble
+ * Signature: (ID)D
+ */
+JNIEXPORT jdouble JNICALL
+Java_edu_wpi_first_networktables_NetworkTablesJNI_getDouble
+ (JNIEnv*, jclass, jint entry, jdouble defaultValue)
+{
+ auto val = nt::GetEntryValue(entry);
+ if (!val || !val->IsDouble()) return defaultValue;
+ return val->GetDouble();
+}
+
+/*
+ * Class: edu_wpi_first_networktables_NetworkTablesJNI
+ * Method: getString
+ * Signature: (ILjava/lang/String;)Ljava/lang/String;
+ */
+JNIEXPORT jstring JNICALL
+Java_edu_wpi_first_networktables_NetworkTablesJNI_getString
+ (JNIEnv* env, jclass, jint entry, jstring defaultValue)
+{
+ auto val = nt::GetEntryValue(entry);
+ if (!val || !val->IsString()) return defaultValue;
+ return MakeJString(env, val->GetString());
+}
+
+/*
+ * Class: edu_wpi_first_networktables_NetworkTablesJNI
+ * Method: getRaw
+ * Signature: (I[B)[B
+ */
+JNIEXPORT jbyteArray JNICALL
+Java_edu_wpi_first_networktables_NetworkTablesJNI_getRaw
+ (JNIEnv* env, jclass, jint entry, jbyteArray defaultValue)
+{
+ auto val = nt::GetEntryValue(entry);
+ if (!val || !val->IsRaw()) return defaultValue;
+ return MakeJByteArray(env, val->GetRaw());
+}
+
+/*
+ * Class: edu_wpi_first_networktables_NetworkTablesJNI
+ * Method: getBooleanArray
+ * Signature: (I[Z)[Z
+ */
+JNIEXPORT jbooleanArray JNICALL
+Java_edu_wpi_first_networktables_NetworkTablesJNI_getBooleanArray
+ (JNIEnv* env, jclass, jint entry, jbooleanArray defaultValue)
+{
+ auto val = nt::GetEntryValue(entry);
+ if (!val || !val->IsBooleanArray()) return defaultValue;
+ return MakeJBooleanArray(env, val->GetBooleanArray());
+}
+
+/*
+ * Class: edu_wpi_first_networktables_NetworkTablesJNI
+ * Method: getDoubleArray
+ * Signature: (I[D)[D
+ */
+JNIEXPORT jdoubleArray JNICALL
+Java_edu_wpi_first_networktables_NetworkTablesJNI_getDoubleArray
+ (JNIEnv* env, jclass, jint entry, jdoubleArray defaultValue)
+{
+ auto val = nt::GetEntryValue(entry);
+ if (!val || !val->IsDoubleArray()) return defaultValue;
+ return MakeJDoubleArray(env, val->GetDoubleArray());
+}
+
+/*
+ * Class: edu_wpi_first_networktables_NetworkTablesJNI
+ * Method: getStringArray
+ * Signature: (I[Ljava/lang/Object;)[Ljava/lang/Object;
+ */
+JNIEXPORT jobjectArray JNICALL
+Java_edu_wpi_first_networktables_NetworkTablesJNI_getStringArray
+ (JNIEnv* env, jclass, jint entry, jobjectArray defaultValue)
+{
+ auto val = nt::GetEntryValue(entry);
+ if (!val || !val->IsStringArray()) return defaultValue;
+ return MakeJStringArray(env, val->GetStringArray());
+}
+
+/*
+ * Class: edu_wpi_first_networktables_NetworkTablesJNI
+ * Method: setDefaultBoolean
+ * Signature: (IJZ)Z
+ */
+JNIEXPORT jboolean JNICALL
+Java_edu_wpi_first_networktables_NetworkTablesJNI_setDefaultBoolean
+ (JNIEnv*, jclass, jint entry, jlong time, jboolean defaultValue)
+{
+ return nt::SetDefaultEntryValue(
+ entry, nt::Value::MakeBoolean(defaultValue != JNI_FALSE, time));
+}
+
+/*
+ * Class: edu_wpi_first_networktables_NetworkTablesJNI
+ * Method: setDefaultDouble
+ * Signature: (IJD)Z
+ */
+JNIEXPORT jboolean JNICALL
+Java_edu_wpi_first_networktables_NetworkTablesJNI_setDefaultDouble
+ (JNIEnv*, jclass, jint entry, jlong time, jdouble defaultValue)
+{
+ return nt::SetDefaultEntryValue(entry,
+ nt::Value::MakeDouble(defaultValue, time));
+}
+
+/*
+ * Class: edu_wpi_first_networktables_NetworkTablesJNI
+ * Method: setDefaultString
+ * Signature: (IJLjava/lang/String;)Z
+ */
+JNIEXPORT jboolean JNICALL
+Java_edu_wpi_first_networktables_NetworkTablesJNI_setDefaultString
+ (JNIEnv* env, jclass, jint entry, jlong time, jstring defaultValue)
+{
+ if (!defaultValue) {
+ nullPointerEx.Throw(env, "defaultValue cannot be null");
+ return false;
+ }
+ return nt::SetDefaultEntryValue(
+ entry, nt::Value::MakeString(JStringRef{env, defaultValue}.str(), time));
+}
+
+/*
+ * Class: edu_wpi_first_networktables_NetworkTablesJNI
+ * Method: setDefaultRaw
+ * Signature: (IJ[B)Z
+ */
+JNIEXPORT jboolean JNICALL
+Java_edu_wpi_first_networktables_NetworkTablesJNI_setDefaultRaw
+ (JNIEnv* env, jclass, jint entry, jlong time, jbyteArray defaultValue)
+{
+ if (!defaultValue) {
+ nullPointerEx.Throw(env, "defaultValue cannot be null");
+ return false;
+ }
+ auto v = FromJavaRaw(env, defaultValue, time);
+ return nt::SetDefaultEntryValue(entry, v);
+}
+
+/*
+ * Class: edu_wpi_first_networktables_NetworkTablesJNI
+ * Method: setDefaultBooleanArray
+ * Signature: (IJ[Z)Z
+ */
+JNIEXPORT jboolean JNICALL
+Java_edu_wpi_first_networktables_NetworkTablesJNI_setDefaultBooleanArray
+ (JNIEnv* env, jclass, jint entry, jlong time, jbooleanArray defaultValue)
+{
+ if (!defaultValue) {
+ nullPointerEx.Throw(env, "defaultValue cannot be null");
+ return false;
+ }
+ auto v = FromJavaBooleanArray(env, defaultValue, time);
+ return nt::SetDefaultEntryValue(entry, v);
+}
+
+/*
+ * Class: edu_wpi_first_networktables_NetworkTablesJNI
+ * Method: setDefaultDoubleArray
+ * Signature: (IJ[D)Z
+ */
+JNIEXPORT jboolean JNICALL
+Java_edu_wpi_first_networktables_NetworkTablesJNI_setDefaultDoubleArray
+ (JNIEnv* env, jclass, jint entry, jlong time, jdoubleArray defaultValue)
+{
+ if (!defaultValue) {
+ nullPointerEx.Throw(env, "defaultValue cannot be null");
+ return false;
+ }
+ auto v = FromJavaDoubleArray(env, defaultValue, time);
+ return nt::SetDefaultEntryValue(entry, v);
+}
+
+/*
+ * Class: edu_wpi_first_networktables_NetworkTablesJNI
+ * Method: setDefaultStringArray
+ * Signature: (IJ[Ljava/lang/Object;)Z
+ */
+JNIEXPORT jboolean JNICALL
+Java_edu_wpi_first_networktables_NetworkTablesJNI_setDefaultStringArray
+ (JNIEnv* env, jclass, jint entry, jlong time, jobjectArray defaultValue)
+{
+ if (!defaultValue) {
+ nullPointerEx.Throw(env, "defaultValue cannot be null");
+ return false;
+ }
+ auto v = FromJavaStringArray(env, defaultValue, time);
+ return nt::SetDefaultEntryValue(entry, v);
+}
+
+/*
+ * Class: edu_wpi_first_networktables_NetworkTablesJNI
+ * Method: setEntryFlags
+ * Signature: (II)V
+ */
+JNIEXPORT void JNICALL
+Java_edu_wpi_first_networktables_NetworkTablesJNI_setEntryFlags
+ (JNIEnv*, jclass, jint entry, jint flags)
+{
+ nt::SetEntryFlags(entry, flags);
+}
+
+/*
+ * Class: edu_wpi_first_networktables_NetworkTablesJNI
+ * Method: getEntryFlags
+ * Signature: (I)I
+ */
+JNIEXPORT jint JNICALL
+Java_edu_wpi_first_networktables_NetworkTablesJNI_getEntryFlags
+ (JNIEnv*, jclass, jint entry)
+{
+ return nt::GetEntryFlags(entry);
+}
+
+/*
+ * Class: edu_wpi_first_networktables_NetworkTablesJNI
+ * Method: deleteEntry
+ * Signature: (I)V
+ */
+JNIEXPORT void JNICALL
+Java_edu_wpi_first_networktables_NetworkTablesJNI_deleteEntry
+ (JNIEnv*, jclass, jint entry)
+{
+ nt::DeleteEntry(entry);
+}
+
+/*
+ * Class: edu_wpi_first_networktables_NetworkTablesJNI
+ * Method: deleteAllEntries
+ * Signature: (I)V
+ */
+JNIEXPORT void JNICALL
+Java_edu_wpi_first_networktables_NetworkTablesJNI_deleteAllEntries
+ (JNIEnv*, jclass, jint inst)
+{
+ nt::DeleteAllEntries(inst);
+}
+
+/*
+ * Class: edu_wpi_first_networktables_NetworkTablesJNI
+ * Method: getEntryInfoHandle
+ * Signature: (Ljava/lang/Object;I)Ljava/lang/Object;
+ */
+JNIEXPORT jobject JNICALL
+Java_edu_wpi_first_networktables_NetworkTablesJNI_getEntryInfoHandle
+ (JNIEnv* env, jclass, jobject inst, jint entry)
+{
+ return MakeJObject(env, inst, nt::GetEntryInfo(entry));
+}
+
+/*
+ * Class: edu_wpi_first_networktables_NetworkTablesJNI
+ * Method: getEntryInfo
+ * Signature: (Ljava/lang/Object;ILjava/lang/String;I)[Ljava/lang/Object;
+ */
+JNIEXPORT jobjectArray JNICALL
+Java_edu_wpi_first_networktables_NetworkTablesJNI_getEntryInfo
+ (JNIEnv* env, jclass, jobject instObject, jint inst, jstring prefix,
+ jint types)
+{
+ if (!prefix) {
+ nullPointerEx.Throw(env, "prefix cannot be null");
+ return nullptr;
+ }
+ auto arr = nt::GetEntryInfo(inst, JStringRef{env, prefix}.str(), types);
+ jobjectArray jarr = env->NewObjectArray(arr.size(), entryInfoCls, nullptr);
+ if (!jarr) return nullptr;
+ for (size_t i = 0; i < arr.size(); ++i) {
+ JLocal<jobject> jelem{env, MakeJObject(env, instObject, arr[i])};
+ env->SetObjectArrayElement(jarr, i, jelem);
+ }
+ return jarr;
+}
+
+/*
+ * Class: edu_wpi_first_networktables_NetworkTablesJNI
+ * Method: createEntryListenerPoller
+ * Signature: (I)I
+ */
+JNIEXPORT jint JNICALL
+Java_edu_wpi_first_networktables_NetworkTablesJNI_createEntryListenerPoller
+ (JNIEnv*, jclass, jint inst)
+{
+ return nt::CreateEntryListenerPoller(inst);
+}
+
+/*
+ * Class: edu_wpi_first_networktables_NetworkTablesJNI
+ * Method: destroyEntryListenerPoller
+ * Signature: (I)V
+ */
+JNIEXPORT void JNICALL
+Java_edu_wpi_first_networktables_NetworkTablesJNI_destroyEntryListenerPoller
+ (JNIEnv*, jclass, jint poller)
+{
+ nt::DestroyEntryListenerPoller(poller);
+}
+
+/*
+ * Class: edu_wpi_first_networktables_NetworkTablesJNI
+ * Method: addPolledEntryListener
+ * Signature: (ILjava/lang/String;I)I
+ */
+JNIEXPORT jint JNICALL
+Java_edu_wpi_first_networktables_NetworkTablesJNI_addPolledEntryListener__ILjava_lang_String_2I
+ (JNIEnv* env, jclass, jint poller, jstring prefix, jint flags)
+{
+ if (!prefix) {
+ nullPointerEx.Throw(env, "prefix cannot be null");
+ return 0;
+ }
+ return nt::AddPolledEntryListener(poller, JStringRef{env, prefix}.str(),
+ flags);
+}
+
+/*
+ * Class: edu_wpi_first_networktables_NetworkTablesJNI
+ * Method: addPolledEntryListener
+ * Signature: (III)I
+ */
+JNIEXPORT jint JNICALL
+Java_edu_wpi_first_networktables_NetworkTablesJNI_addPolledEntryListener__III
+ (JNIEnv* env, jclass, jint poller, jint entry, jint flags)
+{
+ return nt::AddPolledEntryListener(poller, entry, flags);
+}
+
+/*
+ * Class: edu_wpi_first_networktables_NetworkTablesJNI
+ * Method: pollEntryListener
+ * Signature: (Ljava/lang/Object;I)[Ljava/lang/Object;
+ */
+JNIEXPORT jobjectArray JNICALL
+Java_edu_wpi_first_networktables_NetworkTablesJNI_pollEntryListener
+ (JNIEnv* env, jclass, jobject inst, jint poller)
+{
+ auto events = nt::PollEntryListener(poller);
+ if (events.empty()) {
+ interruptedEx.Throw(env, "PollEntryListener interrupted");
+ return nullptr;
+ }
+ return MakeJObject(env, inst, events);
+}
+
+/*
+ * Class: edu_wpi_first_networktables_NetworkTablesJNI
+ * Method: pollEntryListenerTimeout
+ * Signature: (Ljava/lang/Object;ID)[Ljava/lang/Object;
+ */
+JNIEXPORT jobjectArray JNICALL
+Java_edu_wpi_first_networktables_NetworkTablesJNI_pollEntryListenerTimeout
+ (JNIEnv* env, jclass, jobject inst, jint poller, jdouble timeout)
+{
+ bool timed_out = false;
+ auto events = nt::PollEntryListener(poller, timeout, &timed_out);
+ if (events.empty() && !timed_out) {
+ interruptedEx.Throw(env, "PollEntryListener interrupted");
+ return nullptr;
+ }
+ return MakeJObject(env, inst, events);
+}
+
+/*
+ * Class: edu_wpi_first_networktables_NetworkTablesJNI
+ * Method: cancelPollEntryListener
+ * Signature: (I)V
+ */
+JNIEXPORT void JNICALL
+Java_edu_wpi_first_networktables_NetworkTablesJNI_cancelPollEntryListener
+ (JNIEnv*, jclass, jint poller)
+{
+ nt::CancelPollEntryListener(poller);
+}
+
+/*
+ * Class: edu_wpi_first_networktables_NetworkTablesJNI
+ * Method: removeEntryListener
+ * Signature: (I)V
+ */
+JNIEXPORT void JNICALL
+Java_edu_wpi_first_networktables_NetworkTablesJNI_removeEntryListener
+ (JNIEnv*, jclass, jint entryListenerUid)
+{
+ nt::RemoveEntryListener(entryListenerUid);
+}
+
+/*
+ * Class: edu_wpi_first_networktables_NetworkTablesJNI
+ * Method: waitForEntryListenerQueue
+ * Signature: (ID)Z
+ */
+JNIEXPORT jboolean JNICALL
+Java_edu_wpi_first_networktables_NetworkTablesJNI_waitForEntryListenerQueue
+ (JNIEnv*, jclass, jint inst, jdouble timeout)
+{
+ return nt::WaitForEntryListenerQueue(inst, timeout);
+}
+
+/*
+ * Class: edu_wpi_first_networktables_NetworkTablesJNI
+ * Method: createConnectionListenerPoller
+ * Signature: (I)I
+ */
+JNIEXPORT jint JNICALL
+Java_edu_wpi_first_networktables_NetworkTablesJNI_createConnectionListenerPoller
+ (JNIEnv*, jclass, jint inst)
+{
+ return nt::CreateConnectionListenerPoller(inst);
+}
+
+/*
+ * Class: edu_wpi_first_networktables_NetworkTablesJNI
+ * Method: destroyConnectionListenerPoller
+ * Signature: (I)V
+ */
+JNIEXPORT void JNICALL
+Java_edu_wpi_first_networktables_NetworkTablesJNI_destroyConnectionListenerPoller
+ (JNIEnv*, jclass, jint poller)
+{
+ nt::DestroyConnectionListenerPoller(poller);
+}
+
+/*
+ * Class: edu_wpi_first_networktables_NetworkTablesJNI
+ * Method: addPolledConnectionListener
+ * Signature: (IZ)I
+ */
+JNIEXPORT jint JNICALL
+Java_edu_wpi_first_networktables_NetworkTablesJNI_addPolledConnectionListener
+ (JNIEnv* env, jclass, jint poller, jboolean immediateNotify)
+{
+ return nt::AddPolledConnectionListener(poller, immediateNotify);
+}
+
+/*
+ * Class: edu_wpi_first_networktables_NetworkTablesJNI
+ * Method: pollConnectionListener
+ * Signature: (Ljava/lang/Object;I)[Ljava/lang/Object;
+ */
+JNIEXPORT jobjectArray JNICALL
+Java_edu_wpi_first_networktables_NetworkTablesJNI_pollConnectionListener
+ (JNIEnv* env, jclass, jobject inst, jint poller)
+{
+ auto events = nt::PollConnectionListener(poller);
+ if (events.empty()) {
+ interruptedEx.Throw(env, "PollConnectionListener interrupted");
+ return nullptr;
+ }
+ return MakeJObject(env, inst, events);
+}
+
+/*
+ * Class: edu_wpi_first_networktables_NetworkTablesJNI
+ * Method: pollConnectionListenerTimeout
+ * Signature: (Ljava/lang/Object;ID)[Ljava/lang/Object;
+ */
+JNIEXPORT jobjectArray JNICALL
+Java_edu_wpi_first_networktables_NetworkTablesJNI_pollConnectionListenerTimeout
+ (JNIEnv* env, jclass, jobject inst, jint poller, jdouble timeout)
+{
+ bool timed_out = false;
+ auto events = nt::PollConnectionListener(poller, timeout, &timed_out);
+ if (events.empty() && !timed_out) {
+ interruptedEx.Throw(env, "PollConnectionListener interrupted");
+ return nullptr;
+ }
+ return MakeJObject(env, inst, events);
+}
+
+/*
+ * Class: edu_wpi_first_networktables_NetworkTablesJNI
+ * Method: cancelPollConnectionListener
+ * Signature: (I)V
+ */
+JNIEXPORT void JNICALL
+Java_edu_wpi_first_networktables_NetworkTablesJNI_cancelPollConnectionListener
+ (JNIEnv*, jclass, jint poller)
+{
+ nt::CancelPollConnectionListener(poller);
+}
+
+/*
+ * Class: edu_wpi_first_networktables_NetworkTablesJNI
+ * Method: removeConnectionListener
+ * Signature: (I)V
+ */
+JNIEXPORT void JNICALL
+Java_edu_wpi_first_networktables_NetworkTablesJNI_removeConnectionListener
+ (JNIEnv*, jclass, jint connListenerUid)
+{
+ nt::RemoveConnectionListener(connListenerUid);
+}
+
+/*
+ * Class: edu_wpi_first_networktables_NetworkTablesJNI
+ * Method: waitForConnectionListenerQueue
+ * Signature: (ID)Z
+ */
+JNIEXPORT jboolean JNICALL
+Java_edu_wpi_first_networktables_NetworkTablesJNI_waitForConnectionListenerQueue
+ (JNIEnv*, jclass, jint inst, jdouble timeout)
+{
+ return nt::WaitForConnectionListenerQueue(inst, timeout);
+}
+
+/*
+ * Class: edu_wpi_first_networktables_NetworkTablesJNI
+ * Method: createRpcCallPoller
+ * Signature: (I)I
+ */
+JNIEXPORT jint JNICALL
+Java_edu_wpi_first_networktables_NetworkTablesJNI_createRpcCallPoller
+ (JNIEnv*, jclass, jint inst)
+{
+ return nt::CreateRpcCallPoller(inst);
+}
+
+/*
+ * Class: edu_wpi_first_networktables_NetworkTablesJNI
+ * Method: destroyRpcCallPoller
+ * Signature: (I)V
+ */
+JNIEXPORT void JNICALL
+Java_edu_wpi_first_networktables_NetworkTablesJNI_destroyRpcCallPoller
+ (JNIEnv*, jclass, jint poller)
+{
+ nt::DestroyRpcCallPoller(poller);
+}
+
+/*
+ * Class: edu_wpi_first_networktables_NetworkTablesJNI
+ * Method: createPolledRpc
+ * Signature: (I[BI)V
+ */
+JNIEXPORT void JNICALL
+Java_edu_wpi_first_networktables_NetworkTablesJNI_createPolledRpc
+ (JNIEnv* env, jclass, jint entry, jbyteArray def, jint poller)
+{
+ if (!def) {
+ nullPointerEx.Throw(env, "def cannot be null");
+ return;
+ }
+ nt::CreatePolledRpc(entry, JByteArrayRef{env, def}, poller);
+}
+
+/*
+ * Class: edu_wpi_first_networktables_NetworkTablesJNI
+ * Method: pollRpc
+ * Signature: (Ljava/lang/Object;I)[Ljava/lang/Object;
+ */
+JNIEXPORT jobjectArray JNICALL
+Java_edu_wpi_first_networktables_NetworkTablesJNI_pollRpc
+ (JNIEnv* env, jclass, jobject inst, jint poller)
+{
+ auto infos = nt::PollRpc(poller);
+ if (infos.empty()) {
+ interruptedEx.Throw(env, "PollRpc interrupted");
+ return nullptr;
+ }
+ return MakeJObject(env, inst, infos);
+}
+
+/*
+ * Class: edu_wpi_first_networktables_NetworkTablesJNI
+ * Method: pollRpcTimeout
+ * Signature: (Ljava/lang/Object;ID)[Ljava/lang/Object;
+ */
+JNIEXPORT jobjectArray JNICALL
+Java_edu_wpi_first_networktables_NetworkTablesJNI_pollRpcTimeout
+ (JNIEnv* env, jclass, jobject inst, jint poller, jdouble timeout)
+{
+ bool timed_out = false;
+ auto infos = nt::PollRpc(poller, timeout, &timed_out);
+ if (infos.empty() && !timed_out) {
+ interruptedEx.Throw(env, "PollRpc interrupted");
+ return nullptr;
+ }
+ return MakeJObject(env, inst, infos);
+}
+
+/*
+ * Class: edu_wpi_first_networktables_NetworkTablesJNI
+ * Method: cancelPollRpc
+ * Signature: (I)V
+ */
+JNIEXPORT void JNICALL
+Java_edu_wpi_first_networktables_NetworkTablesJNI_cancelPollRpc
+ (JNIEnv*, jclass, jint poller)
+{
+ nt::CancelPollRpc(poller);
+}
+
+/*
+ * Class: edu_wpi_first_networktables_NetworkTablesJNI
+ * Method: waitForRpcCallQueue
+ * Signature: (ID)Z
+ */
+JNIEXPORT jboolean JNICALL
+Java_edu_wpi_first_networktables_NetworkTablesJNI_waitForRpcCallQueue
+ (JNIEnv*, jclass, jint inst, jdouble timeout)
+{
+ return nt::WaitForRpcCallQueue(inst, timeout);
+}
+
+/*
+ * Class: edu_wpi_first_networktables_NetworkTablesJNI
+ * Method: postRpcResponse
+ * Signature: (II[B)Z
+ */
+JNIEXPORT jboolean JNICALL
+Java_edu_wpi_first_networktables_NetworkTablesJNI_postRpcResponse
+ (JNIEnv* env, jclass, jint entry, jint call, jbyteArray result)
+{
+ if (!result) {
+ nullPointerEx.Throw(env, "result cannot be null");
+ return false;
+ }
+ return nt::PostRpcResponse(entry, call, JByteArrayRef{env, result});
+}
+
+/*
+ * Class: edu_wpi_first_networktables_NetworkTablesJNI
+ * Method: callRpc
+ * Signature: (I[B)I
+ */
+JNIEXPORT jint JNICALL
+Java_edu_wpi_first_networktables_NetworkTablesJNI_callRpc
+ (JNIEnv* env, jclass, jint entry, jbyteArray params)
+{
+ if (!params) {
+ nullPointerEx.Throw(env, "params cannot be null");
+ return 0;
+ }
+ return nt::CallRpc(entry, JByteArrayRef{env, params});
+}
+
+/*
+ * Class: edu_wpi_first_networktables_NetworkTablesJNI
+ * Method: getRpcResult
+ * Signature: (II)[B
+ */
+JNIEXPORT jbyteArray JNICALL
+Java_edu_wpi_first_networktables_NetworkTablesJNI_getRpcResult__II
+ (JNIEnv* env, jclass, jint entry, jint call)
+{
+ std::string result;
+ if (!nt::GetRpcResult(entry, call, &result)) return nullptr;
+ return MakeJByteArray(env, result);
+}
+
+/*
+ * Class: edu_wpi_first_networktables_NetworkTablesJNI
+ * Method: getRpcResult
+ * Signature: (IID)[B
+ */
+JNIEXPORT jbyteArray JNICALL
+Java_edu_wpi_first_networktables_NetworkTablesJNI_getRpcResult__IID
+ (JNIEnv* env, jclass, jint entry, jint call, jdouble timeout)
+{
+ std::string result;
+ bool timed_out = false;
+ if (!nt::GetRpcResult(entry, call, &result, timeout, &timed_out))
+ return nullptr;
+ return MakeJByteArray(env, result);
+}
+
+/*
+ * Class: edu_wpi_first_networktables_NetworkTablesJNI
+ * Method: cancelRpcResult
+ * Signature: (II)V
+ */
+JNIEXPORT void JNICALL
+Java_edu_wpi_first_networktables_NetworkTablesJNI_cancelRpcResult
+ (JNIEnv*, jclass, jint entry, jint call)
+{
+ nt::CancelRpcResult(entry, call);
+}
+
+/*
+ * Class: edu_wpi_first_networktables_NetworkTablesJNI
+ * Method: getRpc
+ * Signature: (I[B)[B
+ */
+JNIEXPORT jbyteArray JNICALL
+Java_edu_wpi_first_networktables_NetworkTablesJNI_getRpc
+ (JNIEnv* env, jclass, jint entry, jbyteArray defaultValue)
+{
+ auto val = nt::GetEntryValue(entry);
+ if (!val || !val->IsRpc()) return defaultValue;
+ return MakeJByteArray(env, val->GetRpc());
+}
+
+/*
+ * Class: edu_wpi_first_networktables_NetworkTablesJNI
+ * Method: setNetworkIdentity
+ * Signature: (ILjava/lang/String;)V
+ */
+JNIEXPORT void JNICALL
+Java_edu_wpi_first_networktables_NetworkTablesJNI_setNetworkIdentity
+ (JNIEnv* env, jclass, jint inst, jstring name)
+{
+ if (!name) {
+ nullPointerEx.Throw(env, "name cannot be null");
+ return;
+ }
+ nt::SetNetworkIdentity(inst, JStringRef{env, name}.str());
+}
+
+/*
+ * Class: edu_wpi_first_networktables_NetworkTablesJNI
+ * Method: getNetworkMode
+ * Signature: (I)I
+ */
+JNIEXPORT jint JNICALL
+Java_edu_wpi_first_networktables_NetworkTablesJNI_getNetworkMode
+ (JNIEnv*, jclass, jint inst)
+{
+ return nt::GetNetworkMode(inst);
+}
+
+/*
+ * Class: edu_wpi_first_networktables_NetworkTablesJNI
+ * Method: startServer
+ * 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 port)
+{
+ if (!persistFilename) {
+ nullPointerEx.Throw(env, "persistFilename cannot be null");
+ return;
+ }
+ if (!listenAddress) {
+ nullPointerEx.Throw(env, "listenAddress cannot be null");
+ return;
+ }
+ nt::StartServer(inst, JStringRef{env, persistFilename}.str(),
+ JStringRef{env, listenAddress}.c_str(), port);
+}
+
+/*
+ * Class: edu_wpi_first_networktables_NetworkTablesJNI
+ * Method: stopServer
+ * Signature: (I)V
+ */
+JNIEXPORT void JNICALL
+Java_edu_wpi_first_networktables_NetworkTablesJNI_stopServer
+ (JNIEnv*, jclass, jint inst)
+{
+ nt::StopServer(inst);
+}
+
+/*
+ * Class: edu_wpi_first_networktables_NetworkTablesJNI
+ * Method: startClient
+ * Signature: (I)V
+ */
+JNIEXPORT void JNICALL
+Java_edu_wpi_first_networktables_NetworkTablesJNI_startClient__I
+ (JNIEnv*, jclass, jint inst)
+{
+ nt::StartClient(inst);
+}
+
+/*
+ * Class: edu_wpi_first_networktables_NetworkTablesJNI
+ * Method: startClient
+ * Signature: (ILjava/lang/String;I)V
+ */
+JNIEXPORT void JNICALL
+Java_edu_wpi_first_networktables_NetworkTablesJNI_startClient__ILjava_lang_String_2I
+ (JNIEnv* env, jclass, jint inst, jstring serverName, jint port)
+{
+ if (!serverName) {
+ nullPointerEx.Throw(env, "serverName cannot be null");
+ return;
+ }
+ nt::StartClient(inst, JStringRef{env, serverName}.c_str(), port);
+}
+
+/*
+ * Class: edu_wpi_first_networktables_NetworkTablesJNI
+ * Method: startClient
+ * Signature: (I[Ljava/lang/Object;[I)V
+ */
+JNIEXPORT void JNICALL
+Java_edu_wpi_first_networktables_NetworkTablesJNI_startClient__I_3Ljava_lang_String_2_3I
+ (JNIEnv* env, jclass, jint inst, jobjectArray serverNames, jintArray ports)
+{
+ if (!serverNames) {
+ nullPointerEx.Throw(env, "serverNames cannot be null");
+ return;
+ }
+ if (!ports) {
+ nullPointerEx.Throw(env, "ports cannot be null");
+ return;
+ }
+ int len = env->GetArrayLength(serverNames);
+ if (len != env->GetArrayLength(ports)) {
+ illegalArgEx.Throw(env,
+ "serverNames and ports arrays must be the same size");
+ return;
+ }
+ jint* portInts = env->GetIntArrayElements(ports, nullptr);
+ if (!portInts) return;
+
+ std::vector<std::string> names;
+ std::vector<std::pair<nt::StringRef, unsigned int>> servers;
+ names.reserve(len);
+ servers.reserve(len);
+ for (int i = 0; i < len; ++i) {
+ JLocal<jstring> elem{
+ env, static_cast<jstring>(env->GetObjectArrayElement(serverNames, i))};
+ if (!elem) {
+ nullPointerEx.Throw(env, "null string in serverNames");
+ return;
+ }
+ names.emplace_back(JStringRef{env, elem}.str());
+ servers.emplace_back(
+ std::make_pair(nt::StringRef(names.back()), portInts[i]));
+ }
+ env->ReleaseIntArrayElements(ports, portInts, JNI_ABORT);
+ nt::StartClient(inst, servers);
+}
+
+/*
+ * Class: edu_wpi_first_networktables_NetworkTablesJNI
+ * Method: startClientTeam
+ * Signature: (III)V
+ */
+JNIEXPORT void JNICALL
+Java_edu_wpi_first_networktables_NetworkTablesJNI_startClientTeam
+ (JNIEnv* env, jclass cls, jint inst, jint team, jint port)
+{
+ nt::StartClientTeam(inst, team, port);
+}
+
+/*
+ * Class: edu_wpi_first_networktables_NetworkTablesJNI
+ * Method: stopClient
+ * Signature: (I)V
+ */
+JNIEXPORT void JNICALL
+Java_edu_wpi_first_networktables_NetworkTablesJNI_stopClient
+ (JNIEnv*, jclass, jint inst)
+{
+ nt::StopClient(inst);
+}
+
+/*
+ * Class: edu_wpi_first_networktables_NetworkTablesJNI
+ * Method: setServer
+ * Signature: (ILjava/lang/String;I)V
+ */
+JNIEXPORT void JNICALL
+Java_edu_wpi_first_networktables_NetworkTablesJNI_setServer__ILjava_lang_String_2I
+ (JNIEnv* env, jclass, jint inst, jstring serverName, jint port)
+{
+ if (!serverName) {
+ nullPointerEx.Throw(env, "serverName cannot be null");
+ return;
+ }
+ nt::SetServer(inst, JStringRef{env, serverName}.c_str(), port);
+}
+
+/*
+ * Class: edu_wpi_first_networktables_NetworkTablesJNI
+ * Method: setServer
+ * Signature: (I[Ljava/lang/Object;[I)V
+ */
+JNIEXPORT void JNICALL
+Java_edu_wpi_first_networktables_NetworkTablesJNI_setServer__I_3Ljava_lang_String_2_3I
+ (JNIEnv* env, jclass, jint inst, jobjectArray serverNames, jintArray ports)
+{
+ if (!serverNames) {
+ nullPointerEx.Throw(env, "serverNames cannot be null");
+ return;
+ }
+ if (!ports) {
+ nullPointerEx.Throw(env, "ports cannot be null");
+ return;
+ }
+ int len = env->GetArrayLength(serverNames);
+ if (len != env->GetArrayLength(ports)) {
+ illegalArgEx.Throw(env,
+ "serverNames and ports arrays must be the same size");
+ return;
+ }
+ jint* portInts = env->GetIntArrayElements(ports, nullptr);
+ if (!portInts) return;
+
+ std::vector<std::string> names;
+ std::vector<std::pair<nt::StringRef, unsigned int>> servers;
+ names.reserve(len);
+ servers.reserve(len);
+ for (int i = 0; i < len; ++i) {
+ JLocal<jstring> elem{
+ env, static_cast<jstring>(env->GetObjectArrayElement(serverNames, i))};
+ if (!elem) {
+ nullPointerEx.Throw(env, "null string in serverNames");
+ return;
+ }
+ names.emplace_back(JStringRef{env, elem}.str());
+ servers.emplace_back(
+ std::make_pair(nt::StringRef(names.back()), portInts[i]));
+ }
+ env->ReleaseIntArrayElements(ports, portInts, JNI_ABORT);
+ nt::SetServer(inst, servers);
+}
+
+/*
+ * Class: edu_wpi_first_networktables_NetworkTablesJNI
+ * Method: setServerTeam
+ * Signature: (III)V
+ */
+JNIEXPORT void JNICALL
+Java_edu_wpi_first_networktables_NetworkTablesJNI_setServerTeam
+ (JNIEnv* env, jclass, jint inst, jint team, jint port)
+{
+ nt::SetServerTeam(inst, team, port);
+}
+
+/*
+ * Class: edu_wpi_first_networktables_NetworkTablesJNI
+ * Method: startDSClient
+ * Signature: (II)V
+ */
+JNIEXPORT void JNICALL
+Java_edu_wpi_first_networktables_NetworkTablesJNI_startDSClient
+ (JNIEnv*, jclass, jint inst, jint port)
+{
+ nt::StartDSClient(inst, port);
+}
+
+/*
+ * Class: edu_wpi_first_networktables_NetworkTablesJNI
+ * Method: stopDSClient
+ * Signature: (I)V
+ */
+JNIEXPORT void JNICALL
+Java_edu_wpi_first_networktables_NetworkTablesJNI_stopDSClient
+ (JNIEnv*, jclass, jint inst)
+{
+ nt::StopDSClient(inst);
+}
+
+/*
+ * Class: edu_wpi_first_networktables_NetworkTablesJNI
+ * Method: setUpdateRate
+ * Signature: (ID)V
+ */
+JNIEXPORT void JNICALL
+Java_edu_wpi_first_networktables_NetworkTablesJNI_setUpdateRate
+ (JNIEnv*, jclass, jint inst, jdouble interval)
+{
+ nt::SetUpdateRate(inst, interval);
+}
+
+/*
+ * Class: edu_wpi_first_networktables_NetworkTablesJNI
+ * Method: flush
+ * Signature: (I)V
+ */
+JNIEXPORT void JNICALL
+Java_edu_wpi_first_networktables_NetworkTablesJNI_flush
+ (JNIEnv*, jclass, jint inst)
+{
+ nt::Flush(inst);
+}
+
+/*
+ * Class: edu_wpi_first_networktables_NetworkTablesJNI
+ * Method: getConnections
+ * Signature: (I)[Ljava/lang/Object;
+ */
+JNIEXPORT jobjectArray JNICALL
+Java_edu_wpi_first_networktables_NetworkTablesJNI_getConnections
+ (JNIEnv* env, jclass, jint inst)
+{
+ auto arr = nt::GetConnections(inst);
+ jobjectArray jarr =
+ env->NewObjectArray(arr.size(), connectionInfoCls, nullptr);
+ if (!jarr) return nullptr;
+ for (size_t i = 0; i < arr.size(); ++i) {
+ JLocal<jobject> jelem{env, MakeJObject(env, arr[i])};
+ env->SetObjectArrayElement(jarr, i, jelem);
+ }
+ return jarr;
+}
+
+/*
+ * Class: edu_wpi_first_networktables_NetworkTablesJNI
+ * Method: isConnected
+ * Signature: (I)Z
+ */
+JNIEXPORT jboolean JNICALL
+Java_edu_wpi_first_networktables_NetworkTablesJNI_isConnected
+ (JNIEnv*, jclass, jint inst)
+{
+ return nt::IsConnected(inst);
+}
+
+/*
+ * Class: edu_wpi_first_networktables_NetworkTablesJNI
+ * Method: savePersistent
+ * Signature: (ILjava/lang/String;)V
+ */
+JNIEXPORT void JNICALL
+Java_edu_wpi_first_networktables_NetworkTablesJNI_savePersistent
+ (JNIEnv* env, jclass, jint inst, jstring filename)
+{
+ if (!filename) {
+ nullPointerEx.Throw(env, "filename cannot be null");
+ return;
+ }
+ const char* err = nt::SavePersistent(inst, JStringRef{env, filename}.str());
+ if (err) persistentEx.Throw(env, err);
+}
+
+/*
+ * Class: edu_wpi_first_networktables_NetworkTablesJNI
+ * Method: loadPersistent
+ * Signature: (ILjava/lang/String;)[Ljava/lang/Object;
+ */
+JNIEXPORT jobjectArray JNICALL
+Java_edu_wpi_first_networktables_NetworkTablesJNI_loadPersistent
+ (JNIEnv* env, jclass, jint inst, jstring filename)
+{
+ if (!filename) {
+ nullPointerEx.Throw(env, "filename cannot be null");
+ return nullptr;
+ }
+ std::vector<std::string> warns;
+ const char* err = nt::LoadPersistent(inst, JStringRef{env, filename}.str(),
+ [&](size_t line, const char* msg) {
+ wpi::SmallString<128> warn;
+ wpi::raw_svector_ostream oss(warn);
+ oss << line << ": " << msg;
+ warns.emplace_back(oss.str());
+ });
+ if (err) {
+ persistentEx.Throw(env, err);
+ return nullptr;
+ }
+ return MakeJStringArray(env, warns);
+}
+
+/*
+ * Class: edu_wpi_first_networktables_NetworkTablesJNI
+ * Method: saveEntries
+ * Signature: (ILjava/lang/String;Ljava/lang/String;)V
+ */
+JNIEXPORT void JNICALL
+Java_edu_wpi_first_networktables_NetworkTablesJNI_saveEntries
+ (JNIEnv* env, jclass, jint inst, jstring filename, jstring prefix)
+{
+ if (!filename) {
+ nullPointerEx.Throw(env, "filename cannot be null");
+ return;
+ }
+ if (!prefix) {
+ nullPointerEx.Throw(env, "prefix cannot be null");
+ return;
+ }
+ const char* err = nt::SaveEntries(inst, JStringRef{env, filename}.str(),
+ JStringRef{env, prefix}.str());
+ if (err) persistentEx.Throw(env, err);
+}
+
+/*
+ * Class: edu_wpi_first_networktables_NetworkTablesJNI
+ * Method: loadEntries
+ * Signature: (ILjava/lang/String;Ljava/lang/String;)[Ljava/lang/Object;
+ */
+JNIEXPORT jobjectArray JNICALL
+Java_edu_wpi_first_networktables_NetworkTablesJNI_loadEntries
+ (JNIEnv* env, jclass, jint inst, jstring filename, jstring prefix)
+{
+ if (!filename) {
+ nullPointerEx.Throw(env, "filename cannot be null");
+ return nullptr;
+ }
+ if (!prefix) {
+ nullPointerEx.Throw(env, "prefix cannot be null");
+ return nullptr;
+ }
+ std::vector<std::string> warns;
+ const char* err = nt::LoadEntries(inst, JStringRef{env, filename}.str(),
+ JStringRef{env, prefix}.str(),
+ [&](size_t line, const char* msg) {
+ wpi::SmallString<128> warn;
+ wpi::raw_svector_ostream oss(warn);
+ oss << line << ": " << msg;
+ warns.emplace_back(oss.str());
+ });
+ if (err) {
+ persistentEx.Throw(env, err);
+ return nullptr;
+ }
+ return MakeJStringArray(env, warns);
+}
+
+/*
+ * Class: edu_wpi_first_networktables_NetworkTablesJNI
+ * Method: now
+ * Signature: ()J
+ */
+JNIEXPORT jlong JNICALL
+Java_edu_wpi_first_networktables_NetworkTablesJNI_now
+ (JNIEnv*, jclass)
+{
+ return nt::Now();
+}
+
+/*
+ * Class: edu_wpi_first_networktables_NetworkTablesJNI
+ * Method: createLoggerPoller
+ * Signature: (I)I
+ */
+JNIEXPORT jint JNICALL
+Java_edu_wpi_first_networktables_NetworkTablesJNI_createLoggerPoller
+ (JNIEnv*, jclass, jint inst)
+{
+ return nt::CreateLoggerPoller(inst);
+}
+
+/*
+ * Class: edu_wpi_first_networktables_NetworkTablesJNI
+ * Method: destroyLoggerPoller
+ * Signature: (I)V
+ */
+JNIEXPORT void JNICALL
+Java_edu_wpi_first_networktables_NetworkTablesJNI_destroyLoggerPoller
+ (JNIEnv*, jclass, jint poller)
+{
+ nt::DestroyLoggerPoller(poller);
+}
+
+/*
+ * Class: edu_wpi_first_networktables_NetworkTablesJNI
+ * Method: addPolledLogger
+ * Signature: (III)I
+ */
+JNIEXPORT jint JNICALL
+Java_edu_wpi_first_networktables_NetworkTablesJNI_addPolledLogger
+ (JNIEnv*, jclass, jint poller, jint minLevel, jint maxLevel)
+{
+ return nt::AddPolledLogger(poller, minLevel, maxLevel);
+}
+
+/*
+ * Class: edu_wpi_first_networktables_NetworkTablesJNI
+ * Method: pollLogger
+ * Signature: (Ljava/lang/Object;I)[Ljava/lang/Object;
+ */
+JNIEXPORT jobjectArray JNICALL
+Java_edu_wpi_first_networktables_NetworkTablesJNI_pollLogger
+ (JNIEnv* env, jclass, jobject inst, jint poller)
+{
+ auto events = nt::PollLogger(poller);
+ if (events.empty()) {
+ interruptedEx.Throw(env, "PollLogger interrupted");
+ return nullptr;
+ }
+ return MakeJObject(env, inst, events);
+}
+
+/*
+ * Class: edu_wpi_first_networktables_NetworkTablesJNI
+ * Method: pollLoggerTimeout
+ * Signature: (Ljava/lang/Object;ID)[Ljava/lang/Object;
+ */
+JNIEXPORT jobjectArray JNICALL
+Java_edu_wpi_first_networktables_NetworkTablesJNI_pollLoggerTimeout
+ (JNIEnv* env, jclass, jobject inst, jint poller, jdouble timeout)
+{
+ bool timed_out = false;
+ auto events = nt::PollLogger(poller, timeout, &timed_out);
+ if (events.empty() && !timed_out) {
+ interruptedEx.Throw(env, "PollLogger interrupted");
+ return nullptr;
+ }
+ return MakeJObject(env, inst, events);
+}
+
+/*
+ * Class: edu_wpi_first_networktables_NetworkTablesJNI
+ * Method: cancelPollLogger
+ * Signature: (I)V
+ */
+JNIEXPORT void JNICALL
+Java_edu_wpi_first_networktables_NetworkTablesJNI_cancelPollLogger
+ (JNIEnv*, jclass, jint poller)
+{
+ nt::CancelPollLogger(poller);
+}
+
+/*
+ * Class: edu_wpi_first_networktables_NetworkTablesJNI
+ * Method: removeLogger
+ * Signature: (I)V
+ */
+JNIEXPORT void JNICALL
+Java_edu_wpi_first_networktables_NetworkTablesJNI_removeLogger
+ (JNIEnv*, jclass, jint logger)
+{
+ nt::RemoveLogger(logger);
+}
+
+/*
+ * Class: edu_wpi_first_networktables_NetworkTablesJNI
+ * Method: waitForLoggerQueue
+ * Signature: (ID)Z
+ */
+JNIEXPORT jboolean JNICALL
+Java_edu_wpi_first_networktables_NetworkTablesJNI_waitForLoggerQueue
+ (JNIEnv*, jclass, jint inst, jdouble timeout)
+{
+ return nt::WaitForLoggerQueue(inst, timeout);
+}
+
+} // extern "C"
diff --git a/ntcore/src/main/native/cpp/networktables/NetworkTable.cpp b/ntcore/src/main/native/cpp/networktables/NetworkTable.cpp
new file mode 100644
index 0000000..1ec0942
--- /dev/null
+++ b/ntcore/src/main/native/cpp/networktables/NetworkTable.cpp
@@ -0,0 +1,562 @@
+/*----------------------------------------------------------------------------*/
+/* Copyright (c) 2017-2018 FIRST. All Rights Reserved. */
+/* Open Source Software - may be modified and shared by FRC teams. The code */
+/* must be accompanied by the FIRST BSD license file in the root directory of */
+/* the project. */
+/*----------------------------------------------------------------------------*/
+
+#include "networktables/NetworkTable.h"
+
+#include <algorithm>
+
+#include <wpi/SmallString.h>
+#include <wpi/StringMap.h>
+#include <wpi/raw_ostream.h>
+
+#include "networktables/NetworkTableInstance.h"
+#include "ntcore.h"
+#include "tables/ITableListener.h"
+
+using namespace nt;
+
+#ifdef __GNUC__
+#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
+#endif
+
+const char NetworkTable::PATH_SEPARATOR_CHAR = '/';
+std::string NetworkTable::s_persistent_filename = "networktables.ini";
+bool NetworkTable::s_client = false;
+bool NetworkTable::s_enable_ds = true;
+bool NetworkTable::s_running = false;
+unsigned int NetworkTable::s_port = NT_DEFAULT_PORT;
+
+StringRef NetworkTable::BasenameKey(StringRef key) {
+ size_t slash = key.rfind(PATH_SEPARATOR_CHAR);
+ if (slash == StringRef::npos) return key;
+ return key.substr(slash + 1);
+}
+
+std::string NetworkTable::NormalizeKey(const Twine& key,
+ bool withLeadingSlash) {
+ wpi::SmallString<128> buf;
+ return NormalizeKey(key, buf, withLeadingSlash);
+}
+
+StringRef NetworkTable::NormalizeKey(const Twine& key,
+ wpi::SmallVectorImpl<char>& buf,
+ bool withLeadingSlash) {
+ buf.clear();
+ if (withLeadingSlash) buf.push_back(PATH_SEPARATOR_CHAR);
+ // for each path element, add it with a slash following
+ wpi::SmallString<128> keyBuf;
+ StringRef keyStr = key.toStringRef(keyBuf);
+ wpi::SmallVector<StringRef, 16> parts;
+ keyStr.split(parts, PATH_SEPARATOR_CHAR, -1, false);
+ for (auto i = parts.begin(); i != parts.end(); ++i) {
+ buf.append(i->begin(), i->end());
+ buf.push_back(PATH_SEPARATOR_CHAR);
+ }
+ // remove trailing slash if the input key didn't have one
+ if (!keyStr.empty() && keyStr.back() != PATH_SEPARATOR_CHAR) buf.pop_back();
+ return StringRef(buf.data(), buf.size());
+}
+
+std::vector<std::string> NetworkTable::GetHierarchy(const Twine& key) {
+ std::vector<std::string> hierarchy;
+ hierarchy.emplace_back(1, PATH_SEPARATOR_CHAR);
+ // for each path element, add it to the end of what we built previously
+ wpi::SmallString<128> keyBuf;
+ StringRef keyStr = key.toStringRef(keyBuf);
+ wpi::SmallString<128> path;
+ wpi::SmallVector<StringRef, 16> parts;
+ keyStr.split(parts, PATH_SEPARATOR_CHAR, -1, false);
+ if (!parts.empty()) {
+ for (auto i = parts.begin(); i != parts.end(); ++i) {
+ path += PATH_SEPARATOR_CHAR;
+ path += *i;
+ hierarchy.emplace_back(path.str());
+ }
+ // handle trailing slash
+ if (keyStr.back() == PATH_SEPARATOR_CHAR) {
+ path += PATH_SEPARATOR_CHAR;
+ hierarchy.emplace_back(path.str());
+ }
+ }
+ return hierarchy;
+}
+
+void NetworkTable::Initialize() {
+ if (s_running) Shutdown();
+ auto inst = NetworkTableInstance::GetDefault();
+ if (s_client) {
+ inst.StartClient();
+ if (s_enable_ds) inst.StartDSClient(s_port);
+ } else {
+ inst.StartServer(s_persistent_filename, "", s_port);
+ }
+ s_running = true;
+}
+
+void NetworkTable::Shutdown() {
+ if (!s_running) return;
+ auto inst = NetworkTableInstance::GetDefault();
+ if (s_client) {
+ inst.StopDSClient();
+ inst.StopClient();
+ } else {
+ inst.StopServer();
+ }
+ s_running = false;
+}
+
+void NetworkTable::SetClientMode() { s_client = true; }
+
+void NetworkTable::SetServerMode() { s_client = false; }
+
+void NetworkTable::SetTeam(int team) {
+ auto inst = NetworkTableInstance::GetDefault();
+ inst.SetServerTeam(team, s_port);
+ if (s_enable_ds) inst.StartDSClient(s_port);
+}
+
+void NetworkTable::SetIPAddress(StringRef address) {
+ auto inst = NetworkTableInstance::GetDefault();
+ wpi::SmallString<32> addr_copy{address};
+ inst.SetServer(addr_copy.c_str(), s_port);
+
+ // Stop the DS client if we're explicitly connecting to localhost
+ if (address == "localhost" || address == "127.0.0.1")
+ inst.StopDSClient();
+ else if (s_enable_ds)
+ inst.StartDSClient(s_port);
+}
+
+void NetworkTable::SetIPAddress(ArrayRef<std::string> addresses) {
+ auto inst = NetworkTableInstance::GetDefault();
+ wpi::SmallVector<StringRef, 8> servers;
+ for (const auto& ip_address : addresses) servers.emplace_back(ip_address);
+ inst.SetServer(servers, s_port);
+
+ // Stop the DS client if we're explicitly connecting to localhost
+ if (!addresses.empty() &&
+ (addresses[0] == "localhost" || addresses[0] == "127.0.0.1"))
+ inst.StopDSClient();
+ else if (s_enable_ds)
+ inst.StartDSClient(s_port);
+}
+
+void NetworkTable::SetPort(unsigned int port) { s_port = port; }
+
+void NetworkTable::SetDSClientEnabled(bool enabled) {
+ auto inst = NetworkTableInstance::GetDefault();
+ s_enable_ds = enabled;
+ if (s_enable_ds)
+ inst.StartDSClient(s_port);
+ else
+ inst.StopDSClient();
+}
+
+void NetworkTable::SetPersistentFilename(StringRef filename) {
+ s_persistent_filename = filename;
+}
+
+void NetworkTable::SetNetworkIdentity(StringRef name) {
+ NetworkTableInstance::GetDefault().SetNetworkIdentity(name);
+}
+
+void NetworkTable::GlobalDeleteAll() {
+ NetworkTableInstance::GetDefault().DeleteAllEntries();
+}
+
+void NetworkTable::Flush() { NetworkTableInstance::GetDefault().Flush(); }
+
+void NetworkTable::SetUpdateRate(double interval) {
+ NetworkTableInstance::GetDefault().SetUpdateRate(interval);
+}
+
+const char* NetworkTable::SavePersistent(StringRef filename) {
+ return NetworkTableInstance::GetDefault().SavePersistent(filename);
+}
+
+const char* NetworkTable::LoadPersistent(
+ StringRef filename,
+ std::function<void(size_t line, const char* msg)> warn) {
+ return NetworkTableInstance::GetDefault().LoadPersistent(filename, warn);
+}
+
+std::shared_ptr<NetworkTable> NetworkTable::GetTable(StringRef key) {
+ if (!s_running) Initialize();
+ return NetworkTableInstance::GetDefault().GetTable(key);
+}
+
+NetworkTable::NetworkTable(NT_Inst inst, const Twine& path, const private_init&)
+ : m_inst(inst), m_path(path.str()) {}
+
+NetworkTable::~NetworkTable() {
+ for (auto& i : m_listeners) RemoveEntryListener(i.second);
+ for (auto i : m_lambdaListeners) RemoveEntryListener(i);
+}
+
+NetworkTableInstance NetworkTable::GetInstance() const {
+ return NetworkTableInstance{m_inst};
+}
+
+NetworkTableEntry NetworkTable::GetEntry(const Twine& key) const {
+ wpi::SmallString<128> keyBuf;
+ StringRef keyStr = key.toStringRef(keyBuf);
+ std::lock_guard<wpi::mutex> lock(m_mutex);
+ NT_Entry& entry = m_entries[keyStr];
+ if (entry == 0) {
+ entry = nt::GetEntry(m_inst, m_path + Twine(PATH_SEPARATOR_CHAR) + keyStr);
+ }
+ return NetworkTableEntry{entry};
+}
+
+NT_EntryListener NetworkTable::AddEntryListener(TableEntryListener listener,
+ unsigned int flags) const {
+ size_t prefix_len = m_path.size() + 1;
+ return nt::AddEntryListener(
+ m_inst, m_path + Twine(PATH_SEPARATOR_CHAR),
+ [=](const EntryNotification& event) {
+ StringRef relative_key = event.name.substr(prefix_len);
+ if (relative_key.find(PATH_SEPARATOR_CHAR) != StringRef::npos) return;
+ listener(const_cast<NetworkTable*>(this), relative_key,
+ NetworkTableEntry{event.entry}, event.value, event.flags);
+ },
+ flags);
+}
+
+NT_EntryListener NetworkTable::AddEntryListener(const Twine& key,
+ TableEntryListener listener,
+ unsigned int flags) const {
+ size_t prefix_len = m_path.size() + 1;
+ auto entry = GetEntry(key);
+ return nt::AddEntryListener(entry.GetHandle(),
+ [=](const EntryNotification& event) {
+ listener(const_cast<NetworkTable*>(this),
+ event.name.substr(prefix_len), entry,
+ event.value, event.flags);
+ },
+ flags);
+}
+
+void NetworkTable::RemoveEntryListener(NT_EntryListener listener) const {
+ nt::RemoveEntryListener(listener);
+}
+
+void NetworkTable::AddTableListener(ITableListener* listener) {
+ AddTableListenerEx(listener, NT_NOTIFY_NEW | NT_NOTIFY_UPDATE);
+}
+
+void NetworkTable::AddTableListener(ITableListener* listener,
+ bool immediateNotify) {
+ unsigned int flags = NT_NOTIFY_NEW | NT_NOTIFY_UPDATE;
+ if (immediateNotify) flags |= NT_NOTIFY_IMMEDIATE;
+ AddTableListenerEx(listener, flags);
+}
+
+void NetworkTable::AddTableListenerEx(ITableListener* listener,
+ unsigned int flags) {
+ std::lock_guard<wpi::mutex> lock(m_mutex);
+ wpi::SmallString<128> path(m_path);
+ path += PATH_SEPARATOR_CHAR;
+ size_t prefix_len = path.size();
+ NT_EntryListener id = nt::AddEntryListener(
+ m_inst, path,
+ [=](const EntryNotification& event) {
+ StringRef relative_key = event.name.substr(prefix_len);
+ if (relative_key.find(PATH_SEPARATOR_CHAR) != StringRef::npos) return;
+ listener->ValueChangedEx(this, relative_key, event.value, event.flags);
+ },
+ flags);
+ m_listeners.emplace_back(listener, id);
+}
+
+void NetworkTable::AddTableListener(StringRef key, ITableListener* listener,
+ bool immediateNotify) {
+ unsigned int flags = NT_NOTIFY_NEW | NT_NOTIFY_UPDATE;
+ if (immediateNotify) flags |= NT_NOTIFY_IMMEDIATE;
+ AddTableListenerEx(key, listener, flags);
+}
+
+void NetworkTable::AddTableListenerEx(StringRef key, ITableListener* listener,
+ unsigned int flags) {
+ std::lock_guard<wpi::mutex> lock(m_mutex);
+ size_t prefix_len = m_path.size() + 1;
+ auto entry = GetEntry(key);
+ NT_EntryListener id = nt::AddEntryListener(
+ entry.GetHandle(),
+ [=](const EntryNotification& event) {
+ listener->ValueChangedEx(this, event.name.substr(prefix_len),
+ event.value, event.flags);
+ },
+ flags);
+ m_listeners.emplace_back(listener, id);
+}
+
+void NetworkTable::AddSubTableListener(ITableListener* listener) {
+ AddSubTableListener(listener, false);
+}
+
+NT_EntryListener NetworkTable::AddSubTableListener(TableListener listener,
+ bool localNotify) {
+ size_t prefix_len = m_path.size() + 1;
+
+ // The lambda needs to be copyable, but StringMap is not, so use
+ // a shared_ptr to it.
+ auto notified_tables = std::make_shared<wpi::StringMap<char>>();
+
+ unsigned int flags = NT_NOTIFY_NEW | NT_NOTIFY_IMMEDIATE;
+ if (localNotify) flags |= NT_NOTIFY_LOCAL;
+ NT_EntryListener id = nt::AddEntryListener(
+ m_inst, m_path + Twine(PATH_SEPARATOR_CHAR),
+ [=](const EntryNotification& event) {
+ StringRef relative_key = event.name.substr(prefix_len);
+ auto end_sub_table = relative_key.find(PATH_SEPARATOR_CHAR);
+ if (end_sub_table == StringRef::npos) return;
+ StringRef sub_table_key = relative_key.substr(0, end_sub_table);
+ if (notified_tables->find(sub_table_key) == notified_tables->end())
+ return;
+ notified_tables->insert(std::make_pair(sub_table_key, '\0'));
+ listener(this, sub_table_key, this->GetSubTable(sub_table_key));
+ },
+ flags);
+ m_lambdaListeners.emplace_back(id);
+ return id;
+}
+
+void NetworkTable::RemoveTableListener(NT_EntryListener listener) {
+ nt::RemoveEntryListener(listener);
+ auto matches_begin =
+ std::remove(m_lambdaListeners.begin(), m_lambdaListeners.end(), listener);
+ m_lambdaListeners.erase(matches_begin, m_lambdaListeners.end());
+}
+
+void NetworkTable::AddSubTableListener(ITableListener* listener,
+ bool localNotify) {
+ std::lock_guard<wpi::mutex> lock(m_mutex);
+ size_t prefix_len = m_path.size() + 1;
+
+ // The lambda needs to be copyable, but StringMap is not, so use
+ // a shared_ptr to it.
+ auto notified_tables = std::make_shared<wpi::StringMap<char>>();
+
+ unsigned int flags = NT_NOTIFY_NEW | NT_NOTIFY_IMMEDIATE;
+ if (localNotify) flags |= NT_NOTIFY_LOCAL;
+ NT_EntryListener id = nt::AddEntryListener(
+ m_inst, m_path + Twine(PATH_SEPARATOR_CHAR),
+ [=](const EntryNotification& event) {
+ StringRef relative_key = event.name.substr(prefix_len);
+ auto end_sub_table = relative_key.find(PATH_SEPARATOR_CHAR);
+ if (end_sub_table == StringRef::npos) return;
+ StringRef sub_table_key = relative_key.substr(0, end_sub_table);
+ if (notified_tables->find(sub_table_key) == notified_tables->end())
+ return;
+ notified_tables->insert(std::make_pair(sub_table_key, '\0'));
+ listener->ValueChangedEx(this, sub_table_key, nullptr, event.flags);
+ },
+ flags);
+ m_listeners.emplace_back(listener, id);
+}
+
+void NetworkTable::RemoveTableListener(ITableListener* listener) {
+ std::lock_guard<wpi::mutex> lock(m_mutex);
+ auto matches_begin =
+ std::remove_if(m_listeners.begin(), m_listeners.end(),
+ [=](const Listener& x) { return x.first == listener; });
+
+ for (auto i = matches_begin; i != m_listeners.end(); ++i)
+ RemoveEntryListener(i->second);
+ m_listeners.erase(matches_begin, m_listeners.end());
+}
+
+std::shared_ptr<NetworkTable> NetworkTable::GetSubTable(
+ const Twine& key) const {
+ return std::make_shared<NetworkTable>(
+ m_inst, m_path + Twine(PATH_SEPARATOR_CHAR) + key, private_init{});
+}
+
+bool NetworkTable::ContainsKey(const Twine& key) const {
+ if (key.isTriviallyEmpty() ||
+ (key.isSingleStringRef() && key.getSingleStringRef().empty()))
+ return false;
+ return GetEntry(key).Exists();
+}
+
+bool NetworkTable::ContainsSubTable(const Twine& key) const {
+ return !GetEntryInfo(m_inst,
+ m_path + Twine(PATH_SEPARATOR_CHAR) + key +
+ Twine(PATH_SEPARATOR_CHAR),
+ 0)
+ .empty();
+}
+
+std::vector<std::string> NetworkTable::GetKeys(int types) const {
+ std::vector<std::string> keys;
+ size_t prefix_len = m_path.size() + 1;
+ auto infos = GetEntryInfo(m_inst, m_path + Twine(PATH_SEPARATOR_CHAR), types);
+ std::lock_guard<wpi::mutex> lock(m_mutex);
+ for (auto& info : infos) {
+ auto relative_key = StringRef(info.name).substr(prefix_len);
+ if (relative_key.find(PATH_SEPARATOR_CHAR) != StringRef::npos) continue;
+ keys.push_back(relative_key);
+ m_entries[relative_key] = info.entry;
+ }
+ return keys;
+}
+
+std::vector<std::string> NetworkTable::GetSubTables() const {
+ std::vector<std::string> keys;
+ size_t prefix_len = m_path.size() + 1;
+ for (auto& entry :
+ GetEntryInfo(m_inst, m_path + Twine(PATH_SEPARATOR_CHAR), 0)) {
+ auto relative_key = StringRef(entry.name).substr(prefix_len);
+ size_t end_subtable = relative_key.find(PATH_SEPARATOR_CHAR);
+ if (end_subtable == StringRef::npos) continue;
+ keys.push_back(relative_key.substr(0, end_subtable));
+ }
+ return keys;
+}
+
+void NetworkTable::SetPersistent(StringRef key) {
+ GetEntry(key).SetPersistent();
+}
+
+void NetworkTable::ClearPersistent(StringRef key) {
+ GetEntry(key).ClearPersistent();
+}
+
+bool NetworkTable::IsPersistent(StringRef key) const {
+ return GetEntry(key).IsPersistent();
+}
+
+void NetworkTable::SetFlags(StringRef key, unsigned int flags) {
+ GetEntry(key).SetFlags(flags);
+}
+
+void NetworkTable::ClearFlags(StringRef key, unsigned int flags) {
+ GetEntry(key).ClearFlags(flags);
+}
+
+unsigned int NetworkTable::GetFlags(StringRef key) const {
+ return GetEntry(key).GetFlags();
+}
+
+void NetworkTable::Delete(const Twine& key) { GetEntry(key).Delete(); }
+
+bool NetworkTable::PutNumber(StringRef key, double value) {
+ return GetEntry(key).SetDouble(value);
+}
+
+bool NetworkTable::SetDefaultNumber(StringRef key, double defaultValue) {
+ return GetEntry(key).SetDefaultDouble(defaultValue);
+}
+
+double NetworkTable::GetNumber(StringRef key, double defaultValue) const {
+ return GetEntry(key).GetDouble(defaultValue);
+}
+
+bool NetworkTable::PutString(StringRef key, StringRef value) {
+ return GetEntry(key).SetString(value);
+}
+
+bool NetworkTable::SetDefaultString(StringRef key, StringRef defaultValue) {
+ return GetEntry(key).SetDefaultString(defaultValue);
+}
+
+std::string NetworkTable::GetString(StringRef key,
+ StringRef defaultValue) const {
+ return GetEntry(key).GetString(defaultValue);
+}
+
+bool NetworkTable::PutBoolean(StringRef key, bool value) {
+ return GetEntry(key).SetBoolean(value);
+}
+
+bool NetworkTable::SetDefaultBoolean(StringRef key, bool defaultValue) {
+ return GetEntry(key).SetDefaultBoolean(defaultValue);
+}
+
+bool NetworkTable::GetBoolean(StringRef key, bool defaultValue) const {
+ return GetEntry(key).GetBoolean(defaultValue);
+}
+
+bool NetworkTable::PutBooleanArray(StringRef key, ArrayRef<int> value) {
+ return GetEntry(key).SetBooleanArray(value);
+}
+
+bool NetworkTable::SetDefaultBooleanArray(StringRef key,
+ ArrayRef<int> defaultValue) {
+ return GetEntry(key).SetDefaultBooleanArray(defaultValue);
+}
+
+std::vector<int> NetworkTable::GetBooleanArray(
+ StringRef key, ArrayRef<int> defaultValue) const {
+ return GetEntry(key).GetBooleanArray(defaultValue);
+}
+
+bool NetworkTable::PutNumberArray(StringRef key, ArrayRef<double> value) {
+ return GetEntry(key).SetDoubleArray(value);
+}
+
+bool NetworkTable::SetDefaultNumberArray(StringRef key,
+ ArrayRef<double> defaultValue) {
+ return GetEntry(key).SetDefaultDoubleArray(defaultValue);
+}
+
+std::vector<double> NetworkTable::GetNumberArray(
+ StringRef key, ArrayRef<double> defaultValue) const {
+ return GetEntry(key).GetDoubleArray(defaultValue);
+}
+
+bool NetworkTable::PutStringArray(StringRef key, ArrayRef<std::string> value) {
+ return GetEntry(key).SetStringArray(value);
+}
+
+bool NetworkTable::SetDefaultStringArray(StringRef key,
+ ArrayRef<std::string> defaultValue) {
+ return GetEntry(key).SetDefaultStringArray(defaultValue);
+}
+
+std::vector<std::string> NetworkTable::GetStringArray(
+ StringRef key, ArrayRef<std::string> defaultValue) const {
+ return GetEntry(key).GetStringArray(defaultValue);
+}
+
+bool NetworkTable::PutRaw(StringRef key, StringRef value) {
+ return GetEntry(key).SetRaw(value);
+}
+
+bool NetworkTable::SetDefaultRaw(StringRef key, StringRef defaultValue) {
+ return GetEntry(key).SetDefaultRaw(defaultValue);
+}
+
+std::string NetworkTable::GetRaw(StringRef key, StringRef defaultValue) const {
+ return GetEntry(key).GetRaw(defaultValue);
+}
+
+bool NetworkTable::PutValue(const Twine& key, std::shared_ptr<Value> value) {
+ return GetEntry(key).SetValue(value);
+}
+
+bool NetworkTable::SetDefaultValue(const Twine& key,
+ std::shared_ptr<Value> defaultValue) {
+ return GetEntry(key).SetDefaultValue(defaultValue);
+}
+
+std::shared_ptr<Value> NetworkTable::GetValue(const Twine& key) const {
+ return GetEntry(key).GetValue();
+}
+
+StringRef NetworkTable::GetPath() const { return m_path; }
+
+const char* NetworkTable::SaveEntries(const Twine& filename) const {
+ return nt::SaveEntries(m_inst, filename, m_path + Twine(PATH_SEPARATOR_CHAR));
+}
+
+const char* NetworkTable::LoadEntries(
+ const Twine& filename,
+ std::function<void(size_t line, const char* msg)> warn) {
+ return nt::LoadEntries(m_inst, filename, m_path + Twine(PATH_SEPARATOR_CHAR),
+ warn);
+}
diff --git a/ntcore/src/main/native/cpp/networktables/NetworkTableEntry.cpp b/ntcore/src/main/native/cpp/networktables/NetworkTableEntry.cpp
new file mode 100644
index 0000000..5507ac0
--- /dev/null
+++ b/ntcore/src/main/native/cpp/networktables/NetworkTableEntry.cpp
@@ -0,0 +1,16 @@
+/*----------------------------------------------------------------------------*/
+/* Copyright (c) 2017-2018 FIRST. All Rights Reserved. */
+/* Open Source Software - may be modified and shared by FRC teams. The code */
+/* must be accompanied by the FIRST BSD license file in the root directory of */
+/* the project. */
+/*----------------------------------------------------------------------------*/
+
+#include "networktables/NetworkTableEntry.h"
+
+#include "networktables/NetworkTableInstance.h"
+
+using namespace nt;
+
+NetworkTableInstance NetworkTableEntry::GetInstance() const {
+ return NetworkTableInstance{GetInstanceFromHandle(m_handle)};
+}
diff --git a/ntcore/src/main/native/cpp/networktables/NetworkTableInstance.cpp b/ntcore/src/main/native/cpp/networktables/NetworkTableInstance.cpp
new file mode 100644
index 0000000..018572e
--- /dev/null
+++ b/ntcore/src/main/native/cpp/networktables/NetworkTableInstance.cpp
@@ -0,0 +1,59 @@
+/*----------------------------------------------------------------------------*/
+/* Copyright (c) 2017-2018 FIRST. All Rights Reserved. */
+/* Open Source Software - may be modified and shared by FRC teams. The code */
+/* must be accompanied by the FIRST BSD license file in the root directory of */
+/* the project. */
+/*----------------------------------------------------------------------------*/
+
+#include "networktables/NetworkTableInstance.h"
+
+#include <wpi/SmallString.h>
+
+using namespace nt;
+
+std::shared_ptr<NetworkTable> NetworkTableInstance::GetTable(
+ const Twine& key) const {
+ StringRef simple;
+ bool isSimple = key.isSingleStringRef();
+ if (isSimple) simple = key.getSingleStringRef();
+ if (isSimple && (simple.empty() || simple == "/")) {
+ return std::make_shared<NetworkTable>(m_handle, "",
+ NetworkTable::private_init{});
+ } else if (isSimple && simple[0] == NetworkTable::PATH_SEPARATOR_CHAR) {
+ return std::make_shared<NetworkTable>(m_handle, key,
+ NetworkTable::private_init{});
+ } else {
+ return std::make_shared<NetworkTable>(
+ m_handle, Twine(NetworkTable::PATH_SEPARATOR_CHAR) + key,
+ NetworkTable::private_init{});
+ }
+}
+
+void NetworkTableInstance::StartClient(ArrayRef<StringRef> servers,
+ unsigned int port) {
+ wpi::SmallVector<std::pair<StringRef, unsigned int>, 8> server_ports;
+ for (const auto& server : servers)
+ server_ports.emplace_back(std::make_pair(server, port));
+ StartClient(server_ports);
+}
+
+void NetworkTableInstance::SetServer(ArrayRef<StringRef> servers,
+ unsigned int port) {
+ wpi::SmallVector<std::pair<StringRef, unsigned int>, 8> server_ports;
+ for (const auto& server : servers)
+ server_ports.emplace_back(std::make_pair(server, port));
+ SetServer(server_ports);
+}
+
+NT_EntryListener NetworkTableInstance::AddEntryListener(
+ const Twine& prefix,
+ std::function<void(const EntryNotification& event)> callback,
+ unsigned int flags) const {
+ return ::nt::AddEntryListener(m_handle, prefix, callback, flags);
+}
+
+NT_ConnectionListener NetworkTableInstance::AddConnectionListener(
+ std::function<void(const ConnectionNotification& event)> callback,
+ bool immediate_notify) const {
+ return ::nt::AddConnectionListener(m_handle, callback, immediate_notify);
+}
diff --git a/ntcore/src/main/native/cpp/networktables/RpcCall.cpp b/ntcore/src/main/native/cpp/networktables/RpcCall.cpp
new file mode 100644
index 0000000..1149681
--- /dev/null
+++ b/ntcore/src/main/native/cpp/networktables/RpcCall.cpp
@@ -0,0 +1,16 @@
+/*----------------------------------------------------------------------------*/
+/* Copyright (c) 2017-2018 FIRST. All Rights Reserved. */
+/* Open Source Software - may be modified and shared by FRC teams. The code */
+/* must be accompanied by the FIRST BSD license file in the root directory of */
+/* the project. */
+/*----------------------------------------------------------------------------*/
+
+#include "networktables/RpcCall.h"
+
+#include "networktables/NetworkTableEntry.h"
+
+using namespace nt;
+
+NetworkTableEntry RpcCall::GetEntry() const {
+ return NetworkTableEntry{m_entry};
+}
diff --git a/ntcore/src/main/native/cpp/ntcore_c.cpp b/ntcore/src/main/native/cpp/ntcore_c.cpp
new file mode 100644
index 0000000..6c71e55
--- /dev/null
+++ b/ntcore/src/main/native/cpp/ntcore_c.cpp
@@ -0,0 +1,1136 @@
+/*----------------------------------------------------------------------------*/
+/* Copyright (c) 2015-2018 FIRST. All Rights Reserved. */
+/* Open Source Software - may be modified and shared by FRC teams. The code */
+/* must be accompanied by the FIRST BSD license file in the root directory of */
+/* the project. */
+/*----------------------------------------------------------------------------*/
+
+#include <stdint.h>
+
+#include <cassert>
+#include <cstdlib>
+
+#include <wpi/memory.h>
+#include <wpi/timestamp.h>
+
+#include "Value_internal.h"
+#include "ntcore.h"
+
+using namespace nt;
+
+// Conversion helpers
+
+static void ConvertToC(wpi::StringRef in, char** out) {
+ *out = static_cast<char*>(wpi::CheckedMalloc(in.size() + 1));
+ std::memmove(*out, in.data(), in.size());
+ (*out)[in.size()] = '\0';
+}
+
+static void ConvertToC(const EntryInfo& in, NT_EntryInfo* out) {
+ out->entry = in.entry;
+ ConvertToC(in.name, &out->name);
+ out->type = in.type;
+ out->flags = in.flags;
+ out->last_change = in.last_change;
+}
+
+static void ConvertToC(const ConnectionInfo& in, NT_ConnectionInfo* out) {
+ ConvertToC(in.remote_id, &out->remote_id);
+ ConvertToC(in.remote_ip, &out->remote_ip);
+ out->remote_port = in.remote_port;
+ out->last_update = in.last_update;
+ out->protocol_version = in.protocol_version;
+}
+
+static void ConvertToC(const RpcParamDef& in, NT_RpcParamDef* out) {
+ ConvertToC(in.name, &out->name);
+ ConvertToC(*in.def_value, &out->def_value);
+}
+
+static void ConvertToC(const RpcResultDef& in, NT_RpcResultDef* out) {
+ ConvertToC(in.name, &out->name);
+ out->type = in.type;
+}
+
+static void ConvertToC(const RpcDefinition& in, NT_RpcDefinition* out) {
+ out->version = in.version;
+ ConvertToC(in.name, &out->name);
+
+ out->num_params = in.params.size();
+ out->params = static_cast<NT_RpcParamDef*>(
+ wpi::CheckedMalloc(in.params.size() * sizeof(NT_RpcParamDef)));
+ for (size_t i = 0; i < in.params.size(); ++i)
+ ConvertToC(in.params[i], &out->params[i]);
+
+ out->num_results = in.results.size();
+ out->results = static_cast<NT_RpcResultDef*>(
+ wpi::CheckedMalloc(in.results.size() * sizeof(NT_RpcResultDef)));
+ for (size_t i = 0; i < in.results.size(); ++i)
+ ConvertToC(in.results[i], &out->results[i]);
+}
+
+static void ConvertToC(const RpcAnswer& in, NT_RpcAnswer* out) {
+ out->entry = in.entry;
+ out->call = in.call;
+ ConvertToC(in.name, &out->name);
+ ConvertToC(in.params, &out->params);
+ ConvertToC(in.conn, &out->conn);
+}
+
+static void ConvertToC(const EntryNotification& in, NT_EntryNotification* out) {
+ out->listener = in.listener;
+ out->entry = in.entry;
+ ConvertToC(in.name, &out->name);
+ ConvertToC(*in.value, &out->value);
+ out->flags = in.flags;
+}
+
+static void ConvertToC(const ConnectionNotification& in,
+ NT_ConnectionNotification* out) {
+ out->listener = in.listener;
+ out->connected = in.connected;
+ ConvertToC(in.conn, &out->conn);
+}
+
+static void ConvertToC(const LogMessage& in, NT_LogMessage* out) {
+ out->logger = in.logger;
+ out->level = in.level;
+ out->filename = in.filename;
+ out->line = in.line;
+ ConvertToC(in.message, &out->message);
+}
+
+template <typename O, typename I>
+static O* ConvertToC(const std::vector<I>& in, size_t* out_len) {
+ if (!out_len) return nullptr;
+ *out_len = in.size();
+ if (in.empty()) return nullptr;
+ O* out = static_cast<O*>(wpi::CheckedMalloc(sizeof(O) * in.size()));
+ for (size_t i = 0; i < in.size(); ++i) ConvertToC(in[i], &out[i]);
+ return out;
+}
+
+static void DisposeConnectionInfo(NT_ConnectionInfo* info) {
+ std::free(info->remote_id.str);
+ std::free(info->remote_ip.str);
+}
+
+static void DisposeEntryInfo(NT_EntryInfo* info) { std::free(info->name.str); }
+
+static void DisposeEntryNotification(NT_EntryNotification* info) {
+ std::free(info->name.str);
+ NT_DisposeValue(&info->value);
+}
+
+static void DisposeConnectionNotification(NT_ConnectionNotification* info) {
+ DisposeConnectionInfo(&info->conn);
+}
+
+static RpcParamDef ConvertFromC(const NT_RpcParamDef& in) {
+ RpcParamDef out;
+ out.name = ConvertFromC(in.name);
+ out.def_value = ConvertFromC(in.def_value);
+ return out;
+}
+
+static RpcResultDef ConvertFromC(const NT_RpcResultDef& in) {
+ RpcResultDef out;
+ out.name = ConvertFromC(in.name);
+ out.type = in.type;
+ return out;
+}
+
+static RpcDefinition ConvertFromC(const NT_RpcDefinition& in) {
+ RpcDefinition out;
+ out.version = in.version;
+ out.name = ConvertFromC(in.name);
+
+ out.params.reserve(in.num_params);
+ for (size_t i = 0; i < in.num_params; ++i)
+ out.params.push_back(ConvertFromC(in.params[i]));
+
+ out.results.reserve(in.num_results);
+ for (size_t i = 0; i < in.num_results; ++i)
+ out.results.push_back(ConvertFromC(in.results[i]));
+
+ return out;
+}
+
+extern "C" {
+
+/*
+ * Instance Functions
+ */
+
+NT_Inst NT_GetDefaultInstance(void) { return nt::GetDefaultInstance(); }
+
+NT_Inst NT_CreateInstance(void) { return nt::CreateInstance(); }
+
+void NT_DestroyInstance(NT_Inst inst) { return nt::DestroyInstance(inst); }
+
+NT_Inst NT_GetInstanceFromHandle(NT_Handle handle) {
+ return nt::GetInstanceFromHandle(handle);
+}
+
+/*
+ * Table Functions
+ */
+
+NT_Entry NT_GetEntry(NT_Inst inst, const char* name, size_t name_len) {
+ return nt::GetEntry(inst, StringRef(name, name_len));
+}
+
+NT_Entry* NT_GetEntries(NT_Inst inst, const char* prefix, size_t prefix_len,
+ unsigned int types, size_t* count) {
+ auto info_v = nt::GetEntries(inst, StringRef(prefix, prefix_len), types);
+ *count = info_v.size();
+ if (info_v.size() == 0) return nullptr;
+
+ // create array and copy into it
+ NT_Entry* info = static_cast<NT_Entry*>(
+ wpi::CheckedMalloc(info_v.size() * sizeof(NT_Entry)));
+ std::memcpy(info, info_v.data(), info_v.size() * sizeof(NT_Entry));
+ return info;
+}
+
+char* NT_GetEntryName(NT_Entry entry, size_t* name_len) {
+ struct NT_String v_name;
+ nt::ConvertToC(nt::GetEntryName(entry), &v_name);
+ *name_len = v_name.len;
+ return v_name.str;
+}
+
+enum NT_Type NT_GetEntryType(NT_Entry entry) { return nt::GetEntryType(entry); }
+
+uint64_t NT_GetEntryLastChange(NT_Entry entry) {
+ return nt::GetEntryLastChange(entry);
+}
+
+void NT_GetEntryValue(NT_Entry entry, struct NT_Value* value) {
+ NT_InitValue(value);
+ auto v = nt::GetEntryValue(entry);
+ if (!v) return;
+ ConvertToC(*v, value);
+}
+
+int NT_SetDefaultEntryValue(NT_Entry entry,
+ const struct NT_Value* default_value) {
+ return nt::SetDefaultEntryValue(entry, ConvertFromC(*default_value));
+}
+
+int NT_SetEntryValue(NT_Entry entry, const struct NT_Value* value) {
+ return nt::SetEntryValue(entry, ConvertFromC(*value));
+}
+
+void NT_SetEntryTypeValue(NT_Entry entry, const struct NT_Value* value) {
+ nt::SetEntryTypeValue(entry, ConvertFromC(*value));
+}
+
+void NT_SetEntryFlags(NT_Entry entry, unsigned int flags) {
+ nt::SetEntryFlags(entry, flags);
+}
+
+unsigned int NT_GetEntryFlags(NT_Entry entry) {
+ return nt::GetEntryFlags(entry);
+}
+
+void NT_DeleteEntry(NT_Entry entry) { nt::DeleteEntry(entry); }
+
+void NT_DeleteAllEntries(NT_Inst inst) { nt::DeleteAllEntries(inst); }
+
+struct NT_EntryInfo* NT_GetEntryInfo(NT_Inst inst, const char* prefix,
+ size_t prefix_len, unsigned int types,
+ size_t* count) {
+ auto info_v = nt::GetEntryInfo(inst, StringRef(prefix, prefix_len), types);
+ return ConvertToC<NT_EntryInfo>(info_v, count);
+}
+
+NT_Bool NT_GetEntryInfoHandle(NT_Entry entry, struct NT_EntryInfo* info) {
+ auto info_v = nt::GetEntryInfo(entry);
+ if (info_v.name.empty()) return false;
+ ConvertToC(info_v, info);
+ return true;
+}
+
+/*
+ * Callback Creation Functions
+ */
+
+NT_EntryListener NT_AddEntryListener(NT_Inst inst, const char* prefix,
+ size_t prefix_len, void* data,
+ NT_EntryListenerCallback callback,
+ unsigned int flags) {
+ return nt::AddEntryListener(inst, StringRef(prefix, prefix_len),
+ [=](const EntryNotification& event) {
+ NT_EntryNotification c_event;
+ ConvertToC(event, &c_event);
+ callback(data, &c_event);
+ DisposeEntryNotification(&c_event);
+ },
+ flags);
+}
+
+NT_EntryListener NT_AddEntryListenerSingle(NT_Entry entry, void* data,
+ NT_EntryListenerCallback callback,
+ unsigned int flags) {
+ return nt::AddEntryListener(entry,
+ [=](const EntryNotification& event) {
+ NT_EntryNotification c_event;
+ ConvertToC(event, &c_event);
+ callback(data, &c_event);
+ DisposeEntryNotification(&c_event);
+ },
+ flags);
+}
+
+NT_EntryListenerPoller NT_CreateEntryListenerPoller(NT_Inst inst) {
+ return nt::CreateEntryListenerPoller(inst);
+}
+
+void NT_DestroyEntryListenerPoller(NT_EntryListenerPoller poller) {
+ nt::DestroyEntryListenerPoller(poller);
+}
+
+NT_EntryListener NT_AddPolledEntryListener(NT_EntryListenerPoller poller,
+ const char* prefix,
+ size_t prefix_len,
+ unsigned int flags) {
+ return nt::AddPolledEntryListener(poller, StringRef(prefix, prefix_len),
+ flags);
+}
+
+NT_EntryListener NT_AddPolledEntryListenerSingle(NT_EntryListenerPoller poller,
+ NT_Entry entry,
+ unsigned int flags) {
+ return nt::AddPolledEntryListener(poller, entry, flags);
+}
+
+struct NT_EntryNotification* NT_PollEntryListener(NT_EntryListenerPoller poller,
+ size_t* len) {
+ auto arr_cpp = nt::PollEntryListener(poller);
+ return ConvertToC<NT_EntryNotification>(arr_cpp, len);
+}
+
+struct NT_EntryNotification* NT_PollEntryListenerTimeout(
+ NT_EntryListenerPoller poller, size_t* len, double timeout,
+ NT_Bool* timed_out) {
+ bool cpp_timed_out = false;
+ auto arr_cpp = nt::PollEntryListener(poller, timeout, &cpp_timed_out);
+ *timed_out = cpp_timed_out;
+ return ConvertToC<NT_EntryNotification>(arr_cpp, len);
+}
+
+void NT_CancelPollEntryListener(NT_EntryListenerPoller poller) {
+ nt::CancelPollEntryListener(poller);
+}
+
+void NT_RemoveEntryListener(NT_EntryListener entry_listener) {
+ nt::RemoveEntryListener(entry_listener);
+}
+
+NT_Bool NT_WaitForEntryListenerQueue(NT_Inst inst, double timeout) {
+ return nt::WaitForEntryListenerQueue(inst, timeout);
+}
+
+NT_ConnectionListener NT_AddConnectionListener(
+ NT_Inst inst, void* data, NT_ConnectionListenerCallback callback,
+ NT_Bool immediate_notify) {
+ return nt::AddConnectionListener(inst,
+ [=](const ConnectionNotification& event) {
+ NT_ConnectionNotification event_c;
+ ConvertToC(event, &event_c);
+ callback(data, &event_c);
+ DisposeConnectionNotification(&event_c);
+ },
+ immediate_notify != 0);
+}
+
+NT_ConnectionListenerPoller NT_CreateConnectionListenerPoller(NT_Inst inst) {
+ return nt::CreateConnectionListenerPoller(inst);
+}
+
+void NT_DestroyConnectionListenerPoller(NT_ConnectionListenerPoller poller) {
+ nt::DestroyConnectionListenerPoller(poller);
+}
+
+NT_ConnectionListener NT_AddPolledConnectionListener(
+ NT_ConnectionListenerPoller poller, NT_Bool immediate_notify) {
+ return nt::AddPolledConnectionListener(poller, immediate_notify);
+}
+
+struct NT_ConnectionNotification* NT_PollConnectionListener(
+ NT_ConnectionListenerPoller poller, size_t* len) {
+ auto arr_cpp = nt::PollConnectionListener(poller);
+ return ConvertToC<NT_ConnectionNotification>(arr_cpp, len);
+}
+
+struct NT_ConnectionNotification* NT_PollConnectionListenerTimeout(
+ NT_ConnectionListenerPoller poller, size_t* len, double timeout,
+ NT_Bool* timed_out) {
+ bool cpp_timed_out = false;
+ auto arr_cpp = nt::PollConnectionListener(poller, timeout, &cpp_timed_out);
+ *timed_out = cpp_timed_out;
+ return ConvertToC<NT_ConnectionNotification>(arr_cpp, len);
+}
+
+void NT_CancelPollConnectionListener(NT_ConnectionListenerPoller poller) {
+ nt::CancelPollConnectionListener(poller);
+}
+
+void NT_RemoveConnectionListener(NT_ConnectionListener conn_listener) {
+ nt::RemoveConnectionListener(conn_listener);
+}
+
+NT_Bool NT_WaitForConnectionListenerQueue(NT_Inst inst, double timeout) {
+ return nt::WaitForConnectionListenerQueue(inst, timeout);
+}
+
+/*
+ * Remote Procedure Call Functions
+ */
+
+void NT_CreateRpc(NT_Entry entry, const char* def, size_t def_len, void* data,
+ NT_RpcCallback callback) {
+ nt::CreateRpc(entry, StringRef(def, def_len), [=](const RpcAnswer& answer) {
+ NT_RpcAnswer answer_c;
+ ConvertToC(answer, &answer_c);
+ callback(data, &answer_c);
+ NT_DisposeRpcAnswer(&answer_c);
+ });
+}
+
+NT_RpcCallPoller NT_CreateRpcCallPoller(NT_Inst inst) {
+ return nt::CreateRpcCallPoller(inst);
+}
+
+void NT_DestroyRpcCallPoller(NT_RpcCallPoller poller) {
+ nt::DestroyRpcCallPoller(poller);
+}
+
+void NT_CreatePolledRpc(NT_Entry entry, const char* def, size_t def_len,
+ NT_RpcCallPoller poller) {
+ nt::CreatePolledRpc(entry, StringRef(def, def_len), poller);
+}
+
+NT_RpcAnswer* NT_PollRpc(NT_RpcCallPoller poller, size_t* len) {
+ auto arr_cpp = nt::PollRpc(poller);
+ return ConvertToC<NT_RpcAnswer>(arr_cpp, len);
+}
+
+NT_RpcAnswer* NT_PollRpcTimeout(NT_RpcCallPoller poller, size_t* len,
+ double timeout, NT_Bool* timed_out) {
+ bool cpp_timed_out = false;
+ auto arr_cpp = nt::PollRpc(poller, timeout, &cpp_timed_out);
+ *timed_out = cpp_timed_out;
+ return ConvertToC<NT_RpcAnswer>(arr_cpp, len);
+}
+
+void NT_CancelPollRpc(NT_RpcCallPoller poller) { nt::CancelPollRpc(poller); }
+
+NT_Bool NT_WaitForRpcCallQueue(NT_Inst inst, double timeout) {
+ return nt::WaitForRpcCallQueue(inst, timeout);
+}
+
+NT_Bool NT_PostRpcResponse(NT_Entry entry, NT_RpcCall call, const char* result,
+ size_t result_len) {
+ return nt::PostRpcResponse(entry, call, StringRef(result, result_len));
+}
+
+NT_RpcCall NT_CallRpc(NT_Entry entry, const char* params, size_t params_len) {
+ return nt::CallRpc(entry, StringRef(params, params_len));
+}
+
+char* NT_GetRpcResult(NT_Entry entry, NT_RpcCall call, size_t* result_len) {
+ std::string result;
+ if (!nt::GetRpcResult(entry, call, &result)) return nullptr;
+
+ // convert result
+ *result_len = result.size();
+ char* result_cstr;
+ ConvertToC(result, &result_cstr);
+ return result_cstr;
+}
+
+char* NT_GetRpcResultTimeout(NT_Entry entry, NT_RpcCall call,
+ size_t* result_len, double timeout,
+ NT_Bool* timed_out) {
+ std::string result;
+ bool cpp_timed_out = false;
+ if (!nt::GetRpcResult(entry, call, &result, timeout, &cpp_timed_out)) {
+ *timed_out = cpp_timed_out;
+ return nullptr;
+ }
+
+ *timed_out = cpp_timed_out;
+ // convert result
+ *result_len = result.size();
+ char* result_cstr;
+ ConvertToC(result, &result_cstr);
+ return result_cstr;
+}
+
+void NT_CancelRpcResult(NT_Entry entry, NT_RpcCall call) {
+ nt::CancelRpcResult(entry, call);
+}
+
+char* NT_PackRpcDefinition(const NT_RpcDefinition* def, size_t* packed_len) {
+ auto packed = nt::PackRpcDefinition(ConvertFromC(*def));
+
+ // convert result
+ *packed_len = packed.size();
+ char* packed_cstr;
+ ConvertToC(packed, &packed_cstr);
+ return packed_cstr;
+}
+
+NT_Bool NT_UnpackRpcDefinition(const char* packed, size_t packed_len,
+ NT_RpcDefinition* def) {
+ nt::RpcDefinition def_v;
+ if (!nt::UnpackRpcDefinition(StringRef(packed, packed_len), &def_v)) return 0;
+
+ // convert result
+ ConvertToC(def_v, def);
+ return 1;
+}
+
+char* NT_PackRpcValues(const NT_Value** values, size_t values_len,
+ size_t* packed_len) {
+ // create input vector
+ std::vector<std::shared_ptr<Value>> values_v;
+ values_v.reserve(values_len);
+ for (size_t i = 0; i < values_len; ++i)
+ values_v.push_back(ConvertFromC(*values[i]));
+
+ // make the call
+ auto packed = nt::PackRpcValues(values_v);
+
+ // convert result
+ *packed_len = packed.size();
+ char* packed_cstr;
+ ConvertToC(packed, &packed_cstr);
+ return packed_cstr;
+}
+
+NT_Value** NT_UnpackRpcValues(const char* packed, size_t packed_len,
+ const NT_Type* types, size_t types_len) {
+ auto values_v = nt::UnpackRpcValues(StringRef(packed, packed_len),
+ ArrayRef<NT_Type>(types, types_len));
+ if (values_v.size() == 0) return nullptr;
+
+ // create array and copy into it
+ NT_Value** values = static_cast<NT_Value**>(
+ wpi::CheckedMalloc(values_v.size() * sizeof(NT_Value*)));
+ for (size_t i = 0; i < values_v.size(); ++i) {
+ values[i] = static_cast<NT_Value*>(wpi::CheckedMalloc(sizeof(NT_Value)));
+ ConvertToC(*values_v[i], values[i]);
+ }
+ return values;
+}
+
+/*
+ * Client/Server Functions
+ */
+
+void NT_SetNetworkIdentity(NT_Inst inst, const char* name, size_t name_len) {
+ nt::SetNetworkIdentity(inst, StringRef(name, name_len));
+}
+
+unsigned int NT_GetNetworkMode(NT_Inst inst) {
+ return nt::GetNetworkMode(inst);
+}
+
+void NT_StartServer(NT_Inst inst, const char* persist_filename,
+ const char* listen_address, unsigned int port) {
+ nt::StartServer(inst, persist_filename, listen_address, port);
+}
+
+void NT_StopServer(NT_Inst inst) { nt::StopServer(inst); }
+
+void NT_StartClientNone(NT_Inst inst) { nt::StartClient(inst); }
+
+void NT_StartClient(NT_Inst inst, const char* server_name, unsigned int port) {
+ nt::StartClient(inst, server_name, port);
+}
+
+void NT_StartClientMulti(NT_Inst inst, size_t count, const char** server_names,
+ const unsigned int* ports) {
+ std::vector<std::pair<StringRef, unsigned int>> servers;
+ servers.reserve(count);
+ for (size_t i = 0; i < count; ++i)
+ servers.emplace_back(std::make_pair(server_names[i], ports[i]));
+ nt::StartClient(inst, servers);
+}
+
+void NT_StartClientTeam(NT_Inst inst, unsigned int team, unsigned int port) {
+ nt::StartClientTeam(inst, team, port);
+}
+
+void NT_StopClient(NT_Inst inst) { nt::StopClient(inst); }
+
+void NT_SetServer(NT_Inst inst, const char* server_name, unsigned int port) {
+ nt::SetServer(inst, server_name, port);
+}
+
+void NT_SetServerMulti(NT_Inst inst, size_t count, const char** server_names,
+ const unsigned int* ports) {
+ std::vector<std::pair<StringRef, unsigned int>> servers;
+ servers.reserve(count);
+ for (size_t i = 0; i < count; ++i)
+ servers.emplace_back(std::make_pair(server_names[i], ports[i]));
+ nt::SetServer(inst, servers);
+}
+
+void NT_SetServerTeam(NT_Inst inst, unsigned int team, unsigned int port) {
+ nt::SetServerTeam(inst, team, port);
+}
+
+void NT_StartDSClient(NT_Inst inst, unsigned int port) {
+ nt::StartDSClient(inst, port);
+}
+
+void NT_StopDSClient(NT_Inst inst) { nt::StopDSClient(inst); }
+
+void NT_SetUpdateRate(NT_Inst inst, double interval) {
+ nt::SetUpdateRate(inst, interval);
+}
+
+void NT_Flush(NT_Inst inst) { nt::Flush(inst); }
+
+NT_Bool NT_IsConnected(NT_Inst inst) { return nt::IsConnected(inst); }
+
+struct NT_ConnectionInfo* NT_GetConnections(NT_Inst inst, size_t* count) {
+ auto conn_v = nt::GetConnections(inst);
+ return ConvertToC<NT_ConnectionInfo>(conn_v, count);
+}
+
+/*
+ * File Save/Load Functions
+ */
+
+const char* NT_SavePersistent(NT_Inst inst, const char* filename) {
+ return nt::SavePersistent(inst, filename);
+}
+
+const char* NT_LoadPersistent(NT_Inst inst, const char* filename,
+ void (*warn)(size_t line, const char* msg)) {
+ return nt::LoadPersistent(inst, filename, warn);
+}
+
+const char* NT_SaveEntries(NT_Inst inst, const char* filename,
+ const char* prefix, size_t prefix_len) {
+ return nt::SaveEntries(inst, filename, StringRef(prefix, prefix_len));
+}
+
+const char* NT_LoadEntries(NT_Inst inst, const char* filename,
+ const char* prefix, size_t prefix_len,
+ void (*warn)(size_t line, const char* msg)) {
+ return nt::LoadEntries(inst, filename, StringRef(prefix, prefix_len), warn);
+}
+
+/*
+ * Utility Functions
+ */
+
+uint64_t NT_Now(void) { return wpi::Now(); }
+
+NT_Logger NT_AddLogger(NT_Inst inst, void* data, NT_LogFunc func,
+ unsigned int min_level, unsigned int max_level) {
+ return nt::AddLogger(inst,
+ [=](const LogMessage& msg) {
+ NT_LogMessage msg_c;
+ ConvertToC(msg, &msg_c);
+ func(data, &msg_c);
+ NT_DisposeLogMessage(&msg_c);
+ },
+ min_level, max_level);
+}
+
+NT_LoggerPoller NT_CreateLoggerPoller(NT_Inst inst) {
+ return nt::CreateLoggerPoller(inst);
+}
+
+void NT_DestroyLoggerPoller(NT_LoggerPoller poller) {
+ nt::DestroyLoggerPoller(poller);
+}
+
+NT_Logger NT_AddPolledLogger(NT_LoggerPoller poller, unsigned int min_level,
+ unsigned int max_level) {
+ return nt::AddPolledLogger(poller, min_level, max_level);
+}
+
+struct NT_LogMessage* NT_PollLogger(NT_LoggerPoller poller, size_t* len) {
+ auto arr_cpp = nt::PollLogger(poller);
+ return ConvertToC<NT_LogMessage>(arr_cpp, len);
+}
+
+struct NT_LogMessage* NT_PollLoggerTimeout(NT_LoggerPoller poller, size_t* len,
+ double timeout, NT_Bool* timed_out) {
+ bool cpp_timed_out = false;
+ auto arr_cpp = nt::PollLogger(poller, timeout, &cpp_timed_out);
+ *timed_out = cpp_timed_out;
+ return ConvertToC<NT_LogMessage>(arr_cpp, len);
+}
+
+void NT_CancelPollLogger(NT_LoggerPoller poller) {
+ nt::CancelPollLogger(poller);
+}
+
+void NT_RemoveLogger(NT_Logger logger) { nt::RemoveLogger(logger); }
+
+NT_Bool NT_WaitForLoggerQueue(NT_Inst inst, double timeout) {
+ return nt::WaitForLoggerQueue(inst, timeout);
+}
+
+void NT_DisposeValue(NT_Value* value) {
+ switch (value->type) {
+ case NT_UNASSIGNED:
+ case NT_BOOLEAN:
+ case NT_DOUBLE:
+ break;
+ case NT_STRING:
+ case NT_RAW:
+ case NT_RPC:
+ std::free(value->data.v_string.str);
+ break;
+ case NT_BOOLEAN_ARRAY:
+ std::free(value->data.arr_boolean.arr);
+ break;
+ case NT_DOUBLE_ARRAY:
+ std::free(value->data.arr_double.arr);
+ break;
+ case NT_STRING_ARRAY: {
+ for (size_t i = 0; i < value->data.arr_string.size; i++)
+ std::free(value->data.arr_string.arr[i].str);
+ std::free(value->data.arr_string.arr);
+ break;
+ }
+ default:
+ assert(false && "unknown value type");
+ }
+ value->type = NT_UNASSIGNED;
+ value->last_change = 0;
+}
+
+void NT_InitValue(NT_Value* value) {
+ value->type = NT_UNASSIGNED;
+ value->last_change = 0;
+}
+
+void NT_DisposeString(NT_String* str) {
+ std::free(str->str);
+ str->str = nullptr;
+ str->len = 0;
+}
+
+void NT_InitString(NT_String* str) {
+ str->str = nullptr;
+ str->len = 0;
+}
+
+void NT_DisposeEntryArray(NT_Entry* arr, size_t /*count*/) { std::free(arr); }
+
+void NT_DisposeConnectionInfoArray(NT_ConnectionInfo* arr, size_t count) {
+ for (size_t i = 0; i < count; i++) DisposeConnectionInfo(&arr[i]);
+ std::free(arr);
+}
+
+void NT_DisposeEntryInfoArray(NT_EntryInfo* arr, size_t count) {
+ for (size_t i = 0; i < count; i++) DisposeEntryInfo(&arr[i]);
+ std::free(arr);
+}
+
+void NT_DisposeEntryInfo(NT_EntryInfo* info) { DisposeEntryInfo(info); }
+
+void NT_DisposeEntryNotificationArray(NT_EntryNotification* arr, size_t count) {
+ for (size_t i = 0; i < count; i++) DisposeEntryNotification(&arr[i]);
+ std::free(arr);
+}
+
+void NT_DisposeEntryNotification(NT_EntryNotification* info) {
+ DisposeEntryNotification(info);
+}
+
+void NT_DisposeConnectionNotificationArray(NT_ConnectionNotification* arr,
+ size_t count) {
+ for (size_t i = 0; i < count; i++) DisposeConnectionNotification(&arr[i]);
+ std::free(arr);
+}
+
+void NT_DisposeConnectionNotification(NT_ConnectionNotification* info) {
+ DisposeConnectionNotification(info);
+}
+
+void NT_DisposeLogMessageArray(NT_LogMessage* arr, size_t count) {
+ for (size_t i = 0; i < count; i++) NT_DisposeLogMessage(&arr[i]);
+ std::free(arr);
+}
+
+void NT_DisposeLogMessage(NT_LogMessage* info) { std::free(info->message); }
+
+void NT_DisposeRpcDefinition(NT_RpcDefinition* def) {
+ NT_DisposeString(&def->name);
+
+ for (size_t i = 0; i < def->num_params; ++i) {
+ NT_DisposeString(&def->params[i].name);
+ NT_DisposeValue(&def->params[i].def_value);
+ }
+ std::free(def->params);
+ def->params = nullptr;
+ def->num_params = 0;
+
+ for (size_t i = 0; i < def->num_results; ++i)
+ NT_DisposeString(&def->results[i].name);
+ std::free(def->results);
+ def->results = nullptr;
+ def->num_results = 0;
+}
+
+void NT_DisposeRpcAnswerArray(NT_RpcAnswer* arr, size_t count) {
+ for (size_t i = 0; i < count; i++) NT_DisposeRpcAnswer(&arr[i]);
+ std::free(arr);
+}
+
+void NT_DisposeRpcAnswer(NT_RpcAnswer* call_info) {
+ NT_DisposeString(&call_info->name);
+ NT_DisposeString(&call_info->params);
+ DisposeConnectionInfo(&call_info->conn);
+}
+
+/* Interop Utility Functions */
+
+/* Array and Struct Allocations */
+
+/* Allocates a char array of the specified size.*/
+char* NT_AllocateCharArray(size_t size) {
+ char* retVal = static_cast<char*>(wpi::CheckedMalloc(size * sizeof(char)));
+ return retVal;
+}
+
+/* Allocates an integer or boolean array of the specified size. */
+int* NT_AllocateBooleanArray(size_t size) {
+ int* retVal = static_cast<int*>(wpi::CheckedMalloc(size * sizeof(int)));
+ return retVal;
+}
+
+/* Allocates a double array of the specified size. */
+double* NT_AllocateDoubleArray(size_t size) {
+ double* retVal =
+ static_cast<double*>(wpi::CheckedMalloc(size * sizeof(double)));
+ return retVal;
+}
+
+/* Allocates an NT_String array of the specified size. */
+struct NT_String* NT_AllocateStringArray(size_t size) {
+ NT_String* retVal =
+ static_cast<NT_String*>(wpi::CheckedMalloc(size * sizeof(NT_String)));
+ return retVal;
+}
+
+void NT_FreeCharArray(char* v_char) { std::free(v_char); }
+void NT_FreeDoubleArray(double* v_double) { std::free(v_double); }
+void NT_FreeBooleanArray(int* v_boolean) { std::free(v_boolean); }
+void NT_FreeStringArray(struct NT_String* v_string, size_t arr_size) {
+ for (size_t i = 0; i < arr_size; i++) std::free(v_string[i].str);
+ std::free(v_string);
+}
+
+NT_Bool NT_SetEntryDouble(NT_Entry entry, uint64_t time, double v_double,
+ NT_Bool force) {
+ if (force != 0) {
+ nt::SetEntryTypeValue(entry, Value::MakeDouble(v_double, time));
+ return 1;
+ } else {
+ return nt::SetEntryValue(entry, Value::MakeDouble(v_double, time));
+ }
+}
+
+NT_Bool NT_SetEntryBoolean(NT_Entry entry, uint64_t time, NT_Bool v_boolean,
+ NT_Bool force) {
+ if (force != 0) {
+ nt::SetEntryTypeValue(entry, Value::MakeBoolean(v_boolean != 0, time));
+ return 1;
+ } else {
+ return nt::SetEntryValue(entry, Value::MakeBoolean(v_boolean != 0, time));
+ }
+}
+
+NT_Bool NT_SetEntryString(NT_Entry entry, uint64_t time, const char* str,
+ size_t str_len, NT_Bool force) {
+ if (force != 0) {
+ nt::SetEntryTypeValue(entry,
+ Value::MakeString(StringRef(str, str_len), time));
+ return 1;
+ } else {
+ return nt::SetEntryValue(entry,
+ Value::MakeString(StringRef(str, str_len), time));
+ }
+}
+
+NT_Bool NT_SetEntryRaw(NT_Entry entry, uint64_t time, const char* raw,
+ size_t raw_len, NT_Bool force) {
+ if (force != 0) {
+ nt::SetEntryTypeValue(entry, Value::MakeRaw(StringRef(raw, raw_len), time));
+ return 1;
+ } else {
+ return nt::SetEntryValue(entry,
+ Value::MakeRaw(StringRef(raw, raw_len), time));
+ }
+}
+
+NT_Bool NT_SetEntryBooleanArray(NT_Entry entry, uint64_t time,
+ const NT_Bool* arr, size_t size,
+ NT_Bool force) {
+ if (force != 0) {
+ nt::SetEntryTypeValue(
+ entry, Value::MakeBooleanArray(wpi::makeArrayRef(arr, size), time));
+ return 1;
+ } else {
+ return nt::SetEntryValue(
+ entry, Value::MakeBooleanArray(wpi::makeArrayRef(arr, size), time));
+ }
+}
+
+NT_Bool NT_SetEntryDoubleArray(NT_Entry entry, uint64_t time, const double* arr,
+ size_t size, NT_Bool force) {
+ if (force != 0) {
+ nt::SetEntryTypeValue(
+ entry, Value::MakeDoubleArray(wpi::makeArrayRef(arr, size), time));
+ return 1;
+ } else {
+ return nt::SetEntryValue(
+ entry, Value::MakeDoubleArray(wpi::makeArrayRef(arr, size), time));
+ }
+}
+
+NT_Bool NT_SetEntryStringArray(NT_Entry entry, uint64_t time,
+ const struct NT_String* arr, size_t size,
+ NT_Bool force) {
+ std::vector<std::string> v;
+ v.reserve(size);
+ for (size_t i = 0; i < size; ++i) v.push_back(ConvertFromC(arr[i]));
+
+ if (force != 0) {
+ nt::SetEntryTypeValue(entry, Value::MakeStringArray(std::move(v), time));
+ return 1;
+ } else {
+ return nt::SetEntryValue(entry, Value::MakeStringArray(std::move(v), time));
+ }
+}
+
+enum NT_Type NT_GetValueType(const struct NT_Value* value) {
+ if (!value) return NT_Type::NT_UNASSIGNED;
+ return value->type;
+}
+
+NT_Bool NT_GetValueBoolean(const struct NT_Value* value, uint64_t* last_change,
+ NT_Bool* v_boolean) {
+ if (!value || value->type != NT_Type::NT_BOOLEAN) return 0;
+ *v_boolean = value->data.v_boolean;
+ *last_change = value->last_change;
+ return 1;
+}
+
+NT_Bool NT_GetValueDouble(const struct NT_Value* value, uint64_t* last_change,
+ double* v_double) {
+ if (!value || value->type != NT_Type::NT_DOUBLE) return 0;
+ *last_change = value->last_change;
+ *v_double = value->data.v_double;
+ return 1;
+}
+
+char* NT_GetValueString(const struct NT_Value* value, uint64_t* last_change,
+ size_t* str_len) {
+ if (!value || value->type != NT_Type::NT_STRING) return nullptr;
+ *last_change = value->last_change;
+ *str_len = value->data.v_string.len;
+ char* str =
+ static_cast<char*>(wpi::CheckedMalloc(value->data.v_string.len + 1));
+ std::memcpy(str, value->data.v_string.str, value->data.v_string.len + 1);
+ return str;
+}
+
+char* NT_GetValueRaw(const struct NT_Value* value, uint64_t* last_change,
+ size_t* raw_len) {
+ if (!value || value->type != NT_Type::NT_RAW) return nullptr;
+ *last_change = value->last_change;
+ *raw_len = value->data.v_string.len;
+ char* raw =
+ static_cast<char*>(wpi::CheckedMalloc(value->data.v_string.len + 1));
+ std::memcpy(raw, value->data.v_string.str, value->data.v_string.len + 1);
+ return raw;
+}
+
+NT_Bool* NT_GetValueBooleanArray(const struct NT_Value* value,
+ uint64_t* last_change, size_t* arr_size) {
+ if (!value || value->type != NT_Type::NT_BOOLEAN_ARRAY) return nullptr;
+ *last_change = value->last_change;
+ *arr_size = value->data.arr_boolean.size;
+ NT_Bool* arr = static_cast<int*>(
+ wpi::CheckedMalloc(value->data.arr_boolean.size * sizeof(NT_Bool)));
+ std::memcpy(arr, value->data.arr_boolean.arr,
+ value->data.arr_boolean.size * sizeof(NT_Bool));
+ return arr;
+}
+
+double* NT_GetValueDoubleArray(const struct NT_Value* value,
+ uint64_t* last_change, size_t* arr_size) {
+ if (!value || value->type != NT_Type::NT_DOUBLE_ARRAY) return nullptr;
+ *last_change = value->last_change;
+ *arr_size = value->data.arr_double.size;
+ double* arr = static_cast<double*>(
+ wpi::CheckedMalloc(value->data.arr_double.size * sizeof(double)));
+ std::memcpy(arr, value->data.arr_double.arr,
+ value->data.arr_double.size * sizeof(double));
+ return arr;
+}
+
+NT_String* NT_GetValueStringArray(const struct NT_Value* value,
+ uint64_t* last_change, size_t* arr_size) {
+ if (!value || value->type != NT_Type::NT_STRING_ARRAY) return nullptr;
+ *last_change = value->last_change;
+ *arr_size = value->data.arr_string.size;
+ NT_String* arr = static_cast<NT_String*>(
+ wpi::CheckedMalloc(value->data.arr_string.size * sizeof(NT_String)));
+ for (size_t i = 0; i < value->data.arr_string.size; ++i) {
+ size_t len = value->data.arr_string.arr[i].len;
+ arr[i].len = len;
+ arr[i].str = static_cast<char*>(wpi::CheckedMalloc(len + 1));
+ std::memcpy(arr[i].str, value->data.arr_string.arr[i].str, len + 1);
+ }
+ return arr;
+}
+
+NT_Bool NT_SetDefaultEntryBoolean(NT_Entry entry, uint64_t time,
+ NT_Bool default_boolean) {
+ return nt::SetDefaultEntryValue(
+ entry, Value::MakeBoolean(default_boolean != 0, time));
+}
+
+NT_Bool NT_SetDefaultEntryDouble(NT_Entry entry, uint64_t time,
+ double default_double) {
+ return nt::SetDefaultEntryValue(entry,
+ Value::MakeDouble(default_double, time));
+}
+
+NT_Bool NT_SetDefaultEntryString(NT_Entry entry, uint64_t time,
+ const char* default_value,
+ size_t default_len) {
+ return nt::SetDefaultEntryValue(
+ entry, Value::MakeString(StringRef(default_value, default_len), time));
+}
+
+NT_Bool NT_SetDefaultEntryRaw(NT_Entry entry, uint64_t time,
+ const char* default_value, size_t default_len) {
+ return nt::SetDefaultEntryValue(
+ entry, Value::MakeRaw(StringRef(default_value, default_len), time));
+}
+
+NT_Bool NT_SetDefaultEntryBooleanArray(NT_Entry entry, uint64_t time,
+ const NT_Bool* default_value,
+ size_t default_size) {
+ return nt::SetDefaultEntryValue(
+ entry, Value::MakeBooleanArray(
+ wpi::makeArrayRef(default_value, default_size), time));
+}
+
+NT_Bool NT_SetDefaultEntryDoubleArray(NT_Entry entry, uint64_t time,
+ const double* default_value,
+ size_t default_size) {
+ return nt::SetDefaultEntryValue(
+ entry, Value::MakeDoubleArray(
+ wpi::makeArrayRef(default_value, default_size), time));
+}
+
+NT_Bool NT_SetDefaultEntryStringArray(NT_Entry entry, uint64_t time,
+ const struct NT_String* default_value,
+ size_t default_size) {
+ std::vector<std::string> vec;
+ vec.reserve(default_size);
+ for (size_t i = 0; i < default_size; ++i)
+ vec.push_back(ConvertFromC(default_value[i]));
+
+ return nt::SetDefaultEntryValue(entry,
+ Value::MakeStringArray(std::move(vec), time));
+}
+
+NT_Bool NT_GetEntryBoolean(NT_Entry entry, uint64_t* last_change,
+ NT_Bool* v_boolean) {
+ auto v = nt::GetEntryValue(entry);
+ if (!v || !v->IsBoolean()) return 0;
+ *v_boolean = v->GetBoolean();
+ *last_change = v->last_change();
+ return 1;
+}
+
+NT_Bool NT_GetEntryDouble(NT_Entry entry, uint64_t* last_change,
+ double* v_double) {
+ auto v = nt::GetEntryValue(entry);
+ if (!v || !v->IsDouble()) return 0;
+ *last_change = v->last_change();
+ *v_double = v->GetDouble();
+ return 1;
+}
+
+char* NT_GetEntryString(NT_Entry entry, uint64_t* last_change,
+ size_t* str_len) {
+ auto v = nt::GetEntryValue(entry);
+ if (!v || !v->IsString()) return nullptr;
+ *last_change = v->last_change();
+ struct NT_String v_string;
+ nt::ConvertToC(v->GetString(), &v_string);
+ *str_len = v_string.len;
+ return v_string.str;
+}
+
+char* NT_GetEntryRaw(NT_Entry entry, uint64_t* last_change, size_t* raw_len) {
+ auto v = nt::GetEntryValue(entry);
+ if (!v || !v->IsRaw()) return nullptr;
+ *last_change = v->last_change();
+ struct NT_String v_raw;
+ nt::ConvertToC(v->GetRaw(), &v_raw);
+ *raw_len = v_raw.len;
+ return v_raw.str;
+}
+
+NT_Bool* NT_GetEntryBooleanArray(NT_Entry entry, uint64_t* last_change,
+ size_t* arr_size) {
+ auto v = nt::GetEntryValue(entry);
+ if (!v || !v->IsBooleanArray()) return nullptr;
+ *last_change = v->last_change();
+ auto vArr = v->GetBooleanArray();
+ NT_Bool* arr =
+ static_cast<int*>(wpi::CheckedMalloc(vArr.size() * sizeof(NT_Bool)));
+ *arr_size = vArr.size();
+ std::copy(vArr.begin(), vArr.end(), arr);
+ return arr;
+}
+
+double* NT_GetEntryDoubleArray(NT_Entry entry, uint64_t* last_change,
+ size_t* arr_size) {
+ auto v = nt::GetEntryValue(entry);
+ if (!v || !v->IsDoubleArray()) return nullptr;
+ *last_change = v->last_change();
+ auto vArr = v->GetDoubleArray();
+ double* arr =
+ static_cast<double*>(wpi::CheckedMalloc(vArr.size() * sizeof(double)));
+ *arr_size = vArr.size();
+ std::copy(vArr.begin(), vArr.end(), arr);
+ return arr;
+}
+
+NT_String* NT_GetEntryStringArray(NT_Entry entry, uint64_t* last_change,
+ size_t* arr_size) {
+ auto v = nt::GetEntryValue(entry);
+ if (!v || !v->IsStringArray()) return nullptr;
+ *last_change = v->last_change();
+ auto vArr = v->GetStringArray();
+ NT_String* arr = static_cast<NT_String*>(
+ wpi::CheckedMalloc(vArr.size() * sizeof(NT_String)));
+ for (size_t i = 0; i < vArr.size(); ++i) {
+ ConvertToC(vArr[i], &arr[i]);
+ }
+ *arr_size = vArr.size();
+ return arr;
+}
+
+} // extern "C"
diff --git a/ntcore/src/main/native/cpp/ntcore_cpp.cpp b/ntcore/src/main/native/cpp/ntcore_cpp.cpp
new file mode 100644
index 0000000..18256f5
--- /dev/null
+++ b/ntcore/src/main/native/cpp/ntcore_cpp.cpp
@@ -0,0 +1,1062 @@
+/*----------------------------------------------------------------------------*/
+/* Copyright (c) 2015-2018 FIRST. All Rights Reserved. */
+/* Open Source Software - may be modified and shared by FRC teams. The code */
+/* must be accompanied by the FIRST BSD license file in the root directory of */
+/* the project. */
+/*----------------------------------------------------------------------------*/
+
+#include <stdint.h>
+
+#include <cassert>
+#include <cstdio>
+#include <cstdlib>
+
+#include <wpi/timestamp.h>
+
+#include "Handle.h"
+#include "InstanceImpl.h"
+#include "Log.h"
+#include "WireDecoder.h"
+#include "WireEncoder.h"
+#include "ntcore.h"
+
+namespace nt {
+
+/*
+ * Instance Functions
+ */
+
+NT_Inst GetDefaultInstance() {
+ return Handle{InstanceImpl::GetDefaultIndex(), 0, Handle::kInstance};
+}
+
+NT_Inst CreateInstance() {
+ return Handle{InstanceImpl::Alloc(), 0, Handle::kInstance};
+}
+
+void DestroyInstance(NT_Inst inst) {
+ int i = Handle{inst}.GetTypedInst(Handle::kInstance);
+ if (i < 0) return;
+ InstanceImpl::Destroy(i);
+}
+
+NT_Inst GetInstanceFromHandle(NT_Handle handle) {
+ Handle h{handle};
+ auto type = h.GetType();
+ if (type >= Handle::kConnectionListener && type <= Handle::kRpcCallPoller)
+ return Handle(h.GetInst(), 0, Handle::kInstance);
+
+ return 0;
+}
+
+/*
+ * Table Functions
+ */
+
+NT_Entry GetEntry(NT_Inst inst, const Twine& name) {
+ int i = Handle{inst}.GetTypedInst(Handle::kInstance);
+ auto ii = InstanceImpl::Get(i);
+ if (!ii) return 0;
+
+ unsigned int id = ii->storage.GetEntry(name);
+ if (id == UINT_MAX) return 0;
+ return Handle(i, id, Handle::kEntry);
+}
+
+std::vector<NT_Entry> GetEntries(NT_Inst inst, const Twine& prefix,
+ unsigned int types) {
+ int i = Handle{inst}.GetTypedInst(Handle::kInstance);
+ auto ii = InstanceImpl::Get(i);
+ if (!ii) return std::vector<NT_Entry>{};
+
+ auto arr = ii->storage.GetEntries(prefix, types);
+ // convert indices to handles
+ for (auto& val : arr) val = Handle(i, val, Handle::kEntry);
+ return arr;
+}
+
+std::string GetEntryName(NT_Entry entry) {
+ Handle handle{entry};
+ int id = handle.GetTypedIndex(Handle::kEntry);
+ auto ii = InstanceImpl::Get(handle.GetInst());
+ if (id < 0 || !ii) return std::string{};
+
+ return ii->storage.GetEntryName(id);
+}
+
+NT_Type GetEntryType(NT_Entry entry) {
+ Handle handle{entry};
+ int id = handle.GetTypedIndex(Handle::kEntry);
+ auto ii = InstanceImpl::Get(handle.GetInst());
+ if (id < 0 || !ii) return NT_UNASSIGNED;
+
+ return ii->storage.GetEntryType(id);
+}
+
+uint64_t GetEntryLastChange(NT_Entry entry) {
+ Handle handle{entry};
+ int id = handle.GetTypedIndex(Handle::kEntry);
+ auto ii = InstanceImpl::Get(handle.GetInst());
+ if (id < 0 || !ii) return 0;
+
+ return ii->storage.GetEntryLastChange(id);
+}
+
+std::shared_ptr<Value> GetEntryValue(StringRef name) {
+ return InstanceImpl::GetDefault()->storage.GetEntryValue(name);
+}
+
+std::shared_ptr<Value> GetEntryValue(NT_Entry entry) {
+ Handle handle{entry};
+ int id = handle.GetTypedIndex(Handle::kEntry);
+ auto ii = InstanceImpl::Get(handle.GetInst());
+ if (id < 0 || !ii) return nullptr;
+
+ return ii->storage.GetEntryValue(id);
+}
+
+bool SetDefaultEntryValue(StringRef name, std::shared_ptr<Value> value) {
+ return InstanceImpl::GetDefault()->storage.SetDefaultEntryValue(name, value);
+}
+
+bool SetDefaultEntryValue(NT_Entry entry, std::shared_ptr<Value> value) {
+ Handle handle{entry};
+ int id = handle.GetTypedIndex(Handle::kEntry);
+ auto ii = InstanceImpl::Get(handle.GetInst());
+ if (id < 0 || !ii) return false;
+
+ return ii->storage.SetDefaultEntryValue(id, value);
+}
+
+bool SetEntryValue(StringRef name, std::shared_ptr<Value> value) {
+ return InstanceImpl::GetDefault()->storage.SetEntryValue(name, value);
+}
+
+bool SetEntryValue(NT_Entry entry, std::shared_ptr<Value> value) {
+ Handle handle{entry};
+ int id = handle.GetTypedIndex(Handle::kEntry);
+ auto ii = InstanceImpl::Get(handle.GetInst());
+ if (id < 0 || !ii) return false;
+
+ return ii->storage.SetEntryValue(id, value);
+}
+
+void SetEntryTypeValue(StringRef name, std::shared_ptr<Value> value) {
+ InstanceImpl::GetDefault()->storage.SetEntryTypeValue(name, value);
+}
+
+void SetEntryTypeValue(NT_Entry entry, std::shared_ptr<Value> value) {
+ Handle handle{entry};
+ int id = handle.GetTypedIndex(Handle::kEntry);
+ auto ii = InstanceImpl::Get(handle.GetInst());
+ if (id < 0 || !ii) return;
+
+ ii->storage.SetEntryTypeValue(id, value);
+}
+
+void SetEntryFlags(StringRef name, unsigned int flags) {
+ InstanceImpl::GetDefault()->storage.SetEntryFlags(name, flags);
+}
+
+void SetEntryFlags(NT_Entry entry, unsigned int flags) {
+ Handle handle{entry};
+ int id = handle.GetTypedIndex(Handle::kEntry);
+ auto ii = InstanceImpl::Get(handle.GetInst());
+ if (id < 0 || !ii) return;
+
+ ii->storage.SetEntryFlags(id, flags);
+}
+
+unsigned int GetEntryFlags(StringRef name) {
+ return InstanceImpl::GetDefault()->storage.GetEntryFlags(name);
+}
+
+unsigned int GetEntryFlags(NT_Entry entry) {
+ Handle handle{entry};
+ int id = handle.GetTypedIndex(Handle::kEntry);
+ auto ii = InstanceImpl::Get(handle.GetInst());
+ if (id < 0 || !ii) return 0;
+
+ return ii->storage.GetEntryFlags(id);
+}
+
+void DeleteEntry(StringRef name) {
+ InstanceImpl::GetDefault()->storage.DeleteEntry(name);
+}
+
+void DeleteEntry(NT_Entry entry) {
+ Handle handle{entry};
+ int id = handle.GetTypedIndex(Handle::kEntry);
+ auto ii = InstanceImpl::Get(handle.GetInst());
+ if (id < 0 || !ii) return;
+
+ ii->storage.DeleteEntry(id);
+}
+
+void DeleteAllEntries() {
+ InstanceImpl::GetDefault()->storage.DeleteAllEntries();
+}
+
+void DeleteAllEntries(NT_Inst inst) {
+ int i = Handle{inst}.GetTypedInst(Handle::kInstance);
+ auto ii = InstanceImpl::Get(i);
+ if (i < 0 || !ii) return;
+
+ ii->storage.DeleteAllEntries();
+}
+
+std::vector<EntryInfo> GetEntryInfo(StringRef prefix, unsigned int types) {
+ return InstanceImpl::GetDefault()->storage.GetEntryInfo(0, prefix, types);
+}
+
+std::vector<EntryInfo> GetEntryInfo(NT_Inst inst, const Twine& prefix,
+ unsigned int types) {
+ int i = Handle{inst}.GetTypedInst(Handle::kInstance);
+ auto ii = InstanceImpl::Get(i);
+ if (!ii) return std::vector<EntryInfo>{};
+
+ return ii->storage.GetEntryInfo(i, prefix, types);
+}
+
+EntryInfo GetEntryInfo(NT_Entry entry) {
+ Handle handle{entry};
+ int id = handle.GetTypedIndex(Handle::kEntry);
+ int i = handle.GetInst();
+ auto ii = InstanceImpl::Get(i);
+ if (id < 0 || !ii) {
+ EntryInfo info;
+ info.entry = 0;
+ info.type = NT_UNASSIGNED;
+ info.flags = 0;
+ info.last_change = 0;
+ return info;
+ }
+
+ return ii->storage.GetEntryInfo(i, id);
+}
+
+/*
+ * Callback Creation Functions
+ */
+
+NT_EntryListener AddEntryListener(StringRef prefix,
+ EntryListenerCallback callback,
+ unsigned int flags) {
+ return AddEntryListener(
+ Handle(InstanceImpl::GetDefaultIndex(), 0, Handle::kInstance), prefix,
+ [=](const EntryNotification& event) {
+ callback(event.listener, event.name, event.value, event.flags);
+ },
+ flags);
+}
+
+NT_EntryListener AddEntryListener(
+ NT_Inst inst, const Twine& prefix,
+ std::function<void(const EntryNotification& event)> callback,
+ unsigned int flags) {
+ int i = Handle{inst}.GetTypedInst(Handle::kInstance);
+ auto ii = InstanceImpl::Get(i);
+ if (i < 0 || !ii) return 0;
+
+ unsigned int uid = ii->storage.AddListener(prefix, callback, flags);
+ return Handle(i, uid, Handle::kEntryListener);
+}
+
+NT_EntryListener AddEntryListener(
+ NT_Entry entry,
+ std::function<void(const EntryNotification& event)> callback,
+ unsigned int flags) {
+ Handle handle{entry};
+ int id = handle.GetTypedIndex(Handle::kEntry);
+ int i = handle.GetInst();
+ auto ii = InstanceImpl::Get(i);
+ if (id < 0 || !ii) return 0;
+
+ unsigned int uid = ii->storage.AddListener(id, callback, flags);
+ return Handle(i, uid, Handle::kEntryListener);
+}
+
+NT_EntryListenerPoller CreateEntryListenerPoller(NT_Inst inst) {
+ int i = Handle{inst}.GetTypedInst(Handle::kInstance);
+ auto ii = InstanceImpl::Get(i);
+ if (!ii) return 0;
+
+ return Handle(i, ii->entry_notifier.CreatePoller(),
+ Handle::kEntryListenerPoller);
+}
+
+void DestroyEntryListenerPoller(NT_EntryListenerPoller poller) {
+ Handle handle{poller};
+ int id = handle.GetTypedIndex(Handle::kEntryListenerPoller);
+ auto ii = InstanceImpl::Get(handle.GetInst());
+ if (id < 0 || !ii) return;
+
+ ii->entry_notifier.RemovePoller(id);
+}
+
+NT_EntryListener AddPolledEntryListener(NT_EntryListenerPoller poller,
+ const Twine& prefix,
+ unsigned int flags) {
+ Handle handle{poller};
+ int id = handle.GetTypedIndex(Handle::kEntryListenerPoller);
+ int i = handle.GetInst();
+ auto ii = InstanceImpl::Get(i);
+ if (id < 0 || !ii) return 0;
+
+ unsigned int uid = ii->storage.AddPolledListener(id, prefix, flags);
+ return Handle(i, uid, Handle::kEntryListener);
+}
+
+NT_EntryListener AddPolledEntryListener(NT_EntryListenerPoller poller,
+ NT_Entry entry, unsigned int flags) {
+ Handle handle{entry};
+ int id = handle.GetTypedIndex(Handle::kEntry);
+ int i = handle.GetInst();
+ auto ii = InstanceImpl::Get(i);
+ if (id < 0 || !ii) return 0;
+
+ Handle phandle{poller};
+ int p_id = phandle.GetTypedIndex(Handle::kEntryListenerPoller);
+ if (p_id < 0) return 0;
+ if (handle.GetInst() != phandle.GetInst()) return 0;
+
+ unsigned int uid = ii->storage.AddPolledListener(p_id, id, flags);
+ return Handle(i, uid, Handle::kEntryListener);
+}
+
+std::vector<EntryNotification> PollEntryListener(
+ NT_EntryListenerPoller poller) {
+ Handle handle{poller};
+ int id = handle.GetTypedIndex(Handle::kEntryListenerPoller);
+ auto ii = InstanceImpl::Get(handle.GetInst());
+ if (id < 0 || !ii) return std::vector<EntryNotification>{};
+
+ return ii->entry_notifier.Poll(static_cast<unsigned int>(id));
+}
+
+std::vector<EntryNotification> PollEntryListener(NT_EntryListenerPoller poller,
+ double timeout,
+ bool* timed_out) {
+ *timed_out = false;
+ Handle handle{poller};
+ int id = handle.GetTypedIndex(Handle::kEntryListenerPoller);
+ auto ii = InstanceImpl::Get(handle.GetInst());
+ if (id < 0 || !ii) return std::vector<EntryNotification>{};
+
+ return ii->entry_notifier.Poll(static_cast<unsigned int>(id), timeout,
+ timed_out);
+}
+
+void CancelPollEntryListener(NT_EntryListenerPoller poller) {
+ Handle handle{poller};
+ int id = handle.GetTypedIndex(Handle::kEntryListenerPoller);
+ auto ii = InstanceImpl::Get(handle.GetInst());
+ if (id < 0 || !ii) return;
+
+ ii->entry_notifier.CancelPoll(id);
+}
+
+void RemoveEntryListener(NT_EntryListener entry_listener) {
+ Handle handle{entry_listener};
+ int uid = handle.GetTypedIndex(Handle::kEntryListener);
+ auto ii = InstanceImpl::Get(handle.GetInst());
+ if (uid < 0 || !ii) return;
+
+ ii->entry_notifier.Remove(uid);
+}
+
+bool WaitForEntryListenerQueue(NT_Inst inst, double timeout) {
+ int i = Handle{inst}.GetTypedInst(Handle::kInstance);
+ auto ii = InstanceImpl::Get(i);
+ if (!ii) return true;
+ return ii->entry_notifier.WaitForQueue(timeout);
+}
+
+NT_ConnectionListener AddConnectionListener(ConnectionListenerCallback callback,
+ bool immediate_notify) {
+ return AddConnectionListener(
+ Handle(InstanceImpl::GetDefaultIndex(), 0, Handle::kInstance),
+ [=](const ConnectionNotification& event) {
+ callback(event.listener, event.connected, event.conn);
+ },
+ immediate_notify);
+}
+
+NT_ConnectionListener AddConnectionListener(
+ NT_Inst inst,
+ std::function<void(const ConnectionNotification& event)> callback,
+ bool immediate_notify) {
+ int i = Handle{inst}.GetTypedInst(Handle::kInstance);
+ auto ii = InstanceImpl::Get(i);
+ if (!ii) return 0;
+
+ unsigned int uid = ii->dispatcher.AddListener(callback, immediate_notify);
+ return Handle(i, uid, Handle::kConnectionListener);
+}
+
+NT_ConnectionListenerPoller CreateConnectionListenerPoller(NT_Inst inst) {
+ int i = Handle{inst}.GetTypedInst(Handle::kInstance);
+ auto ii = InstanceImpl::Get(i);
+ if (!ii) return 0;
+
+ return Handle(i, ii->connection_notifier.CreatePoller(),
+ Handle::kConnectionListenerPoller);
+}
+
+void DestroyConnectionListenerPoller(NT_ConnectionListenerPoller poller) {
+ Handle handle{poller};
+ int id = handle.GetTypedIndex(Handle::kConnectionListenerPoller);
+ auto ii = InstanceImpl::Get(handle.GetInst());
+ if (id < 0 || !ii) return;
+
+ ii->connection_notifier.RemovePoller(id);
+}
+
+NT_ConnectionListener AddPolledConnectionListener(
+ NT_ConnectionListenerPoller poller, bool immediate_notify) {
+ Handle handle{poller};
+ int id = handle.GetTypedIndex(Handle::kConnectionListenerPoller);
+ int i = handle.GetInst();
+ auto ii = InstanceImpl::Get(i);
+ if (id < 0 || !ii) return 0;
+
+ unsigned int uid = ii->dispatcher.AddPolledListener(id, immediate_notify);
+ return Handle(i, uid, Handle::kConnectionListener);
+}
+
+std::vector<ConnectionNotification> PollConnectionListener(
+ NT_ConnectionListenerPoller poller) {
+ Handle handle{poller};
+ int id = handle.GetTypedIndex(Handle::kConnectionListenerPoller);
+ auto ii = InstanceImpl::Get(handle.GetInst());
+ if (id < 0 || !ii) return std::vector<ConnectionNotification>{};
+
+ return ii->connection_notifier.Poll(static_cast<unsigned int>(id));
+}
+
+std::vector<ConnectionNotification> PollConnectionListener(
+ NT_ConnectionListenerPoller poller, double timeout, bool* timed_out) {
+ *timed_out = false;
+ Handle handle{poller};
+ int id = handle.GetTypedIndex(Handle::kConnectionListenerPoller);
+ auto ii = InstanceImpl::Get(handle.GetInst());
+ if (id < 0 || !ii) return std::vector<ConnectionNotification>{};
+
+ return ii->connection_notifier.Poll(static_cast<unsigned int>(id), timeout,
+ timed_out);
+}
+
+void CancelPollConnectionListener(NT_ConnectionListenerPoller poller) {
+ Handle handle{poller};
+ int id = handle.GetTypedIndex(Handle::kConnectionListenerPoller);
+ auto ii = InstanceImpl::Get(handle.GetInst());
+ if (id < 0 || !ii) return;
+
+ ii->connection_notifier.CancelPoll(id);
+}
+
+void RemoveConnectionListener(NT_ConnectionListener conn_listener) {
+ Handle handle{conn_listener};
+ int uid = handle.GetTypedIndex(Handle::kConnectionListener);
+ auto ii = InstanceImpl::Get(handle.GetInst());
+ if (uid < 0 || !ii) return;
+
+ ii->connection_notifier.Remove(uid);
+}
+
+bool WaitForConnectionListenerQueue(NT_Inst inst, double timeout) {
+ int i = Handle{inst}.GetTypedInst(Handle::kInstance);
+ auto ii = InstanceImpl::Get(i);
+ if (!ii) return true;
+ return ii->connection_notifier.WaitForQueue(timeout);
+}
+
+/*
+ * Remote Procedure Call Functions
+ */
+
+void CreateRpc(NT_Entry entry, StringRef def,
+ std::function<void(const RpcAnswer& answer)> callback) {
+ Handle handle{entry};
+ int id = handle.GetTypedIndex(Handle::kEntry);
+ auto ii = InstanceImpl::Get(handle.GetInst());
+ if (id < 0 || !ii) return;
+
+ // only server can create RPCs
+ if ((ii->dispatcher.GetNetworkMode() & NT_NET_MODE_SERVER) == 0) return;
+ if (def.empty() || !callback) return;
+
+ ii->storage.CreateRpc(id, def, ii->rpc_server.Add(callback));
+}
+
+NT_RpcCallPoller CreateRpcCallPoller(NT_Inst inst) {
+ int i = Handle{inst}.GetTypedInst(Handle::kInstance);
+ auto ii = InstanceImpl::Get(i);
+ if (!ii) return 0;
+
+ return Handle(i, ii->rpc_server.CreatePoller(), Handle::kRpcCallPoller);
+}
+
+void DestroyRpcCallPoller(NT_RpcCallPoller poller) {
+ Handle handle{poller};
+ int id = handle.GetTypedIndex(Handle::kRpcCallPoller);
+ auto ii = InstanceImpl::Get(handle.GetInst());
+ if (id < 0 || !ii) return;
+
+ ii->rpc_server.RemovePoller(id);
+}
+
+void CreatePolledRpc(NT_Entry entry, StringRef def, NT_RpcCallPoller poller) {
+ Handle handle{entry};
+ int id = handle.GetTypedIndex(Handle::kEntry);
+ auto ii = InstanceImpl::Get(handle.GetInst());
+ if (id < 0 || !ii) return;
+
+ Handle phandle{poller};
+ int p_id = phandle.GetTypedIndex(Handle::kRpcCallPoller);
+ if (p_id < 0) return;
+ if (handle.GetInst() != phandle.GetInst()) return;
+
+ // only server can create RPCs
+ if ((ii->dispatcher.GetNetworkMode() & NT_NET_MODE_SERVER) == 0) return;
+ if (def.empty()) return;
+
+ ii->storage.CreateRpc(id, def, ii->rpc_server.AddPolled(p_id));
+}
+
+std::vector<RpcAnswer> PollRpc(NT_RpcCallPoller poller) {
+ Handle handle{poller};
+ int id = handle.GetTypedIndex(Handle::kRpcCallPoller);
+ auto ii = InstanceImpl::Get(handle.GetInst());
+ if (id < 0 || !ii) return std::vector<RpcAnswer>{};
+
+ return ii->rpc_server.Poll(static_cast<unsigned int>(id));
+}
+
+std::vector<RpcAnswer> PollRpc(NT_RpcCallPoller poller, double timeout,
+ bool* timed_out) {
+ *timed_out = false;
+ Handle handle{poller};
+ int id = handle.GetTypedIndex(Handle::kRpcCallPoller);
+ auto ii = InstanceImpl::Get(handle.GetInst());
+ if (id < 0 || !ii) return std::vector<RpcAnswer>{};
+
+ return ii->rpc_server.Poll(static_cast<unsigned int>(id), timeout, timed_out);
+}
+
+void CancelPollRpc(NT_RpcCallPoller poller) {
+ Handle handle{poller};
+ int id = handle.GetTypedIndex(Handle::kRpcCallPoller);
+ auto ii = InstanceImpl::Get(handle.GetInst());
+ if (id < 0 || !ii) return;
+
+ ii->rpc_server.CancelPoll(id);
+}
+
+bool WaitForRpcCallQueue(NT_Inst inst, double timeout) {
+ int i = Handle{inst}.GetTypedInst(Handle::kInstance);
+ auto ii = InstanceImpl::Get(i);
+ if (!ii) return true;
+ return ii->rpc_server.WaitForQueue(timeout);
+}
+
+bool PostRpcResponse(NT_Entry entry, NT_RpcCall call, StringRef result) {
+ Handle handle{entry};
+ int id = handle.GetTypedIndex(Handle::kEntry);
+ auto ii = InstanceImpl::Get(handle.GetInst());
+ if (id < 0 || !ii) return false;
+
+ Handle chandle{call};
+ int call_uid = chandle.GetTypedIndex(Handle::kRpcCall);
+ if (call_uid < 0) return false;
+ if (handle.GetInst() != chandle.GetInst()) return false;
+
+ return ii->rpc_server.PostRpcResponse(id, call_uid, result);
+}
+
+NT_RpcCall CallRpc(NT_Entry entry, StringRef params) {
+ Handle handle{entry};
+ int id = handle.GetTypedIndex(Handle::kEntry);
+ int i = handle.GetInst();
+ auto ii = InstanceImpl::Get(i);
+ if (id < 0 || !ii) return 0;
+
+ unsigned int call_uid = ii->storage.CallRpc(id, params);
+ if (call_uid == 0) return 0;
+ return Handle(i, call_uid, Handle::kRpcCall);
+}
+
+bool GetRpcResult(NT_Entry entry, NT_RpcCall call, std::string* result) {
+ Handle handle{entry};
+ int id = handle.GetTypedIndex(Handle::kEntry);
+ auto ii = InstanceImpl::Get(handle.GetInst());
+ if (id < 0 || !ii) return false;
+
+ Handle chandle{call};
+ int call_uid = chandle.GetTypedIndex(Handle::kRpcCall);
+ if (call_uid < 0) return false;
+ if (handle.GetInst() != chandle.GetInst()) return false;
+
+ return ii->storage.GetRpcResult(id, call_uid, result);
+}
+
+bool GetRpcResult(NT_Entry entry, NT_RpcCall call, std::string* result,
+ double timeout, bool* timed_out) {
+ *timed_out = false;
+ Handle handle{entry};
+ int id = handle.GetTypedIndex(Handle::kEntry);
+ auto ii = InstanceImpl::Get(handle.GetInst());
+ if (id < 0 || !ii) return false;
+
+ Handle chandle{call};
+ int call_uid = chandle.GetTypedIndex(Handle::kRpcCall);
+ if (call_uid < 0) return false;
+ if (handle.GetInst() != chandle.GetInst()) return false;
+
+ return ii->storage.GetRpcResult(id, call_uid, result, timeout, timed_out);
+}
+
+void CancelRpcResult(NT_Entry entry, NT_RpcCall call) {
+ Handle handle{entry};
+ int id = handle.GetTypedIndex(Handle::kEntry);
+ auto ii = InstanceImpl::Get(handle.GetInst());
+ if (id < 0 || !ii) return;
+
+ Handle chandle{call};
+ int call_uid = chandle.GetTypedIndex(Handle::kRpcCall);
+ if (call_uid < 0) return;
+ if (handle.GetInst() != chandle.GetInst()) return;
+
+ ii->storage.CancelRpcResult(id, call_uid);
+}
+
+std::string PackRpcDefinition(const RpcDefinition& def) {
+ WireEncoder enc(0x0300);
+ enc.Write8(def.version);
+ enc.WriteString(def.name);
+
+ // parameters
+ unsigned int params_size = def.params.size();
+ if (params_size > 0xff) params_size = 0xff;
+ enc.Write8(params_size);
+ for (size_t i = 0; i < params_size; ++i) {
+ enc.WriteType(def.params[i].def_value->type());
+ enc.WriteString(def.params[i].name);
+ enc.WriteValue(*def.params[i].def_value);
+ }
+
+ // results
+ unsigned int results_size = def.results.size();
+ if (results_size > 0xff) results_size = 0xff;
+ enc.Write8(results_size);
+ for (size_t i = 0; i < results_size; ++i) {
+ enc.WriteType(def.results[i].type);
+ enc.WriteString(def.results[i].name);
+ }
+
+ return enc.ToStringRef();
+}
+
+bool UnpackRpcDefinition(StringRef packed, RpcDefinition* def) {
+ wpi::raw_mem_istream is(packed.data(), packed.size());
+ wpi::Logger logger;
+ WireDecoder dec(is, 0x0300, logger);
+ if (!dec.Read8(&def->version)) return false;
+ if (!dec.ReadString(&def->name)) return false;
+
+ // parameters
+ unsigned int params_size;
+ if (!dec.Read8(¶ms_size)) return false;
+ def->params.resize(0);
+ def->params.reserve(params_size);
+ for (size_t i = 0; i < params_size; ++i) {
+ RpcParamDef pdef;
+ NT_Type type;
+ if (!dec.ReadType(&type)) return false;
+ if (!dec.ReadString(&pdef.name)) return false;
+ pdef.def_value = dec.ReadValue(type);
+ if (!pdef.def_value) return false;
+ def->params.emplace_back(std::move(pdef));
+ }
+
+ // results
+ unsigned int results_size;
+ if (!dec.Read8(&results_size)) return false;
+ def->results.resize(0);
+ def->results.reserve(results_size);
+ for (size_t i = 0; i < results_size; ++i) {
+ RpcResultDef rdef;
+ if (!dec.ReadType(&rdef.type)) return false;
+ if (!dec.ReadString(&rdef.name)) return false;
+ def->results.emplace_back(std::move(rdef));
+ }
+
+ return true;
+}
+
+std::string PackRpcValues(ArrayRef<std::shared_ptr<Value>> values) {
+ WireEncoder enc(0x0300);
+ for (auto& value : values) enc.WriteValue(*value);
+ return enc.ToStringRef();
+}
+
+std::vector<std::shared_ptr<Value>> UnpackRpcValues(StringRef packed,
+ ArrayRef<NT_Type> types) {
+ wpi::raw_mem_istream is(packed.data(), packed.size());
+ wpi::Logger logger;
+ WireDecoder dec(is, 0x0300, logger);
+ std::vector<std::shared_ptr<Value>> vec;
+ for (auto type : types) {
+ auto item = dec.ReadValue(type);
+ if (!item) return std::vector<std::shared_ptr<Value>>();
+ vec.emplace_back(std::move(item));
+ }
+ return vec;
+}
+
+uint64_t Now() { return wpi::Now(); }
+
+/*
+ * Client/Server Functions
+ */
+
+void SetNetworkIdentity(StringRef name) {
+ InstanceImpl::GetDefault()->dispatcher.SetIdentity(name);
+}
+
+void SetNetworkIdentity(NT_Inst inst, const Twine& name) {
+ auto ii = InstanceImpl::Get(Handle{inst}.GetTypedInst(Handle::kInstance));
+ if (!ii) return;
+
+ ii->dispatcher.SetIdentity(name);
+}
+
+unsigned int GetNetworkMode() {
+ return InstanceImpl::GetDefault()->dispatcher.GetNetworkMode();
+}
+
+unsigned int GetNetworkMode(NT_Inst inst) {
+ auto ii = InstanceImpl::Get(Handle{inst}.GetTypedInst(Handle::kInstance));
+ if (!ii) return 0;
+
+ return ii->dispatcher.GetNetworkMode();
+}
+
+void StartServer(StringRef persist_filename, const char* listen_address,
+ unsigned int port) {
+ auto ii = InstanceImpl::GetDefault();
+ ii->dispatcher.StartServer(persist_filename, listen_address, port);
+}
+
+void StartServer(NT_Inst inst, const Twine& persist_filename,
+ const char* listen_address, unsigned int port) {
+ auto ii = InstanceImpl::Get(Handle{inst}.GetTypedInst(Handle::kInstance));
+ if (!ii) return;
+
+ ii->dispatcher.StartServer(persist_filename, listen_address, port);
+}
+
+void StopServer() { InstanceImpl::GetDefault()->dispatcher.Stop(); }
+
+void StopServer(NT_Inst inst) {
+ auto ii = InstanceImpl::Get(Handle{inst}.GetTypedInst(Handle::kInstance));
+ if (!ii) return;
+
+ ii->dispatcher.Stop();
+}
+
+void StartClient() { InstanceImpl::GetDefault()->dispatcher.StartClient(); }
+
+void StartClient(NT_Inst inst) {
+ auto ii = InstanceImpl::Get(Handle{inst}.GetTypedInst(Handle::kInstance));
+ if (!ii) return;
+
+ ii->dispatcher.StartClient();
+}
+
+void StartClient(const char* server_name, unsigned int port) {
+ auto ii = InstanceImpl::GetDefault();
+ ii->dispatcher.SetServer(server_name, port);
+ ii->dispatcher.StartClient();
+}
+
+void StartClient(NT_Inst inst, const char* server_name, unsigned int port) {
+ auto ii = InstanceImpl::Get(Handle{inst}.GetTypedInst(Handle::kInstance));
+ if (!ii) return;
+
+ ii->dispatcher.SetServer(server_name, port);
+ ii->dispatcher.StartClient();
+}
+
+void StartClient(ArrayRef<std::pair<StringRef, unsigned int>> servers) {
+ auto ii = InstanceImpl::GetDefault();
+ ii->dispatcher.SetServer(servers);
+ ii->dispatcher.StartClient();
+}
+
+void StartClient(NT_Inst inst,
+ ArrayRef<std::pair<StringRef, unsigned int>> servers) {
+ auto ii = InstanceImpl::Get(Handle{inst}.GetTypedInst(Handle::kInstance));
+ if (!ii) return;
+
+ ii->dispatcher.SetServer(servers);
+ ii->dispatcher.StartClient();
+}
+
+void StartClientTeam(NT_Inst inst, unsigned int team, unsigned int port) {
+ auto ii = InstanceImpl::Get(Handle{inst}.GetTypedInst(Handle::kInstance));
+ if (!ii) return;
+
+ ii->dispatcher.SetServerTeam(team, port);
+ ii->dispatcher.StartClient();
+}
+
+void StopClient() { InstanceImpl::GetDefault()->dispatcher.Stop(); }
+
+void StopClient(NT_Inst inst) {
+ auto ii = InstanceImpl::Get(Handle{inst}.GetTypedInst(Handle::kInstance));
+ if (!ii) return;
+
+ ii->dispatcher.Stop();
+}
+
+void SetServer(const char* server_name, unsigned int port) {
+ InstanceImpl::GetDefault()->dispatcher.SetServer(server_name, port);
+}
+
+void SetServer(NT_Inst inst, const char* server_name, unsigned int port) {
+ auto ii = InstanceImpl::Get(Handle{inst}.GetTypedInst(Handle::kInstance));
+ if (!ii) return;
+
+ ii->dispatcher.SetServer(server_name, port);
+}
+
+void SetServer(ArrayRef<std::pair<StringRef, unsigned int>> servers) {
+ InstanceImpl::GetDefault()->dispatcher.SetServer(servers);
+}
+
+void SetServer(NT_Inst inst,
+ ArrayRef<std::pair<StringRef, unsigned int>> servers) {
+ auto ii = InstanceImpl::Get(Handle{inst}.GetTypedInst(Handle::kInstance));
+ if (!ii) return;
+
+ ii->dispatcher.SetServer(servers);
+}
+
+void SetServerTeam(NT_Inst inst, unsigned int team, unsigned int port) {
+ auto ii = InstanceImpl::Get(Handle{inst}.GetTypedInst(Handle::kInstance));
+ if (!ii) return;
+
+ ii->dispatcher.SetServerTeam(team, port);
+}
+
+void StartDSClient(unsigned int port) {
+ InstanceImpl::GetDefault()->ds_client.Start(port);
+}
+
+void StartDSClient(NT_Inst inst, unsigned int port) {
+ auto ii = InstanceImpl::Get(Handle{inst}.GetTypedInst(Handle::kInstance));
+ if (!ii) return;
+
+ ii->ds_client.Start(port);
+}
+
+void StopDSClient() { InstanceImpl::GetDefault()->ds_client.Stop(); }
+
+void StopDSClient(NT_Inst inst) {
+ auto ii = InstanceImpl::Get(Handle{inst}.GetTypedInst(Handle::kInstance));
+ if (!ii) return;
+
+ ii->ds_client.Stop();
+}
+
+void SetUpdateRate(double interval) {
+ InstanceImpl::GetDefault()->dispatcher.SetUpdateRate(interval);
+}
+
+void SetUpdateRate(NT_Inst inst, double interval) {
+ auto ii = InstanceImpl::Get(Handle{inst}.GetTypedInst(Handle::kInstance));
+ if (!ii) return;
+
+ ii->dispatcher.SetUpdateRate(interval);
+}
+
+void Flush() { InstanceImpl::GetDefault()->dispatcher.Flush(); }
+
+void Flush(NT_Inst inst) {
+ auto ii = InstanceImpl::Get(Handle{inst}.GetTypedInst(Handle::kInstance));
+ if (!ii) return;
+
+ ii->dispatcher.Flush();
+}
+
+std::vector<ConnectionInfo> GetConnections() {
+ return InstanceImpl::GetDefault()->dispatcher.GetConnections();
+}
+
+std::vector<ConnectionInfo> GetConnections(NT_Inst inst) {
+ auto ii = InstanceImpl::Get(Handle{inst}.GetTypedInst(Handle::kInstance));
+ if (!ii) return std::vector<ConnectionInfo>{};
+
+ return ii->dispatcher.GetConnections();
+}
+
+bool IsConnected(NT_Inst inst) {
+ auto ii = InstanceImpl::Get(Handle{inst}.GetTypedInst(Handle::kInstance));
+ if (!ii) return false;
+
+ return ii->dispatcher.IsConnected();
+}
+
+/*
+ * Persistent Functions
+ */
+
+const char* SavePersistent(StringRef filename) {
+ return InstanceImpl::GetDefault()->storage.SavePersistent(filename, false);
+}
+
+const char* SavePersistent(NT_Inst inst, const Twine& filename) {
+ auto ii = InstanceImpl::Get(Handle{inst}.GetTypedInst(Handle::kInstance));
+ if (!ii) return "invalid instance handle";
+
+ return ii->storage.SavePersistent(filename, false);
+}
+
+const char* LoadPersistent(
+ StringRef filename,
+ std::function<void(size_t line, const char* msg)> warn) {
+ return InstanceImpl::GetDefault()->storage.LoadPersistent(filename, warn);
+}
+
+const char* LoadPersistent(
+ NT_Inst inst, const Twine& filename,
+ std::function<void(size_t line, const char* msg)> warn) {
+ auto ii = InstanceImpl::Get(Handle{inst}.GetTypedInst(Handle::kInstance));
+ if (!ii) return "invalid instance handle";
+
+ return ii->storage.LoadPersistent(filename, warn);
+}
+
+const char* SaveEntries(NT_Inst inst, const Twine& filename,
+ const Twine& prefix) {
+ auto ii = InstanceImpl::Get(Handle{inst}.GetTypedInst(Handle::kInstance));
+ if (!ii) return "invalid instance handle";
+
+ return ii->storage.SaveEntries(filename, prefix);
+}
+
+const char* LoadEntries(
+ NT_Inst inst, const Twine& filename, const Twine& prefix,
+ std::function<void(size_t line, const char* msg)> warn) {
+ auto ii = InstanceImpl::Get(Handle{inst}.GetTypedInst(Handle::kInstance));
+ if (!ii) return "invalid instance handle";
+
+ return ii->storage.LoadEntries(filename, prefix, warn);
+}
+
+void SetLogger(LogFunc func, unsigned int min_level) {
+ auto ii = InstanceImpl::GetDefault();
+ static wpi::mutex mutex;
+ static unsigned int logger = 0;
+ std::lock_guard<wpi::mutex> lock(mutex);
+ if (logger != 0) ii->logger_impl.Remove(logger);
+ logger = ii->logger_impl.Add(
+ [=](const LogMessage& msg) {
+ func(msg.level, msg.filename, msg.line, msg.message.c_str());
+ },
+ min_level, UINT_MAX);
+}
+
+NT_Logger AddLogger(NT_Inst inst,
+ std::function<void(const LogMessage& msg)> func,
+ unsigned int min_level, unsigned int max_level) {
+ int i = Handle{inst}.GetTypedInst(Handle::kInstance);
+ auto ii = InstanceImpl::Get(i);
+ if (!ii) return 0;
+
+ if (min_level < ii->logger.min_level()) ii->logger.set_min_level(min_level);
+
+ return Handle(i, ii->logger_impl.Add(func, min_level, max_level),
+ Handle::kLogger);
+}
+
+NT_LoggerPoller CreateLoggerPoller(NT_Inst inst) {
+ int i = Handle{inst}.GetTypedInst(Handle::kInstance);
+ auto ii = InstanceImpl::Get(i);
+ if (!ii) return 0;
+
+ return Handle(i, ii->logger_impl.CreatePoller(), Handle::kLoggerPoller);
+}
+
+void DestroyLoggerPoller(NT_LoggerPoller poller) {
+ Handle handle{poller};
+ int id = handle.GetTypedIndex(Handle::kLoggerPoller);
+ auto ii = InstanceImpl::Get(handle.GetInst());
+ if (id < 0 || !ii) return;
+
+ ii->logger_impl.RemovePoller(id);
+}
+
+NT_Logger AddPolledLogger(NT_LoggerPoller poller, unsigned int min_level,
+ unsigned int max_level) {
+ Handle handle{poller};
+ int id = handle.GetTypedIndex(Handle::kLoggerPoller);
+ int i = handle.GetInst();
+ auto ii = InstanceImpl::Get(i);
+ if (id < 0 || !ii) return 0;
+
+ if (min_level < ii->logger.min_level()) ii->logger.set_min_level(min_level);
+
+ return Handle(i, ii->logger_impl.AddPolled(id, min_level, max_level),
+ Handle::kLogger);
+}
+
+std::vector<LogMessage> PollLogger(NT_LoggerPoller poller) {
+ Handle handle{poller};
+ int id = handle.GetTypedIndex(Handle::kLoggerPoller);
+ auto ii = InstanceImpl::Get(handle.GetInst());
+ if (id < 0 || !ii) return std::vector<LogMessage>{};
+
+ return ii->logger_impl.Poll(static_cast<unsigned int>(id));
+}
+
+std::vector<LogMessage> PollLogger(NT_LoggerPoller poller, double timeout,
+ bool* timed_out) {
+ *timed_out = false;
+ Handle handle{poller};
+ int id = handle.GetTypedIndex(Handle::kLoggerPoller);
+ auto ii = InstanceImpl::Get(handle.GetInst());
+ if (id < 0 || !ii) return std::vector<LogMessage>{};
+
+ return ii->logger_impl.Poll(static_cast<unsigned int>(id), timeout,
+ timed_out);
+}
+
+void CancelPollLogger(NT_LoggerPoller poller) {
+ Handle handle{poller};
+ int id = handle.GetTypedIndex(Handle::kLoggerPoller);
+ auto ii = InstanceImpl::Get(handle.GetInst());
+ if (id < 0 || !ii) return;
+
+ ii->logger_impl.CancelPoll(id);
+}
+
+void RemoveLogger(NT_Logger logger) {
+ Handle handle{logger};
+ int uid = handle.GetTypedIndex(Handle::kLogger);
+ auto ii = InstanceImpl::Get(handle.GetInst());
+ if (uid < 0 || !ii) return;
+
+ ii->logger_impl.Remove(uid);
+ ii->logger.set_min_level(ii->logger_impl.GetMinLevel());
+}
+
+bool WaitForLoggerQueue(NT_Inst inst, double timeout) {
+ int i = Handle{inst}.GetTypedInst(Handle::kInstance);
+ auto ii = InstanceImpl::Get(i);
+ if (!ii) return true;
+ return ii->logger_impl.WaitForQueue(timeout);
+}
+
+} // namespace nt
diff --git a/ntcore/src/main/native/cpp/ntcore_test.cpp b/ntcore/src/main/native/cpp/ntcore_test.cpp
new file mode 100644
index 0000000..f74172e
--- /dev/null
+++ b/ntcore/src/main/native/cpp/ntcore_test.cpp
@@ -0,0 +1,246 @@
+/*----------------------------------------------------------------------------*/
+/* Copyright (c) 2016-2018 FIRST. All Rights Reserved. */
+/* Open Source Software - may be modified and shared by FRC teams. The code */
+/* must be accompanied by the FIRST BSD license file in the root directory of */
+/* the project. */
+/*----------------------------------------------------------------------------*/
+
+#include "ntcore_test.h"
+
+#include <wpi/memory.h>
+
+#include "Value_internal.h"
+
+extern "C" {
+struct NT_String* NT_GetStringForTesting(const char* string, int* struct_size) {
+ struct NT_String* str =
+ static_cast<NT_String*>(wpi::CheckedCalloc(1, sizeof(NT_String)));
+ nt::ConvertToC(wpi::StringRef(string), str);
+ *struct_size = sizeof(NT_String);
+ return str;
+}
+
+struct NT_EntryInfo* NT_GetEntryInfoForTesting(const char* name,
+ enum NT_Type type,
+ unsigned int flags,
+ uint64_t last_change,
+ int* struct_size) {
+ struct NT_EntryInfo* entry_info =
+ static_cast<NT_EntryInfo*>(wpi::CheckedCalloc(1, sizeof(NT_EntryInfo)));
+ nt::ConvertToC(wpi::StringRef(name), &entry_info->name);
+ entry_info->type = type;
+ entry_info->flags = flags;
+ entry_info->last_change = last_change;
+ *struct_size = sizeof(NT_EntryInfo);
+ return entry_info;
+}
+
+void NT_FreeEntryInfoForTesting(struct NT_EntryInfo* info) {
+ std::free(info->name.str);
+ std::free(info);
+}
+
+struct NT_ConnectionInfo* NT_GetConnectionInfoForTesting(
+ const char* remote_id, const char* remote_ip, unsigned int remote_port,
+ uint64_t last_update, unsigned int protocol_version, int* struct_size) {
+ struct NT_ConnectionInfo* conn_info = static_cast<NT_ConnectionInfo*>(
+ wpi::CheckedCalloc(1, sizeof(NT_ConnectionInfo)));
+ nt::ConvertToC(wpi::StringRef(remote_id), &conn_info->remote_id);
+ nt::ConvertToC(wpi::StringRef(remote_ip), &conn_info->remote_ip);
+ conn_info->remote_port = remote_port;
+ conn_info->last_update = last_update;
+ conn_info->protocol_version = protocol_version;
+ *struct_size = sizeof(NT_ConnectionInfo);
+ return conn_info;
+}
+
+void NT_FreeConnectionInfoForTesting(struct NT_ConnectionInfo* info) {
+ std::free(info->remote_id.str);
+ std::free(info->remote_ip.str);
+ std::free(info);
+}
+
+struct NT_Value* NT_GetValueBooleanForTesting(uint64_t last_change, int val,
+ int* struct_size) {
+ struct NT_Value* value =
+ static_cast<NT_Value*>(wpi::CheckedCalloc(1, sizeof(NT_Value)));
+ value->type = NT_BOOLEAN;
+ value->last_change = last_change;
+ value->data.v_boolean = val;
+ *struct_size = sizeof(NT_Value);
+ return value;
+}
+
+struct NT_Value* NT_GetValueDoubleForTesting(uint64_t last_change, double val,
+ int* struct_size) {
+ struct NT_Value* value =
+ static_cast<NT_Value*>(wpi::CheckedCalloc(1, sizeof(NT_Value)));
+ value->type = NT_DOUBLE;
+ value->last_change = last_change;
+ value->data.v_double = val;
+ *struct_size = sizeof(NT_Value);
+ return value;
+}
+
+struct NT_Value* NT_GetValueStringForTesting(uint64_t last_change,
+ const char* str,
+ int* struct_size) {
+ struct NT_Value* value =
+ static_cast<NT_Value*>(wpi::CheckedCalloc(1, sizeof(NT_Value)));
+ value->type = NT_STRING;
+ value->last_change = last_change;
+ nt::ConvertToC(wpi::StringRef(str), &value->data.v_string);
+ *struct_size = sizeof(NT_Value);
+ return value;
+}
+
+struct NT_Value* NT_GetValueRawForTesting(uint64_t last_change, const char* raw,
+ int raw_len, int* struct_size) {
+ struct NT_Value* value =
+ static_cast<NT_Value*>(wpi::CheckedCalloc(1, sizeof(NT_Value)));
+ value->type = NT_RAW;
+ value->last_change = last_change;
+ nt::ConvertToC(wpi::StringRef(raw, raw_len), &value->data.v_string);
+ *struct_size = sizeof(NT_Value);
+ return value;
+}
+
+struct NT_Value* NT_GetValueBooleanArrayForTesting(uint64_t last_change,
+ const int* arr,
+ size_t array_len,
+ int* struct_size) {
+ struct NT_Value* value =
+ static_cast<NT_Value*>(wpi::CheckedCalloc(1, sizeof(NT_Value)));
+ value->type = NT_BOOLEAN_ARRAY;
+ value->last_change = last_change;
+ value->data.arr_boolean.arr = NT_AllocateBooleanArray(array_len);
+ value->data.arr_boolean.size = array_len;
+ std::memcpy(value->data.arr_boolean.arr, arr,
+ value->data.arr_boolean.size * sizeof(int));
+ *struct_size = sizeof(NT_Value);
+ return value;
+}
+
+struct NT_Value* NT_GetValueDoubleArrayForTesting(uint64_t last_change,
+ const double* arr,
+ size_t array_len,
+ int* struct_size) {
+ struct NT_Value* value =
+ static_cast<NT_Value*>(wpi::CheckedCalloc(1, sizeof(NT_Value)));
+ value->type = NT_BOOLEAN;
+ value->last_change = last_change;
+ value->data.arr_double.arr = NT_AllocateDoubleArray(array_len);
+ value->data.arr_double.size = array_len;
+ std::memcpy(value->data.arr_double.arr, arr,
+ value->data.arr_double.size * sizeof(int));
+ *struct_size = sizeof(NT_Value);
+ return value;
+}
+
+struct NT_Value* NT_GetValueStringArrayForTesting(uint64_t last_change,
+ const struct NT_String* arr,
+ size_t array_len,
+ int* struct_size) {
+ struct NT_Value* value =
+ static_cast<NT_Value*>(wpi::CheckedCalloc(1, sizeof(NT_Value)));
+ value->type = NT_BOOLEAN;
+ value->last_change = last_change;
+ value->data.arr_string.arr = NT_AllocateStringArray(array_len);
+ value->data.arr_string.size = array_len;
+ for (size_t i = 0; i < value->data.arr_string.size; ++i) {
+ size_t len = arr[i].len;
+ value->data.arr_string.arr[i].len = len;
+ value->data.arr_string.arr[i].str =
+ static_cast<char*>(wpi::CheckedMalloc(len + 1));
+ std::memcpy(value->data.arr_string.arr[i].str, arr[i].str, len + 1);
+ }
+ *struct_size = sizeof(NT_Value);
+ return value;
+}
+// No need for free as one already exists in the main library
+
+static void CopyNtValue(const struct NT_Value* copy_from,
+ struct NT_Value* copy_to) {
+ auto cpp_value = nt::ConvertFromC(*copy_from);
+ nt::ConvertToC(*cpp_value, copy_to);
+}
+
+static void CopyNtString(const struct NT_String* copy_from,
+ struct NT_String* copy_to) {
+ nt::ConvertToC(wpi::StringRef(copy_from->str, copy_from->len), copy_to);
+}
+
+struct NT_RpcParamDef* NT_GetRpcParamDefForTesting(const char* name,
+ const struct NT_Value* val,
+ int* struct_size) {
+ struct NT_RpcParamDef* def = static_cast<NT_RpcParamDef*>(
+ wpi::CheckedCalloc(1, sizeof(NT_RpcParamDef)));
+ nt::ConvertToC(wpi::StringRef(name), &def->name);
+ CopyNtValue(val, &def->def_value);
+ *struct_size = sizeof(NT_RpcParamDef);
+ return def;
+}
+
+void NT_FreeRpcParamDefForTesting(struct NT_RpcParamDef* def) {
+ NT_DisposeValue(&def->def_value);
+ NT_DisposeString(&def->name);
+ std::free(def);
+}
+
+struct NT_RpcResultDef* NT_GetRpcResultsDefForTesting(const char* name,
+ enum NT_Type type,
+ int* struct_size) {
+ struct NT_RpcResultDef* def = static_cast<NT_RpcResultDef*>(
+ wpi::CheckedCalloc(1, sizeof(NT_RpcResultDef)));
+ nt::ConvertToC(wpi::StringRef(name), &def->name);
+ def->type = type;
+ *struct_size = sizeof(NT_RpcResultDef);
+ return def;
+}
+
+void NT_FreeRpcResultsDefForTesting(struct NT_RpcResultDef* def) {
+ NT_DisposeString(&def->name);
+ std::free(def);
+}
+
+struct NT_RpcDefinition* NT_GetRpcDefinitionForTesting(
+ unsigned int version, const char* name, size_t num_params,
+ const struct NT_RpcParamDef* params, size_t num_results,
+ const struct NT_RpcResultDef* results, int* struct_size) {
+ struct NT_RpcDefinition* def = static_cast<NT_RpcDefinition*>(
+ wpi::CheckedCalloc(1, sizeof(NT_RpcDefinition)));
+ def->version = version;
+ nt::ConvertToC(wpi::StringRef(name), &def->name);
+ def->num_params = num_params;
+ def->params = static_cast<NT_RpcParamDef*>(
+ wpi::CheckedMalloc(num_params * sizeof(NT_RpcParamDef)));
+ for (size_t i = 0; i < num_params; ++i) {
+ CopyNtString(¶ms[i].name, &def->params[i].name);
+ CopyNtValue(¶ms[i].def_value, &def->params[i].def_value);
+ }
+ def->num_results = num_results;
+ def->results = static_cast<NT_RpcResultDef*>(
+ wpi::CheckedMalloc(num_results * sizeof(NT_RpcResultDef)));
+ for (size_t i = 0; i < num_results; ++i) {
+ CopyNtString(&results[i].name, &def->results[i].name);
+ def->results[i].type = results[i].type;
+ }
+ *struct_size = sizeof(NT_RpcDefinition);
+ return def;
+}
+// No need for free as one already exists in the main library
+
+struct NT_RpcAnswer* NT_GetRpcAnswerForTesting(
+ unsigned int rpc_id, unsigned int call_uid, const char* name,
+ const char* params, size_t params_len, int* struct_size) {
+ struct NT_RpcAnswer* info =
+ static_cast<NT_RpcAnswer*>(wpi::CheckedCalloc(1, sizeof(NT_RpcAnswer)));
+ info->entry = rpc_id;
+ info->call = call_uid;
+ nt::ConvertToC(wpi::StringRef(name), &info->name);
+ nt::ConvertToC(wpi::StringRef(params, params_len), &info->params);
+ *struct_size = sizeof(NT_RpcAnswer);
+ return info;
+}
+// No need for free as one already exists in the main library
+} // extern "C"
diff --git a/ntcore/src/main/native/cpp/tables/ITableListener.cpp b/ntcore/src/main/native/cpp/tables/ITableListener.cpp
new file mode 100644
index 0000000..6abd3bb
--- /dev/null
+++ b/ntcore/src/main/native/cpp/tables/ITableListener.cpp
@@ -0,0 +1,16 @@
+/*----------------------------------------------------------------------------*/
+/* Copyright (c) 2017-2018 FIRST. All Rights Reserved. */
+/* Open Source Software - may be modified and shared by FRC teams. The code */
+/* must be accompanied by the FIRST BSD license file in the root directory of */
+/* the project. */
+/*----------------------------------------------------------------------------*/
+
+#include "tables/ITableListener.h"
+
+#include "ntcore_c.h"
+
+void ITableListener::ValueChangedEx(ITable* source, wpi::StringRef key,
+ std::shared_ptr<nt::Value> value,
+ unsigned int flags) {
+ ValueChanged(source, key, value, (flags & NT_NOTIFY_NEW) != 0);
+}
diff --git a/ntcore/src/main/native/include/networktables/EntryListenerFlags.h b/ntcore/src/main/native/include/networktables/EntryListenerFlags.h
new file mode 100644
index 0000000..c5cc620
--- /dev/null
+++ b/ntcore/src/main/native/include/networktables/EntryListenerFlags.h
@@ -0,0 +1,82 @@
+/*----------------------------------------------------------------------------*/
+/* Copyright (c) 2017-2018 FIRST. All Rights Reserved. */
+/* Open Source Software - may be modified and shared by FRC teams. The code */
+/* must be accompanied by the FIRST BSD license file in the root directory of */
+/* the project. */
+/*----------------------------------------------------------------------------*/
+
+#ifndef NTCORE_NETWORKTABLES_ENTRYLISTENERFLAGS_H_
+#define NTCORE_NETWORKTABLES_ENTRYLISTENERFLAGS_H_
+
+#include "ntcore_c.h"
+
+namespace nt {
+
+/** Entry listener flags */
+namespace EntryListenerFlags {
+
+/**
+ * Flag values for use with entry listeners.
+ *
+ * The flags are a bitmask and must be OR'ed together to indicate the
+ * combination of events desired to be received.
+ *
+ * The constants kNew, kDelete, kUpdate, and kFlags represent different events
+ * that can occur to entries.
+ *
+ * By default, notifications are only generated for remote changes occurring
+ * after the listener is created. The constants kImmediate and kLocal are
+ * modifiers that cause notifications to be generated at other times.
+ *
+ * @ingroup ntcore_cpp_api
+ */
+enum {
+ /**
+ * Initial listener addition.
+ * Set this flag to receive immediate notification of entries matching the
+ * flag criteria (generally only useful when combined with kNew).
+ */
+ kImmediate = NT_NOTIFY_IMMEDIATE,
+
+ /**
+ * Changed locally.
+ * Set this flag to receive notification of both local changes and changes
+ * coming from remote nodes. By default, notifications are only generated
+ * for remote changes. Must be combined with some combination of kNew,
+ * kDelete, kUpdate, and kFlags to receive notifications of those respective
+ * events.
+ */
+ kLocal = NT_NOTIFY_LOCAL,
+
+ /**
+ * Newly created entry.
+ * Set this flag to receive a notification when an entry is created.
+ */
+ kNew = NT_NOTIFY_NEW,
+
+ /**
+ * Entry was deleted.
+ * Set this flag to receive a notification when an entry is deleted.
+ */
+ kDelete = NT_NOTIFY_DELETE,
+
+ /**
+ * Entry's value changed.
+ * Set this flag to receive a notification when an entry's value (or type)
+ * changes.
+ */
+ kUpdate = NT_NOTIFY_UPDATE,
+
+ /**
+ * Entry's flags changed.
+ * Set this flag to receive a notification when an entry's flags value
+ * changes.
+ */
+ kFlags = NT_NOTIFY_FLAGS
+};
+
+} // namespace EntryListenerFlags
+
+} // namespace nt
+
+#endif // NTCORE_NETWORKTABLES_ENTRYLISTENERFLAGS_H_
diff --git a/ntcore/src/main/native/include/networktables/NetworkTable.h b/ntcore/src/main/native/include/networktables/NetworkTable.h
new file mode 100644
index 0000000..6bc2af6
--- /dev/null
+++ b/ntcore/src/main/native/include/networktables/NetworkTable.h
@@ -0,0 +1,777 @@
+/*----------------------------------------------------------------------------*/
+/* Copyright (c) 2015-2018 FIRST. All Rights Reserved. */
+/* Open Source Software - may be modified and shared by FRC teams. The code */
+/* must be accompanied by the FIRST BSD license file in the root directory of */
+/* the project. */
+/*----------------------------------------------------------------------------*/
+
+#ifndef NTCORE_NETWORKTABLES_NETWORKTABLE_H_
+#define NTCORE_NETWORKTABLES_NETWORKTABLE_H_
+
+#include <functional>
+#include <memory>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include <wpi/ArrayRef.h>
+#include <wpi/StringMap.h>
+#include <wpi/Twine.h>
+#include <wpi/mutex.h>
+
+#include "networktables/NetworkTableEntry.h"
+#include "networktables/TableEntryListener.h"
+#include "networktables/TableListener.h"
+#include "ntcore_c.h"
+#include "tables/ITable.h"
+
+namespace nt {
+
+using wpi::ArrayRef;
+using wpi::StringRef;
+using wpi::Twine;
+
+class NetworkTableInstance;
+
+#ifdef __GNUC__
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
+#endif
+
+/**
+ * @defgroup ntcore_cpp_api ntcore C++ object-oriented API
+ *
+ * Recommended interface for C++, identical to Java API.
+ */
+
+/**
+ * A network table that knows its subtable path.
+ * @ingroup ntcore_cpp_api
+ */
+class NetworkTable final : public ITable {
+ private:
+ NT_Inst m_inst;
+ std::string m_path;
+ mutable wpi::mutex m_mutex;
+ mutable wpi::StringMap<NT_Entry> m_entries;
+ typedef std::pair<ITableListener*, NT_EntryListener> Listener;
+ std::vector<Listener> m_listeners;
+ std::vector<NT_EntryListener> m_lambdaListeners;
+
+ static std::vector<std::string> s_ip_addresses;
+ static std::string s_persistent_filename;
+ static bool s_client;
+ static bool s_enable_ds;
+ static bool s_running;
+ static unsigned int s_port;
+
+ struct private_init {};
+ friend class NetworkTableInstance;
+
+ public:
+ /**
+ * Gets the "base name" of a key. For example, "/foo/bar" becomes "bar".
+ * If the key has a trailing slash, returns an empty string.
+ *
+ * @param key key
+ * @return base name
+ */
+ static StringRef BasenameKey(StringRef key);
+
+ /**
+ * Normalizes an network table key to contain no consecutive slashes and
+ * optionally start with a leading slash. For example:
+ *
+ * <pre><code>
+ * normalizeKey("/foo/bar", true) == "/foo/bar"
+ * normalizeKey("foo/bar", true) == "/foo/bar"
+ * normalizeKey("/foo/bar", false) == "foo/bar"
+ * normalizeKey("foo//bar", false) == "foo/bar"
+ * </code></pre>
+ *
+ * @param key the key to normalize
+ * @param withLeadingSlash whether or not the normalized key should begin
+ * with a leading slash
+ * @return normalized key
+ */
+ static std::string NormalizeKey(const Twine& key,
+ bool withLeadingSlash = true);
+
+ static StringRef NormalizeKey(const Twine& key,
+ wpi::SmallVectorImpl<char>& buf,
+ bool withLeadingSlash = true);
+
+ /**
+ * Gets a list of the names of all the super tables of a given key. For
+ * example, the key "/foo/bar/baz" has a hierarchy of "/", "/foo",
+ * "/foo/bar", and "/foo/bar/baz".
+ *
+ * @param key the key
+ * @return List of super tables
+ */
+ static std::vector<std::string> GetHierarchy(const Twine& key);
+
+ /**
+ * Constructor. Use NetworkTableInstance::GetTable() or GetSubTable()
+ * instead.
+ */
+ NetworkTable(NT_Inst inst, const Twine& path, const private_init&);
+ virtual ~NetworkTable();
+
+ /**
+ * Gets the instance for the table.
+ *
+ * @return Instance
+ */
+ NetworkTableInstance GetInstance() const;
+
+ /**
+ * The path separator for sub-tables and keys
+ */
+ static const char PATH_SEPARATOR_CHAR;
+
+ /**
+ * Initializes network tables
+ */
+ WPI_DEPRECATED(
+ "use NetworkTableInstance::StartServer() or "
+ "NetworkTableInstance::StartClient() instead")
+ static void Initialize();
+
+ /**
+ * Shuts down network tables
+ */
+ WPI_DEPRECATED(
+ "use NetworkTableInstance::StopServer() or "
+ "NetworkTableInstance::StopClient() instead")
+ static void Shutdown();
+
+ /**
+ * set that network tables should be a client
+ * This must be called before initialize or GetTable
+ */
+ WPI_DEPRECATED("use NetworkTableInstance::StartClient() instead")
+ static void SetClientMode();
+
+ /**
+ * set that network tables should be a server
+ * This must be called before initialize or GetTable
+ */
+ WPI_DEPRECATED("use NetworkTableInstance::StartServer() instead")
+ static void SetServerMode();
+
+ /**
+ * set the team the robot is configured for (this will set the mdns address
+ * that network tables will connect to in client mode)
+ * This must be called before initialize or GetTable
+ *
+ * @param team the team number
+ */
+ WPI_DEPRECATED(
+ "use NetworkTableInstance::SetServerTeam() or "
+ "NetworkTableInstance::StartClientTeam() instead")
+ static void SetTeam(int team);
+
+ /**
+ * @param address the adress that network tables will connect to in client
+ * mode
+ */
+ WPI_DEPRECATED(
+ "use NetworkTableInstance::SetServer() or "
+ "NetworkTableInstance::StartClient() instead")
+ static void SetIPAddress(StringRef address);
+
+ /**
+ * @param addresses the addresses that network tables will connect to in
+ * client mode (in round robin order)
+ */
+ WPI_DEPRECATED(
+ "use NetworkTableInstance::SetServer() or "
+ "NetworkTableInstance::StartClient() instead")
+ static void SetIPAddress(ArrayRef<std::string> addresses);
+
+ /**
+ * Set the port number that network tables will connect to in client
+ * mode or listen to in server mode.
+ *
+ * @param port the port number
+ */
+ WPI_DEPRECATED(
+ "use the appropriate parameters to NetworkTableInstance::SetServer(), "
+ "NetworkTableInstance::StartClient(), "
+ "NetworkTableInstance::StartServer(), and "
+ "NetworkTableInstance::StartDSClient() instead")
+ static void SetPort(unsigned int port);
+
+ /**
+ * Enable requesting the server address from the Driver Station.
+ *
+ * @param enabled whether to enable the connection to the local DS
+ */
+ WPI_DEPRECATED(
+ "use NetworkTableInstance::StartDSClient() and "
+ "NetworkTableInstance::StopDSClient() instead")
+ static void SetDSClientEnabled(bool enabled);
+
+ /**
+ * Sets the persistent filename.
+ *
+ * @param filename the filename that the network tables server uses for
+ * automatic loading and saving of persistent values
+ */
+ WPI_DEPRECATED(
+ "use the appropriate parameter to NetworkTableInstance::StartServer() "
+ "instead")
+ static void SetPersistentFilename(StringRef filename);
+
+ /**
+ * Sets the network identity.
+ * This is provided in the connection info on the remote end.
+ *
+ * @param name identity
+ */
+ WPI_DEPRECATED("use NetworkTableInstance::SetNetworkIdentity() instead")
+ static void SetNetworkIdentity(StringRef name);
+
+ /**
+ * Deletes ALL keys in ALL subtables. Use with caution!
+ */
+ WPI_DEPRECATED("use NetworkTableInstance::DeleteAllEntries() instead")
+ static void GlobalDeleteAll();
+
+ /**
+ * Flushes all updated values immediately to the network.
+ * Note: This is rate-limited to protect the network from flooding.
+ * This is primarily useful for synchronizing network updates with
+ * user code.
+ */
+ WPI_DEPRECATED("use NetworkTableInstance::Flush() instead")
+ static void Flush();
+
+ /**
+ * Set the periodic update rate.
+ * Sets how frequently updates are sent to other nodes over the network.
+ *
+ * @param interval update interval in seconds (range 0.01 to 1.0)
+ */
+ WPI_DEPRECATED("use NetworkTableInstance::SetUpdateRate() instead")
+ static void SetUpdateRate(double interval);
+
+ /**
+ * Saves persistent keys to a file. The server does this automatically.
+ *
+ * @param filename file name
+ * @return Error (or nullptr).
+ */
+ WPI_DEPRECATED("use NetworkTableInstance::SavePersistent() instead")
+ static const char* SavePersistent(StringRef filename);
+
+ /**
+ * Loads persistent keys from a file. The server does this automatically.
+ *
+ * @param filename file name
+ * @param warn callback function called for warnings
+ * @return Error (or nullptr).
+ */
+ WPI_DEPRECATED("use NetworkTableInstance::LoadPersistent() instead")
+ static const char* LoadPersistent(
+ StringRef filename,
+ std::function<void(size_t line, const char* msg)> warn);
+
+ /**
+ * Gets the table with the specified key. If the table does not exist, a new
+ * table will be created.<br>
+ * This will automatically initialize network tables if it has not been
+ * already.
+ *
+ * @param key the key name
+ * @return the network table requested
+ */
+ WPI_DEPRECATED(
+ "use NetworkTableInstance::GetTable() or "
+ "NetworkTableInstance::GetEntry() instead")
+ static std::shared_ptr<NetworkTable> GetTable(StringRef key);
+
+ /**
+ * Gets the entry for a subkey.
+ *
+ * @param key the key name
+ * @return Network table entry.
+ */
+ NetworkTableEntry GetEntry(const Twine& key) const;
+
+ /**
+ * Listen to keys only within this table.
+ *
+ * @param listener listener to add
+ * @param flags EntryListenerFlags bitmask
+ * @return Listener handle
+ */
+ NT_EntryListener AddEntryListener(TableEntryListener listener,
+ unsigned int flags) const;
+
+ /**
+ * Listen to a single key.
+ *
+ * @param key the key name
+ * @param listener listener to add
+ * @param flags EntryListenerFlags bitmask
+ * @return Listener handle
+ */
+ NT_EntryListener AddEntryListener(const Twine& key,
+ TableEntryListener listener,
+ unsigned int flags) const;
+
+ /**
+ * Remove an entry listener.
+ *
+ * @param listener listener handle
+ */
+ void RemoveEntryListener(NT_EntryListener listener) const;
+
+ /**
+ * Listen for sub-table creation.
+ * This calls the listener once for each newly created sub-table.
+ * It immediately calls the listener for any existing sub-tables.
+ *
+ * @param listener listener to add
+ * @param localNotify notify local changes as well as remote
+ * @return Listener handle
+ */
+ NT_EntryListener AddSubTableListener(TableListener listener,
+ bool localNotify = false);
+
+ /**
+ * Remove a sub-table listener.
+ *
+ * @param listener listener handle
+ */
+ void RemoveTableListener(NT_EntryListener listener);
+
+ WPI_DEPRECATED(
+ "use AddEntryListener() instead with flags value of NT_NOTIFY_NEW | "
+ "NT_NOTIFY_UPDATE")
+ void AddTableListener(ITableListener* listener) override;
+
+ WPI_DEPRECATED(
+ "use AddEntryListener() instead with flags value of NT_NOTIFY_NEW | "
+ "NT_NOTIFY_UPDATE | NT_NOTIFY_IMMEDIATE")
+ void AddTableListener(ITableListener* listener,
+ bool immediateNotify) override;
+
+ WPI_DEPRECATED("use AddEntryListener() instead")
+ void AddTableListenerEx(ITableListener* listener,
+ unsigned int flags) override;
+
+ WPI_DEPRECATED("use AddEntryListener() instead")
+ void AddTableListener(StringRef key, ITableListener* listener,
+ bool immediateNotify) override;
+
+ WPI_DEPRECATED("use AddEntryListener() instead")
+ void AddTableListenerEx(StringRef key, ITableListener* listener,
+ unsigned int flags) override;
+
+ WPI_DEPRECATED("use AddSubTableListener(TableListener, bool) instead")
+ void AddSubTableListener(ITableListener* listener) override;
+
+ WPI_DEPRECATED("use AddSubTableListener(TableListener, bool) instead")
+ void AddSubTableListener(ITableListener* listener, bool localNotify) override;
+
+ WPI_DEPRECATED("use RemoveTableListener(NT_EntryListener) instead")
+ void RemoveTableListener(ITableListener* listener) override;
+
+ /**
+ * Returns the table at the specified key. If there is no table at the
+ * specified key, it will create a new table
+ *
+ * @param key the key name
+ * @return the networktable to be returned
+ */
+ std::shared_ptr<NetworkTable> GetSubTable(const Twine& key) const override;
+
+ /**
+ * Determines whether the given key is in this table.
+ *
+ * @param key the key to search for
+ * @return true if the table as a value assigned to the given key
+ */
+ bool ContainsKey(const Twine& key) const override;
+
+ /**
+ * Determines whether there exists a non-empty subtable for this key
+ * in this table.
+ *
+ * @param key the key to search for
+ * @return true if there is a subtable with the key which contains at least
+ * one key/subtable of its own
+ */
+ bool ContainsSubTable(const Twine& key) const override;
+
+ /**
+ * Gets all keys in the table (not including sub-tables).
+ *
+ * @param types bitmask of types; 0 is treated as a "don't care".
+ * @return keys currently in the table
+ */
+ std::vector<std::string> GetKeys(int types = 0) const override;
+
+ /**
+ * Gets the names of all subtables in the table.
+ *
+ * @return subtables currently in the table
+ */
+ std::vector<std::string> GetSubTables() const override;
+
+ /**
+ * Makes a key's value persistent through program restarts.
+ *
+ * @param key the key to make persistent
+ */
+ void SetPersistent(StringRef key) override;
+
+ /**
+ * Stop making a key's value persistent through program restarts.
+ * The key cannot be null.
+ *
+ * @param key the key name
+ */
+ void ClearPersistent(StringRef key) override;
+
+ /**
+ * Returns whether the value is persistent through program restarts.
+ * The key cannot be null.
+ *
+ * @param key the key name
+ */
+ bool IsPersistent(StringRef key) const override;
+
+ /**
+ * Sets flags on the specified key in this table. The key can
+ * not be null.
+ *
+ * @param key the key name
+ * @param flags the flags to set (bitmask)
+ */
+ void SetFlags(StringRef key, unsigned int flags) override;
+
+ /**
+ * Clears flags on the specified key in this table. The key can
+ * not be null.
+ *
+ * @param key the key name
+ * @param flags the flags to clear (bitmask)
+ */
+ void ClearFlags(StringRef key, unsigned int flags) override;
+
+ /**
+ * Returns the flags for the specified key.
+ *
+ * @param key the key name
+ * @return the flags, or 0 if the key is not defined
+ */
+ unsigned int GetFlags(StringRef key) const override;
+
+ /**
+ * Deletes the specified key in this table.
+ *
+ * @param key the key name
+ */
+ void Delete(const Twine& key) override;
+
+ /**
+ * Put a number in the table
+ *
+ * @param key the key to be assigned to
+ * @param value the value that will be assigned
+ * @return False if the table key already exists with a different type
+ */
+ bool PutNumber(StringRef key, double value) override;
+
+ /**
+ * Gets the current value in the table, setting it if it does not exist.
+ *
+ * @param key the key
+ * @param defaultValue the default value to set if key doesn't exist.
+ * @returns False if the table key exists with a different type
+ */
+ bool SetDefaultNumber(StringRef key, double defaultValue) override;
+
+ /**
+ * Gets the number associated with the given name.
+ *
+ * @param key the key to look up
+ * @param defaultValue the value to be returned if no value is found
+ * @return the value associated with the given key or the given default value
+ * if there is no value associated with the key
+ */
+ double GetNumber(StringRef key, double defaultValue) const override;
+
+ /**
+ * Put a string in the table
+ *
+ * @param key the key to be assigned to
+ * @param value the value that will be assigned
+ * @return False if the table key already exists with a different type
+ */
+ bool PutString(StringRef key, StringRef value) override;
+
+ /**
+ * Gets the current value in the table, setting it if it does not exist.
+ *
+ * @param key the key
+ * @param defaultValue the default value to set if key doesn't exist.
+ * @returns False if the table key exists with a different type
+ */
+ bool SetDefaultString(StringRef key, StringRef defaultValue) override;
+
+ /**
+ * Gets the string associated with the given name. If the key does not
+ * exist or is of different type, it will return the default value.
+ *
+ * @param key the key to look up
+ * @param defaultValue the value to be returned if no value is found
+ * @return the value associated with the given key or the given default value
+ * if there is no value associated with the key
+ */
+ std::string GetString(StringRef key, StringRef defaultValue) const override;
+
+ /**
+ * Put a boolean in the table
+ *
+ * @param key the key to be assigned to
+ * @param value the value that will be assigned
+ * @return False if the table key already exists with a different type
+ */
+ bool PutBoolean(StringRef key, bool value) override;
+
+ /**
+ * Gets the current value in the table, setting it if it does not exist.
+ *
+ * @param key the key
+ * @param defaultValue the default value to set if key doesn't exist.
+ * @returns False if the table key exists with a different type
+ */
+ bool SetDefaultBoolean(StringRef key, bool defaultValue) override;
+
+ /**
+ * Gets the boolean associated with the given name. If the key does not
+ * exist or is of different type, it will return the default value.
+ *
+ * @param key the key to look up
+ * @param defaultValue the value to be returned if no value is found
+ * @return the value associated with the given key or the given default value
+ * if there is no value associated with the key
+ */
+ bool GetBoolean(StringRef key, bool defaultValue) const override;
+
+ /**
+ * Put a boolean array in the table
+ *
+ * @param key the key to be assigned to
+ * @param value the value that will be assigned
+ * @return False if the table key already exists with a different type
+ *
+ * @note The array must be of int's rather than of bool's because
+ * std::vector<bool> is special-cased in C++. 0 is false, any
+ * non-zero value is true.
+ */
+ bool PutBooleanArray(StringRef key, ArrayRef<int> value) override;
+
+ /**
+ * Gets the current value in the table, setting it if it does not exist.
+ *
+ * @param key the key
+ * @param defaultValue the default value to set if key doesn't exist.
+ * @return False if the table key exists with a different type
+ */
+ bool SetDefaultBooleanArray(StringRef key,
+ ArrayRef<int> defaultValue) override;
+
+ /**
+ * Returns the boolean array the key maps to. If the key does not exist or is
+ * of different type, it will return the default value.
+ *
+ * @param key the key to look up
+ * @param defaultValue the value to be returned if no value is found
+ * @return the value associated with the given key or the given default value
+ * if there is no value associated with the key
+ *
+ * @note This makes a copy of the array. If the overhead of this is a
+ * concern, use GetValue() instead.
+ *
+ * @note The returned array is std::vector<int> instead of std::vector<bool>
+ * because std::vector<bool> is special-cased in C++. 0 is false, any
+ * non-zero value is true.
+ */
+ std::vector<int> GetBooleanArray(StringRef key,
+ ArrayRef<int> defaultValue) const override;
+
+ /**
+ * Put a number array in the table
+ *
+ * @param key the key to be assigned to
+ * @param value the value that will be assigned
+ * @return False if the table key already exists with a different type
+ */
+ bool PutNumberArray(StringRef key, ArrayRef<double> value) override;
+
+ /**
+ * Gets the current value in the table, setting it if it does not exist.
+ *
+ * @param key the key
+ * @param defaultValue the default value to set if key doesn't exist.
+ * @returns False if the table key exists with a different type
+ */
+ bool SetDefaultNumberArray(StringRef key,
+ ArrayRef<double> defaultValue) override;
+
+ /**
+ * Returns the number array the key maps to. If the key does not exist or is
+ * of different type, it will return the default value.
+ *
+ * @param key the key to look up
+ * @param defaultValue the value to be returned if no value is found
+ * @return the value associated with the given key or the given default value
+ * if there is no value associated with the key
+ *
+ * @note This makes a copy of the array. If the overhead of this is a
+ * concern, use GetValue() instead.
+ */
+ std::vector<double> GetNumberArray(
+ StringRef key, ArrayRef<double> defaultValue) const override;
+
+ /**
+ * Put a string array in the table
+ *
+ * @param key the key to be assigned to
+ * @param value the value that will be assigned
+ * @return False if the table key already exists with a different type
+ */
+ bool PutStringArray(StringRef key, ArrayRef<std::string> value) override;
+
+ /**
+ * Gets the current value in the table, setting it if it does not exist.
+ *
+ * @param key the key
+ * @param defaultValue the default value to set if key doesn't exist.
+ * @returns False if the table key exists with a different type
+ */
+ bool SetDefaultStringArray(StringRef key,
+ ArrayRef<std::string> defaultValue) override;
+
+ /**
+ * Returns the string array the key maps to. If the key does not exist or is
+ * of different type, it will return the default value.
+ *
+ * @param key the key to look up
+ * @param defaultValue the value to be returned if no value is found
+ * @return the value associated with the given key or the given default value
+ * if there is no value associated with the key
+ *
+ * @note This makes a copy of the array. If the overhead of this is a
+ * concern, use GetValue() instead.
+ */
+ std::vector<std::string> GetStringArray(
+ StringRef key, ArrayRef<std::string> defaultValue) const override;
+
+ /**
+ * Put a raw value (byte array) in the table
+ *
+ * @param key the key to be assigned to
+ * @param value the value that will be assigned
+ * @return False if the table key already exists with a different type
+ */
+ bool PutRaw(StringRef key, StringRef value) override;
+
+ /**
+ * Gets the current value in the table, setting it if it does not exist.
+ *
+ * @param key the key
+ * @param defaultValue the default value to set if key doesn't exist.
+ * @return False if the table key exists with a different type
+ */
+ bool SetDefaultRaw(StringRef key, StringRef defaultValue) override;
+
+ /**
+ * Returns the raw value (byte array) the key maps to. If the key does not
+ * exist or is of different type, it will return the default value.
+ *
+ * @param key the key to look up
+ * @param defaultValue the value to be returned if no value is found
+ * @return the value associated with the given key or the given default value
+ * if there is no value associated with the key
+ *
+ * @note This makes a copy of the raw contents. If the overhead of this is a
+ * concern, use GetValue() instead.
+ */
+ std::string GetRaw(StringRef key, StringRef defaultValue) const override;
+
+ /**
+ * Put a value in the table
+ *
+ * @param key the key to be assigned to
+ * @param value the value that will be assigned
+ * @return False if the table key already exists with a different type
+ */
+ bool PutValue(const Twine& key, std::shared_ptr<Value> value) override;
+
+ /**
+ * Gets the current value in the table, setting it if it does not exist.
+ *
+ * @param key the key
+ * @param defaultValue the default value to set if key doesn't exist.
+ * @return False if the table key exists with a different type
+ */
+ bool SetDefaultValue(const Twine& key,
+ std::shared_ptr<Value> defaultValue) override;
+
+ /**
+ * Gets the value associated with a key as an object
+ *
+ * @param key the key of the value to look up
+ * @return the value associated with the given key, or nullptr if the key
+ * does not exist
+ */
+ std::shared_ptr<Value> GetValue(const Twine& key) const override;
+
+ /**
+ * Gets the full path of this table. Does not include the trailing "/".
+ *
+ * @return The path (e.g "", "/foo").
+ */
+ StringRef GetPath() const override;
+
+ /**
+ * Save table values to a file. The file format used is identical to
+ * that used for SavePersistent.
+ *
+ * @param filename filename
+ * @return error string, or nullptr if successful
+ */
+ const char* SaveEntries(const Twine& filename) const;
+
+ /**
+ * Load table values from a file. The file format used is identical to
+ * that used for SavePersistent / LoadPersistent.
+ *
+ * @param filename filename
+ * @param warn callback function for warnings
+ * @return error string, or nullptr if successful
+ */
+ const char* LoadEntries(
+ const Twine& filename,
+ std::function<void(size_t line, const char* msg)> warn);
+};
+
+#ifdef __GNUC__
+#pragma GCC diagnostic pop
+#endif
+
+} // namespace nt
+
+// For backwards compatability
+#ifndef NAMESPACED_NT
+using nt::NetworkTable; // NOLINT
+#endif
+
+#endif // NTCORE_NETWORKTABLES_NETWORKTABLE_H_
diff --git a/ntcore/src/main/native/include/networktables/NetworkTableEntry.h b/ntcore/src/main/native/include/networktables/NetworkTableEntry.h
new file mode 100644
index 0000000..16b5c61
--- /dev/null
+++ b/ntcore/src/main/native/include/networktables/NetworkTableEntry.h
@@ -0,0 +1,500 @@
+/*----------------------------------------------------------------------------*/
+/* Copyright (c) 2017-2018 FIRST. All Rights Reserved. */
+/* Open Source Software - may be modified and shared by FRC teams. The code */
+/* must be accompanied by the FIRST BSD license file in the root directory of */
+/* the project. */
+/*----------------------------------------------------------------------------*/
+
+#ifndef NTCORE_NETWORKTABLES_NETWORKTABLEENTRY_H_
+#define NTCORE_NETWORKTABLES_NETWORKTABLEENTRY_H_
+
+#include <stdint.h>
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include <wpi/StringRef.h>
+#include <wpi/Twine.h>
+
+#include "networktables/NetworkTableType.h"
+#include "networktables/NetworkTableValue.h"
+#include "networktables/RpcCall.h"
+#include "ntcore_c.h"
+#include "ntcore_cpp.h"
+
+namespace nt {
+
+using wpi::ArrayRef;
+using wpi::StringRef;
+using wpi::Twine;
+
+class NetworkTableInstance;
+
+/**
+ * NetworkTables Entry
+ * @ingroup ntcore_cpp_api
+ */
+class NetworkTableEntry final {
+ public:
+ /**
+ * Flag values (as returned by GetFlags()).
+ */
+ enum Flags { kPersistent = NT_PERSISTENT };
+
+ /**
+ * Construct invalid instance.
+ */
+ NetworkTableEntry();
+
+ /**
+ * Construct from native handle.
+ *
+ * @param handle Native handle
+ */
+ explicit NetworkTableEntry(NT_Entry handle);
+
+ /**
+ * Determines if the native handle is valid.
+ *
+ * @return True if the native handle is valid, false otherwise.
+ */
+ explicit operator bool() const { return m_handle != 0; }
+
+ /**
+ * Gets the native handle for the entry.
+ *
+ * @return Native handle
+ */
+ NT_Entry GetHandle() const;
+
+ /**
+ * Gets the instance for the entry.
+ *
+ * @return Instance
+ */
+ NetworkTableInstance GetInstance() const;
+
+ /**
+ * Determines if the entry currently exists.
+ *
+ * @return True if the entry exists, false otherwise.
+ */
+ bool Exists() const;
+
+ /**
+ * Gets the name of the entry (the key).
+ *
+ * @return the entry's name
+ */
+ std::string GetName() const;
+
+ /**
+ * Gets the type of the entry.
+ *
+ * @return the entry's type
+ */
+ NetworkTableType GetType() const;
+
+ /**
+ * Returns the flags.
+ *
+ * @return the flags (bitmask)
+ */
+ unsigned int GetFlags() const;
+
+ /**
+ * Gets the last time the entry's value was changed.
+ *
+ * @return Entry last change time
+ */
+ uint64_t GetLastChange() const;
+
+ /**
+ * Gets combined information about the entry.
+ *
+ * @return Entry information
+ */
+ EntryInfo GetInfo() const;
+
+ /**
+ * Gets the entry's value. If the entry does not exist, returns nullptr.
+ *
+ * @return the entry's value or nullptr if it does not exist.
+ */
+ std::shared_ptr<Value> GetValue() const;
+
+ /**
+ * Gets the entry's value as a boolean. If the entry does not exist or is of
+ * different type, it will return the default value.
+ *
+ * @param defaultValue the value to be returned if no value is found
+ * @return the entry's value or the given default value
+ */
+ bool GetBoolean(bool defaultValue) const;
+
+ /**
+ * Gets the entry's value as a double. If the entry does not exist or is of
+ * different type, it will return the default value.
+ *
+ * @param defaultValue the value to be returned if no value is found
+ * @return the entry's value or the given default value
+ */
+ double GetDouble(double defaultValue) const;
+
+ /**
+ * Gets the entry's value as a string. If the entry does not exist or is of
+ * different type, it will return the default value.
+ *
+ * @param defaultValue the value to be returned if no value is found
+ * @return the entry's value or the given default value
+ */
+ std::string GetString(StringRef defaultValue) const;
+
+ /**
+ * Gets the entry's value as a raw. If the entry does not exist or is of
+ * different type, it will return the default value.
+ *
+ * @param defaultValue the value to be returned if no value is found
+ * @return the entry's value or the given default value
+ */
+ std::string GetRaw(StringRef defaultValue) const;
+
+ /**
+ * Gets the entry's value as a boolean array. If the entry does not exist
+ * or is of different type, it will return the default value.
+ *
+ * @param defaultValue the value to be returned if no value is found
+ * @return the entry's value or the given default value
+ *
+ * @note This makes a copy of the array. If the overhead of this is a
+ * concern, use GetValue() instead.
+ *
+ * @note The returned array is std::vector<int> instead of std::vector<bool>
+ * because std::vector<bool> is special-cased in C++. 0 is false, any
+ * non-zero value is true.
+ */
+ std::vector<int> GetBooleanArray(ArrayRef<int> defaultValue) const;
+
+ /**
+ * Gets the entry's value as a double array. If the entry does not exist
+ * or is of different type, it will return the default value.
+ *
+ * @param defaultValue the value to be returned if no value is found
+ * @return the entry's value or the given default value
+ *
+ * @note This makes a copy of the array. If the overhead of this is a
+ * concern, use GetValue() instead.
+ */
+ std::vector<double> GetDoubleArray(ArrayRef<double> defaultValue) const;
+
+ /**
+ * Gets the entry's value as a string array. If the entry does not exist
+ * or is of different type, it will return the default value.
+ *
+ * @param defaultValue the value to be returned if no value is found
+ * @return the entry's value or the given default value
+ *
+ * @note This makes a copy of the array. If the overhead of this is a
+ * concern, use GetValue() instead.
+ */
+ std::vector<std::string> GetStringArray(
+ ArrayRef<std::string> defaultValue) const;
+
+ /**
+ * Sets the entry's value if it does not exist.
+ *
+ * @param defaultValue the default value to set
+ * @return False if the entry exists with a different type
+ */
+ bool SetDefaultValue(std::shared_ptr<Value> value);
+
+ /**
+ * Sets the entry's value if it does not exist.
+ *
+ * @param defaultValue the default value to set
+ * @return False if the entry exists with a different type
+ */
+ bool SetDefaultBoolean(bool defaultValue);
+
+ /**
+ * Sets the entry's value if it does not exist.
+ *
+ * @param defaultValue the default value to set
+ * @return False if the entry exists with a different type
+ */
+ bool SetDefaultDouble(double defaultValue);
+
+ /**
+ * Sets the entry's value if it does not exist.
+ *
+ * @param defaultValue the default value to set
+ * @return False if the entry exists with a different type
+ */
+ bool SetDefaultString(const Twine& defaultValue);
+
+ /**
+ * Sets the entry's value if it does not exist.
+ *
+ * @param defaultValue the default value to set
+ * @return False if the entry exists with a different type
+ */
+ bool SetDefaultRaw(StringRef defaultValue);
+
+ /**
+ * Sets the entry's value if it does not exist.
+ *
+ * @param defaultValue the default value to set
+ * @return False if the entry exists with a different type
+ */
+ bool SetDefaultBooleanArray(ArrayRef<int> defaultValue);
+
+ /**
+ * Sets the entry's value if it does not exist.
+ *
+ * @param defaultValue the default value to set
+ * @return False if the entry exists with a different type
+ */
+ bool SetDefaultDoubleArray(ArrayRef<double> defaultValue);
+
+ /**
+ * Sets the entry's value if it does not exist.
+ *
+ * @param defaultValue the default value to set
+ * @return False if the entry exists with a different type
+ */
+ bool SetDefaultStringArray(ArrayRef<std::string> defaultValue);
+
+ /**
+ * Sets the entry's value.
+ *
+ * @param value the value to set
+ * @return False if the entry exists with a different type
+ */
+ bool SetValue(std::shared_ptr<Value> value);
+
+ /**
+ * Sets the entry's value.
+ *
+ * @param value the value to set
+ * @return False if the entry exists with a different type
+ */
+ bool SetBoolean(bool value);
+
+ /**
+ * Sets the entry's value.
+ *
+ * @param value the value to set
+ * @return False if the entry exists with a different type
+ */
+ bool SetDouble(double value);
+
+ /**
+ * Sets the entry's value.
+ *
+ * @param value the value to set
+ * @return False if the entry exists with a different type
+ */
+ bool SetString(const Twine& value);
+
+ /**
+ * Sets the entry's value.
+ *
+ * @param value the value to set
+ * @return False if the entry exists with a different type
+ */
+ bool SetRaw(StringRef value);
+
+ /**
+ * Sets the entry's value.
+ *
+ * @param value the value to set
+ * @return False if the entry exists with a different type
+ */
+ bool SetBooleanArray(ArrayRef<int> value);
+
+ /**
+ * Sets the entry's value.
+ *
+ * @param value the value to set
+ * @return False if the entry exists with a different type
+ */
+ bool SetDoubleArray(ArrayRef<double> value);
+
+ /**
+ * Sets the entry's value.
+ *
+ * @param value the value to set
+ * @return False if the entry exists with a different type
+ */
+ bool SetStringArray(ArrayRef<std::string> value);
+
+ /**
+ * Sets the entry's value. If the value is of different type, the type is
+ * changed to match the new value.
+ *
+ * @param value the value to set
+ */
+ void ForceSetValue(std::shared_ptr<Value> value);
+
+ /**
+ * Sets the entry's value. If the value is of different type, the type is
+ * changed to match the new value.
+ *
+ * @param value the value to set
+ */
+ void ForceSetBoolean(bool value);
+
+ /**
+ * Sets the entry's value. If the value is of different type, the type is
+ * changed to match the new value.
+ *
+ * @param value the value to set
+ */
+ void ForceSetDouble(double value);
+
+ /**
+ * Sets the entry's value. If the value is of different type, the type is
+ * changed to match the new value.
+ *
+ * @param value the value to set
+ */
+ void ForceSetString(const Twine& value);
+
+ /**
+ * Sets the entry's value. If the value is of different type, the type is
+ * changed to match the new value.
+ *
+ * @param value the value to set
+ */
+ void ForceSetRaw(StringRef value);
+
+ /**
+ * Sets the entry's value. If the value is of different type, the type is
+ * changed to match the new value.
+ *
+ * @param value the value to set
+ */
+ void ForceSetBooleanArray(ArrayRef<int> value);
+
+ /**
+ * Sets the entry's value. If the value is of different type, the type is
+ * changed to match the new value.
+ *
+ * @param value the value to set
+ */
+ void ForceSetDoubleArray(ArrayRef<double> value);
+
+ /**
+ * Sets the entry's value. If the value is of different type, the type is
+ * changed to match the new value.
+ *
+ * @param value the value to set
+ */
+ void ForceSetStringArray(ArrayRef<std::string> value);
+
+ /**
+ * Sets flags.
+ *
+ * @param flags the flags to set (bitmask)
+ */
+ void SetFlags(unsigned int flags);
+
+ /**
+ * Clears flags.
+ *
+ * @param flags the flags to clear (bitmask)
+ */
+ void ClearFlags(unsigned int flags);
+
+ /**
+ * Make value persistent through program restarts.
+ */
+ void SetPersistent();
+
+ /**
+ * Stop making value persistent through program restarts.
+ */
+ void ClearPersistent();
+
+ /**
+ * Returns whether the value is persistent through program restarts.
+ *
+ * @return True if the value is persistent.
+ */
+ bool IsPersistent() const;
+
+ /**
+ * Deletes the entry.
+ */
+ void Delete();
+
+ /**
+ * Create a callback-based RPC entry point. Only valid to use on the server.
+ * The callback function will be called when the RPC is called.
+ * This function creates RPC version 0 definitions (raw data in and out).
+ *
+ * @param callback callback function
+ */
+ void CreateRpc(std::function<void(const RpcAnswer& answer)> callback);
+
+ /**
+ * Create a polled RPC entry point. Only valid to use on the server.
+ * The caller is responsible for calling NetworkTableInstance::PollRpc()
+ * to poll for servicing incoming RPC calls.
+ * This function creates RPC version 0 definitions (raw data in and out).
+ */
+ void CreatePolledRpc();
+
+ /**
+ * Call a RPC function. May be used on either the client or server.
+ * This function is non-blocking. Either RpcCall::GetResult() or
+ * RpcCall::CancelResult() must be called on the return value to either
+ * get or ignore the result of the call.
+ *
+ * @param params parameter
+ * @return RPC call object.
+ */
+ RpcCall CallRpc(StringRef params);
+
+ /**
+ * Add a listener for changes to this entry.
+ *
+ * @param callback listener to add
+ * @param flags NotifyKind bitmask
+ * @return Listener handle
+ */
+ NT_EntryListener AddListener(
+ std::function<void(const EntryNotification& event)> callback,
+ unsigned int flags) const;
+
+ /**
+ * Remove an entry listener.
+ *
+ * @param entry_listener Listener handle to remove
+ */
+ void RemoveListener(NT_EntryListener entry_listener);
+
+ /**
+ * Equality operator. Returns true if both instances refer to the same
+ * native handle.
+ */
+ bool operator==(const NetworkTableEntry& oth) const {
+ return m_handle == oth.m_handle;
+ }
+
+ /** Inequality operator. */
+ bool operator!=(const NetworkTableEntry& oth) const {
+ return !(*this == oth);
+ }
+
+ protected:
+ /* Native handle */
+ NT_Entry m_handle;
+};
+
+} // namespace nt
+
+#include "networktables/NetworkTableEntry.inl"
+
+#endif // NTCORE_NETWORKTABLES_NETWORKTABLEENTRY_H_
diff --git a/ntcore/src/main/native/include/networktables/NetworkTableEntry.inl b/ntcore/src/main/native/include/networktables/NetworkTableEntry.inl
new file mode 100644
index 0000000..f95b1a8
--- /dev/null
+++ b/ntcore/src/main/native/include/networktables/NetworkTableEntry.inl
@@ -0,0 +1,232 @@
+/*----------------------------------------------------------------------------*/
+/* Copyright (c) FIRST 2017. All Rights Reserved. */
+/* Open Source Software - may be modified and shared by FRC teams. The code */
+/* must be accompanied by the FIRST BSD license file in the root directory of */
+/* the project. */
+/*----------------------------------------------------------------------------*/
+
+#ifndef NT_ENTRY_INL_
+#define NT_ENTRY_INL_
+
+namespace nt {
+
+inline NetworkTableEntry::NetworkTableEntry() : m_handle{0} {}
+
+inline NetworkTableEntry::NetworkTableEntry(NT_Entry handle)
+ : m_handle{handle} {}
+
+inline NT_Entry NetworkTableEntry::GetHandle() const { return m_handle; }
+
+inline bool NetworkTableEntry::Exists() const {
+ return GetEntryType(m_handle) != NT_UNASSIGNED;
+}
+
+inline std::string NetworkTableEntry::GetName() const {
+ return GetEntryName(m_handle);
+}
+
+inline NetworkTableType NetworkTableEntry::GetType() const {
+ return static_cast<NetworkTableType>(GetEntryType(m_handle));
+}
+
+inline unsigned int NetworkTableEntry::GetFlags() const {
+ return GetEntryFlags(m_handle);
+}
+
+inline uint64_t NetworkTableEntry::GetLastChange() const {
+ return GetEntryLastChange(m_handle);
+}
+
+inline EntryInfo NetworkTableEntry::GetInfo() const {
+ return GetEntryInfo(m_handle);
+}
+
+inline std::shared_ptr<Value> NetworkTableEntry::GetValue() const {
+ return GetEntryValue(m_handle);
+}
+
+inline bool NetworkTableEntry::GetBoolean(bool defaultValue) const {
+ auto value = GetEntryValue(m_handle);
+ if (!value || value->type() != NT_BOOLEAN) return defaultValue;
+ return value->GetBoolean();
+}
+
+inline double NetworkTableEntry::GetDouble(double defaultValue) const {
+ auto value = GetEntryValue(m_handle);
+ if (!value || value->type() != NT_DOUBLE) return defaultValue;
+ return value->GetDouble();
+}
+
+inline std::string NetworkTableEntry::GetString(StringRef defaultValue) const {
+ auto value = GetEntryValue(m_handle);
+ if (!value || value->type() != NT_STRING) return defaultValue;
+ return value->GetString();
+}
+
+inline std::string NetworkTableEntry::GetRaw(StringRef defaultValue) const {
+ auto value = GetEntryValue(m_handle);
+ if (!value || value->type() != NT_RAW) return defaultValue;
+ return value->GetString();
+}
+
+inline std::vector<int> NetworkTableEntry::GetBooleanArray(
+ ArrayRef<int> defaultValue) const {
+ auto value = GetEntryValue(m_handle);
+ if (!value || value->type() != NT_BOOLEAN_ARRAY) return defaultValue;
+ return value->GetBooleanArray();
+}
+
+inline std::vector<double> NetworkTableEntry::GetDoubleArray(
+ ArrayRef<double> defaultValue) const {
+ auto value = GetEntryValue(m_handle);
+ if (!value || value->type() != NT_DOUBLE_ARRAY) return defaultValue;
+ return value->GetDoubleArray();
+}
+
+inline std::vector<std::string> NetworkTableEntry::GetStringArray(
+ ArrayRef<std::string> defaultValue) const {
+ auto value = GetEntryValue(m_handle);
+ if (!value || value->type() != NT_STRING_ARRAY) return defaultValue;
+ return value->GetStringArray();
+}
+
+inline bool NetworkTableEntry::SetDefaultValue(std::shared_ptr<Value> value) {
+ return SetDefaultEntryValue(m_handle, value);
+}
+
+inline bool NetworkTableEntry::SetDefaultBoolean(bool defaultValue) {
+ return SetDefaultEntryValue(m_handle, Value::MakeBoolean(defaultValue));
+}
+
+inline bool NetworkTableEntry::SetDefaultDouble(double defaultValue) {
+ return SetDefaultEntryValue(m_handle, Value::MakeDouble(defaultValue));
+}
+
+inline bool NetworkTableEntry::SetDefaultString(const Twine& defaultValue) {
+ return SetDefaultEntryValue(m_handle, Value::MakeString(defaultValue));
+}
+
+inline bool NetworkTableEntry::SetDefaultRaw(StringRef defaultValue) {
+ return SetDefaultEntryValue(m_handle, Value::MakeRaw(defaultValue));
+}
+
+inline bool NetworkTableEntry::SetDefaultBooleanArray(
+ ArrayRef<int> defaultValue) {
+ return SetDefaultEntryValue(m_handle, Value::MakeBooleanArray(defaultValue));
+}
+
+inline bool NetworkTableEntry::SetDefaultDoubleArray(
+ ArrayRef<double> defaultValue) {
+ return SetDefaultEntryValue(m_handle, Value::MakeDoubleArray(defaultValue));
+}
+
+inline bool NetworkTableEntry::SetDefaultStringArray(
+ ArrayRef<std::string> defaultValue) {
+ return SetDefaultEntryValue(m_handle, Value::MakeStringArray(defaultValue));
+}
+
+inline bool NetworkTableEntry::SetValue(std::shared_ptr<Value> value) {
+ return SetEntryValue(m_handle, value);
+}
+
+inline bool NetworkTableEntry::SetBoolean(bool value) {
+ return SetEntryValue(m_handle, Value::MakeBoolean(value));
+}
+
+inline bool NetworkTableEntry::SetDouble(double value) {
+ return SetEntryValue(m_handle, Value::MakeDouble(value));
+}
+
+inline bool NetworkTableEntry::SetString(const Twine& value) {
+ return SetEntryValue(m_handle, Value::MakeString(value));
+}
+
+inline bool NetworkTableEntry::SetRaw(StringRef value) {
+ return SetEntryValue(m_handle, Value::MakeRaw(value));
+}
+
+inline bool NetworkTableEntry::SetBooleanArray(ArrayRef<int> value) {
+ return SetEntryValue(m_handle, Value::MakeBooleanArray(value));
+}
+
+inline bool NetworkTableEntry::SetDoubleArray(ArrayRef<double> value) {
+ return SetEntryValue(m_handle, Value::MakeDoubleArray(value));
+}
+
+inline bool NetworkTableEntry::SetStringArray(ArrayRef<std::string> value) {
+ return SetEntryValue(m_handle, Value::MakeStringArray(value));
+}
+
+inline void NetworkTableEntry::ForceSetValue(std::shared_ptr<Value> value) {
+ SetEntryTypeValue(m_handle, value);
+}
+
+inline void NetworkTableEntry::ForceSetBoolean(bool value) {
+ SetEntryTypeValue(m_handle, Value::MakeBoolean(value));
+}
+
+inline void NetworkTableEntry::ForceSetDouble(double value) {
+ SetEntryTypeValue(m_handle, Value::MakeDouble(value));
+}
+
+inline void NetworkTableEntry::ForceSetString(const Twine& value) {
+ SetEntryTypeValue(m_handle, Value::MakeString(value));
+}
+
+inline void NetworkTableEntry::ForceSetRaw(StringRef value) {
+ SetEntryTypeValue(m_handle, Value::MakeRaw(value));
+}
+
+inline void NetworkTableEntry::ForceSetBooleanArray(ArrayRef<int> value) {
+ SetEntryTypeValue(m_handle, Value::MakeBooleanArray(value));
+}
+
+inline void NetworkTableEntry::ForceSetDoubleArray(ArrayRef<double> value) {
+ SetEntryTypeValue(m_handle, Value::MakeDoubleArray(value));
+}
+
+inline void NetworkTableEntry::ForceSetStringArray(
+ ArrayRef<std::string> value) {
+ SetEntryTypeValue(m_handle, Value::MakeStringArray(value));
+}
+
+inline void NetworkTableEntry::SetFlags(unsigned int flags) {
+ SetEntryFlags(m_handle, GetFlags() | flags);
+}
+
+inline void NetworkTableEntry::ClearFlags(unsigned int flags) {
+ SetEntryFlags(m_handle, GetFlags() & ~flags);
+}
+
+inline void NetworkTableEntry::SetPersistent() { SetFlags(kPersistent); }
+
+inline void NetworkTableEntry::ClearPersistent() { ClearFlags(kPersistent); }
+
+inline bool NetworkTableEntry::IsPersistent() const {
+ return (GetFlags() & kPersistent) != 0;
+}
+
+inline void NetworkTableEntry::Delete() { DeleteEntry(m_handle); }
+
+inline void NetworkTableEntry::CreateRpc(
+ std::function<void(const RpcAnswer& answer)> callback) {
+ ::nt::CreateRpc(m_handle, StringRef("\0", 1), callback);
+}
+
+inline RpcCall NetworkTableEntry::CallRpc(StringRef params) {
+ return RpcCall{m_handle, ::nt::CallRpc(m_handle, params)};
+}
+
+inline NT_EntryListener NetworkTableEntry::AddListener(
+ std::function<void(const EntryNotification& event)> callback,
+ unsigned int flags) const {
+ return AddEntryListener(m_handle, callback, flags);
+}
+
+inline void NetworkTableEntry::RemoveListener(NT_EntryListener entry_listener) {
+ RemoveEntryListener(entry_listener);
+}
+
+} // namespace nt
+
+#endif // NT_ENTRY_INL_
diff --git a/ntcore/src/main/native/include/networktables/NetworkTableInstance.h b/ntcore/src/main/native/include/networktables/NetworkTableInstance.h
new file mode 100644
index 0000000..ee08745
--- /dev/null
+++ b/ntcore/src/main/native/include/networktables/NetworkTableInstance.h
@@ -0,0 +1,561 @@
+/*----------------------------------------------------------------------------*/
+/* Copyright (c) 2017-2018 FIRST. All Rights Reserved. */
+/* Open Source Software - may be modified and shared by FRC teams. The code */
+/* must be accompanied by the FIRST BSD license file in the root directory of */
+/* the project. */
+/*----------------------------------------------------------------------------*/
+
+#ifndef NTCORE_NETWORKTABLES_NETWORKTABLEINSTANCE_H_
+#define NTCORE_NETWORKTABLES_NETWORKTABLEINSTANCE_H_
+
+#include <functional>
+#include <memory>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include <wpi/ArrayRef.h>
+#include <wpi/StringRef.h>
+#include <wpi/Twine.h>
+
+#include "networktables/NetworkTable.h"
+#include "networktables/NetworkTableEntry.h"
+#include "ntcore_c.h"
+#include "ntcore_cpp.h"
+
+namespace nt {
+
+using wpi::ArrayRef;
+using wpi::StringRef;
+using wpi::Twine;
+
+/**
+ * NetworkTables Instance.
+ *
+ * Instances are completely independent from each other. Table operations on
+ * one instance will not be visible to other instances unless the instances are
+ * connected via the network. The main limitation on instances is that you
+ * cannot have two servers on the same network port. The main utility of
+ * instances is for unit testing, but they can also enable one program to
+ * connect to two different NetworkTables networks.
+ *
+ * The global "default" instance (as returned by GetDefault()) is
+ * always available, and is intended for the common case when there is only
+ * a single NetworkTables instance being used in the program. The
+ * default instance cannot be destroyed.
+ *
+ * Additional instances can be created with the Create() function.
+ * Instances are not reference counted or RAII. Instead, they must be
+ * explicitly destroyed (with Destroy()).
+ *
+ * @ingroup ntcore_cpp_api
+ */
+class NetworkTableInstance final {
+ public:
+ /**
+ * Client/server mode flag values (as returned by GetNetworkMode()).
+ * This is a bitmask.
+ */
+ enum NetworkMode {
+ kNetModeNone = NT_NET_MODE_NONE,
+ kNetModeServer = NT_NET_MODE_SERVER,
+ kNetModeClient = NT_NET_MODE_CLIENT,
+ kNetModeStarting = NT_NET_MODE_STARTING,
+ kNetModeFailure = NT_NET_MODE_FAILURE
+ };
+
+ /**
+ * Logging levels (as used by SetLogger()).
+ */
+ enum LogLevel {
+ kLogCritical = NT_LOG_CRITICAL,
+ kLogError = NT_LOG_ERROR,
+ kLogWarning = NT_LOG_WARNING,
+ kLogInfo = NT_LOG_INFO,
+ kLogDebug = NT_LOG_DEBUG,
+ kLogDebug1 = NT_LOG_DEBUG1,
+ kLogDebug2 = NT_LOG_DEBUG2,
+ kLogDebug3 = NT_LOG_DEBUG3,
+ kLogDebug4 = NT_LOG_DEBUG4
+ };
+
+ /**
+ * The default port that network tables operates on.
+ */
+ enum { kDefaultPort = NT_DEFAULT_PORT };
+
+ /**
+ * Construct invalid instance.
+ */
+ NetworkTableInstance() noexcept;
+
+ /**
+ * Construct from native handle.
+ *
+ * @param handle Native handle
+ */
+ explicit NetworkTableInstance(NT_Inst inst) noexcept;
+
+ /**
+ * Determines if the native handle is valid.
+ *
+ * @return True if the native handle is valid, false otherwise.
+ */
+ explicit operator bool() const { return m_handle != 0; }
+
+ /**
+ * Get global default instance.
+ *
+ * @return Global default instance
+ */
+ static NetworkTableInstance GetDefault();
+
+ /**
+ * Create an instance.
+ *
+ * @return Newly created instance
+ */
+ static NetworkTableInstance Create();
+
+ /**
+ * Destroys an instance (note: this has global effect).
+ *
+ * @param inst Instance
+ */
+ static void Destroy(NetworkTableInstance inst);
+
+ /**
+ * Gets the native handle for the entry.
+ *
+ * @return Native handle
+ */
+ NT_Inst GetHandle() const;
+
+ /**
+ * Gets the entry for a key.
+ *
+ * @param name Key
+ * @return Network table entry.
+ */
+ NetworkTableEntry GetEntry(const Twine& name);
+
+ /**
+ * Get entries starting with the given prefix.
+ *
+ * The results are optionally filtered by string prefix and entry type to
+ * only return a subset of all entries.
+ *
+ * @param prefix entry name required prefix; only entries whose name
+ * starts with this string are returned
+ * @param types bitmask of types; 0 is treated as a "don't care"
+ * @return Array of entries.
+ */
+ std::vector<NetworkTableEntry> GetEntries(const Twine& prefix,
+ unsigned int types);
+
+ /**
+ * Get information about entries starting with the given prefix.
+ *
+ * The results are optionally filtered by string prefix and entry type to
+ * only return a subset of all entries.
+ *
+ * @param prefix entry name required prefix; only entries whose name
+ * starts with this string are returned
+ * @param types bitmask of types; 0 is treated as a "don't care"
+ * @return Array of entry information.
+ */
+ std::vector<EntryInfo> GetEntryInfo(const Twine& prefix,
+ unsigned int types) const;
+
+ /**
+ * Gets the table with the specified key.
+ *
+ * @param key the key name
+ * @return The network table
+ */
+ std::shared_ptr<NetworkTable> GetTable(const Twine& key) const;
+
+ /**
+ * Deletes ALL keys in ALL subtables (except persistent values).
+ * Use with caution!
+ */
+ void DeleteAllEntries();
+
+ /**
+ * @{
+ * @name Entry Listener Functions
+ */
+
+ /**
+ * Add a listener for all entries starting with a certain prefix.
+ *
+ * @param prefix UTF-8 string prefix
+ * @param callback listener to add
+ * @param flags EntryListenerFlags bitmask
+ * @return Listener handle
+ */
+ NT_EntryListener AddEntryListener(
+ const Twine& prefix,
+ std::function<void(const EntryNotification& event)> callback,
+ unsigned int flags) const;
+
+ /**
+ * Remove an entry listener.
+ *
+ * @param entry_listener Listener handle to remove
+ */
+ static void RemoveEntryListener(NT_EntryListener entry_listener);
+
+ /**
+ * Wait for the entry listener queue to be empty. This is primarily useful
+ * for deterministic testing. This blocks until either the entry listener
+ * queue is empty (e.g. there are no more events that need to be passed along
+ * to callbacks or poll queues) or the timeout expires.
+ *
+ * @param timeout timeout, in seconds. Set to 0 for non-blocking behavior,
+ * or a negative value to block indefinitely
+ * @return False if timed out, otherwise true.
+ */
+ bool WaitForEntryListenerQueue(double timeout);
+
+ /** @} */
+
+ /**
+ * @{
+ * @name Connection Listener Functions
+ */
+
+ /**
+ * Add a connection listener.
+ *
+ * @param callback listener to add
+ * @param immediate_notify notify listener of all existing connections
+ * @return Listener handle
+ */
+ NT_ConnectionListener AddConnectionListener(
+ std::function<void(const ConnectionNotification& event)> callback,
+ bool immediate_notify) const;
+
+ /**
+ * Remove a connection listener.
+ *
+ * @param conn_listener Listener handle to remove
+ */
+ static void RemoveConnectionListener(NT_ConnectionListener conn_listener);
+
+ /**
+ * Wait for the connection listener queue to be empty. This is primarily
+ * useful for deterministic testing. This blocks until either the connection
+ * listener queue is empty (e.g. there are no more events that need to be
+ * passed along to callbacks or poll queues) or the timeout expires.
+ *
+ * @param timeout timeout, in seconds. Set to 0 for non-blocking behavior,
+ * or a negative value to block indefinitely
+ * @return False if timed out, otherwise true.
+ */
+ bool WaitForConnectionListenerQueue(double timeout);
+
+ /** @} */
+
+ /**
+ * @{
+ * @name Remote Procedure Call Functions
+ */
+
+ /**
+ * Wait for the incoming RPC call queue to be empty. This is primarily useful
+ * for deterministic testing. This blocks until either the RPC call
+ * queue is empty (e.g. there are no more events that need to be passed along
+ * to callbacks or poll queues) or the timeout expires.
+ *
+ * @param timeout timeout, in seconds. Set to 0 for non-blocking behavior,
+ * or a negative value to block indefinitely
+ * @return False if timed out, otherwise true.
+ */
+ bool WaitForRpcCallQueue(double timeout);
+
+ /** @} */
+
+ /**
+ * @{
+ * @name Client/Server Functions
+ */
+
+ /**
+ * Set the network identity of this node.
+ *
+ * This is the name used during the initial connection handshake, and is
+ * visible through ConnectionInfo on the remote node.
+ *
+ * @param name identity to advertise
+ */
+ void SetNetworkIdentity(const Twine& name);
+
+ /**
+ * Get the current network mode.
+ *
+ * @return Bitmask of NetworkMode.
+ */
+ unsigned int GetNetworkMode() const;
+
+ /**
+ * Starts a server using the specified filename, listening address, and port.
+ *
+ * @param persist_filename the name of the persist file to use (UTF-8 string,
+ * null terminated)
+ * @param listen_address the address to listen on, or null to listen on any
+ * address (UTF-8 string, null terminated)
+ * @param port port to communicate over
+ */
+ void StartServer(const Twine& persist_filename = "networktables.ini",
+ const char* listen_address = "",
+ unsigned int port = kDefaultPort);
+
+ /**
+ * Stops the server if it is running.
+ */
+ void StopServer();
+
+ /**
+ * Starts a client. Use SetServer to set the server name and port.
+ */
+ void StartClient();
+
+ /**
+ * Starts a client using the specified server and port
+ *
+ * @param server_name server name (UTF-8 string, null terminated)
+ * @param port port to communicate over
+ */
+ void StartClient(const char* server_name, unsigned int port = kDefaultPort);
+
+ /**
+ * Starts a client using the specified (server, port) combinations. The
+ * client will attempt to connect to each server in round robin fashion.
+ *
+ * @param servers array of server name and port pairs
+ */
+ void StartClient(ArrayRef<std::pair<StringRef, unsigned int>> servers);
+
+ /**
+ * Starts a client using the specified servers and port. The
+ * client will attempt to connect to each server in round robin fashion.
+ *
+ * @param servers array of server names
+ * @param port port to communicate over
+ */
+ void StartClient(ArrayRef<StringRef> servers,
+ unsigned int port = kDefaultPort);
+
+ /**
+ * Starts a client using commonly known robot addresses for the specified
+ * team.
+ *
+ * @param team team number
+ * @param port port to communicate over
+ */
+ void StartClientTeam(unsigned int team, unsigned int port = kDefaultPort);
+
+ /**
+ * Stops the client if it is running.
+ */
+ void StopClient();
+
+ /**
+ * Sets server address and port for client (without restarting client).
+ *
+ * @param server_name server name (UTF-8 string, null terminated)
+ * @param port port to communicate over
+ */
+ void SetServer(const char* server_name, unsigned int port = kDefaultPort);
+
+ /**
+ * Sets server addresses and ports for client (without restarting client).
+ * The client will attempt to connect to each server in round robin fashion.
+ *
+ * @param servers array of server name and port pairs
+ */
+ void SetServer(ArrayRef<std::pair<StringRef, unsigned int>> servers);
+
+ /**
+ * Sets server addresses and port for client (without restarting client).
+ * The client will attempt to connect to each server in round robin fashion.
+ *
+ * @param servers array of server names
+ * @param port port to communicate over
+ */
+ void SetServer(ArrayRef<StringRef> servers, unsigned int port = kDefaultPort);
+
+ /**
+ * Sets server addresses and port for client (without restarting client).
+ * Connects using commonly known robot addresses for the specified team.
+ *
+ * @param team team number
+ * @param port port to communicate over
+ */
+ void SetServerTeam(unsigned int team, unsigned int port = kDefaultPort);
+
+ /**
+ * Starts requesting server address from Driver Station.
+ * This connects to the Driver Station running on localhost to obtain the
+ * server IP address.
+ *
+ * @param port server port to use in combination with IP from DS
+ */
+ void StartDSClient(unsigned int port = kDefaultPort);
+
+ /**
+ * Stops requesting server address from Driver Station.
+ */
+ void StopDSClient();
+
+ /**
+ * Set the periodic update rate.
+ * Sets how frequently updates are sent to other nodes over the network.
+ *
+ * @param interval update interval in seconds (range 0.01 to 1.0)
+ */
+ void SetUpdateRate(double interval);
+
+ /**
+ * Flushes all updated values immediately to the network.
+ * @note This is rate-limited to protect the network from flooding.
+ * This is primarily useful for synchronizing network updates with
+ * user code.
+ */
+ void Flush() const;
+
+ /**
+ * Get information on the currently established network connections.
+ * If operating as a client, this will return either zero or one values.
+ *
+ * @return array of connection information
+ */
+ std::vector<ConnectionInfo> GetConnections() const;
+
+ /**
+ * Return whether or not the instance is connected to another node.
+ *
+ * @return True if connected.
+ */
+ bool IsConnected() const;
+
+ /** @} */
+
+ /**
+ * @{
+ * @name File Save/Load Functions
+ */
+
+ /**
+ * Save persistent values to a file. The server automatically does this,
+ * but this function provides a way to save persistent values in the same
+ * format to a file on either a client or a server.
+ *
+ * @param filename filename
+ * @return error string, or nullptr if successful
+ */
+ const char* SavePersistent(const Twine& filename) const;
+
+ /**
+ * Load persistent values from a file. The server automatically does this
+ * at startup, but this function provides a way to restore persistent values
+ * in the same format from a file at any time on either a client or a server.
+ *
+ * @param filename filename
+ * @param warn callback function for warnings
+ * @return error string, or nullptr if successful
+ */
+ const char* LoadPersistent(
+ const Twine& filename,
+ std::function<void(size_t line, const char* msg)> warn);
+
+ /**
+ * Save table values to a file. The file format used is identical to
+ * that used for SavePersistent.
+ *
+ * @param filename filename
+ * @param prefix save only keys starting with this prefix
+ * @return error string, or nullptr if successful
+ */
+ const char* SaveEntries(const Twine& filename, const Twine& prefix) const;
+
+ /**
+ * Load table values from a file. The file format used is identical to
+ * that used for SavePersistent / LoadPersistent.
+ *
+ * @param filename filename
+ * @param prefix load only keys starting with this prefix
+ * @param warn callback function for warnings
+ * @return error string, or nullptr if successful
+ */
+ const char* LoadEntries(
+ const Twine& filename, const Twine& prefix,
+ std::function<void(size_t line, const char* msg)> warn);
+
+ /** @} */
+
+ /**
+ * @{
+ * @name Logger Functions
+ */
+
+ /**
+ * Add logger callback function. By default, log messages are sent to stderr;
+ * this function sends log messages with the specified levels to the provided
+ * callback function instead. The callback function will only be called for
+ * log messages with level greater than or equal to minLevel and less than or
+ * equal to maxLevel; messages outside this range will be silently ignored.
+ *
+ * @param func log callback function
+ * @param minLevel minimum log level
+ * @param maxLevel maximum log level
+ * @return Logger handle
+ */
+ NT_Logger AddLogger(std::function<void(const LogMessage& msg)> func,
+ unsigned int min_level, unsigned int max_level);
+
+ /**
+ * Remove a logger.
+ *
+ * @param logger Logger handle to remove
+ */
+ static void RemoveLogger(NT_Logger logger);
+
+ /**
+ * Wait for the incoming log event queue to be empty. This is primarily
+ * useful for deterministic testing. This blocks until either the log event
+ * queue is empty (e.g. there are no more events that need to be passed along
+ * to callbacks or poll queues) or the timeout expires.
+ *
+ * @param timeout timeout, in seconds. Set to 0 for non-blocking behavior,
+ * or a negative value to block indefinitely
+ * @return False if timed out, otherwise true.
+ */
+ bool WaitForLoggerQueue(double timeout);
+
+ /** @} */
+
+ /**
+ * Equality operator. Returns true if both instances refer to the same
+ * native handle.
+ */
+ bool operator==(const NetworkTableInstance& other) const {
+ return m_handle == other.m_handle;
+ }
+
+ /** Inequality operator. */
+ bool operator!=(const NetworkTableInstance& other) const {
+ return !(*this == other);
+ }
+
+ private:
+ /* Native handle */
+ NT_Inst m_handle;
+};
+
+} // namespace nt
+
+#include "networktables/NetworkTableInstance.inl"
+
+#endif // NTCORE_NETWORKTABLES_NETWORKTABLEINSTANCE_H_
diff --git a/ntcore/src/main/native/include/networktables/NetworkTableInstance.inl b/ntcore/src/main/native/include/networktables/NetworkTableInstance.inl
new file mode 100644
index 0000000..83616d9
--- /dev/null
+++ b/ntcore/src/main/native/include/networktables/NetworkTableInstance.inl
@@ -0,0 +1,187 @@
+/*----------------------------------------------------------------------------*/
+/* Copyright (c) FIRST 2017. All Rights Reserved. */
+/* Open Source Software - may be modified and shared by FRC teams. The code */
+/* must be accompanied by the FIRST BSD license file in the root directory of */
+/* the project. */
+/*----------------------------------------------------------------------------*/
+
+#ifndef NT_INSTANCE_INL_
+#define NT_INSTANCE_INL_
+
+namespace nt {
+
+inline NetworkTableInstance::NetworkTableInstance() noexcept : m_handle{0} {}
+
+inline NetworkTableInstance::NetworkTableInstance(NT_Inst handle) noexcept
+ : m_handle{handle} {}
+
+inline NetworkTableInstance NetworkTableInstance::GetDefault() {
+ return NetworkTableInstance{GetDefaultInstance()};
+}
+
+inline NetworkTableInstance NetworkTableInstance::Create() {
+ return NetworkTableInstance{CreateInstance()};
+}
+
+inline void NetworkTableInstance::Destroy(NetworkTableInstance inst) {
+ if (inst.m_handle != 0) DestroyInstance(inst.m_handle);
+}
+
+inline NT_Inst NetworkTableInstance::GetHandle() const { return m_handle; }
+
+inline NetworkTableEntry NetworkTableInstance::GetEntry(const Twine& name) {
+ return NetworkTableEntry{::nt::GetEntry(m_handle, name)};
+}
+
+inline std::vector<NetworkTableEntry> NetworkTableInstance::GetEntries(
+ const Twine& prefix, unsigned int types) {
+ std::vector<NetworkTableEntry> entries;
+ for (auto entry : ::nt::GetEntries(m_handle, prefix, types))
+ entries.emplace_back(entry);
+ return entries;
+}
+
+inline std::vector<EntryInfo> NetworkTableInstance::GetEntryInfo(
+ const Twine& prefix, unsigned int types) const {
+ return ::nt::GetEntryInfo(m_handle, prefix, types);
+}
+
+inline void NetworkTableInstance::DeleteAllEntries() {
+ ::nt::DeleteAllEntries(m_handle);
+}
+
+inline void NetworkTableInstance::RemoveEntryListener(
+ NT_EntryListener entry_listener) {
+ ::nt::RemoveEntryListener(entry_listener);
+}
+
+inline bool NetworkTableInstance::WaitForEntryListenerQueue(double timeout) {
+ return ::nt::WaitForEntryListenerQueue(m_handle, timeout);
+}
+
+inline void NetworkTableInstance::RemoveConnectionListener(
+ NT_ConnectionListener conn_listener) {
+ ::nt::RemoveConnectionListener(conn_listener);
+}
+
+inline bool NetworkTableInstance::WaitForConnectionListenerQueue(
+ double timeout) {
+ return ::nt::WaitForConnectionListenerQueue(m_handle, timeout);
+}
+
+inline bool NetworkTableInstance::WaitForRpcCallQueue(double timeout) {
+ return ::nt::WaitForRpcCallQueue(m_handle, timeout);
+}
+
+inline void NetworkTableInstance::SetNetworkIdentity(const Twine& name) {
+ ::nt::SetNetworkIdentity(m_handle, name);
+}
+
+inline unsigned int NetworkTableInstance::GetNetworkMode() const {
+ return ::nt::GetNetworkMode(m_handle);
+}
+
+inline void NetworkTableInstance::StartServer(const Twine& persist_filename,
+ const char* listen_address,
+ unsigned int port) {
+ ::nt::StartServer(m_handle, persist_filename, listen_address, port);
+}
+
+inline void NetworkTableInstance::StopServer() { ::nt::StopServer(m_handle); }
+
+inline void NetworkTableInstance::StartClient() { ::nt::StartClient(m_handle); }
+
+inline void NetworkTableInstance::StartClient(const char* server_name,
+ unsigned int port) {
+ ::nt::StartClient(m_handle, server_name, port);
+}
+
+inline void NetworkTableInstance::StartClient(
+ ArrayRef<std::pair<StringRef, unsigned int>> servers) {
+ ::nt::StartClient(m_handle, servers);
+}
+
+inline void NetworkTableInstance::StartClientTeam(unsigned int team,
+ unsigned int port) {
+ ::nt::StartClientTeam(m_handle, team, port);
+}
+
+inline void NetworkTableInstance::StopClient() { ::nt::StopClient(m_handle); }
+
+inline void NetworkTableInstance::SetServer(const char* server_name,
+ unsigned int port) {
+ ::nt::SetServer(m_handle, server_name, port);
+}
+
+inline void NetworkTableInstance::SetServer(
+ ArrayRef<std::pair<StringRef, unsigned int>> servers) {
+ ::nt::SetServer(m_handle, servers);
+}
+
+inline void NetworkTableInstance::SetServerTeam(unsigned int team,
+ unsigned int port) {
+ ::nt::SetServerTeam(m_handle, team, port);
+}
+
+inline void NetworkTableInstance::StartDSClient(unsigned int port) {
+ ::nt::StartDSClient(m_handle, port);
+}
+
+inline void NetworkTableInstance::StopDSClient() {
+ ::nt::StopDSClient(m_handle);
+}
+
+inline void NetworkTableInstance::SetUpdateRate(double interval) {
+ ::nt::SetUpdateRate(m_handle, interval);
+}
+
+inline void NetworkTableInstance::Flush() const { ::nt::Flush(m_handle); }
+
+inline std::vector<ConnectionInfo> NetworkTableInstance::GetConnections()
+ const {
+ return ::nt::GetConnections(m_handle);
+}
+
+inline bool NetworkTableInstance::IsConnected() const {
+ return ::nt::IsConnected(m_handle);
+}
+
+inline const char* NetworkTableInstance::SavePersistent(
+ const Twine& filename) const {
+ return ::nt::SavePersistent(m_handle, filename);
+}
+
+inline const char* NetworkTableInstance::LoadPersistent(
+ const Twine& filename,
+ std::function<void(size_t line, const char* msg)> warn) {
+ return ::nt::LoadPersistent(m_handle, filename, warn);
+}
+
+inline const char* NetworkTableInstance::SaveEntries(
+ const Twine& filename, const Twine& prefix) const {
+ return ::nt::SaveEntries(m_handle, filename, prefix);
+}
+
+inline const char* NetworkTableInstance::LoadEntries(
+ const Twine& filename, const Twine& prefix,
+ std::function<void(size_t line, const char* msg)> warn) {
+ return ::nt::LoadEntries(m_handle, filename, prefix, warn);
+}
+
+inline NT_Logger NetworkTableInstance::AddLogger(
+ std::function<void(const LogMessage& msg)> func, unsigned int min_level,
+ unsigned int max_level) {
+ return ::nt::AddLogger(m_handle, func, min_level, max_level);
+}
+
+inline void NetworkTableInstance::RemoveLogger(NT_Logger logger) {
+ ::nt::RemoveLogger(logger);
+}
+
+inline bool NetworkTableInstance::WaitForLoggerQueue(double timeout) {
+ return ::nt::WaitForLoggerQueue(m_handle, timeout);
+}
+
+} // namespace nt
+
+#endif // NT_INSTANCE_INL_
diff --git a/ntcore/src/main/native/include/networktables/NetworkTableType.h b/ntcore/src/main/native/include/networktables/NetworkTableType.h
new file mode 100644
index 0000000..7ac3d9a
--- /dev/null
+++ b/ntcore/src/main/native/include/networktables/NetworkTableType.h
@@ -0,0 +1,33 @@
+/*----------------------------------------------------------------------------*/
+/* Copyright (c) 2017-2018 FIRST. All Rights Reserved. */
+/* Open Source Software - may be modified and shared by FRC teams. The code */
+/* must be accompanied by the FIRST BSD license file in the root directory of */
+/* the project. */
+/*----------------------------------------------------------------------------*/
+
+#ifndef NTCORE_NETWORKTABLES_NETWORKTABLETYPE_H_
+#define NTCORE_NETWORKTABLES_NETWORKTABLETYPE_H_
+
+#include "ntcore_c.h"
+
+namespace nt {
+
+/**
+ * NetworkTable entry type.
+ * @ingroup ntcore_cpp_api
+ */
+enum class NetworkTableType {
+ kUnassigned = NT_UNASSIGNED,
+ kBoolean = NT_BOOLEAN,
+ kDouble = NT_DOUBLE,
+ kString = NT_STRING,
+ kRaw = NT_RAW,
+ kBooleanArray = NT_BOOLEAN_ARRAY,
+ kDoubleArray = NT_DOUBLE_ARRAY,
+ kStringArray = NT_STRING_ARRAY,
+ kRpc = NT_RPC
+};
+
+} // namespace nt
+
+#endif // NTCORE_NETWORKTABLES_NETWORKTABLETYPE_H_
diff --git a/ntcore/src/main/native/include/networktables/NetworkTableValue.h b/ntcore/src/main/native/include/networktables/NetworkTableValue.h
new file mode 100644
index 0000000..3aa0c01
--- /dev/null
+++ b/ntcore/src/main/native/include/networktables/NetworkTableValue.h
@@ -0,0 +1,458 @@
+/*----------------------------------------------------------------------------*/
+/* Copyright (c) 2015-2018 FIRST. All Rights Reserved. */
+/* Open Source Software - may be modified and shared by FRC teams. The code */
+/* must be accompanied by the FIRST BSD license file in the root directory of */
+/* the project. */
+/*----------------------------------------------------------------------------*/
+
+#ifndef NTCORE_NETWORKTABLES_NETWORKTABLEVALUE_H_
+#define NTCORE_NETWORKTABLES_NETWORKTABLEVALUE_H_
+
+#include <stdint.h>
+
+#include <cassert>
+#include <memory>
+#include <string>
+#include <type_traits>
+#include <utility>
+#include <vector>
+
+#include <wpi/ArrayRef.h>
+#include <wpi/StringRef.h>
+#include <wpi/Twine.h>
+
+#include "ntcore_c.h"
+
+namespace nt {
+
+using wpi::ArrayRef;
+using wpi::StringRef;
+using wpi::Twine;
+
+/**
+ * A network table entry value.
+ * @ingroup ntcore_cpp_api
+ */
+class Value final {
+ struct private_init {};
+
+ public:
+ Value();
+ Value(NT_Type type, uint64_t time, const private_init&);
+ ~Value();
+
+ /**
+ * Get the data type.
+ *
+ * @return The type.
+ */
+ NT_Type type() const { return m_val.type; }
+
+ /**
+ * Get the data value stored.
+ *
+ * @return The type.
+ */
+ const NT_Value& value() const { return m_val; }
+
+ /**
+ * Get the creation time of the value.
+ *
+ * @return The time, in the units returned by nt::Now().
+ */
+ uint64_t last_change() const { return m_val.last_change; }
+
+ /**
+ * Get the creation time of the value.
+ *
+ * @return The time, in the units returned by nt::Now().
+ */
+ uint64_t time() const { return m_val.last_change; }
+
+ /**
+ * @{
+ * @name Type Checkers
+ */
+
+ /**
+ * Determine if entry value contains a value or is unassigned.
+ *
+ * @return True if the entry value contains a value.
+ */
+ bool IsValid() const { return m_val.type != NT_UNASSIGNED; }
+
+ /**
+ * Determine if entry value contains a boolean.
+ *
+ * @return True if the entry value is of boolean type.
+ */
+ bool IsBoolean() const { return m_val.type == NT_BOOLEAN; }
+
+ /**
+ * Determine if entry value contains a double.
+ *
+ * @return True if the entry value is of double type.
+ */
+ bool IsDouble() const { return m_val.type == NT_DOUBLE; }
+
+ /**
+ * Determine if entry value contains a string.
+ *
+ * @return True if the entry value is of string type.
+ */
+ bool IsString() const { return m_val.type == NT_STRING; }
+
+ /**
+ * Determine if entry value contains a raw.
+ *
+ * @return True if the entry value is of raw type.
+ */
+ bool IsRaw() const { return m_val.type == NT_RAW; }
+
+ /**
+ * Determine if entry value contains a rpc definition.
+ *
+ * @return True if the entry value is of rpc definition type.
+ */
+ bool IsRpc() const { return m_val.type == NT_RPC; }
+
+ /**
+ * Determine if entry value contains a boolean array.
+ *
+ * @return True if the entry value is of boolean array type.
+ */
+ bool IsBooleanArray() const { return m_val.type == NT_BOOLEAN_ARRAY; }
+
+ /**
+ * Determine if entry value contains a double array.
+ *
+ * @return True if the entry value is of double array type.
+ */
+ bool IsDoubleArray() const { return m_val.type == NT_DOUBLE_ARRAY; }
+
+ /**
+ * Determine if entry value contains a string array.
+ *
+ * @return True if the entry value is of string array type.
+ */
+ bool IsStringArray() const { return m_val.type == NT_STRING_ARRAY; }
+
+ /** @} */
+
+ /**
+ * @{
+ * @name Type-Safe Getters
+ */
+
+ /**
+ * Get the entry's boolean value.
+ *
+ * @return The boolean value.
+ */
+ bool GetBoolean() const {
+ assert(m_val.type == NT_BOOLEAN);
+ return m_val.data.v_boolean != 0;
+ }
+
+ /**
+ * Get the entry's double value.
+ *
+ * @return The double value.
+ */
+ double GetDouble() const {
+ assert(m_val.type == NT_DOUBLE);
+ return m_val.data.v_double;
+ }
+
+ /**
+ * Get the entry's string value.
+ *
+ * @return The string value.
+ */
+ StringRef GetString() const {
+ assert(m_val.type == NT_STRING);
+ return m_string;
+ }
+
+ /**
+ * Get the entry's raw value.
+ *
+ * @return The raw value.
+ */
+ StringRef GetRaw() const {
+ assert(m_val.type == NT_RAW);
+ return m_string;
+ }
+
+ /**
+ * Get the entry's rpc definition value.
+ *
+ * @return The rpc definition value.
+ */
+ StringRef GetRpc() const {
+ assert(m_val.type == NT_RPC);
+ return m_string;
+ }
+
+ /**
+ * Get the entry's boolean array value.
+ *
+ * @return The boolean array value.
+ */
+ ArrayRef<int> GetBooleanArray() const {
+ assert(m_val.type == NT_BOOLEAN_ARRAY);
+ return ArrayRef<int>(m_val.data.arr_boolean.arr,
+ m_val.data.arr_boolean.size);
+ }
+
+ /**
+ * Get the entry's double array value.
+ *
+ * @return The double array value.
+ */
+ ArrayRef<double> GetDoubleArray() const {
+ assert(m_val.type == NT_DOUBLE_ARRAY);
+ return ArrayRef<double>(m_val.data.arr_double.arr,
+ m_val.data.arr_double.size);
+ }
+
+ /**
+ * Get the entry's string array value.
+ *
+ * @return The string array value.
+ */
+ ArrayRef<std::string> GetStringArray() const {
+ assert(m_val.type == NT_STRING_ARRAY);
+ return m_string_array;
+ }
+
+ /** @} */
+
+ /**
+ * @{
+ * @name Factory functions
+ */
+
+ /**
+ * Creates a boolean entry value.
+ *
+ * @param value the value
+ * @param time if nonzero, the creation time to use (instead of the current
+ * time)
+ * @return The entry value
+ */
+ static std::shared_ptr<Value> MakeBoolean(bool value, uint64_t time = 0) {
+ auto val = std::make_shared<Value>(NT_BOOLEAN, time, private_init());
+ val->m_val.data.v_boolean = value;
+ return val;
+ }
+
+ /**
+ * Creates a double entry value.
+ *
+ * @param value the value
+ * @param time if nonzero, the creation time to use (instead of the current
+ * time)
+ * @return The entry value
+ */
+ static std::shared_ptr<Value> MakeDouble(double value, uint64_t time = 0) {
+ auto val = std::make_shared<Value>(NT_DOUBLE, time, private_init());
+ val->m_val.data.v_double = value;
+ return val;
+ }
+
+ /**
+ * Creates a string entry value.
+ *
+ * @param value the value
+ * @param time if nonzero, the creation time to use (instead of the current
+ * time)
+ * @return The entry value
+ */
+ static std::shared_ptr<Value> MakeString(const Twine& value,
+ uint64_t time = 0) {
+ auto val = std::make_shared<Value>(NT_STRING, time, private_init());
+ val->m_string = value.str();
+ val->m_val.data.v_string.str = const_cast<char*>(val->m_string.c_str());
+ val->m_val.data.v_string.len = val->m_string.size();
+ return val;
+ }
+
+/**
+ * Creates a string entry value.
+ *
+ * @param value the value
+ * @param time if nonzero, the creation time to use (instead of the current
+ * time)
+ * @return The entry value
+ */
+#ifdef _MSC_VER
+ template <typename T,
+ typename = std::enable_if_t<std::is_same<T, std::string>>>
+#else
+ template <typename T,
+ typename std::enable_if<std::is_same<T, std::string>::value>::type>
+#endif
+ static std::shared_ptr<Value> MakeString(T&& value, uint64_t time = 0) {
+ auto val = std::make_shared<Value>(NT_STRING, time, private_init());
+ val->m_string = std::move(value);
+ val->m_val.data.v_string.str = const_cast<char*>(val->m_string.c_str());
+ val->m_val.data.v_string.len = val->m_string.size();
+ return val;
+ }
+
+ /**
+ * Creates a raw entry value.
+ *
+ * @param value the value
+ * @param time if nonzero, the creation time to use (instead of the current
+ * time)
+ * @return The entry value
+ */
+ static std::shared_ptr<Value> MakeRaw(StringRef value, uint64_t time = 0) {
+ auto val = std::make_shared<Value>(NT_RAW, time, private_init());
+ val->m_string = value;
+ val->m_val.data.v_raw.str = const_cast<char*>(val->m_string.c_str());
+ val->m_val.data.v_raw.len = val->m_string.size();
+ return val;
+ }
+
+/**
+ * Creates a raw entry value.
+ *
+ * @param value the value
+ * @param time if nonzero, the creation time to use (instead of the current
+ * time)
+ * @return The entry value
+ */
+#ifdef _MSC_VER
+ template <typename T,
+ typename = std::enable_if_t<std::is_same<T, std::string>>>
+#else
+ template <typename T,
+ typename std::enable_if<std::is_same<T, std::string>::value>::type>
+#endif
+ static std::shared_ptr<Value> MakeRaw(T&& value, uint64_t time = 0) {
+ auto val = std::make_shared<Value>(NT_RAW, time, private_init());
+ val->m_string = std::move(value);
+ val->m_val.data.v_raw.str = const_cast<char*>(val->m_string.c_str());
+ val->m_val.data.v_raw.len = val->m_string.size();
+ return val;
+ }
+
+ /**
+ * Creates a rpc entry value.
+ *
+ * @param value the value
+ * @param time if nonzero, the creation time to use (instead of the current
+ * time)
+ * @return The entry value
+ */
+ static std::shared_ptr<Value> MakeRpc(StringRef value, uint64_t time = 0) {
+ auto val = std::make_shared<Value>(NT_RPC, time, private_init());
+ val->m_string = value;
+ val->m_val.data.v_raw.str = const_cast<char*>(val->m_string.c_str());
+ val->m_val.data.v_raw.len = val->m_string.size();
+ return val;
+ }
+
+ /**
+ * Creates a rpc entry value.
+ *
+ * @param value the value
+ * @param time if nonzero, the creation time to use (instead of the current
+ * time)
+ * @return The entry value
+ */
+ template <typename T>
+ static std::shared_ptr<Value> MakeRpc(T&& value, uint64_t time = 0) {
+ auto val = std::make_shared<Value>(NT_RPC, time, private_init());
+ val->m_string = std::move(value);
+ val->m_val.data.v_raw.str = const_cast<char*>(val->m_string.c_str());
+ val->m_val.data.v_raw.len = val->m_string.size();
+ return val;
+ }
+
+ /**
+ * Creates a boolean array entry value.
+ *
+ * @param value the value
+ * @param time if nonzero, the creation time to use (instead of the current
+ * time)
+ * @return The entry value
+ */
+ static std::shared_ptr<Value> MakeBooleanArray(ArrayRef<bool> value,
+ uint64_t time = 0);
+
+ /**
+ * Creates a boolean array entry value.
+ *
+ * @param value the value
+ * @param time if nonzero, the creation time to use (instead of the current
+ * time)
+ * @return The entry value
+ */
+ static std::shared_ptr<Value> MakeBooleanArray(ArrayRef<int> value,
+ uint64_t time = 0);
+
+ /**
+ * Creates a double array entry value.
+ *
+ * @param value the value
+ * @param time if nonzero, the creation time to use (instead of the current
+ * time)
+ * @return The entry value
+ */
+ static std::shared_ptr<Value> MakeDoubleArray(ArrayRef<double> value,
+ uint64_t time = 0);
+
+ /**
+ * Creates a string array entry value.
+ *
+ * @param value the value
+ * @param time if nonzero, the creation time to use (instead of the current
+ * time)
+ * @return The entry value
+ */
+ static std::shared_ptr<Value> MakeStringArray(ArrayRef<std::string> value,
+ uint64_t time = 0);
+
+ /**
+ * Creates a string array entry value.
+ *
+ * @param value the value
+ * @param time if nonzero, the creation time to use (instead of the current
+ * time)
+ * @return The entry value
+ *
+ * @note This function moves the values out of the vector.
+ */
+ static std::shared_ptr<Value> MakeStringArray(
+ std::vector<std::string>&& value, uint64_t time = 0);
+
+ /** @} */
+
+ Value(const Value&) = delete;
+ Value& operator=(const Value&) = delete;
+ friend bool operator==(const Value& lhs, const Value& rhs);
+
+ private:
+ NT_Value m_val;
+ std::string m_string;
+ std::vector<std::string> m_string_array;
+};
+
+bool operator==(const Value& lhs, const Value& rhs);
+inline bool operator!=(const Value& lhs, const Value& rhs) {
+ return !(lhs == rhs);
+}
+
+/**
+ * NetworkTable Value alias for similarity with Java.
+ * @ingroup ntcore_cpp_api
+ */
+typedef Value NetworkTableValue;
+
+} // namespace nt
+
+#endif // NTCORE_NETWORKTABLES_NETWORKTABLEVALUE_H_
diff --git a/ntcore/src/main/native/include/networktables/RpcCall.h b/ntcore/src/main/native/include/networktables/RpcCall.h
new file mode 100644
index 0000000..7d83140
--- /dev/null
+++ b/ntcore/src/main/native/include/networktables/RpcCall.h
@@ -0,0 +1,109 @@
+/*----------------------------------------------------------------------------*/
+/* Copyright (c) 2017-2018 FIRST. All Rights Reserved. */
+/* Open Source Software - may be modified and shared by FRC teams. The code */
+/* must be accompanied by the FIRST BSD license file in the root directory of */
+/* the project. */
+/*----------------------------------------------------------------------------*/
+
+#ifndef NTCORE_NETWORKTABLES_RPCCALL_H_
+#define NTCORE_NETWORKTABLES_RPCCALL_H_
+
+#include <string>
+#include <utility>
+
+#include "ntcore_c.h"
+
+namespace nt {
+
+class NetworkTableEntry;
+
+/**
+ * NetworkTables Remote Procedure Call
+ * @ingroup ntcore_cpp_api
+ */
+class RpcCall final {
+ public:
+ /**
+ * Construct invalid instance.
+ */
+ RpcCall() : m_entry(0), m_call(0) {}
+
+ /**
+ * Construct from native handles.
+ *
+ * @param entry Entry handle
+ * @param call Call handle
+ */
+ RpcCall(NT_Entry entry, NT_RpcCall call) : m_entry(entry), m_call(call) {}
+
+ RpcCall(RpcCall&& other);
+ RpcCall(const RpcCall&) = delete;
+ RpcCall& operator=(const RpcCall&) = delete;
+
+ /**
+ * Destructor. Cancels the result if no other action taken.
+ */
+ ~RpcCall();
+
+ /**
+ * Determines if the native handle is valid.
+ *
+ * @return True if the native handle is valid, false otherwise.
+ */
+ explicit operator bool() const { return m_call != 0; }
+
+ /**
+ * Get the RPC entry.
+ *
+ * @return NetworkTableEntry for the RPC.
+ */
+ NetworkTableEntry GetEntry() const;
+
+ /**
+ * Get the call native handle.
+ *
+ * @return Native handle.
+ */
+ NT_RpcCall GetCall() const { return m_call; }
+
+ /**
+ * Get the result (return value). This function blocks until
+ * the result is received.
+ *
+ * @param result received result (output)
+ * @return False on error, true otherwise.
+ */
+ bool GetResult(std::string* result);
+
+ /**
+ * Get the result (return value). This function blocks until
+ * the result is received or it times out.
+ *
+ * @param result received result (output)
+ * @param timeout timeout, in seconds
+ * @param timed_out true if the timeout period elapsed (output)
+ * @return False on error or timeout, true otherwise.
+ */
+ bool GetResult(std::string* result, double timeout, bool* timed_out);
+
+ /**
+ * Ignore the result. This function is non-blocking.
+ */
+ void CancelResult();
+
+ friend void swap(RpcCall& first, RpcCall& second) {
+ using std::swap;
+ swap(first.m_entry, second.m_entry);
+ swap(first.m_call, second.m_call);
+ }
+
+ private:
+ NT_Entry m_entry;
+ NT_RpcCall m_call;
+};
+
+} // namespace nt
+
+#include "networktables/RpcCall.inl"
+
+#endif // NTCORE_NETWORKTABLES_RPCCALL_H_
diff --git a/ntcore/src/main/native/include/networktables/RpcCall.inl b/ntcore/src/main/native/include/networktables/RpcCall.inl
new file mode 100644
index 0000000..d7dacf5
--- /dev/null
+++ b/ntcore/src/main/native/include/networktables/RpcCall.inl
@@ -0,0 +1,48 @@
+/*----------------------------------------------------------------------------*/
+/* Copyright (c) FIRST 2017. All Rights Reserved. */
+/* Open Source Software - may be modified and shared by FRC teams. The code */
+/* must be accompanied by the FIRST BSD license file in the root directory of */
+/* the project. */
+/*----------------------------------------------------------------------------*/
+
+#ifndef NT_RPCCALL_INL_
+#define NT_RPCCALL_INL_
+
+#include "ntcore_cpp.h"
+
+namespace nt {
+
+inline RpcCall::RpcCall(RpcCall&& other) : RpcCall() {
+ swap(*this, other);
+}
+
+inline RpcCall::~RpcCall() {
+ // automatically cancel result if user didn't request it
+ if (m_call != 0) CancelResult();
+}
+
+inline bool RpcCall::GetResult(std::string* result) {
+ if (GetRpcResult(m_entry, m_call, result)) {
+ m_call = 0;
+ return true;
+ }
+ return false;
+}
+
+inline bool RpcCall::GetResult(std::string* result, double timeout,
+ bool* timed_out) {
+ if (GetRpcResult(m_entry, m_call, result, timeout, timed_out)) {
+ m_call = 0;
+ return true;
+ }
+ return false;
+}
+
+inline void RpcCall::CancelResult() {
+ CancelRpcResult(m_entry, m_call);
+ m_call = 0;
+}
+
+} // namespace nt
+
+#endif // NT_RPCCALL_INL_
diff --git a/ntcore/src/main/native/include/networktables/TableEntryListener.h b/ntcore/src/main/native/include/networktables/TableEntryListener.h
new file mode 100644
index 0000000..c4552678
--- /dev/null
+++ b/ntcore/src/main/native/include/networktables/TableEntryListener.h
@@ -0,0 +1,45 @@
+/*----------------------------------------------------------------------------*/
+/* Copyright (c) 2017-2018 FIRST. All Rights Reserved. */
+/* Open Source Software - may be modified and shared by FRC teams. The code */
+/* must be accompanied by the FIRST BSD license file in the root directory of */
+/* the project. */
+/*----------------------------------------------------------------------------*/
+
+#ifndef NTCORE_NETWORKTABLES_TABLEENTRYLISTENER_H_
+#define NTCORE_NETWORKTABLES_TABLEENTRYLISTENER_H_
+
+#include <functional>
+#include <memory>
+
+#include <wpi/StringRef.h>
+
+namespace nt {
+
+class NetworkTable;
+class NetworkTableEntry;
+class Value;
+
+using wpi::StringRef;
+
+/**
+ * A listener that listens to changes in values in a NetworkTable.
+ *
+ * Called when a key-value pair is changed in a NetworkTable.
+ *
+ * @param table the table the key-value pair exists in
+ * @param key the key associated with the value that changed
+ * @param entry the entry associated with the value that changed
+ * @param value the new value
+ * @param flags update flags; for example, EntryListenerFlags.kNew if the key
+ * did not previously exist
+ *
+ * @ingroup ntcore_cpp_api
+ */
+typedef std::function<void(NetworkTable* table, StringRef name,
+ NetworkTableEntry entry,
+ std::shared_ptr<Value> value, int flags)>
+ TableEntryListener;
+
+} // namespace nt
+
+#endif // NTCORE_NETWORKTABLES_TABLEENTRYLISTENER_H_
diff --git a/ntcore/src/main/native/include/networktables/TableListener.h b/ntcore/src/main/native/include/networktables/TableListener.h
new file mode 100644
index 0000000..9940bad
--- /dev/null
+++ b/ntcore/src/main/native/include/networktables/TableListener.h
@@ -0,0 +1,39 @@
+/*----------------------------------------------------------------------------*/
+/* Copyright (c) 2017-2018 FIRST. All Rights Reserved. */
+/* Open Source Software - may be modified and shared by FRC teams. The code */
+/* must be accompanied by the FIRST BSD license file in the root directory of */
+/* the project. */
+/*----------------------------------------------------------------------------*/
+
+#ifndef NTCORE_NETWORKTABLES_TABLELISTENER_H_
+#define NTCORE_NETWORKTABLES_TABLELISTENER_H_
+
+#include <functional>
+#include <memory>
+
+#include <wpi/StringRef.h>
+
+namespace nt {
+
+class NetworkTable;
+
+using wpi::StringRef;
+
+/**
+ * A listener that listens to new sub-tables in a NetworkTable.
+ *
+ * Called when a new table is created.
+ *
+ * @param parent the parent of the table
+ * @param name the name of the new table
+ * @param table the new table
+ *
+ * @ingroup ntcore_cpp_api
+ */
+typedef std::function<void(NetworkTable* parent, StringRef name,
+ std::shared_ptr<NetworkTable> table)>
+ TableListener;
+
+} // namespace nt
+
+#endif // NTCORE_NETWORKTABLES_TABLELISTENER_H_
diff --git a/ntcore/src/main/native/include/ntcore.h b/ntcore/src/main/native/include/ntcore.h
new file mode 100644
index 0000000..ff0511a
--- /dev/null
+++ b/ntcore/src/main/native/include/ntcore.h
@@ -0,0 +1,19 @@
+/*----------------------------------------------------------------------------*/
+/* Copyright (c) 2015-2018 FIRST. All Rights Reserved. */
+/* Open Source Software - may be modified and shared by FRC teams. The code */
+/* must be accompanied by the FIRST BSD license file in the root directory of */
+/* the project. */
+/*----------------------------------------------------------------------------*/
+
+#ifndef NTCORE_NTCORE_H_
+#define NTCORE_NTCORE_H_
+
+/* C API */
+#include "ntcore_c.h"
+
+#ifdef __cplusplus
+/* C++ API */
+#include "ntcore_cpp.h"
+#endif /* __cplusplus */
+
+#endif // NTCORE_NTCORE_H_
diff --git a/ntcore/src/main/native/include/ntcore_c.h b/ntcore/src/main/native/include/ntcore_c.h
new file mode 100644
index 0000000..60773ea
--- /dev/null
+++ b/ntcore/src/main/native/include/ntcore_c.h
@@ -0,0 +1,2071 @@
+/*----------------------------------------------------------------------------*/
+/* Copyright (c) 2015-2018 FIRST. All Rights Reserved. */
+/* Open Source Software - may be modified and shared by FRC teams. The code */
+/* must be accompanied by the FIRST BSD license file in the root directory of */
+/* the project. */
+/*----------------------------------------------------------------------------*/
+
+#ifndef NTCORE_NTCORE_C_H_
+#define NTCORE_NTCORE_C_H_
+
+#include <stdint.h>
+
+#ifdef __cplusplus
+#include <cstddef>
+#else
+#include <stddef.h>
+#endif
+
+#include <wpi/deprecated.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * @defgroup ntcore_c_api ntcore C API
+ *
+ * Handle-based interface for C.
+ *
+ * @{
+ */
+
+/** Typedefs */
+typedef int NT_Bool;
+
+typedef unsigned int NT_Handle;
+typedef NT_Handle NT_ConnectionListener;
+typedef NT_Handle NT_ConnectionListenerPoller;
+typedef NT_Handle NT_Entry;
+typedef NT_Handle NT_EntryListener;
+typedef NT_Handle NT_EntryListenerPoller;
+typedef NT_Handle NT_Inst;
+typedef NT_Handle NT_Logger;
+typedef NT_Handle NT_LoggerPoller;
+typedef NT_Handle NT_RpcCall;
+typedef NT_Handle NT_RpcCallPoller;
+
+/** Default network tables port number */
+#define NT_DEFAULT_PORT 1735
+
+/** NetworkTables data types. */
+enum NT_Type {
+ NT_UNASSIGNED = 0,
+ NT_BOOLEAN = 0x01,
+ NT_DOUBLE = 0x02,
+ NT_STRING = 0x04,
+ NT_RAW = 0x08,
+ NT_BOOLEAN_ARRAY = 0x10,
+ NT_DOUBLE_ARRAY = 0x20,
+ NT_STRING_ARRAY = 0x40,
+ NT_RPC = 0x80
+};
+
+/** NetworkTables entry flags. */
+enum NT_EntryFlags { NT_PERSISTENT = 0x01 };
+
+/** NetworkTables logging levels. */
+enum NT_LogLevel {
+ NT_LOG_CRITICAL = 50,
+ NT_LOG_ERROR = 40,
+ NT_LOG_WARNING = 30,
+ NT_LOG_INFO = 20,
+ NT_LOG_DEBUG = 10,
+ NT_LOG_DEBUG1 = 9,
+ NT_LOG_DEBUG2 = 8,
+ NT_LOG_DEBUG3 = 7,
+ NT_LOG_DEBUG4 = 6
+};
+
+/** NetworkTables notifier kinds. */
+enum NT_NotifyKind {
+ NT_NOTIFY_NONE = 0,
+ NT_NOTIFY_IMMEDIATE = 0x01, /* initial listener addition */
+ NT_NOTIFY_LOCAL = 0x02, /* changed locally */
+ NT_NOTIFY_NEW = 0x04, /* newly created entry */
+ NT_NOTIFY_DELETE = 0x08, /* deleted */
+ NT_NOTIFY_UPDATE = 0x10, /* value changed */
+ NT_NOTIFY_FLAGS = 0x20 /* flags changed */
+};
+
+/** Client/server modes */
+enum NT_NetworkMode {
+ NT_NET_MODE_NONE = 0x00, /* not running */
+ NT_NET_MODE_SERVER = 0x01, /* running in server mode */
+ NT_NET_MODE_CLIENT = 0x02, /* running in client mode */
+ NT_NET_MODE_STARTING = 0x04, /* flag for starting (either client or server) */
+ NT_NET_MODE_FAILURE = 0x08, /* flag for failure (either client or server) */
+};
+
+/*
+ * Structures
+ */
+
+/** A NetworkTables string. */
+struct NT_String {
+ /**
+ * String contents (UTF-8).
+ * The string is NOT required to be zero-terminated.
+ * When returned by the library, this is zero-terminated and allocated with
+ * std::malloc().
+ */
+ char* str;
+
+ /**
+ * Length of the string in bytes. If the string happens to be zero
+ * terminated, this does not include the zero-termination.
+ */
+ size_t len;
+};
+
+/** NetworkTables Entry Value. Note this is a typed union. */
+struct NT_Value {
+ enum NT_Type type;
+ uint64_t last_change;
+ union {
+ NT_Bool v_boolean;
+ double v_double;
+ struct NT_String v_string;
+ struct NT_String v_raw;
+ struct {
+ NT_Bool* arr;
+ size_t size;
+ } arr_boolean;
+ struct {
+ double* arr;
+ size_t size;
+ } arr_double;
+ struct {
+ struct NT_String* arr;
+ size_t size;
+ } arr_string;
+ } data;
+};
+
+/** NetworkTables Entry Information */
+struct NT_EntryInfo {
+ /** Entry handle */
+ NT_Entry entry;
+
+ /** Entry name */
+ struct NT_String name;
+
+ /** Entry type */
+ enum NT_Type type;
+
+ /** Entry flags */
+ unsigned int flags;
+
+ /** Timestamp of last change to entry (type or value). */
+ uint64_t last_change;
+};
+
+/** NetworkTables Connection Information */
+struct NT_ConnectionInfo {
+ /**
+ * The remote identifier (as set on the remote node by
+ * NetworkTableInstance::SetNetworkIdentity() or nt::SetNetworkIdentity()).
+ */
+ struct NT_String remote_id;
+
+ /** The IP address of the remote node. */
+ struct NT_String remote_ip;
+
+ /** The port number of the remote node. */
+ unsigned int remote_port;
+
+ /**
+ * The last time any update was received from the remote node (same scale as
+ * returned by nt::Now()).
+ */
+ uint64_t last_update;
+
+ /**
+ * The protocol version being used for this connection. This in protocol
+ * layer format, so 0x0200 = 2.0, 0x0300 = 3.0).
+ */
+ unsigned int protocol_version;
+};
+
+/** NetworkTables RPC Version 1 Definition Parameter */
+struct NT_RpcParamDef {
+ struct NT_String name;
+ struct NT_Value def_value;
+};
+
+/** NetworkTables RPC Version 1 Definition Result */
+struct NT_RpcResultDef {
+ struct NT_String name;
+ enum NT_Type type;
+};
+
+/** NetworkTables RPC Version 1 Definition */
+struct NT_RpcDefinition {
+ unsigned int version;
+ struct NT_String name;
+ size_t num_params;
+ struct NT_RpcParamDef* params;
+ size_t num_results;
+ struct NT_RpcResultDef* results;
+};
+
+/** NetworkTables RPC Call Data */
+struct NT_RpcAnswer {
+ NT_Entry entry;
+ NT_RpcCall call;
+ struct NT_String name;
+ struct NT_String params;
+ struct NT_ConnectionInfo conn;
+};
+
+/** NetworkTables Entry Notification */
+struct NT_EntryNotification {
+ /** Listener that was triggered. */
+ NT_EntryListener listener;
+
+ /** Entry handle. */
+ NT_Entry entry;
+
+ /** Entry name. */
+ struct NT_String name;
+
+ /** The new value. */
+ struct NT_Value value;
+
+ /**
+ * Update flags. For example, NT_NOTIFY_NEW if the key did not previously
+ * exist.
+ */
+ unsigned int flags;
+};
+
+/** NetworkTables Connection Notification */
+struct NT_ConnectionNotification {
+ /** Listener that was triggered. */
+ NT_ConnectionListener listener;
+
+ /** True if event is due to connection being established. */
+ NT_Bool connected;
+
+ /** Connection info. */
+ struct NT_ConnectionInfo conn;
+};
+
+/** NetworkTables log message. */
+struct NT_LogMessage {
+ /** The logger that generated the message. */
+ NT_Logger logger;
+
+ /** Log level of the message. See NT_LogLevel. */
+ unsigned int level;
+
+ /** The filename of the source file that generated the message. */
+ const char* filename;
+
+ /** The line number in the source file that generated the message. */
+ unsigned int line;
+
+ /** The message. */
+ char* message;
+};
+
+/**
+ * @defgroup ntcore_instance_cfunc Instance Functions
+ * @{
+ */
+
+/**
+ * Get default instance.
+ * This is the instance used by non-handle-taking functions.
+ *
+ * @return Instance handle
+ */
+NT_Inst NT_GetDefaultInstance(void);
+
+/**
+ * Create an instance.
+ *
+ * @return Instance handle
+ */
+NT_Inst NT_CreateInstance(void);
+
+/**
+ * Destroy an instance.
+ * The default instance cannot be destroyed.
+ *
+ * @param inst Instance handle
+ */
+void NT_DestroyInstance(NT_Inst inst);
+
+/**
+ * Get instance handle from another handle.
+ *
+ * @param handle handle
+ * @return Instance handle
+ */
+NT_Inst NT_GetInstanceFromHandle(NT_Handle handle);
+
+/** @} */
+
+/**
+ * @defgroup ntcore_table_cfunc Table Functions
+ * @{
+ */
+
+/**
+ * Get Entry Handle.
+ *
+ * @param inst instance handle
+ * @param name entry name (UTF-8 string)
+ * @param name_len length of name in bytes
+ * @return entry handle
+ */
+NT_Entry NT_GetEntry(NT_Inst inst, const char* name, size_t name_len);
+
+/**
+ * Get Entry Handles.
+ *
+ * Returns an array of entry handles. The results are optionally
+ * filtered by string prefix and entry type to only return a subset of all
+ * entries.
+ *
+ * @param prefix entry name required prefix; only entries whose name
+ * starts with this string are returned
+ * @param prefix_len length of prefix in bytes
+ * @param types bitmask of NT_Type values; 0 is treated specially
+ * as a "don't care"
+ * @return Array of entry handles.
+ */
+NT_Entry* NT_GetEntries(NT_Inst inst, const char* prefix, size_t prefix_len,
+ unsigned int types, size_t* count);
+
+/**
+ * Gets the name of the specified entry.
+ * Returns an empty string if the handle is invalid.
+ *
+ * @param entry entry handle
+ * @param name_len length of the returned string (output parameter)
+ * @return Entry name
+ */
+char* NT_GetEntryName(NT_Entry entry, size_t* name_len);
+
+/**
+ * Gets the type for the specified key, or unassigned if non existent.
+ *
+ * @param entry entry handle
+ * @return Entry type
+ */
+enum NT_Type NT_GetEntryType(NT_Entry entry);
+
+/**
+ * Gets the last time the entry was changed.
+ * Returns 0 if the handle is invalid.
+ *
+ * @param entry entry handle
+ * @return Entry last change time
+ */
+uint64_t NT_GetEntryLastChange(NT_Entry entry);
+
+/**
+ * Get Entry Value.
+ *
+ * Returns copy of current entry value.
+ * Note that one of the type options is "unassigned".
+ *
+ * @param entry entry handle
+ * @param value storage for returned entry value
+ *
+ * It is the caller's responsibility to free value once it's no longer
+ * needed (the utility function NT_DisposeValue() is useful for this
+ * purpose).
+ */
+void NT_GetEntryValue(NT_Entry entry, struct NT_Value* value);
+
+/**
+ * Set Default Entry Value.
+ *
+ * Returns copy of current entry value if it exists.
+ * Otherwise, sets passed in value, and returns set value.
+ * Note that one of the type options is "unassigned".
+ *
+ * @param entry entry handle
+ * @param default_value value to be set if name does not exist
+ * @return 0 on error (value not set), 1 on success
+ */
+NT_Bool NT_SetDefaultEntryValue(NT_Entry entry,
+ const struct NT_Value* default_value);
+
+/**
+ * Set Entry Value.
+ *
+ * Sets new entry value. If type of new value differs from the type of the
+ * currently stored entry, returns error and does not update value.
+ *
+ * @param entry entry handle
+ * @param value new entry value
+ * @return 0 on error (type mismatch), 1 on success
+ */
+NT_Bool NT_SetEntryValue(NT_Entry entry, const struct NT_Value* value);
+
+/**
+ * Set Entry Type and Value.
+ *
+ * Sets new entry value. If type of new value differs from the type of the
+ * currently stored entry, the currently stored entry type is overridden
+ * (generally this will generate an Entry Assignment message).
+ *
+ * This is NOT the preferred method to update a value; generally
+ * NT_SetEntryValue() should be used instead, with appropriate error handling.
+ *
+ * @param entry entry handle
+ * @param value new entry value
+ */
+void NT_SetEntryTypeValue(NT_Entry entry, const struct NT_Value* value);
+
+/**
+ * Set Entry Flags.
+ *
+ * @param entry entry handle
+ * @param flags flags value (bitmask of NT_EntryFlags)
+ */
+void NT_SetEntryFlags(NT_Entry entry, unsigned int flags);
+
+/**
+ * Get Entry Flags.
+ *
+ * @param entry entry handle
+ * @return Flags value (bitmask of NT_EntryFlags)
+ */
+unsigned int NT_GetEntryFlags(NT_Entry entry);
+
+/**
+ * Delete Entry.
+ *
+ * Deletes an entry. This is a new feature in version 3.0 of the protocol,
+ * so this may not have an effect if any other node in the network is not
+ * version 3.0 or newer.
+ *
+ * Note: NT_GetConnections() can be used to determine the protocol version
+ * of direct remote connection(s), but this is not sufficient to determine
+ * if all nodes in the network are version 3.0 or newer.
+ *
+ * @param entry entry handle
+ */
+void NT_DeleteEntry(NT_Entry entry);
+
+/**
+ * Delete All Entries.
+ *
+ * Deletes ALL table entries. This is a new feature in version 3.0 of the
+ * so this may not have an effect if any other node in the network is not
+ * version 3.0 or newer.
+ *
+ * Note: NT_GetConnections() can be used to determine the protocol version
+ * of direct remote connection(s), but this is not sufficient to determine
+ * if all nodes in the network are version 3.0 or newer.
+ *
+ * @param inst instance handle
+ */
+void NT_DeleteAllEntries(NT_Inst inst);
+
+/**
+ * Get Entry Information.
+ *
+ * Returns an array of entry information (entry handle, name, entry type,
+ * and timestamp of last change to type/value). The results are optionally
+ * filtered by string prefix and entry type to only return a subset of all
+ * entries.
+ *
+ * @param inst instance handle
+ * @param prefix entry name required prefix; only entries whose name
+ * starts with this string are returned
+ * @param prefix_len length of prefix in bytes
+ * @param types bitmask of NT_Type values; 0 is treated specially
+ * as a "don't care"
+ * @param count output parameter; set to length of returned array
+ * @return Array of entry information.
+ */
+struct NT_EntryInfo* NT_GetEntryInfo(NT_Inst inst, const char* prefix,
+ size_t prefix_len, unsigned int types,
+ size_t* count);
+
+/**
+ * Get Entry Information.
+ *
+ * Returns information about an entry (name, entry type,
+ * and timestamp of last change to type/value).
+ *
+ * @param entry entry handle
+ * @param info entry information (output)
+ * @return True if successful, false on error.
+ */
+NT_Bool NT_GetEntryInfoHandle(NT_Entry entry, struct NT_EntryInfo* info);
+
+/** @} */
+
+/**
+ * @defgroup ntcore_entrylistener_cfunc Entry Listener Functions
+ * @{
+ */
+
+/**
+ * Entry listener callback function.
+ * Called when a key-value pair is changed.
+ *
+ * @param data data pointer provided to callback creation function
+ * @param event event information
+ */
+typedef void (*NT_EntryListenerCallback)(
+ void* data, const struct NT_EntryNotification* event);
+
+/**
+ * Add a listener for all entries starting with a certain prefix.
+ *
+ * @param inst instance handle
+ * @param prefix UTF-8 string prefix
+ * @param prefix_len length of prefix in bytes
+ * @param data data pointer to pass to callback
+ * @param callback listener to add
+ * @param flags NT_NotifyKind bitmask
+ * @return Listener handle
+ */
+NT_EntryListener NT_AddEntryListener(NT_Inst inst, const char* prefix,
+ size_t prefix_len, void* data,
+ NT_EntryListenerCallback callback,
+ unsigned int flags);
+
+/**
+ * Add a listener for a single entry.
+ *
+ * @param entry entry handle
+ * @param data data pointer to pass to callback
+ * @param callback listener to add
+ * @param flags NT_NotifyKind bitmask
+ * @return Listener handle
+ */
+NT_EntryListener NT_AddEntryListenerSingle(NT_Entry entry, void* data,
+ NT_EntryListenerCallback callback,
+ unsigned int flags);
+
+/**
+ * Create a entry listener poller.
+ *
+ * A poller provides a single queue of poll events. Events linked to this
+ * poller (using NT_AddPolledEntryListener()) will be stored in the queue and
+ * must be collected by calling NT_PollEntryListener().
+ * The returned handle must be destroyed with NT_DestroyEntryListenerPoller().
+ *
+ * @param inst instance handle
+ * @return poller handle
+ */
+NT_EntryListenerPoller NT_CreateEntryListenerPoller(NT_Inst inst);
+
+/**
+ * Destroy a entry listener poller. This will abort any blocked polling
+ * call and prevent additional events from being generated for this poller.
+ *
+ * @param poller poller handle
+ */
+void NT_DestroyEntryListenerPoller(NT_EntryListenerPoller poller);
+
+/**
+ * Create a polled entry listener.
+ * The caller is responsible for calling NT_PollEntryListener() to poll.
+ *
+ * @param poller poller handle
+ * @param prefix UTF-8 string prefix
+ * @param flags NT_NotifyKind bitmask
+ * @return Listener handle
+ */
+NT_EntryListener NT_AddPolledEntryListener(NT_EntryListenerPoller poller,
+ const char* prefix,
+ size_t prefix_len,
+ unsigned int flags);
+
+/**
+ * Create a polled entry listener.
+ * The caller is responsible for calling NT_PollEntryListener() to poll.
+ *
+ * @param poller poller handle
+ * @param prefix UTF-8 string prefix
+ * @param flags NT_NotifyKind bitmask
+ * @return Listener handle
+ */
+NT_EntryListener NT_AddPolledEntryListenerSingle(NT_EntryListenerPoller poller,
+ NT_Entry entry,
+ unsigned int flags);
+
+/**
+ * Get the next entry listener event. This blocks until the next event occurs.
+ *
+ * This is intended to be used with NT_AddPolledEntryListener(void); entry
+ * listeners created using NT_AddEntryListener() will not be serviced through
+ * this function.
+ *
+ * @param poller poller handle
+ * @param len length of returned array (output)
+ * @return Array of information on the entry listener events. Returns NULL if
+ * an erroroccurred (e.g. the instance was invalid or is shutting down).
+ */
+struct NT_EntryNotification* NT_PollEntryListener(NT_EntryListenerPoller poller,
+ size_t* len);
+
+/**
+ * Get the next entry listener event. This blocks until the next event occurs
+ * or it times out. This is intended to be used with
+ * NT_AddPolledEntryListener(); entry listeners created using
+ * NT_AddEntryListener() will not be serviced through this function.
+ *
+ * @param poller poller handle
+ * @param len length of returned array (output)
+ * @param timeout timeout, in seconds
+ * @param timed_out true if the timeout period elapsed (output)
+ * @return Array of information on the entry listener events. If NULL is
+ * returned and timed_out is also false, an error occurred (e.g. the
+ * instance was invalid or is shutting down).
+ */
+struct NT_EntryNotification* NT_PollEntryListenerTimeout(
+ NT_EntryListenerPoller poller, size_t* len, double timeout,
+ NT_Bool* timed_out);
+
+/**
+ * Cancel a PollEntryListener call. This wakes up a call to
+ * PollEntryListener for this poller and causes it to immediately return
+ * an empty array.
+ *
+ * @param poller poller handle
+ */
+void NT_CancelPollEntryListener(NT_EntryListenerPoller poller);
+
+/**
+ * Remove an entry listener.
+ *
+ * @param entry_listener Listener handle to remove
+ */
+void NT_RemoveEntryListener(NT_EntryListener entry_listener);
+
+/**
+ * Wait for the entry listener queue to be empty. This is primarily useful
+ * for deterministic testing. This blocks until either the entry listener
+ * queue is empty (e.g. there are no more events that need to be passed along
+ * to callbacks or poll queues) or the timeout expires.
+ *
+ * @param inst instance handle
+ * @param timeout timeout, in seconds. Set to 0 for non-blocking behavior,
+ * or a negative value to block indefinitely
+ * @return False if timed out, otherwise true.
+ */
+NT_Bool NT_WaitForEntryListenerQueue(NT_Inst inst, double timeout);
+
+/** @} */
+
+/**
+ * @defgroup ntcore_connectionlistener_cfunc Connection Listener Functions
+ * @{
+ */
+
+/**
+ * Connection listener callback function.
+ * Called when a network connection is made or lost.
+ *
+ * @param data data pointer provided to callback creation function
+ * @param event event info
+ */
+typedef void (*NT_ConnectionListenerCallback)(
+ void* data, const struct NT_ConnectionNotification* event);
+
+/**
+ * Add a connection listener.
+ *
+ * @param inst instance handle
+ * @param data data pointer to pass to callback
+ * @param callback listener to add
+ * @param immediate_notify notify listener of all existing connections
+ * @return Listener handle
+ */
+NT_ConnectionListener NT_AddConnectionListener(
+ NT_Inst inst, void* data, NT_ConnectionListenerCallback callback,
+ NT_Bool immediate_notify);
+
+/**
+ * Create a connection listener poller.
+ * A poller provides a single queue of poll events. Events linked to this
+ * poller (using NT_AddPolledConnectionListener()) will be stored in the queue
+ * and must be collected by calling NT_PollConnectionListener().
+ * The returned handle must be destroyed with
+ * NT_DestroyConnectionListenerPoller().
+ *
+ * @param inst instance handle
+ * @return poller handle
+ */
+NT_ConnectionListenerPoller NT_CreateConnectionListenerPoller(NT_Inst inst);
+
+/**
+ * Destroy a connection listener poller. This will abort any blocked polling
+ * call and prevent additional events from being generated for this poller.
+ *
+ * @param poller poller handle
+ */
+void NT_DestroyConnectionListenerPoller(NT_ConnectionListenerPoller poller);
+
+/**
+ * Create a polled connection listener.
+ * The caller is responsible for calling NT_PollConnectionListener() to poll.
+ *
+ * @param poller poller handle
+ * @param immediate_notify notify listener of all existing connections
+ */
+NT_ConnectionListener NT_AddPolledConnectionListener(
+ NT_ConnectionListenerPoller poller, NT_Bool immediate_notify);
+
+/**
+ * Get the next connection event. This blocks until the next connect or
+ * disconnect occurs. This is intended to be used with
+ * NT_AddPolledConnectionListener(); connection listeners created using
+ * NT_AddConnectionListener() will not be serviced through this function.
+ *
+ * @param poller poller handle
+ * @param len length of returned array (output)
+ * @return Array of information on the connection events. Only returns NULL
+ * if an error occurred (e.g. the instance was invalid or is shutting
+ * down).
+ */
+struct NT_ConnectionNotification* NT_PollConnectionListener(
+ NT_ConnectionListenerPoller poller, size_t* len);
+
+/**
+ * Get the next connection event. This blocks until the next connect or
+ * disconnect occurs or it times out. This is intended to be used with
+ * NT_AddPolledConnectionListener(); connection listeners created using
+ * NT_AddConnectionListener() will not be serviced through this function.
+ *
+ * @param poller poller handle
+ * @param len length of returned array (output)
+ * @param timeout timeout, in seconds
+ * @param timed_out true if the timeout period elapsed (output)
+ * @return Array of information on the connection events. If NULL is returned
+ * and timed_out is also false, an error occurred (e.g. the instance
+ * was invalid or is shutting down).
+ */
+struct NT_ConnectionNotification* NT_PollConnectionListenerTimeout(
+ NT_ConnectionListenerPoller poller, size_t* len, double timeout,
+ NT_Bool* timed_out);
+
+/**
+ * Cancel a PollConnectionListener call. This wakes up a call to
+ * PollConnectionListener for this poller and causes it to immediately return
+ * an empty array.
+ *
+ * @param poller poller handle
+ */
+void NT_CancelPollConnectionListener(NT_ConnectionListenerPoller poller);
+
+/**
+ * Remove a connection listener.
+ *
+ * @param conn_listener Listener handle to remove
+ */
+void NT_RemoveConnectionListener(NT_ConnectionListener conn_listener);
+
+/**
+ * Wait for the connection listener queue to be empty. This is primarily useful
+ * for deterministic testing. This blocks until either the connection listener
+ * queue is empty (e.g. there are no more events that need to be passed along
+ * to callbacks or poll queues) or the timeout expires.
+ *
+ * @param inst instance handle
+ * @param timeout timeout, in seconds. Set to 0 for non-blocking behavior,
+ * or a negative value to block indefinitely
+ * @return False if timed out, otherwise true.
+ */
+NT_Bool NT_WaitForConnectionListenerQueue(NT_Inst inst, double timeout);
+
+/** @} */
+
+/**
+ * @defgroup ntcore_rpc_cfunc Remote Procedure Call Functions
+ * @{
+ */
+
+/**
+ * Remote Procedure Call (RPC) callback function.
+ *
+ * @param data data pointer provided to NT_CreateRpc()
+ * @param call call information
+ *
+ * Note: NT_PostRpcResponse() must be called by the callback to provide a
+ * response to the call.
+ */
+typedef void (*NT_RpcCallback)(void* data, const struct NT_RpcAnswer* call);
+
+/**
+ * Create a callback-based RPC entry point. Only valid to use on the server.
+ * The callback function will be called when the RPC is called.
+ *
+ * @param entry entry handle of RPC entry
+ * @param def RPC definition
+ * @param def_len length of def in bytes
+ * @param data data pointer to pass to callback function
+ * @param callback callback function
+ */
+void NT_CreateRpc(NT_Entry entry, const char* def, size_t def_len, void* data,
+ NT_RpcCallback callback);
+
+/**
+ * Create a RPC call poller. Only valid to use on the server.
+ *
+ * A poller provides a single queue of poll events. Events linked to this
+ * poller (using NT_CreatePolledRpc()) will be stored in the queue and must be
+ * collected by calling NT_PollRpc() or NT_PollRpcTimeout().
+ * The returned handle must be destroyed with NT_DestroyRpcCallPoller().
+ *
+ * @param inst instance handle
+ * @return poller handle
+ */
+NT_RpcCallPoller NT_CreateRpcCallPoller(NT_Inst inst);
+
+/**
+ * Destroy a RPC call poller. This will abort any blocked polling call and
+ * prevent additional events from being generated for this poller.
+ *
+ * @param poller poller handle
+ */
+void NT_DestroyRpcCallPoller(NT_RpcCallPoller poller);
+
+/**
+ * Create a polled RPC entry point. Only valid to use on the server.
+ *
+ * The caller is responsible for calling NT_PollRpc() or NT_PollRpcTimeout()
+ * to poll for servicing incoming RPC calls.
+ *
+ * @param entry entry handle of RPC entry
+ * @param def RPC definition
+ * @param def_len length of def in bytes
+ * @param poller poller handle
+ */
+void NT_CreatePolledRpc(NT_Entry entry, const char* def, size_t def_len,
+ NT_RpcCallPoller poller);
+
+/**
+ * Get the next incoming RPC call. This blocks until the next incoming RPC
+ * call is received. This is intended to be used with NT_CreatePolledRpc(void);
+ * RPC calls created using NT_CreateRpc() will not be serviced through this
+ * function. Upon successful return, NT_PostRpcResponse() must be called to
+ * send the return value to the caller. The returned array must be freed
+ * using NT_DisposeRpcAnswerArray().
+ *
+ * @param poller poller handle
+ * @param len length of returned array (output)
+ * @return Array of RPC call information. Only returns NULL if an error
+ * occurred (e.g. the instance was invalid or is shutting down).
+ */
+struct NT_RpcAnswer* NT_PollRpc(NT_RpcCallPoller poller, size_t* len);
+
+/**
+ * Get the next incoming RPC call. This blocks until the next incoming RPC
+ * call is received or it times out. This is intended to be used with
+ * NT_CreatePolledRpc(); RPC calls created using NT_CreateRpc() will not be
+ * serviced through this function. Upon successful return,
+ * NT_PostRpcResponse() must be called to send the return value to the caller.
+ * The returned array must be freed using NT_DisposeRpcAnswerArray().
+ *
+ * @param poller poller handle
+ * @param len length of returned array (output)
+ * @param timeout timeout, in seconds
+ * @param timed_out true if the timeout period elapsed (output)
+ * @return Array of RPC call information. If NULL is returned and timed_out
+ * is also false, an error occurred (e.g. the instance was invalid or
+ * is shutting down).
+ */
+struct NT_RpcAnswer* NT_PollRpcTimeout(NT_RpcCallPoller poller, size_t* len,
+ double timeout, NT_Bool* timed_out);
+
+/**
+ * Cancel a PollRpc call. This wakes up a call to PollRpc for this poller
+ * and causes it to immediately return an empty array.
+ *
+ * @param poller poller handle
+ */
+void NT_CancelPollRpc(NT_RpcCallPoller poller);
+
+/**
+ * Wait for the incoming RPC call queue to be empty. This is primarily useful
+ * for deterministic testing. This blocks until either the RPC call
+ * queue is empty (e.g. there are no more events that need to be passed along
+ * to callbacks or poll queues) or the timeout expires.
+ *
+ * @param inst instance handle
+ * @param timeout timeout, in seconds. Set to 0 for non-blocking behavior,
+ * or a negative value to block indefinitely
+ * @return False if timed out, otherwise true.
+ */
+NT_Bool NT_WaitForRpcCallQueue(NT_Inst inst, double timeout);
+
+/**
+ * Post RPC response (return value) for a polled RPC.
+ *
+ * The rpc and call parameters should come from the NT_RpcAnswer returned
+ * by NT_PollRpc().
+ *
+ * @param entry entry handle of RPC entry (from NT_RpcAnswer)
+ * @param call RPC call handle (from NT_RpcAnswer)
+ * @param result result raw data that will be provided to remote caller
+ * @param result_len length of result in bytes
+ * @return true if the response was posted, otherwise false
+ */
+NT_Bool NT_PostRpcResponse(NT_Entry entry, NT_RpcCall call, const char* result,
+ size_t result_len);
+
+/**
+ * Call a RPC function. May be used on either the client or server.
+ *
+ * This function is non-blocking. Either NT_GetRpcResult() or
+ * NT_CancelRpcResult() must be called to either get or ignore the result of
+ * the call.
+ *
+ * @param entry entry handle of RPC entry
+ * @param params parameter
+ * @param params_len length of param in bytes
+ * @return RPC call handle (for use with NT_GetRpcResult() or
+ * NT_CancelRpcResult()).
+ */
+NT_RpcCall NT_CallRpc(NT_Entry entry, const char* params, size_t params_len);
+
+/**
+ * Get the result (return value) of a RPC call. This function blocks until
+ * the result is received.
+ *
+ * @param entry entry handle of RPC entry
+ * @param call RPC call handle returned by NT_CallRpc()
+ * @param result_len length of returned result in bytes
+ * @return NULL on error, or result.
+ */
+char* NT_GetRpcResult(NT_Entry entry, NT_RpcCall call, size_t* result_len);
+
+/**
+ * Get the result (return value) of a RPC call. This function blocks until
+ * the result is received or it times out.
+ *
+ * @param entry entry handle of RPC entry
+ * @param call RPC call handle returned by NT_CallRpc()
+ * @param result_len length of returned result in bytes
+ * @param timeout timeout, in seconds
+ * @param timed_out true if the timeout period elapsed (output)
+ * @return NULL on error or timeout, or result.
+ */
+char* NT_GetRpcResultTimeout(NT_Entry entry, NT_RpcCall call,
+ size_t* result_len, double timeout,
+ NT_Bool* timed_out);
+
+/**
+ * Ignore the result of a RPC call. This function is non-blocking.
+ *
+ * @param entry entry handle of RPC entry
+ * @param call RPC call handle returned by NT_CallRpc()
+ */
+void NT_CancelRpcResult(NT_Entry entry, NT_RpcCall call);
+
+/**
+ * Pack a RPC version 1 definition.
+ *
+ * @param def RPC version 1 definition
+ * @param packed_len length of return value in bytes
+ * @return Raw packed bytes. Use C standard library std::free() to release.
+ */
+char* NT_PackRpcDefinition(const struct NT_RpcDefinition* def,
+ size_t* packed_len);
+
+/**
+ * Unpack a RPC version 1 definition. This can be used for introspection or
+ * validation.
+ *
+ * @param packed raw packed bytes
+ * @param packed_len length of packed in bytes
+ * @param def RPC version 1 definition (output)
+ * @return True if successfully unpacked, false otherwise.
+ */
+NT_Bool NT_UnpackRpcDefinition(const char* packed, size_t packed_len,
+ struct NT_RpcDefinition* def);
+
+/**
+ * Pack RPC values as required for RPC version 1 definition messages.
+ *
+ * @param values array of values to pack
+ * @param values_len length of values
+ * @param packed_len length of return value in bytes
+ * @return Raw packed bytes. Use C standard library std::free() to release.
+ */
+char* NT_PackRpcValues(const struct NT_Value** values, size_t values_len,
+ size_t* packed_len);
+
+/**
+ * Unpack RPC values as required for RPC version 1 definition messages.
+ *
+ * @param packed raw packed bytes
+ * @param packed_len length of packed in bytes
+ * @param types array of data types (as provided in the RPC definition)
+ * @param types_len length of types
+ * @return Array of NT_Value's.
+ */
+struct NT_Value** NT_UnpackRpcValues(const char* packed, size_t packed_len,
+ const enum NT_Type* types,
+ size_t types_len);
+
+/** @} */
+
+/**
+ * @defgroup ntcore_network_cfunc Client/Server Functions
+ * @{
+ */
+
+/**
+ * Set the network identity of this node.
+ * This is the name used during the initial connection handshake, and is
+ * visible through NT_ConnectionInfo on the remote node.
+ *
+ * @param inst instance handle
+ * @param name identity to advertise
+ * @param name_len length of name in bytes
+ */
+void NT_SetNetworkIdentity(NT_Inst inst, const char* name, size_t name_len);
+
+/**
+ * Get the current network mode.
+ *
+ * @param inst instance handle
+ * @return Bitmask of NT_NetworkMode.
+ */
+unsigned int NT_GetNetworkMode(NT_Inst inst);
+
+/**
+ * Starts a server using the specified filename, listening address, and port.
+ *
+ * @param inst instance handle
+ * @param persist_filename the name of the persist file to use (UTF-8 string,
+ * null terminated)
+ * @param listen_address the address to listen on, or null to listen on any
+ * address. (UTF-8 string, null terminated)
+ * @param port port to communicate over.
+ */
+void NT_StartServer(NT_Inst inst, const char* persist_filename,
+ const char* listen_address, unsigned int port);
+
+/**
+ * Stops the server if it is running.
+ *
+ * @param inst instance handle
+ */
+void NT_StopServer(NT_Inst inst);
+
+/**
+ * Starts a client. Use NT_SetServer to set the server name and port.
+ *
+ * @param inst instance handle
+ */
+void NT_StartClientNone(NT_Inst inst);
+
+/**
+ * Starts a client using the specified server and port
+ *
+ * @param inst instance handle
+ * @param server_name server name (UTF-8 string, null terminated)
+ * @param port port to communicate over
+ */
+void NT_StartClient(NT_Inst inst, const char* server_name, unsigned int port);
+
+/**
+ * Starts a client using the specified (server, port) combinations. The
+ * client will attempt to connect to each server in round robin fashion.
+ *
+ * @param inst instance handle
+ * @param count length of the server_names and ports arrays
+ * @param server_names array of server names (each a UTF-8 string, null
+ * terminated)
+ * @param ports array of ports to communicate over (one for each server)
+ */
+void NT_StartClientMulti(NT_Inst inst, size_t count, const char** server_names,
+ const unsigned int* ports);
+
+/**
+ * Starts a client using commonly known robot addresses for the specified team.
+ *
+ * @param inst instance handle
+ * @param team team number
+ * @param port port to communicate over
+ */
+void NT_StartClientTeam(NT_Inst inst, unsigned int team, unsigned int port);
+
+/**
+ * Stops the client if it is running.
+ *
+ * @param inst instance handle
+ */
+void NT_StopClient(NT_Inst inst);
+
+/**
+ * Sets server address and port for client (without restarting client).
+ *
+ * @param inst instance handle
+ * @param server_name server name (UTF-8 string, null terminated)
+ * @param port port to communicate over
+ */
+void NT_SetServer(NT_Inst inst, const char* server_name, unsigned int port);
+
+/**
+ * Sets server addresses for client (without restarting client).
+ * The client will attempt to connect to each server in round robin fashion.
+ *
+ * @param inst instance handle
+ * @param count length of the server_names and ports arrays
+ * @param server_names array of server names (each a UTF-8 string, null
+ * terminated)
+ * @param ports array of ports to communicate over (one for each server)
+ */
+void NT_SetServerMulti(NT_Inst inst, size_t count, const char** server_names,
+ const unsigned int* ports);
+
+/**
+ * Sets server addresses and port for client (without restarting client).
+ * Connects using commonly known robot addresses for the specified team.
+ *
+ * @param inst instance handle
+ * @param team team number
+ * @param port port to communicate over
+ */
+void NT_SetServerTeam(NT_Inst inst, unsigned int team, unsigned int port);
+
+/**
+ * Starts requesting server address from Driver Station.
+ * This connects to the Driver Station running on localhost to obtain the
+ * server IP address.
+ *
+ * @param inst instance handle
+ * @param port server port to use in combination with IP from DS
+ */
+void NT_StartDSClient(NT_Inst inst, unsigned int port);
+
+/**
+ * Stops requesting server address from Driver Station.
+ *
+ * @param inst instance handle
+ */
+void NT_StopDSClient(NT_Inst inst);
+
+/**
+ * Set the periodic update rate.
+ * Sets how frequently updates are sent to other nodes over the network.
+ *
+ * @param inst instance handle
+ * @param interval update interval in seconds (range 0.01 to 1.0)
+ */
+void NT_SetUpdateRate(NT_Inst inst, double interval);
+
+/**
+ * Flush Entries.
+ *
+ * Forces an immediate flush of all local entry changes to network.
+ * Normally this is done on a regularly scheduled interval (see
+ * NT_SetUpdateRate()).
+ *
+ * Note: flushes are rate limited to avoid excessive network traffic. If
+ * the time between calls is too short, the flush will occur after the minimum
+ * time elapses (rather than immediately).
+ *
+ * @param inst instance handle
+ */
+void NT_Flush(NT_Inst inst);
+
+/**
+ * Get information on the currently established network connections.
+ * If operating as a client, this will return either zero or one values.
+ *
+ * @param inst instance handle
+ * @param count returns the number of elements in the array
+ * @return array of connection information
+ *
+ * It is the caller's responsibility to free the array. The
+ * NT_DisposeConnectionInfoArray function is useful for this purpose.
+ */
+struct NT_ConnectionInfo* NT_GetConnections(NT_Inst inst, size_t* count);
+
+/**
+ * Return whether or not the instance is connected to another node.
+ *
+ * @param inst instance handle
+ * @return True if connected.
+ */
+NT_Bool NT_IsConnected(NT_Inst inst);
+
+/** @} */
+
+/**
+ * @defgroup ntcore_file_cfunc File Save/Load Functions
+ * @{
+ */
+
+/**
+ * Save persistent values to a file. The server automatically does this,
+ * but this function provides a way to save persistent values in the same
+ * format to a file on either a client or a server.
+ *
+ * @param inst instance handle
+ * @param filename filename
+ * @return error string, or NULL if successful
+ */
+const char* NT_SavePersistent(NT_Inst inst, const char* filename);
+
+/**
+ * Load persistent values from a file. The server automatically does this
+ * at startup, but this function provides a way to restore persistent values
+ * in the same format from a file at any time on either a client or a server.
+ *
+ * @param inst instance handle
+ * @param filename filename
+ * @param warn callback function for warnings
+ * @return error string, or NULL if successful
+ */
+const char* NT_LoadPersistent(NT_Inst inst, const char* filename,
+ void (*warn)(size_t line, const char* msg));
+
+/**
+ * Save table values to a file. The file format used is identical to
+ * that used for SavePersistent.
+ *
+ * @param inst instance handle
+ * @param filename filename
+ * @param prefix save only keys starting with this prefix
+ * @param prefix_len length of prefix in bytes
+ * @return error string, or nullptr if successful
+ */
+const char* NT_SaveEntries(NT_Inst inst, const char* filename,
+ const char* prefix, size_t prefix_len);
+
+/**
+ * Load table values from a file. The file format used is identical to
+ * that used for SavePersistent / LoadPersistent.
+ *
+ * @param inst instance handle
+ * @param filename filename
+ * @param prefix load only keys starting with this prefix
+ * @param prefix_len length of prefix in bytes
+ * @param warn callback function for warnings
+ * @return error string, or nullptr if successful
+ */
+const char* NT_LoadEntries(NT_Inst inst, const char* filename,
+ const char* prefix, size_t prefix_len,
+ void (*warn)(size_t line, const char* msg));
+
+/** @} */
+
+/**
+ * @defgroup ntcore_utility_cfunc Utility Functions
+ * @{
+ */
+
+/**
+ * Frees value memory.
+ *
+ * @param value value to free
+ */
+void NT_DisposeValue(struct NT_Value* value);
+
+/**
+ * Initializes a NT_Value.
+ * Sets type to NT_UNASSIGNED and clears rest of struct.
+ *
+ * @param value value to initialize
+ */
+void NT_InitValue(struct NT_Value* value);
+
+/**
+ * Frees string memory.
+ *
+ * @param str string to free
+ */
+void NT_DisposeString(struct NT_String* str);
+
+/**
+ * Initializes a NT_String.
+ * Sets length to zero and pointer to null.
+ *
+ * @param str string to initialize
+ */
+void NT_InitString(struct NT_String* str);
+
+/**
+ * Disposes an entry handle array.
+ *
+ * @param arr pointer to the array to dispose
+ * @param count number of elements in the array
+ */
+void NT_DisposeEntryArray(NT_Entry* arr, size_t count);
+
+/**
+ * Disposes a connection info array.
+ *
+ * @param arr pointer to the array to dispose
+ * @param count number of elements in the array
+ */
+void NT_DisposeConnectionInfoArray(struct NT_ConnectionInfo* arr, size_t count);
+
+/**
+ * Disposes an entry info array.
+ *
+ * @param arr pointer to the array to dispose
+ * @param count number of elements in the array
+ */
+void NT_DisposeEntryInfoArray(struct NT_EntryInfo* arr, size_t count);
+
+/**
+ * Disposes a single entry info (as returned by NT_GetEntryInfoHandle).
+ *
+ * @param info pointer to the info to dispose
+ */
+void NT_DisposeEntryInfo(struct NT_EntryInfo* info);
+
+/**
+ * Disposes a Rpc Definition structure.
+ *
+ * @param def pointer to the struct to dispose
+ */
+void NT_DisposeRpcDefinition(struct NT_RpcDefinition* def);
+
+/**
+ * Disposes a Rpc Answer array.
+ *
+ * @param arr pointer to the array to dispose
+ * @param count number of elements in the array
+ */
+void NT_DisposeRpcAnswerArray(struct NT_RpcAnswer* arr, size_t count);
+
+/**
+ * Disposes a Rpc Answer structure.
+ *
+ * @param answer pointer to the struct to dispose
+ */
+void NT_DisposeRpcAnswer(struct NT_RpcAnswer* answer);
+
+/**
+ * Disposes an entry notification array.
+ *
+ * @param arr pointer to the array to dispose
+ * @param count number of elements in the array
+ */
+void NT_DisposeEntryNotificationArray(struct NT_EntryNotification* arr,
+ size_t count);
+
+/**
+ * Disposes a single entry notification.
+ *
+ * @param info pointer to the info to dispose
+ */
+void NT_DisposeEntryNotification(struct NT_EntryNotification* info);
+
+/**
+ * Disposes a connection notification array.
+ *
+ * @param arr pointer to the array to dispose
+ * @param count number of elements in the array
+ */
+void NT_DisposeConnectionNotificationArray(
+ struct NT_ConnectionNotification* arr, size_t count);
+
+/**
+ * Disposes a single connection notification.
+ *
+ * @param info pointer to the info to dispose
+ */
+void NT_DisposeConnectionNotification(struct NT_ConnectionNotification* info);
+
+/**
+ * Disposes a log message array.
+ *
+ * @param arr pointer to the array to dispose
+ * @param count number of elements in the array
+ */
+void NT_DisposeLogMessageArray(struct NT_LogMessage* arr, size_t count);
+
+/**
+ * Disposes a single log message.
+ *
+ * @param info pointer to the info to dispose
+ */
+void NT_DisposeLogMessage(struct NT_LogMessage* info);
+
+/**
+ * Returns monotonic current time in 1 us increments.
+ * This is the same time base used for entry and connection timestamps.
+ * This function is a compatibility wrapper around WPI_Now().
+ *
+ * @return Timestamp
+ */
+uint64_t NT_Now(void);
+
+/** @} */
+
+/**
+ * @defgroup ntcore_logger_cfunc Logger Functions
+ * @{
+ */
+
+/**
+ * Log function.
+ *
+ * @param data data pointer passed to NT_AddLogger()
+ * @param msg message information
+ */
+typedef void (*NT_LogFunc)(void* data, const struct NT_LogMessage* msg);
+
+/**
+ * Add logger callback function. By default, log messages are sent to stderr;
+ * this function sends log messages to the provided callback function instead.
+ * The callback function will only be called for log messages with level
+ * greater than or equal to min_level and less than or equal to max_level;
+ * messages outside this range will be silently ignored.
+ *
+ * @param inst instance handle
+ * @param data data pointer to pass to func
+ * @param func log callback function
+ * @param min_level minimum log level
+ * @param max_level maximum log level
+ * @return Logger handle
+ */
+NT_Logger NT_AddLogger(NT_Inst inst, void* data, NT_LogFunc func,
+ unsigned int min_level, unsigned int max_level);
+
+/**
+ * Create a log poller. A poller provides a single queue of poll events.
+ * The returned handle must be destroyed with NT_DestroyLoggerPoller().
+ *
+ * @param inst instance handle
+ * @return poller handle
+ */
+NT_LoggerPoller NT_CreateLoggerPoller(NT_Inst inst);
+
+/**
+ * Destroy a log poller. This will abort any blocked polling call and prevent
+ * additional events from being generated for this poller.
+ *
+ * @param poller poller handle
+ */
+void NT_DestroyLoggerPoller(NT_LoggerPoller poller);
+
+/**
+ * Set the log level for a log poller. Events will only be generated for
+ * log messages with level greater than or equal to min_level and less than or
+ * equal to max_level; messages outside this range will be silently ignored.
+ *
+ * @param poller poller handle
+ * @param min_level minimum log level
+ * @param max_level maximum log level
+ * @return Logger handle
+ */
+NT_Logger NT_AddPolledLogger(NT_LoggerPoller poller, unsigned int min_level,
+ unsigned int max_level);
+
+/**
+ * Get the next log event. This blocks until the next log occurs.
+ *
+ * @param poller poller handle
+ * @param len length of returned array (output)
+ * @return Array of information on the log events. Only returns NULL if an
+ * error occurred (e.g. the instance was invalid or is shutting down).
+ */
+struct NT_LogMessage* NT_PollLogger(NT_LoggerPoller poller, size_t* len);
+
+/**
+ * Get the next log event. This blocks until the next log occurs or it times
+ * out.
+ *
+ * @param poller poller handle
+ * @param len length of returned array (output)
+ * @param timeout timeout, in seconds
+ * @param timed_out true if the timeout period elapsed (output)
+ * @return Array of information on the log events. If NULL is returned and
+ * timed_out is also false, an error occurred (e.g. the instance was
+ * invalid or is shutting down).
+ */
+struct NT_LogMessage* NT_PollLoggerTimeout(NT_LoggerPoller poller, size_t* len,
+ double timeout, NT_Bool* timed_out);
+
+/**
+ * Cancel a PollLogger call. This wakes up a call to PollLogger for this
+ * poller and causes it to immediately return an empty array.
+ *
+ * @param poller poller handle
+ */
+void NT_CancelPollLogger(NT_LoggerPoller poller);
+
+/**
+ * Remove a logger.
+ *
+ * @param logger Logger handle to remove
+ */
+void NT_RemoveLogger(NT_Logger logger);
+
+/**
+ * Wait for the incoming log event queue to be empty. This is primarily useful
+ * for deterministic testing. This blocks until either the log event
+ * queue is empty (e.g. there are no more events that need to be passed along
+ * to callbacks or poll queues) or the timeout expires.
+ *
+ * @param inst instance handle
+ * @param timeout timeout, in seconds. Set to 0 for non-blocking behavior,
+ * or a negative value to block indefinitely
+ * @return False if timed out, otherwise true.
+ */
+NT_Bool NT_WaitForLoggerQueue(NT_Inst inst, double timeout);
+
+/** @} */
+
+/**
+ * @defgroup ntcore_interop_cfunc Interop Utility Functions
+ * @{
+ */
+
+/**
+ * @defgroup ntcore_memoryallocators_cfunc Memory Allocators
+ * @{
+ */
+
+/**
+ * Allocates an array of chars.
+ * Note that the size is the number of elements, and not the
+ * specific number of bytes to allocate. That is calculated internally.
+ *
+ * @param size the number of elements the array will contain
+ * @return the allocated char array
+ *
+ * After use, the array should be freed using the NT_FreeCharArray()
+ * function.
+ */
+char* NT_AllocateCharArray(size_t size);
+
+/**
+ * Allocates an array of booleans.
+ * Note that the size is the number of elements, and not the
+ * specific number of bytes to allocate. That is calculated internally.
+ *
+ * @param size the number of elements the array will contain
+ * @return the allocated boolean array
+ *
+ * After use, the array should be freed using the NT_FreeBooleanArray()
+ * function.
+ */
+NT_Bool* NT_AllocateBooleanArray(size_t size);
+
+/**
+ * Allocates an array of doubles.
+ * Note that the size is the number of elements, and not the
+ * specific number of bytes to allocate. That is calculated internally.
+ *
+ * @param size the number of elements the array will contain
+ * @return the allocated double array
+ *
+ * After use, the array should be freed using the NT_FreeDoubleArray()
+ * function.
+ */
+double* NT_AllocateDoubleArray(size_t size);
+
+/**
+ * Allocates an array of NT_Strings.
+ * Note that the size is the number of elements, and not the
+ * specific number of bytes to allocate. That is calculated internally.
+ *
+ * @param size the number of elements the array will contain
+ * @return the allocated NT_String array
+ *
+ * After use, the array should be freed using the NT_FreeStringArray()
+ * function.
+ */
+struct NT_String* NT_AllocateStringArray(size_t size);
+
+/**
+ * Frees an array of chars.
+ *
+ * @param v_boolean pointer to the char array to free
+ */
+void NT_FreeCharArray(char* v_char);
+
+/**
+ * Frees an array of doubles.
+ *
+ * @param v_boolean pointer to the double array to free
+ */
+void NT_FreeDoubleArray(double* v_double);
+
+/**
+ * Frees an array of booleans.
+ *
+ * @param v_boolean pointer to the boolean array to free
+ */
+void NT_FreeBooleanArray(NT_Bool* v_boolean);
+
+/**
+ * Frees an array of NT_Strings.
+ *
+ * @param v_string pointer to the string array to free
+ * @param arr_size size of the string array to free
+ *
+ * Note that the individual NT_Strings in the array should NOT be
+ * freed before calling this. This function will free all the strings
+ * individually.
+ */
+void NT_FreeStringArray(struct NT_String* v_string, size_t arr_size);
+
+/** @} */
+
+/**
+ * @defgroup ntcore_typedgetters_cfunc Typed Getters
+ * @{
+ */
+
+/**
+ * Returns the type of an NT_Value struct.
+ * Note that one of the type options is "unassigned".
+ *
+ * @param value The NT_Value struct to get the type from.
+ * @return The type of the value, or unassigned if null.
+ */
+enum NT_Type NT_GetValueType(const struct NT_Value* value);
+
+/**
+ * Returns the boolean from the NT_Value.
+ * If the NT_Value is null, or is assigned to a different type, returns 0.
+ *
+ * @param value NT_Value struct to get the boolean from
+ * @param last_change returns time in ms since the last change in the value
+ * @param v_boolean returns the boolean assigned to the name
+ * @return 1 if successful, or 0 if value is null or not a boolean
+ */
+NT_Bool NT_GetValueBoolean(const struct NT_Value* value, uint64_t* last_change,
+ NT_Bool* v_boolean);
+
+/**
+ * Returns the double from the NT_Value.
+ * If the NT_Value is null, or is assigned to a different type, returns 0.
+ *
+ * @param value NT_Value struct to get the double from
+ * @param last_change returns time in ms since the last change in the value
+ * @param v_double returns the boolean assigned to the name
+ * @return 1 if successful, or 0 if value is null or not a double
+ */
+NT_Bool NT_GetValueDouble(const struct NT_Value* value, uint64_t* last_change,
+ double* v_double);
+
+/**
+ * Returns a copy of the string from the NT_Value.
+ * If the NT_Value is null, or is assigned to a different type, returns 0.
+ *
+ * @param value NT_Value struct to get the string from
+ * @param last_change returns time in ms since the last change in the value
+ * @param str_len returns the length of the string
+ * @return pointer to the string (UTF-8), or null if error
+ *
+ * It is the caller's responsibility to free the string once its no longer
+ * needed. The NT_FreeCharArray() function is useful for this purpose. The
+ * returned string is a copy of the string in the value, and must be freed
+ * separately.
+ */
+char* NT_GetValueString(const struct NT_Value* value, uint64_t* last_change,
+ size_t* str_len);
+
+/**
+ * Returns a copy of the raw value from the NT_Value.
+ * If the NT_Value is null, or is assigned to a different type, returns null.
+ *
+ * @param value NT_Value struct to get the string from
+ * @param last_change returns time in ms since the last change in the value
+ * @param raw_len returns the length of the string
+ * @return pointer to the raw value (UTF-8), or null if error
+ *
+ * It is the caller's responsibility to free the raw value once its no longer
+ * needed. The NT_FreeCharArray() function is useful for this purpose. The
+ * returned string is a copy of the string in the value, and must be freed
+ * separately.
+ */
+char* NT_GetValueRaw(const struct NT_Value* value, uint64_t* last_change,
+ size_t* raw_len);
+
+/**
+ * Returns a copy of the boolean array from the NT_Value.
+ * If the NT_Value is null, or is assigned to a different type, returns null.
+ *
+ * @param value NT_Value struct to get the boolean array from
+ * @param last_change returns time in ms since the last change in the value
+ * @param arr_size returns the number of elements in the array
+ * @return pointer to the boolean array, or null if error
+ *
+ * It is the caller's responsibility to free the array once its no longer
+ * needed. The NT_FreeBooleanArray() function is useful for this purpose.
+ * The returned array is a copy of the array in the value, and must be
+ * freed separately.
+ */
+NT_Bool* NT_GetValueBooleanArray(const struct NT_Value* value,
+ uint64_t* last_change, size_t* arr_size);
+
+/**
+ * Returns a copy of the double array from the NT_Value.
+ * If the NT_Value is null, or is assigned to a different type, returns null.
+ *
+ * @param value NT_Value struct to get the double array from
+ * @param last_change returns time in ms since the last change in the value
+ * @param arr_size returns the number of elements in the array
+ * @return pointer to the double array, or null if error
+ *
+ * It is the caller's responsibility to free the array once its no longer
+ * needed. The NT_FreeDoubleArray() function is useful for this purpose.
+ * The returned array is a copy of the array in the value, and must be
+ * freed separately.
+ */
+double* NT_GetValueDoubleArray(const struct NT_Value* value,
+ uint64_t* last_change, size_t* arr_size);
+
+/**
+ * Returns a copy of the NT_String array from the NT_Value.
+ * If the NT_Value is null, or is assigned to a different type, returns null.
+ *
+ * @param value NT_Value struct to get the NT_String array from
+ * @param last_change returns time in ms since the last change in the value
+ * @param arr_size returns the number of elements in the array
+ * @return pointer to the NT_String array, or null if error
+ *
+ * It is the caller's responsibility to free the array once its no longer
+ * needed. The NT_FreeStringArray() function is useful for this purpose.
+ * The returned array is a copy of the array in the value, and must be
+ * freed seperately. Note that the individual NT_Strings should not be freed,
+ * but the entire array should be freed at once. The NT_FreeStringArray()
+ * function will free all the NT_Strings.
+ */
+struct NT_String* NT_GetValueStringArray(const struct NT_Value* value,
+ uint64_t* last_change,
+ size_t* arr_size);
+
+/**
+ * Returns the boolean currently assigned to the entry name.
+ * If the entry name is not currently assigned, or is assigned to a
+ * different type, returns 0.
+ *
+ * @param entry entry handle
+ * @param last_change returns time in ms since the last change in the value
+ * @param v_boolean returns the boolean assigned to the name
+ * @return 1 if successful, or 0 if value is unassigned or not a
+ * boolean
+ */
+NT_Bool NT_GetEntryBoolean(NT_Entry entry, uint64_t* last_change,
+ NT_Bool* v_boolean);
+
+/**
+ * Returns the double currently assigned to the entry name.
+ * If the entry name is not currently assigned, or is assigned to a
+ * different type, returns 0.
+ *
+ * @param entry entry handle
+ * @param last_change returns time in ms since the last change in the value
+ * @param v_double returns the double assigned to the name
+ * @return 1 if successful, or 0 if value is unassigned or not a
+ * double
+ */
+NT_Bool NT_GetEntryDouble(NT_Entry entry, uint64_t* last_change,
+ double* v_double);
+
+/**
+ * Returns a copy of the string assigned to the entry name.
+ * If the entry name is not currently assigned, or is assigned to a
+ * different type, returns null.
+ *
+ * @param entry entry handle
+ * @param last_change returns time in ms since the last change in the value
+ * @param str_len returns the length of the string
+ * @return pointer to the string (UTF-8), or null if error
+ *
+ * It is the caller's responsibility to free the string once its no longer
+ * needed. The NT_FreeCharArray() function is useful for this purpose.
+ */
+char* NT_GetEntryString(NT_Entry entry, uint64_t* last_change, size_t* str_len);
+
+/**
+ * Returns a copy of the raw value assigned to the entry name.
+ * If the entry name is not currently assigned, or is assigned to a
+ * different type, returns null.
+ *
+ * @param entry entry handle
+ * @param last_change returns time in ms since the last change in the value
+ * @param raw_len returns the length of the string
+ * @return pointer to the raw value (UTF-8), or null if error
+ *
+ * It is the caller's responsibility to free the raw value once its no longer
+ * needed. The NT_FreeCharArray() function is useful for this purpose.
+ */
+char* NT_GetEntryRaw(NT_Entry entry, uint64_t* last_change, size_t* raw_len);
+
+/**
+ * Returns a copy of the boolean array assigned to the entry name.
+ * If the entry name is not currently assigned, or is assigned to a
+ * different type, returns null.
+ *
+ * @param entry entry handle
+ * @param last_change returns time in ms since the last change in the value
+ * @param arr_size returns the number of elements in the array
+ * @return pointer to the boolean array, or null if error
+ *
+ * It is the caller's responsibility to free the array once its no longer
+ * needed. The NT_FreeBooleanArray() function is useful for this purpose.
+ */
+NT_Bool* NT_GetEntryBooleanArray(NT_Entry entry, uint64_t* last_change,
+ size_t* arr_size);
+
+/**
+ * Returns a copy of the double array assigned to the entry name.
+ * If the entry name is not currently assigned, or is assigned to a
+ * different type, returns null.
+ *
+ * @param entry entry handle
+ * @param last_change returns time in ms since the last change in the value
+ * @param arr_size returns the number of elements in the array
+ * @return pointer to the double array, or null if error
+ *
+ * It is the caller's responsibility to free the array once its no longer
+ * needed. The NT_FreeDoubleArray() function is useful for this purpose.
+ */
+double* NT_GetEntryDoubleArray(NT_Entry entry, uint64_t* last_change,
+ size_t* arr_size);
+
+/**
+ * Returns a copy of the NT_String array assigned to the entry name.
+ * If the entry name is not currently assigned, or is assigned to a
+ * different type, returns null.
+ *
+ * @param entry entry handle
+ * @param last_change returns time in ms since the last change in the value
+ * @param arr_size returns the number of elements in the array
+ * @return pointer to the NT_String array, or null if error
+ *
+ * It is the caller's responsibility to free the array once its no longer
+ * needed. The NT_FreeStringArray() function is useful for this purpose. Note
+ * that the individual NT_Strings should not be freed, but the entire array
+ * should be freed at once. The NT_FreeStringArray() function will free all the
+ * NT_Strings.
+ */
+struct NT_String* NT_GetEntryStringArray(NT_Entry entry, uint64_t* last_change,
+ size_t* arr_size);
+
+/** @} */
+
+/**
+ * @defgroup ntcore_setdefault_cfunc Set Default Values
+ * @{
+ */
+
+/** Set Default Entry Boolean.
+ * Sets the default for the specified key to be a boolean.
+ * If key exists with same type, does not set value. Otherwise
+ * sets value to the default.
+ *
+ * @param entry entry handle
+ * @param time timestamp
+ * @param default_boolean value to be set if name does not exist
+ * @return 0 on error (value not set), 1 on success
+ */
+NT_Bool NT_SetDefaultEntryBoolean(NT_Entry entry, uint64_t time,
+ NT_Bool default_boolean);
+
+/** Set Default Entry Double.
+ * Sets the default for the specified key.
+ * If key exists with same type, does not set value. Otherwise
+ * sets value to the default.
+ *
+ * @param entry entry handle
+ * @param time timestamp
+ * @param default_double value to be set if name does not exist
+ * @return 0 on error (value not set), 1 on success
+ */
+NT_Bool NT_SetDefaultEntryDouble(NT_Entry entry, uint64_t time,
+ double default_double);
+
+/** Set Default Entry String.
+ * Sets the default for the specified key.
+ * If key exists with same type, does not set value. Otherwise
+ * sets value to the default.
+ *
+ * @param entry entry handle
+ * @param time timestamp
+ * @param default_value value to be set if name does not exist
+ * @param default_len length of value
+ * @return 0 on error (value not set), 1 on success
+ */
+NT_Bool NT_SetDefaultEntryString(NT_Entry entry, uint64_t time,
+ const char* default_value, size_t default_len);
+
+/** Set Default Entry Raw.
+ * Sets the default for the specified key.
+ * If key exists with same type, does not set value. Otherwise
+ * sets value to the default.
+ *
+ * @param entry entry handle
+ * @param time timestamp
+ * @param default_value value to be set if name does not exist
+ * @param default_len length of value array
+ * @return 0 on error (value not set), 1 on success
+ */
+NT_Bool NT_SetDefaultEntryRaw(NT_Entry entry, uint64_t time,
+ const char* default_value, size_t default_len);
+
+/** Set Default Entry Boolean Array.
+ * Sets the default for the specified key.
+ * If key exists with same type, does not set value. Otherwise
+ * sets value to the default.
+ *
+ * @param entry entry handle
+ * @param time timestamp
+ * @param default_value value to be set if name does not exist
+ * @param default_size size of value array
+ * @return 0 on error (value not set), 1 on success
+ */
+NT_Bool NT_SetDefaultEntryBooleanArray(NT_Entry entry, uint64_t time,
+ const int* default_value,
+ size_t default_size);
+
+/** Set Default Entry Double Array.
+ * Sets the default for the specified key.
+ * If key exists with same type, does not set value. Otherwise
+ * sets value to the default.
+ *
+ * @param entry entry handle
+ * @param time timestamp
+ * @param default_value value to be set if name does not exist
+ * @param default_size size of value array
+ * @return 0 on error (value not set), 1 on success
+ */
+NT_Bool NT_SetDefaultEntryDoubleArray(NT_Entry entry, uint64_t time,
+ const double* default_value,
+ size_t default_size);
+
+/** Set Default Entry String Array.
+ * Sets the default for the specified key.
+ * If key exists with same type, does not set value. Otherwise
+ * sets value to the default.
+ *
+ * @param entry entry handle
+ * @param time timestamp
+ * @param default_value value to be set if name does not exist
+ * @param default_size size of value array
+ * @return 0 on error (value not set), 1 on success
+ */
+NT_Bool NT_SetDefaultEntryStringArray(NT_Entry entry, uint64_t time,
+ const struct NT_String* default_value,
+ size_t default_size);
+
+/** @} */
+
+/**
+ * @defgroup ntcore_valuesetters_cfunc Entry Value Setters
+ * @{
+ */
+
+/** Set Entry Boolean
+ * Sets an entry boolean. If the entry name is not currently assigned to a
+ * boolean, returns error unless the force parameter is set.
+ *
+ * @param entry entry handle
+ * @param time timestamp
+ * @param v_boolean boolean value to set
+ * @param force 1 to force the entry to get overwritten, otherwise 0
+ * @return 0 on error (type mismatch), 1 on success
+ */
+NT_Bool NT_SetEntryBoolean(NT_Entry entry, uint64_t time, NT_Bool v_boolean,
+ NT_Bool force);
+
+/** Set Entry Double
+ * Sets an entry double. If the entry name is not currently assigned to a
+ * double, returns error unless the force parameter is set.
+ *
+ * @param entry entry handle
+ * @param time timestamp
+ * @param v_double double value to set
+ * @param force 1 to force the entry to get overwritten, otherwise 0
+ * @return 0 on error (type mismatch), 1 on success
+ */
+NT_Bool NT_SetEntryDouble(NT_Entry entry, uint64_t time, double v_double,
+ NT_Bool force);
+
+/** Set Entry String
+ * Sets an entry string. If the entry name is not currently assigned to a
+ * string, returns error unless the force parameter is set.
+ *
+ * @param entry entry handle
+ * @param time timestamp
+ * @param str string to set (UTF-8 string)
+ * @param str_len length of string to write in bytes
+ * @param force 1 to force the entry to get overwritten, otherwise 0
+ * @return 0 on error (type mismatch), 1 on success
+ */
+NT_Bool NT_SetEntryString(NT_Entry entry, uint64_t time, const char* str,
+ size_t str_len, NT_Bool force);
+
+/** Set Entry Raw
+ * Sets the raw value of an entry. If the entry name is not currently assigned
+ * to a raw value, returns error unless the force parameter is set.
+ *
+ * @param entry entry handle
+ * @param time timestamp
+ * @param raw raw string to set (UTF-8 string)
+ * @param raw_len length of raw string to write in bytes
+ * @param force 1 to force the entry to get overwritten, otherwise 0
+ * @return 0 on error (type mismatch), 1 on success
+ */
+NT_Bool NT_SetEntryRaw(NT_Entry entry, uint64_t time, const char* raw,
+ size_t raw_len, NT_Bool force);
+
+/** Set Entry Boolean Array
+ * Sets an entry boolean array. If the entry name is not currently assigned to
+ * a boolean array, returns error unless the force parameter is set.
+ *
+ * @param entry entry handle
+ * @param time timestamp
+ * @param arr boolean array to write
+ * @param size number of elements in the array
+ * @param force 1 to force the entry to get overwritten, otherwise 0
+ * @return 0 on error (type mismatch), 1 on success
+ */
+NT_Bool NT_SetEntryBooleanArray(NT_Entry entry, uint64_t time, const int* arr,
+ size_t size, NT_Bool force);
+
+/** Set Entry Double Array
+ * Sets an entry double array. If the entry name is not currently assigned to
+ * a double array, returns error unless the force parameter is set.
+ *
+ * @param entry entry handle
+ * @param time timestamp
+ * @param arr double array to write
+ * @param size number of elements in the array
+ * @param force 1 to force the entry to get overwritten, otherwise 0
+ * @return 0 on error (type mismatch), 1 on success
+ */
+NT_Bool NT_SetEntryDoubleArray(NT_Entry entry, uint64_t time, const double* arr,
+ size_t size, NT_Bool force);
+
+/** Set Entry String Array
+ * Sets an entry string array. If the entry name is not currently assigned to
+ * a string array, returns error unless the force parameter is set.
+ *
+ * @param entry entry handle
+ * @param time timestamp
+ * @param arr NT_String array to write
+ * @param size number of elements in the array
+ * @param force 1 to force the entry to get overwritten, otherwise 0
+ * @return 0 on error (type mismatch), 1 on success
+ */
+NT_Bool NT_SetEntryStringArray(NT_Entry entry, uint64_t time,
+ const struct NT_String* arr, size_t size,
+ NT_Bool force);
+
+/** @} */
+/** @} */
+/** @} */
+
+#ifdef __cplusplus
+} // extern "C"
+#endif
+
+#endif // NTCORE_NTCORE_C_H_
diff --git a/ntcore/src/main/native/include/ntcore_cpp.h b/ntcore/src/main/native/include/ntcore_cpp.h
new file mode 100644
index 0000000..56cb5af
--- /dev/null
+++ b/ntcore/src/main/native/include/ntcore_cpp.h
@@ -0,0 +1,1600 @@
+/*----------------------------------------------------------------------------*/
+/* Copyright (c) 2015-2018 FIRST. All Rights Reserved. */
+/* Open Source Software - may be modified and shared by FRC teams. The code */
+/* must be accompanied by the FIRST BSD license file in the root directory of */
+/* the project. */
+/*----------------------------------------------------------------------------*/
+
+#ifndef NTCORE_NTCORE_CPP_H_
+#define NTCORE_NTCORE_CPP_H_
+
+#include <stdint.h>
+
+#include <cassert>
+#include <functional>
+#include <memory>
+#include <string>
+#include <thread>
+#include <utility>
+#include <vector>
+
+#include <wpi/ArrayRef.h>
+#include <wpi/StringRef.h>
+#include <wpi/Twine.h>
+#include <wpi/deprecated.h>
+
+#include "networktables/NetworkTableValue.h"
+
+/** NetworkTables (ntcore) namespace */
+namespace nt {
+
+/**
+ * @defgroup ntcore_cpp_handle_api ntcore C++ API
+ *
+ * Handle-based interface for C++.
+ *
+ * @{
+ */
+
+using wpi::ArrayRef;
+using wpi::StringRef;
+using wpi::Twine;
+
+/** NetworkTables Entry Information */
+struct EntryInfo {
+ /** Entry handle */
+ NT_Entry entry;
+
+ /** Entry name */
+ std::string name;
+
+ /** Entry type */
+ NT_Type type;
+
+ /** Entry flags */
+ unsigned int flags;
+
+ /** Timestamp of last change to entry (type or value). */
+ uint64_t last_change;
+
+ friend void swap(EntryInfo& first, EntryInfo& second) {
+ using std::swap;
+ swap(first.entry, second.entry);
+ swap(first.name, second.name);
+ swap(first.type, second.type);
+ swap(first.flags, second.flags);
+ swap(first.last_change, second.last_change);
+ }
+};
+
+/** NetworkTables Connection Information */
+struct ConnectionInfo {
+ /**
+ * The remote identifier (as set on the remote node by
+ * NetworkTableInstance::SetNetworkIdentity() or nt::SetNetworkIdentity()).
+ */
+ std::string remote_id;
+
+ /** The IP address of the remote node. */
+ std::string remote_ip;
+
+ /** The port number of the remote node. */
+ unsigned int remote_port;
+
+ /**
+ * The last time any update was received from the remote node (same scale as
+ * returned by nt::Now()).
+ */
+ uint64_t last_update;
+
+ /**
+ * The protocol version being used for this connection. This in protocol
+ * layer format, so 0x0200 = 2.0, 0x0300 = 3.0).
+ */
+ unsigned int protocol_version;
+
+ friend void swap(ConnectionInfo& first, ConnectionInfo& second) {
+ using std::swap;
+ swap(first.remote_id, second.remote_id);
+ swap(first.remote_ip, second.remote_ip);
+ swap(first.remote_port, second.remote_port);
+ swap(first.last_update, second.last_update);
+ swap(first.protocol_version, second.protocol_version);
+ }
+};
+
+/** NetworkTables RPC Version 1 Definition Parameter */
+struct RpcParamDef {
+ RpcParamDef() = default;
+ RpcParamDef(StringRef name_, std::shared_ptr<Value> def_value_)
+ : name(name_), def_value(def_value_) {}
+
+ std::string name;
+ std::shared_ptr<Value> def_value;
+};
+
+/** NetworkTables RPC Version 1 Definition Result */
+struct RpcResultDef {
+ RpcResultDef() = default;
+ RpcResultDef(StringRef name_, NT_Type type_) : name(name_), type(type_) {}
+
+ std::string name;
+ NT_Type type;
+};
+
+/** NetworkTables RPC Version 1 Definition */
+struct RpcDefinition {
+ unsigned int version;
+ std::string name;
+ std::vector<RpcParamDef> params;
+ std::vector<RpcResultDef> results;
+};
+
+/** NetworkTables Remote Procedure Call (Server Side) */
+class RpcAnswer {
+ public:
+ RpcAnswer() : entry(0), call(0) {}
+ RpcAnswer(NT_Entry entry_, NT_RpcCall call_, StringRef name_,
+ StringRef params_, const ConnectionInfo& conn_)
+ : entry(entry_), call(call_), name(name_), params(params_), conn(conn_) {}
+
+ /** Entry handle. */
+ NT_Entry entry;
+
+ /** Call handle. */
+ mutable NT_RpcCall call;
+
+ /** Entry name. */
+ std::string name;
+
+ /** Call raw parameters. */
+ std::string params;
+
+ /** Connection that called the RPC. */
+ ConnectionInfo conn;
+
+ /**
+ * Determines if the native handle is valid.
+ * @return True if the native handle is valid, false otherwise.
+ */
+ explicit operator bool() const { return call != 0; }
+
+ /**
+ * Post RPC response (return value) for a polled RPC.
+ * @param result result raw data that will be provided to remote caller
+ * @return True if posting the response is valid, otherwise false
+ */
+ bool PostResponse(StringRef result) const;
+
+ friend void swap(RpcAnswer& first, RpcAnswer& second) {
+ using std::swap;
+ swap(first.entry, second.entry);
+ swap(first.call, second.call);
+ swap(first.name, second.name);
+ swap(first.params, second.params);
+ swap(first.conn, second.conn);
+ }
+};
+
+/** NetworkTables Entry Notification */
+class EntryNotification {
+ public:
+ EntryNotification() : listener(0), entry(0) {}
+ EntryNotification(NT_EntryListener listener_, NT_Entry entry_,
+ StringRef name_, std::shared_ptr<Value> value_,
+ unsigned int flags_)
+ : listener(listener_),
+ entry(entry_),
+ name(name_),
+ value(value_),
+ flags(flags_) {}
+
+ /** Listener that was triggered. */
+ NT_EntryListener listener;
+
+ /** Entry handle. */
+ NT_Entry entry;
+
+ /** Entry name. */
+ std::string name;
+
+ /** The new value. */
+ std::shared_ptr<Value> value;
+
+ /**
+ * Update flags. For example, NT_NOTIFY_NEW if the key did not previously
+ * exist.
+ */
+ unsigned int flags;
+
+ friend void swap(EntryNotification& first, EntryNotification& second) {
+ using std::swap;
+ swap(first.listener, second.listener);
+ swap(first.entry, second.entry);
+ swap(first.name, second.name);
+ swap(first.value, second.value);
+ swap(first.flags, second.flags);
+ }
+};
+
+/** NetworkTables Connection Notification */
+class ConnectionNotification {
+ public:
+ ConnectionNotification() : listener(0), connected(false) {}
+ ConnectionNotification(NT_ConnectionListener listener_, bool connected_,
+ const ConnectionInfo& conn_)
+ : listener(listener_), connected(connected_), conn(conn_) {}
+
+ /** Listener that was triggered. */
+ NT_ConnectionListener listener;
+
+ /** True if event is due to connection being established. */
+ bool connected = false;
+
+ /** Connection info. */
+ ConnectionInfo conn;
+
+ friend void swap(ConnectionNotification& first,
+ ConnectionNotification& second) {
+ using std::swap;
+ swap(first.listener, second.listener);
+ swap(first.connected, second.connected);
+ swap(first.conn, second.conn);
+ }
+};
+
+/** NetworkTables log message. */
+class LogMessage {
+ public:
+ LogMessage() : logger(0), level(0), filename(""), line(0) {}
+ LogMessage(NT_Logger logger_, unsigned int level_, const char* filename_,
+ unsigned int line_, StringRef message_)
+ : logger(logger_),
+ level(level_),
+ filename(filename_),
+ line(line_),
+ message(message_) {}
+
+ /** The logger that generated the message. */
+ NT_Logger logger;
+
+ /** Log level of the message. See NT_LogLevel. */
+ unsigned int level;
+
+ /** The filename of the source file that generated the message. */
+ const char* filename;
+
+ /** The line number in the source file that generated the message. */
+ unsigned int line;
+
+ /** The message. */
+ std::string message;
+
+ friend void swap(LogMessage& first, LogMessage& second) {
+ using std::swap;
+ swap(first.logger, second.logger);
+ swap(first.level, second.level);
+ swap(first.filename, second.filename);
+ swap(first.line, second.line);
+ swap(first.message, second.message);
+ }
+};
+
+/**
+ * @defgroup ntcore_instance_func Instance Functions
+ * @{
+ */
+
+/**
+ * Get default instance.
+ * This is the instance used by non-handle-taking functions.
+ *
+ * @return Instance handle
+ */
+NT_Inst GetDefaultInstance();
+
+/**
+ * Create an instance.
+ *
+ * @return Instance handle
+ */
+NT_Inst CreateInstance();
+
+/**
+ * Destroy an instance.
+ * The default instance cannot be destroyed.
+ *
+ * @param inst Instance handle
+ */
+void DestroyInstance(NT_Inst inst);
+
+/**
+ * Get instance handle from another handle.
+ *
+ * @param handle entry/instance/etc. handle
+ * @return Instance handle
+ */
+NT_Inst GetInstanceFromHandle(NT_Handle handle);
+
+/** @} */
+
+/**
+ * @defgroup ntcore_table_func Table Functions
+ * @{
+ */
+
+/**
+ * Get Entry Handle.
+ *
+ * @param inst instance handle
+ * @param name entry name (UTF-8 string)
+ * @return entry handle
+ */
+NT_Entry GetEntry(NT_Inst inst, const Twine& name);
+
+/**
+ * Get Entry Handles.
+ *
+ * Returns an array of entry handles. The results are optionally
+ * filtered by string prefix and entry type to only return a subset of all
+ * entries.
+ *
+ * @param inst instance handle
+ * @param prefix entry name required prefix; only entries whose name
+ * starts with this string are returned
+ * @param types bitmask of NT_Type values; 0 is treated specially
+ * as a "don't care"
+ * @return Array of entry handles.
+ */
+std::vector<NT_Entry> GetEntries(NT_Inst inst, const Twine& prefix,
+ unsigned int types);
+
+/**
+ * Gets the name of the specified entry.
+ * Returns an empty string if the handle is invalid.
+ *
+ * @param entry entry handle
+ * @return Entry name
+ */
+std::string GetEntryName(NT_Entry entry);
+
+/**
+ * Gets the type for the specified entry, or unassigned if non existent.
+ *
+ * @param entry entry handle
+ * @return Entry type
+ */
+NT_Type GetEntryType(NT_Entry entry);
+
+/**
+ * Gets the last time the entry was changed.
+ * Returns 0 if the handle is invalid.
+ *
+ * @param entry entry handle
+ * @return Entry last change time
+ */
+uint64_t GetEntryLastChange(NT_Entry entry);
+
+/**
+ * Get Entry Value.
+ *
+ * Returns copy of current entry value.
+ * Note that one of the type options is "unassigned".
+ *
+ * @param name entry name (UTF-8 string)
+ * @return entry value
+ */
+WPI_DEPRECATED("use NT_Entry function instead")
+std::shared_ptr<Value> GetEntryValue(StringRef name);
+
+/**
+ * Get Entry Value.
+ *
+ * Returns copy of current entry value.
+ * Note that one of the type options is "unassigned".
+ *
+ * @param entry entry handle
+ * @return entry value
+ */
+std::shared_ptr<Value> GetEntryValue(NT_Entry entry);
+
+/**
+ * Set Default Entry Value
+ *
+ * Returns copy of current entry value if it exists.
+ * Otherwise, sets passed in value, and returns set value.
+ * Note that one of the type options is "unassigned".
+ *
+ * @param name entry name (UTF-8 string)
+ * @param value value to be set if name does not exist
+ * @return False on error (value not set), True on success
+ */
+WPI_DEPRECATED("use NT_Entry function instead")
+bool SetDefaultEntryValue(StringRef name, std::shared_ptr<Value> value);
+
+/**
+ * Set Default Entry Value
+ *
+ * Returns copy of current entry value if it exists.
+ * Otherwise, sets passed in value, and returns set value.
+ * Note that one of the type options is "unassigned".
+ *
+ * @param entry entry handle
+ * @param value value to be set if name does not exist
+ * @return False on error (value not set), True on success
+ */
+bool SetDefaultEntryValue(NT_Entry entry, std::shared_ptr<Value> value);
+
+/**
+ * Set Entry Value.
+ *
+ * Sets new entry value. If type of new value differs from the type of the
+ * currently stored entry, returns error and does not update value.
+ *
+ * @param name entry name (UTF-8 string)
+ * @param value new entry value
+ * @return False on error (type mismatch), True on success
+ */
+WPI_DEPRECATED("use NT_Entry function instead")
+bool SetEntryValue(StringRef name, std::shared_ptr<Value> value);
+
+/**
+ * Set Entry Value.
+ *
+ * Sets new entry value. If type of new value differs from the type of the
+ * currently stored entry, returns error and does not update value.
+ *
+ * @param entry entry handle
+ * @param value new entry value
+ * @return False on error (type mismatch), True on success
+ */
+bool SetEntryValue(NT_Entry entry, std::shared_ptr<Value> value);
+
+/**
+ * Set Entry Type and Value.
+ *
+ * Sets new entry value. If type of new value differs from the type of the
+ * currently stored entry, the currently stored entry type is overridden
+ * (generally this will generate an Entry Assignment message).
+ *
+ * This is NOT the preferred method to update a value; generally
+ * SetEntryValue() should be used instead, with appropriate error handling.
+ *
+ * @param name entry name (UTF-8 string)
+ * @param value new entry value
+ */
+WPI_DEPRECATED("use NT_Entry function instead")
+void SetEntryTypeValue(StringRef name, std::shared_ptr<Value> value);
+
+/**
+ * Set Entry Type and Value.
+ *
+ * Sets new entry value. If type of new value differs from the type of the
+ * currently stored entry, the currently stored entry type is overridden
+ * (generally this will generate an Entry Assignment message).
+ *
+ * This is NOT the preferred method to update a value; generally
+ * SetEntryValue() should be used instead, with appropriate error handling.
+ *
+ * @param entry entry handle
+ * @param value new entry value
+ */
+void SetEntryTypeValue(NT_Entry entry, std::shared_ptr<Value> value);
+
+/**
+ * Set Entry Flags.
+ *
+ * @param name entry name (UTF-8 string)
+ * @param flags flags value (bitmask of NT_EntryFlags)
+ */
+WPI_DEPRECATED("use NT_Entry function instead")
+void SetEntryFlags(StringRef name, unsigned int flags);
+
+/**
+ * Set Entry Flags.
+ *
+ * @param entry entry handle
+ * @param flags flags value (bitmask of NT_EntryFlags)
+ */
+void SetEntryFlags(NT_Entry entry, unsigned int flags);
+
+/**
+ * Get Entry Flags.
+ *
+ * @param name entry name (UTF-8 string)
+ * @return Flags value (bitmask of NT_EntryFlags)
+ */
+WPI_DEPRECATED("use NT_Entry function instead")
+unsigned int GetEntryFlags(StringRef name);
+
+/**
+ * Get Entry Flags.
+ *
+ * @param entry entry handle
+ * @return Flags value (bitmask of NT_EntryFlags)
+ */
+unsigned int GetEntryFlags(NT_Entry entry);
+
+/**
+ * Delete Entry.
+ *
+ * Deletes an entry. This is a new feature in version 3.0 of the protocol,
+ * so this may not have an effect if any other node in the network is not
+ * version 3.0 or newer.
+ *
+ * Note: GetConnections() can be used to determine the protocol version
+ * of direct remote connection(s), but this is not sufficient to determine
+ * if all nodes in the network are version 3.0 or newer.
+ *
+ * @param name entry name (UTF-8 string)
+ */
+WPI_DEPRECATED("use NT_Entry function instead")
+void DeleteEntry(StringRef name);
+
+/**
+ * Delete Entry.
+ *
+ * Deletes an entry. This is a new feature in version 3.0 of the protocol,
+ * so this may not have an effect if any other node in the network is not
+ * version 3.0 or newer.
+ *
+ * Note: GetConnections() can be used to determine the protocol version
+ * of direct remote connection(s), but this is not sufficient to determine
+ * if all nodes in the network are version 3.0 or newer.
+ *
+ * @param entry entry handle
+ */
+void DeleteEntry(NT_Entry entry);
+
+/**
+ * Delete All Entries.
+ *
+ * Deletes ALL table entries. This is a new feature in version 3.0 of the
+ * so this may not have an effect if any other node in the network is not
+ * version 3.0 or newer.
+ *
+ * Note: GetConnections() can be used to determine the protocol version
+ * of direct remote connection(s), but this is not sufficient to determine
+ * if all nodes in the network are version 3.0 or newer.
+ */
+WPI_DEPRECATED("use NT_Inst function instead")
+void DeleteAllEntries();
+
+/**
+ * @copydoc DeleteAllEntries()
+ *
+ * @param inst instance handle
+ */
+void DeleteAllEntries(NT_Inst inst);
+
+/**
+ * Get Entry Information.
+ *
+ * Returns an array of entry information (name, entry type,
+ * and timestamp of last change to type/value). The results are optionally
+ * filtered by string prefix and entry type to only return a subset of all
+ * entries.
+ *
+ * @param prefix entry name required prefix; only entries whose name
+ * starts with this string are returned
+ * @param types bitmask of NT_Type values; 0 is treated specially
+ * as a "don't care"
+ * @return Array of entry information.
+ */
+WPI_DEPRECATED("use NT_Inst function instead")
+std::vector<EntryInfo> GetEntryInfo(StringRef prefix, unsigned int types);
+
+/**
+ * @copydoc GetEntryInfo(StringRef, unsigned int)
+ *
+ * @param inst instance handle
+ */
+std::vector<EntryInfo> GetEntryInfo(NT_Inst inst, const Twine& prefix,
+ unsigned int types);
+
+/**
+ * Get Entry Information.
+ *
+ * Returns information about an entry (name, entry type,
+ * and timestamp of last change to type/value).
+ *
+ * @param entry entry handle
+ * @return Entry information.
+ */
+EntryInfo GetEntryInfo(NT_Entry entry);
+
+/** @} */
+
+/**
+ * @defgroup ntcore_entrylistener_func Entry Listener Functions
+ * @{
+ */
+
+/**
+ * Entry listener callback function.
+ * Called when a key-value pair is changed.
+ *
+ * @param entry_listener entry listener handle returned by callback creation
+ * function
+ * @param name entry name
+ * @param value the new value
+ * @param flags update flags; for example, NT_NOTIFY_NEW if the key
+ * did not previously exist
+ */
+typedef std::function<void(NT_EntryListener entry_listener, StringRef name,
+ std::shared_ptr<Value> value, unsigned int flags)>
+ EntryListenerCallback;
+
+/**
+ * Add a listener for all entries starting with a certain prefix.
+ *
+ * @param prefix UTF-8 string prefix
+ * @param callback listener to add
+ * @param flags NotifyKind bitmask
+ * @return Listener handle
+ */
+WPI_DEPRECATED("use NT_Inst function instead")
+NT_EntryListener AddEntryListener(StringRef prefix,
+ EntryListenerCallback callback,
+ unsigned int flags);
+
+/**
+ * @copydoc AddEntryListener(StringRef, EntryListenerCallback, unsigned int)
+ *
+ * @param inst instance handle
+ */
+NT_EntryListener AddEntryListener(
+ NT_Inst inst, const Twine& prefix,
+ std::function<void(const EntryNotification& event)> callback,
+ unsigned int flags);
+
+/**
+ * Add a listener for a single entry.
+ *
+ * @param entry entry handle
+ * @param callback listener to add
+ * @param flags NotifyKind bitmask
+ * @return Listener handle
+ */
+NT_EntryListener AddEntryListener(
+ NT_Entry entry,
+ std::function<void(const EntryNotification& event)> callback,
+ unsigned int flags);
+
+/**
+ * Create a entry listener poller.
+ *
+ * A poller provides a single queue of poll events. Events linked to this
+ * poller (using AddPolledEntryListener()) will be stored in the queue and
+ * must be collected by calling PollEntryListener().
+ * The returned handle must be destroyed with DestroyEntryListenerPoller().
+ *
+ * @param inst instance handle
+ * @return poller handle
+ */
+NT_EntryListenerPoller CreateEntryListenerPoller(NT_Inst inst);
+
+/**
+ * Destroy a entry listener poller. This will abort any blocked polling
+ * call and prevent additional events from being generated for this poller.
+ *
+ * @param poller poller handle
+ */
+void DestroyEntryListenerPoller(NT_EntryListenerPoller poller);
+
+/**
+ * Create a polled entry listener.
+ * The caller is responsible for calling PollEntryListener() to poll.
+ *
+ * @param poller poller handle
+ * @param prefix UTF-8 string prefix
+ * @param flags NotifyKind bitmask
+ * @return Listener handle
+ */
+NT_EntryListener AddPolledEntryListener(NT_EntryListenerPoller poller,
+ const Twine& prefix,
+ unsigned int flags);
+
+/**
+ * Create a polled entry listener.
+ * The caller is responsible for calling PollEntryListener() to poll.
+ *
+ * @param poller poller handle
+ * @param prefix UTF-8 string prefix
+ * @param flags NotifyKind bitmask
+ * @return Listener handle
+ */
+NT_EntryListener AddPolledEntryListener(NT_EntryListenerPoller poller,
+ NT_Entry entry, unsigned int flags);
+
+/**
+ * Get the next entry listener event. This blocks until the next event occurs.
+ * This is intended to be used with AddPolledEntryListener(); entry listeners
+ * created using AddEntryListener() will not be serviced through this function.
+ *
+ * @param poller poller handle
+ * @return Information on the entry listener events. Only returns empty if an
+ * error occurred (e.g. the instance was invalid or is shutting down).
+ */
+std::vector<EntryNotification> PollEntryListener(NT_EntryListenerPoller poller);
+
+/**
+ * Get the next entry listener event. This blocks until the next event occurs
+ * or it times out. This is intended to be used with AddPolledEntryListener();
+ * entry listeners created using AddEntryListener() will not be serviced
+ * through this function.
+ *
+ * @param poller poller handle
+ * @param timeout timeout, in seconds
+ * @param timed_out true if the timeout period elapsed (output)
+ * @return Information on the entry listener events. If empty is returned and
+ * and timed_out is also false, an error occurred (e.g. the instance
+ * was invalid or is shutting down).
+ */
+std::vector<EntryNotification> PollEntryListener(NT_EntryListenerPoller poller,
+ double timeout,
+ bool* timed_out);
+
+/**
+ * Cancel a PollEntryListener call. This wakes up a call to
+ * PollEntryListener for this poller and causes it to immediately return
+ * an empty array.
+ *
+ * @param poller poller handle
+ */
+void CancelPollEntryListener(NT_EntryListenerPoller poller);
+
+/**
+ * Remove an entry listener.
+ *
+ * @param entry_listener Listener handle to remove
+ */
+void RemoveEntryListener(NT_EntryListener entry_listener);
+
+/**
+ * Wait for the entry listener queue to be empty. This is primarily useful
+ * for deterministic testing. This blocks until either the entry listener
+ * queue is empty (e.g. there are no more events that need to be passed along
+ * to callbacks or poll queues) or the timeout expires.
+ *
+ * @param inst instance handle
+ * @param timeout timeout, in seconds. Set to 0 for non-blocking behavior,
+ * or a negative value to block indefinitely
+ * @return False if timed out, otherwise true.
+ */
+bool WaitForEntryListenerQueue(NT_Inst inst, double timeout);
+
+/** @} */
+
+/**
+ * @defgroup ntcore_connectionlistener_func Connection Listener Functions
+ * @{
+ */
+
+/**
+ * Connection listener callback function.
+ * Called when a network connection is made or lost.
+ *
+ * @param conn_listener connection listener handle returned by callback
+ * creation function
+ * @param connected true if event is due to connection being established
+ * @param conn connection info
+ */
+typedef std::function<void(NT_ConnectionListener conn_listener, bool connected,
+ const ConnectionInfo& conn)>
+ ConnectionListenerCallback;
+
+/**
+ * Add a connection listener.
+ *
+ * @param callback listener to add
+ * @param immediate_notify notify listener of all existing connections
+ * @return Listener handle
+ */
+WPI_DEPRECATED("use NT_Inst function instead")
+NT_ConnectionListener AddConnectionListener(ConnectionListenerCallback callback,
+ bool immediate_notify);
+
+/**
+ * @copydoc AddConnectionListener(ConnectionListenerCallback, bool)
+ *
+ * @param inst instance handle
+ */
+NT_ConnectionListener AddConnectionListener(
+ NT_Inst inst,
+ std::function<void(const ConnectionNotification& event)> callback,
+ bool immediate_notify);
+
+/**
+ * Create a connection listener poller.
+ *
+ * A poller provides a single queue of poll events. Events linked to this
+ * poller (using AddPolledConnectionListener()) will be stored in the queue and
+ * must be collected by calling PollConnectionListener().
+ * The returned handle must be destroyed with DestroyConnectionListenerPoller().
+ *
+ * @param inst instance handle
+ * @return poller handle
+ */
+NT_ConnectionListenerPoller CreateConnectionListenerPoller(NT_Inst inst);
+
+/**
+ * Destroy a connection listener poller. This will abort any blocked polling
+ * call and prevent additional events from being generated for this poller.
+ *
+ * @param poller poller handle
+ */
+void DestroyConnectionListenerPoller(NT_ConnectionListenerPoller poller);
+
+/**
+ * Create a polled connection listener.
+ * The caller is responsible for calling PollConnectionListener() to poll.
+ *
+ * @param poller poller handle
+ * @param immediate_notify notify listener of all existing connections
+ */
+NT_ConnectionListener AddPolledConnectionListener(
+ NT_ConnectionListenerPoller poller, bool immediate_notify);
+
+/**
+ * Get the next connection event. This blocks until the next connect or
+ * disconnect occurs. This is intended to be used with
+ * AddPolledConnectionListener(); connection listeners created using
+ * AddConnectionListener() will not be serviced through this function.
+ *
+ * @param poller poller handle
+ * @return Information on the connection events. Only returns empty if an
+ * error occurred (e.g. the instance was invalid or is shutting down).
+ */
+std::vector<ConnectionNotification> PollConnectionListener(
+ NT_ConnectionListenerPoller poller);
+
+/**
+ * Get the next connection event. This blocks until the next connect or
+ * disconnect occurs or it times out. This is intended to be used with
+ * AddPolledConnectionListener(); connection listeners created using
+ * AddConnectionListener() will not be serviced through this function.
+ *
+ * @param poller poller handle
+ * @param timeout timeout, in seconds
+ * @param timed_out true if the timeout period elapsed (output)
+ * @return Information on the connection events. If empty is returned and
+ * timed_out is also false, an error occurred (e.g. the instance was
+ * invalid or is shutting down).
+ */
+std::vector<ConnectionNotification> PollConnectionListener(
+ NT_ConnectionListenerPoller poller, double timeout, bool* timed_out);
+
+/**
+ * Cancel a PollConnectionListener call. This wakes up a call to
+ * PollConnectionListener for this poller and causes it to immediately return
+ * an empty array.
+ *
+ * @param poller poller handle
+ */
+void CancelPollConnectionListener(NT_ConnectionListenerPoller poller);
+
+/**
+ * Remove a connection listener.
+ *
+ * @param conn_listener Listener handle to remove
+ */
+void RemoveConnectionListener(NT_ConnectionListener conn_listener);
+
+/**
+ * Wait for the connection listener queue to be empty. This is primarily useful
+ * for deterministic testing. This blocks until either the connection listener
+ * queue is empty (e.g. there are no more events that need to be passed along
+ * to callbacks or poll queues) or the timeout expires.
+ *
+ * @param inst instance handle
+ * @param timeout timeout, in seconds. Set to 0 for non-blocking behavior,
+ * or a negative value to block indefinitely
+ * @return False if timed out, otherwise true.
+ */
+bool WaitForConnectionListenerQueue(NT_Inst inst, double timeout);
+
+/** @} */
+
+/**
+ * @defgroup ntcore_rpc_func Remote Procedure Call Functions
+ * @{
+ */
+
+/**
+ * Create a callback-based RPC entry point. Only valid to use on the server.
+ * The callback function will be called when the RPC is called.
+ *
+ * @param entry entry handle of RPC entry
+ * @param def RPC definition
+ * @param callback callback function; note the callback function must call
+ * PostRpcResponse() to provide a response to the call
+ */
+void CreateRpc(NT_Entry entry, StringRef def,
+ std::function<void(const RpcAnswer& answer)> callback);
+
+/**
+ * Create a RPC call poller. Only valid to use on the server.
+ *
+ * A poller provides a single queue of poll events. Events linked to this
+ * poller (using CreatePolledRpc()) will be stored in the queue and must be
+ * collected by calling PollRpc().
+ * The returned handle must be destroyed with DestroyRpcCallPoller().
+ *
+ * @param inst instance handle
+ * @return poller handle
+ */
+NT_RpcCallPoller CreateRpcCallPoller(NT_Inst inst);
+
+/**
+ * Destroy a RPC call poller. This will abort any blocked polling call and
+ * prevent additional events from being generated for this poller.
+ *
+ * @param poller poller handle
+ */
+void DestroyRpcCallPoller(NT_RpcCallPoller poller);
+
+/**
+ * Create a polled RPC entry point. Only valid to use on the server.
+ * The caller is responsible for calling PollRpc() to poll for servicing
+ * incoming RPC calls.
+ *
+ * @param entry entry handle of RPC entry
+ * @param def RPC definition
+ * @param poller poller handle
+ */
+void CreatePolledRpc(NT_Entry entry, StringRef def, NT_RpcCallPoller poller);
+
+/**
+ * Get the next incoming RPC call. This blocks until the next incoming RPC
+ * call is received. This is intended to be used with CreatePolledRpc();
+ * RPC calls created using CreateRpc() will not be serviced through this
+ * function. Upon successful return, PostRpcResponse() must be called to
+ * send the return value to the caller.
+ *
+ * @param poller poller handle
+ * @return Information on the next RPC calls. Only returns empty if an error
+ * occurred (e.g. the instance was invalid or is shutting down).
+ */
+std::vector<RpcAnswer> PollRpc(NT_RpcCallPoller poller);
+
+/**
+ * Get the next incoming RPC call. This blocks until the next incoming RPC
+ * call is received or it times out. This is intended to be used with
+ * CreatePolledRpc(); RPC calls created using CreateRpc() will not be
+ * serviced through this function. Upon successful return,
+ * PostRpcResponse() must be called to send the return value to the caller.
+ *
+ * @param poller poller handle
+ * @param timeout timeout, in seconds
+ * @param timed_out true if the timeout period elapsed (output)
+ * @return Information on the next RPC calls. If empty and timed_out is also
+ * false, an error occurred (e.g. the instance was invalid or is
+ * shutting down).
+ */
+std::vector<RpcAnswer> PollRpc(NT_RpcCallPoller poller, double timeout,
+ bool* timed_out);
+
+/**
+ * Cancel a PollRpc call. This wakes up a call to PollRpc for this poller
+ * and causes it to immediately return an empty array.
+ *
+ * @param poller poller handle
+ */
+void CancelPollRpc(NT_RpcCallPoller poller);
+
+/**
+ * Wait for the incoming RPC call queue to be empty. This is primarily useful
+ * for deterministic testing. This blocks until either the RPC call
+ * queue is empty (e.g. there are no more events that need to be passed along
+ * to callbacks or poll queues) or the timeout expires.
+ *
+ * @param inst instance handle
+ * @param timeout timeout, in seconds. Set to 0 for non-blocking behavior,
+ * or a negative value to block indefinitely
+ * @return False if timed out, otherwise true.
+ */
+bool WaitForRpcCallQueue(NT_Inst inst, double timeout);
+
+/**
+ * Post RPC response (return value) for a polled RPC.
+ * The rpc and call parameters should come from the RpcAnswer returned
+ * by PollRpc().
+ *
+ * @param entry entry handle of RPC entry (from RpcAnswer)
+ * @param call RPC call handle (from RpcAnswer)
+ * @param result result raw data that will be provided to remote caller
+ * @return true if the response was posted, otherwise false
+ */
+bool PostRpcResponse(NT_Entry entry, NT_RpcCall call, StringRef result);
+
+/**
+ * Call a RPC function. May be used on either the client or server.
+ * This function is non-blocking. Either GetRpcResult() or
+ * CancelRpcResult() must be called to either get or ignore the result of
+ * the call.
+ *
+ * @param entry entry handle of RPC entry
+ * @param params parameter
+ * @return RPC call handle (for use with GetRpcResult() or
+ * CancelRpcResult()).
+ */
+NT_RpcCall CallRpc(NT_Entry entry, StringRef params);
+
+/**
+ * Get the result (return value) of a RPC call. This function blocks until
+ * the result is received.
+ *
+ * @param entry entry handle of RPC entry
+ * @param call RPC call handle returned by CallRpc()
+ * @param result received result (output)
+ * @return False on error, true otherwise.
+ */
+bool GetRpcResult(NT_Entry entry, NT_RpcCall call, std::string* result);
+
+/**
+ * Get the result (return value) of a RPC call. This function blocks until
+ * the result is received or it times out.
+ *
+ * @param entry entry handle of RPC entry
+ * @param call RPC call handle returned by CallRpc()
+ * @param result received result (output)
+ * @param timeout timeout, in seconds
+ * @param timed_out true if the timeout period elapsed (output)
+ * @return False on error or timeout, true otherwise.
+ */
+bool GetRpcResult(NT_Entry entry, NT_RpcCall call, std::string* result,
+ double timeout, bool* timed_out);
+
+/**
+ * Ignore the result of a RPC call. This function is non-blocking.
+ *
+ * @param entry entry handle of RPC entry
+ * @param call RPC call handle returned by CallRpc()
+ */
+void CancelRpcResult(NT_Entry entry, NT_RpcCall call);
+
+/**
+ * Pack a RPC version 1 definition.
+ *
+ * @param def RPC version 1 definition
+ * @return Raw packed bytes.
+ */
+std::string PackRpcDefinition(const RpcDefinition& def);
+
+/**
+ * Unpack a RPC version 1 definition. This can be used for introspection or
+ * validation.
+ *
+ * @param packed raw packed bytes
+ * @param def RPC version 1 definition (output)
+ * @return True if successfully unpacked, false otherwise.
+ */
+bool UnpackRpcDefinition(StringRef packed, RpcDefinition* def);
+
+/**
+ * Pack RPC values as required for RPC version 1 definition messages.
+ *
+ * @param values array of values to pack
+ * @return Raw packed bytes.
+ */
+std::string PackRpcValues(ArrayRef<std::shared_ptr<Value>> values);
+
+/**
+ * Unpack RPC values as required for RPC version 1 definition messages.
+ *
+ * @param packed raw packed bytes
+ * @param types array of data types (as provided in the RPC definition)
+ * @return Array of values.
+ */
+std::vector<std::shared_ptr<Value>> UnpackRpcValues(StringRef packed,
+ ArrayRef<NT_Type> types);
+
+/** @} */
+
+/**
+ * @defgroup ntcore_network_func Client/Server Functions
+ * @{
+ */
+
+/**
+ * Set the network identity of this node.
+ * This is the name used during the initial connection handshake, and is
+ * visible through ConnectionInfo on the remote node.
+ *
+ * @param name identity to advertise
+ */
+WPI_DEPRECATED("use NT_Inst function instead")
+void SetNetworkIdentity(StringRef name);
+
+/**
+ * @copydoc SetNetworkIdentity(StringRef)
+ *
+ * @param inst instance handle
+ */
+void SetNetworkIdentity(NT_Inst inst, const Twine& name);
+
+/**
+ * Get the current network mode.
+ *
+ * @return Bitmask of NT_NetworkMode.
+ */
+WPI_DEPRECATED("use NT_Inst function instead")
+unsigned int GetNetworkMode();
+
+/**
+ * Get the current network mode.
+ *
+ * @param inst instance handle
+ * @return Bitmask of NT_NetworkMode.
+ */
+unsigned int GetNetworkMode(NT_Inst inst);
+
+/**
+ * Starts a server using the specified filename, listening address, and port.
+ *
+ * @param persist_filename the name of the persist file to use (UTF-8 string,
+ * null terminated)
+ * @param listen_address the address to listen on, or null to listen on any
+ * address. (UTF-8 string, null terminated)
+ * @param port port to communicate over.
+ */
+WPI_DEPRECATED("use NT_Inst function instead")
+void StartServer(StringRef persist_filename, const char* listen_address,
+ unsigned int port);
+
+/**
+ * @copydoc StartServer(StringRef, const char*, unsigned int)
+ *
+ * @param inst instance handle
+ */
+void StartServer(NT_Inst inst, const Twine& persist_filename,
+ const char* listen_address, unsigned int port);
+
+/**
+ * Stops the server if it is running.
+ */
+WPI_DEPRECATED("use NT_Inst function instead")
+void StopServer();
+
+/**
+ * @copydoc StopServer()
+ *
+ * @param inst instance handle
+ */
+void StopServer(NT_Inst inst);
+
+/**
+ * Starts a client. Use SetServer to set the server name and port.
+ */
+WPI_DEPRECATED("use NT_Inst function instead")
+void StartClient();
+
+/**
+ * Starts a client using the specified server and port
+ *
+ * @param server_name server name (UTF-8 string, null terminated)
+ * @param port port to communicate over
+ */
+WPI_DEPRECATED("use NT_Inst function instead")
+void StartClient(const char* server_name, unsigned int port);
+
+/**
+ * Starts a client using the specified (server, port) combinations. The
+ * client will attempt to connect to each server in round robin fashion.
+ *
+ * @param servers array of server name and port pairs
+ */
+WPI_DEPRECATED("use NT_Inst function instead")
+void StartClient(ArrayRef<std::pair<StringRef, unsigned int>> servers);
+
+/**
+ * @copydoc StartClient()
+ *
+ * @param inst instance handle
+ */
+void StartClient(NT_Inst inst);
+
+/**
+ * @copydoc StartClient(const char*, unsigned int)
+ *
+ * @param inst instance handle
+ */
+void StartClient(NT_Inst inst, const char* server_name, unsigned int port);
+
+/**
+ * @copydoc StartClient(ArrayRef<std::pair<StringRef, unsigned int>>)
+ *
+ * @param inst instance handle
+ */
+void StartClient(NT_Inst inst,
+ ArrayRef<std::pair<StringRef, unsigned int>> servers);
+
+/**
+ * Starts a client using commonly known robot addresses for the specified
+ * team.
+ *
+ * @param inst instance handle
+ * @param team team number
+ * @param port port to communicate over
+ */
+void StartClientTeam(NT_Inst inst, unsigned int team, unsigned int port);
+
+/**
+ * Stops the client if it is running.
+ */
+WPI_DEPRECATED("use NT_Inst function instead")
+void StopClient();
+
+/**
+ * @copydoc StopClient()
+ * @param inst instance handle
+ */
+void StopClient(NT_Inst inst);
+
+/**
+ * Sets server address and port for client (without restarting client).
+ *
+ * @param server_name server name (UTF-8 string, null terminated)
+ * @param port port to communicate over
+ */
+WPI_DEPRECATED("use NT_Inst function instead")
+void SetServer(const char* server_name, unsigned int port);
+
+/**
+ * Sets server addresses for client (without restarting client).
+ * The client will attempt to connect to each server in round robin fashion.
+ *
+ * @param servers array of server name and port pairs
+ */
+WPI_DEPRECATED("use NT_Inst function instead")
+void SetServer(ArrayRef<std::pair<StringRef, unsigned int>> servers);
+
+/**
+ * @copydoc SetServer(const char*, unsigned int)
+ *
+ * @param inst instance handle
+ */
+void SetServer(NT_Inst inst, const char* server_name, unsigned int port);
+
+/**
+ * @copydoc SetServer(ArrayRef<std::pair<StringRef, unsigned int>>)
+ *
+ * @param inst instance handle
+ */
+void SetServer(NT_Inst inst,
+ ArrayRef<std::pair<StringRef, unsigned int>> servers);
+
+/**
+ * Sets server addresses and port for client (without restarting client).
+ * Connects using commonly known robot addresses for the specified team.
+ *
+ * @param inst instance handle
+ * @param team team number
+ * @param port port to communicate over
+ */
+void SetServerTeam(NT_Inst inst, unsigned int team, unsigned int port);
+
+/**
+ * Starts requesting server address from Driver Station.
+ * This connects to the Driver Station running on localhost to obtain the
+ * server IP address.
+ *
+ * @param port server port to use in combination with IP from DS
+ */
+WPI_DEPRECATED("use NT_Inst function instead")
+void StartDSClient(unsigned int port);
+
+/**
+ * @copydoc StartDSClient(unsigned int)
+ * @param inst instance handle
+ */
+void StartDSClient(NT_Inst inst, unsigned int port);
+
+/** Stops requesting server address from Driver Station. */
+WPI_DEPRECATED("use NT_Inst function instead")
+void StopDSClient();
+
+/**
+ * @copydoc StopDSClient()
+ *
+ * @param inst instance handle
+ */
+void StopDSClient(NT_Inst inst);
+
+/** Stops the RPC server if it is running. */
+WPI_DEPRECATED("use NT_Inst function instead")
+void StopRpcServer();
+
+/**
+ * Set the periodic update rate.
+ * Sets how frequently updates are sent to other nodes over the network.
+ *
+ * @param interval update interval in seconds (range 0.01 to 1.0)
+ */
+WPI_DEPRECATED("use NT_Inst function instead")
+void SetUpdateRate(double interval);
+
+/**
+ * @copydoc SetUpdateRate(double)
+ *
+ * @param inst instance handle
+ */
+void SetUpdateRate(NT_Inst inst, double interval);
+
+/**
+ * Flush Entries.
+ *
+ * Forces an immediate flush of all local entry changes to network.
+ * Normally this is done on a regularly scheduled interval (see
+ * SetUpdateRate()).
+ *
+ * Note: flushes are rate limited to avoid excessive network traffic. If
+ * the time between calls is too short, the flush will occur after the minimum
+ * time elapses (rather than immediately).
+ */
+WPI_DEPRECATED("use NT_Inst function instead")
+void Flush();
+
+/**
+ * @copydoc Flush()
+ *
+ * @param inst instance handle
+ */
+void Flush(NT_Inst inst);
+
+/**
+ * Get information on the currently established network connections.
+ * If operating as a client, this will return either zero or one values.
+ *
+ * @return array of connection information
+ */
+WPI_DEPRECATED("use NT_Inst function instead")
+std::vector<ConnectionInfo> GetConnections();
+
+/**
+ * @copydoc GetConnections()
+ *
+ * @param inst instance handle
+ */
+std::vector<ConnectionInfo> GetConnections(NT_Inst inst);
+
+/**
+ * Return whether or not the instance is connected to another node.
+ *
+ * @param inst instance handle
+ * @return True if connected.
+ */
+bool IsConnected(NT_Inst inst);
+
+/** @} */
+
+/**
+ * @defgroup ntcore_file_func File Save/Load Functions
+ * @{
+ */
+
+/**
+ * Save persistent values to a file. The server automatically does this,
+ * but this function provides a way to save persistent values in the same
+ * format to a file on either a client or a server.
+ *
+ * @param filename filename
+ * @return error string, or nullptr if successful
+ */
+WPI_DEPRECATED("use NT_Inst function instead")
+const char* SavePersistent(StringRef filename);
+
+/**
+ * @copydoc SavePersistent(StringRef)
+ * @param inst instance handle
+ */
+const char* SavePersistent(NT_Inst inst, const Twine& filename);
+
+/**
+ * Load persistent values from a file. The server automatically does this
+ * at startup, but this function provides a way to restore persistent values
+ * in the same format from a file at any time on either a client or a server.
+ *
+ * @param filename filename
+ * @param warn callback function for warnings
+ * @return error string, or nullptr if successful
+ */
+WPI_DEPRECATED("use NT_Inst function instead")
+const char* LoadPersistent(
+ StringRef filename, std::function<void(size_t line, const char* msg)> warn);
+
+/**
+ * @copydoc LoadPersistent(StringRef, std::function<void(size_t, const
+ * char*)>)
+ *
+ * @param inst instance handle
+ */
+const char* LoadPersistent(
+ NT_Inst inst, const Twine& filename,
+ std::function<void(size_t line, const char* msg)> warn);
+
+/**
+ * Save table values to a file. The file format used is identical to
+ * that used for SavePersistent.
+ *
+ * @param inst instance handle
+ * @param filename filename
+ * @param prefix save only keys starting with this prefix
+ * @return error string, or nullptr if successful
+ */
+const char* SaveEntries(NT_Inst inst, const Twine& filename,
+ const Twine& prefix);
+
+/**
+ * Load table values from a file. The file format used is identical to
+ * that used for SavePersistent / LoadPersistent.
+ *
+ * @param inst instance handle
+ * @param filename filename
+ * @param prefix load only keys starting with this prefix
+ * @param warn callback function for warnings
+ * @return error string, or nullptr if successful
+ */
+const char* LoadEntries(NT_Inst inst, const Twine& filename,
+ const Twine& prefix,
+ std::function<void(size_t line, const char* msg)> warn);
+
+/** @} */
+
+/**
+ * @defgroup ntcore_utility_func Utility Functions
+ * @{
+ */
+
+/**
+ * Returns monotonic current time in 1 us increments.
+ * This is the same time base used for entry and connection timestamps.
+ * This function is a compatibility wrapper around wpi::Now().
+ *
+ * @return Timestamp
+ */
+uint64_t Now();
+
+/** @} */
+
+/**
+ * @defgroup ntcore_logger_func Logger Functions
+ * @{
+ */
+
+/**
+ * Log function.
+ *
+ * @param level log level of the message (see NT_LogLevel)
+ * @param file origin source filename
+ * @param line origin source line number
+ * @param msg message
+ */
+typedef std::function<void(unsigned int level, const char* file,
+ unsigned int line, const char* msg)>
+ LogFunc;
+
+/**
+ * Set logger callback function. By default, log messages are sent to stderr;
+ * this function changes the log level and sends log messages to the provided
+ * callback function instead. The callback function will only be called for
+ * log messages with level greater than or equal to min_level; messages lower
+ * than this level will be silently ignored.
+ *
+ * @param func log callback function
+ * @param min_level minimum log level
+ */
+WPI_DEPRECATED("use NT_Inst function instead")
+void SetLogger(LogFunc func, unsigned int min_level);
+
+/**
+ * Add logger callback function. By default, log messages are sent to stderr;
+ * this function sends log messages to the provided callback function instead.
+ * The callback function will only be called for log messages with level
+ * greater than or equal to min_level and less than or equal to max_level;
+ * messages outside this range will be silently ignored.
+ *
+ * @param inst instance handle
+ * @param func log callback function
+ * @param min_level minimum log level
+ * @param max_level maximum log level
+ * @return Logger handle
+ */
+NT_Logger AddLogger(NT_Inst inst,
+ std::function<void(const LogMessage& msg)> func,
+ unsigned int min_level, unsigned int max_level);
+
+/**
+ * Create a log poller. A poller provides a single queue of poll events.
+ * The returned handle must be destroyed with DestroyLoggerPoller().
+ *
+ * @param inst instance handle
+ * @return poller handle
+ */
+NT_LoggerPoller CreateLoggerPoller(NT_Inst inst);
+
+/**
+ * Destroy a log poller. This will abort any blocked polling call and prevent
+ * additional events from being generated for this poller.
+ *
+ * @param poller poller handle
+ */
+void DestroyLoggerPoller(NT_LoggerPoller poller);
+
+/**
+ * Set the log level for a log poller. Events will only be generated for
+ * log messages with level greater than or equal to min_level and less than or
+ * equal to max_level; messages outside this range will be silently ignored.
+ *
+ * @param poller poller handle
+ * @param min_level minimum log level
+ * @param max_level maximum log level
+ * @return Logger handle
+ */
+NT_Logger AddPolledLogger(NT_LoggerPoller poller, unsigned int min_level,
+ unsigned int max_level);
+
+/**
+ * Get the next log event. This blocks until the next log occurs.
+ *
+ * @param poller poller handle
+ * @return Information on the log events. Only returns empty if an error
+ * occurred (e.g. the instance was invalid or is shutting down).
+ */
+std::vector<LogMessage> PollLogger(NT_LoggerPoller poller);
+
+/**
+ * Get the next log event. This blocks until the next log occurs or it times
+ * out.
+ *
+ * @param poller poller handle
+ * @param timeout timeout, in seconds
+ * @param timed_out true if the timeout period elapsed (output)
+ * @return Information on the log events. If empty is returned and timed_out
+ * is also false, an error occurred (e.g. the instance was invalid or
+ * is shutting down).
+ */
+std::vector<LogMessage> PollLogger(NT_LoggerPoller poller, double timeout,
+ bool* timed_out);
+
+/**
+ * Cancel a PollLogger call. This wakes up a call to PollLogger for this
+ * poller and causes it to immediately return an empty array.
+ *
+ * @param poller poller handle
+ */
+void CancelPollLogger(NT_LoggerPoller poller);
+
+/**
+ * Remove a logger.
+ *
+ * @param logger Logger handle to remove
+ */
+void RemoveLogger(NT_Logger logger);
+
+/**
+ * Wait for the incoming log event queue to be empty. This is primarily useful
+ * for deterministic testing. This blocks until either the log event
+ * queue is empty (e.g. there are no more events that need to be passed along
+ * to callbacks or poll queues) or the timeout expires.
+ *
+ * @param inst instance handle
+ * @param timeout timeout, in seconds. Set to 0 for non-blocking behavior,
+ * or a negative value to block indefinitely
+ * @return False if timed out, otherwise true.
+ */
+bool WaitForLoggerQueue(NT_Inst inst, double timeout);
+
+/** @} */
+/** @} */
+
+inline bool RpcAnswer::PostResponse(StringRef result) const {
+ auto ret = PostRpcResponse(entry, call, result);
+ call = 0;
+ return ret;
+}
+
+} // namespace nt
+
+#endif // NTCORE_NTCORE_CPP_H_
diff --git a/ntcore/src/main/native/include/ntcore_test.h b/ntcore/src/main/native/include/ntcore_test.h
new file mode 100644
index 0000000..920fd68
--- /dev/null
+++ b/ntcore/src/main/native/include/ntcore_test.h
@@ -0,0 +1,89 @@
+/*----------------------------------------------------------------------------*/
+/* Copyright (c) 2016-2018 FIRST. All Rights Reserved. */
+/* Open Source Software - may be modified and shared by FRC teams. The code */
+/* must be accompanied by the FIRST BSD license file in the root directory of */
+/* the project. */
+/*----------------------------------------------------------------------------*/
+
+#ifndef NTCORE_NTCORE_TEST_H_
+#define NTCORE_NTCORE_TEST_H_
+
+#include <stdint.h>
+
+#include <string>
+
+#include "ntcore.h"
+
+// Functions in this header are to be used only for testing
+
+extern "C" {
+struct NT_String* NT_GetStringForTesting(const char* string, int* struct_size);
+// No need for free as one already exists in main library
+
+struct NT_EntryInfo* NT_GetEntryInfoForTesting(const char* name,
+ enum NT_Type type,
+ unsigned int flags,
+ uint64_t last_change,
+ int* struct_size);
+
+void NT_FreeEntryInfoForTesting(struct NT_EntryInfo* info);
+
+struct NT_ConnectionInfo* NT_GetConnectionInfoForTesting(
+ const char* remote_id, const char* remote_ip, unsigned int remote_port,
+ uint64_t last_update, unsigned int protocol_version, int* struct_size);
+
+void NT_FreeConnectionInfoForTesting(struct NT_ConnectionInfo* info);
+
+struct NT_Value* NT_GetValueBooleanForTesting(uint64_t last_change, int val,
+ int* struct_size);
+
+struct NT_Value* NT_GetValueDoubleForTesting(uint64_t last_change, double val,
+ int* struct_size);
+
+struct NT_Value* NT_GetValueStringForTesting(uint64_t last_change,
+ const char* str, int* struct_size);
+
+struct NT_Value* NT_GetValueRawForTesting(uint64_t last_change, const char* raw,
+ int raw_len, int* struct_size);
+
+struct NT_Value* NT_GetValueBooleanArrayForTesting(uint64_t last_change,
+ const int* arr,
+ size_t array_len,
+ int* struct_size);
+
+struct NT_Value* NT_GetValueDoubleArrayForTesting(uint64_t last_change,
+ const double* arr,
+ size_t array_len,
+ int* struct_size);
+
+struct NT_Value* NT_GetValueStringArrayForTesting(uint64_t last_change,
+ const struct NT_String* arr,
+ size_t array_len,
+ int* struct_size);
+// No need for free as one already exists in the main library
+
+struct NT_RpcParamDef* NT_GetRpcParamDefForTesting(const char* name,
+ const struct NT_Value* val,
+ int* struct_size);
+
+void NT_FreeRpcParamDefForTesting(struct NT_RpcParamDef* def);
+
+struct NT_RpcResultDef* NT_GetRpcResultsDefForTesting(const char* name,
+ enum NT_Type type,
+ int* struct_size);
+
+void NT_FreeRpcResultsDefForTesting(struct NT_RpcResultDef* def);
+
+struct NT_RpcDefinition* NT_GetRpcDefinitionForTesting(
+ unsigned int version, const char* name, size_t num_params,
+ const struct NT_RpcParamDef* params, size_t num_results,
+ const struct NT_RpcResultDef* results, int* struct_size);
+// No need for free as one already exists in the main library
+
+struct NT_RpcCallInfo* NT_GetRpcCallInfoForTesting(
+ unsigned int rpc_id, unsigned int call_uid, const char* name,
+ const char* params, size_t params_len, int* struct_size);
+// No need for free as one already exists in the main library
+} // extern "C"
+
+#endif // NTCORE_NTCORE_TEST_H_
diff --git a/ntcore/src/main/native/include/tables/ITable.h b/ntcore/src/main/native/include/tables/ITable.h
new file mode 100644
index 0000000..d03aaa7
--- /dev/null
+++ b/ntcore/src/main/native/include/tables/ITable.h
@@ -0,0 +1,456 @@
+/*----------------------------------------------------------------------------*/
+/* Copyright (c) 2015-2018 FIRST. All Rights Reserved. */
+/* Open Source Software - may be modified and shared by FRC teams. The code */
+/* must be accompanied by the FIRST BSD license file in the root directory of */
+/* the project. */
+/*----------------------------------------------------------------------------*/
+
+#ifndef NTCORE_TABLES_ITABLE_H_
+#define NTCORE_TABLES_ITABLE_H_
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include <wpi/StringRef.h>
+#include <wpi/Twine.h>
+#include <wpi/deprecated.h>
+
+#include "networktables/NetworkTableValue.h"
+
+namespace nt {
+class NetworkTable;
+} // namespace nt
+
+class ITableListener;
+
+/**
+ * A table whose values can be read and written to
+ */
+class WPI_DEPRECATED("Use NetworkTable directly") ITable {
+ public:
+ /**
+ * Determines whether the given key is in this table.
+ *
+ * @param key the key to search for
+ * @return true if the table as a value assigned to the given key
+ */
+ virtual bool ContainsKey(const wpi::Twine& key) const = 0;
+
+ /**
+ * Determines whether there exists a non-empty subtable for this key
+ * in this table.
+ *
+ * @param key the key to search for
+ * @return true if there is a subtable with the key which contains at least
+ * one key/subtable of its own
+ */
+ virtual bool ContainsSubTable(const wpi::Twine& key) const = 0;
+
+ /**
+ * Gets the subtable in this table for the given name.
+ *
+ * @param key the name of the table relative to this one
+ * @return a sub table relative to this one
+ */
+ virtual std::shared_ptr<nt::NetworkTable> GetSubTable(
+ const wpi::Twine& key) const = 0;
+
+ /**
+ * @param types bitmask of types; 0 is treated as a "don't care".
+ * @return keys currently in the table
+ */
+ virtual std::vector<std::string> GetKeys(int types = 0) const = 0;
+
+ /**
+ * @return subtables currently in the table
+ */
+ virtual std::vector<std::string> GetSubTables() const = 0;
+
+ /**
+ * Makes a key's value persistent through program restarts.
+ *
+ * @param key the key to make persistent
+ */
+ virtual void SetPersistent(wpi::StringRef key) = 0;
+
+ /**
+ * Stop making a key's value persistent through program restarts.
+ * The key cannot be null.
+ *
+ * @param key the key name
+ */
+ virtual void ClearPersistent(wpi::StringRef key) = 0;
+
+ /**
+ * Returns whether the value is persistent through program restarts.
+ * The key cannot be null.
+ *
+ * @param key the key name
+ */
+ virtual bool IsPersistent(wpi::StringRef key) const = 0;
+
+ /**
+ * Sets flags on the specified key in this table. The key can
+ * not be null.
+ *
+ * @param key the key name
+ * @param flags the flags to set (bitmask)
+ */
+ virtual void SetFlags(wpi::StringRef key, unsigned int flags) = 0;
+
+ /**
+ * Clears flags on the specified key in this table. The key can
+ * not be null.
+ *
+ * @param key the key name
+ * @param flags the flags to clear (bitmask)
+ */
+ virtual void ClearFlags(wpi::StringRef key, unsigned int flags) = 0;
+
+ /**
+ * Returns the flags for the specified key.
+ *
+ * @param key the key name
+ * @return the flags, or 0 if the key is not defined
+ */
+ virtual unsigned int GetFlags(wpi::StringRef key) const = 0;
+
+ /**
+ * Deletes the specified key in this table.
+ *
+ * @param key the key name
+ */
+ virtual void Delete(const wpi::Twine& key) = 0;
+
+ /**
+ * Gets the value associated with a key as an object
+ *
+ * @param key the key of the value to look up
+ * @return the value associated with the given key, or nullptr if the key
+ * does not exist
+ */
+ virtual std::shared_ptr<nt::Value> GetValue(const wpi::Twine& key) const = 0;
+
+ /**
+ * Gets the current value in the table, setting it if it does not exist.
+ * @param key the key
+ * @param defaultValue the default value to set if key doesn't exist.
+ * @returns False if the table key exists with a different type
+ */
+ virtual bool SetDefaultValue(const wpi::Twine& key,
+ std::shared_ptr<nt::Value> defaultValue) = 0;
+
+ /**
+ * Put a value in the table
+ *
+ * @param key the key to be assigned to
+ * @param value the value that will be assigned
+ * @return False if the table key already exists with a different type
+ */
+ virtual bool PutValue(const wpi::Twine& key,
+ std::shared_ptr<nt::Value> value) = 0;
+
+ /**
+ * Put a number in the table
+ *
+ * @param key the key to be assigned to
+ * @param value the value that will be assigned
+ * @return False if the table key already exists with a different type
+ */
+ virtual bool PutNumber(wpi::StringRef key, double value) = 0;
+
+ /**
+ * Gets the current value in the table, setting it if it does not exist.
+ * @param key the key
+ * @param defaultValue the default value to set if key doesn't exist.
+ * @returns False if the table key exists with a different type
+ */
+ virtual bool SetDefaultNumber(wpi::StringRef key, double defaultValue) = 0;
+
+ /**
+ * Gets the number associated with the given name.
+ *
+ * @param key the key to look up
+ * @param defaultValue the value to be returned if no value is found
+ * @return the value associated with the given key or the given default value
+ * if there is no value associated with the key
+ */
+ virtual double GetNumber(wpi::StringRef key, double defaultValue) const = 0;
+
+ /**
+ * Put a string in the table
+ *
+ * @param key the key to be assigned to
+ * @param value the value that will be assigned
+ * @return False if the table key already exists with a different type
+ */
+ virtual bool PutString(wpi::StringRef key, wpi::StringRef value) = 0;
+
+ /**
+ * Gets the current value in the table, setting it if it does not exist.
+ * @param key the key
+ * @param defaultValue the default value to set if key doesn't exist.
+ * @returns False if the table key exists with a different type
+ */
+ virtual bool SetDefaultString(wpi::StringRef key,
+ wpi::StringRef defaultValue) = 0;
+
+ /**
+ * Gets the string associated with the given name. If the key does not
+ * exist or is of different type, it will return the default value.
+ *
+ * @param key the key to look up
+ * @param defaultValue the value to be returned if no value is found
+ * @return the value associated with the given key or the given default value
+ * if there is no value associated with the key
+ *
+ * @note This makes a copy of the string. If the overhead of this is a
+ * concern, use GetValue() instead.
+ */
+ virtual std::string GetString(wpi::StringRef key,
+ wpi::StringRef defaultValue) const = 0;
+
+ /**
+ * Put a boolean in the table
+ *
+ * @param key the key to be assigned to
+ * @param value the value that will be assigned
+ * @return False if the table key already exists with a different type
+ */
+ virtual bool PutBoolean(wpi::StringRef key, bool value) = 0;
+
+ /**
+ * Gets the current value in the table, setting it if it does not exist.
+ * @param key the key
+ * @param defaultValue the default value to set if key doesn't exist.
+ * @returns False if the table key exists with a different type
+ */
+ virtual bool SetDefaultBoolean(wpi::StringRef key, bool defaultValue) = 0;
+
+ /**
+ * Gets the boolean associated with the given name. If the key does not
+ * exist or is of different type, it will return the default value.
+ *
+ * @param key the key to look up
+ * @param defaultValue the value to be returned if no value is found
+ * @return the value associated with the given key or the given default value
+ * if there is no value associated with the key
+ */
+ virtual bool GetBoolean(wpi::StringRef key, bool defaultValue) const = 0;
+
+ /**
+ * Put a boolean array in the table
+ * @param key the key to be assigned to
+ * @param value the value that will be assigned
+ * @return False if the table key already exists with a different type
+ *
+ * @note The array must be of int's rather than of bool's because
+ * std::vector<bool> is special-cased in C++. 0 is false, any
+ * non-zero value is true.
+ */
+ virtual bool PutBooleanArray(wpi::StringRef key,
+ wpi::ArrayRef<int> value) = 0;
+
+ /**
+ * Gets the current value in the table, setting it if it does not exist.
+ * @param key the key
+ * @param defaultValue the default value to set if key doesn't exist.
+ * @returns False if the table key exists with a different type
+ */
+ virtual bool SetDefaultBooleanArray(wpi::StringRef key,
+ wpi::ArrayRef<int> defaultValue) = 0;
+
+ /**
+ * Returns the boolean array the key maps to. If the key does not exist or is
+ * of different type, it will return the default value.
+ * @param key the key to look up
+ * @param defaultValue the value to be returned if no value is found
+ * @return the value associated with the given key or the given default value
+ * if there is no value associated with the key
+ *
+ * @note This makes a copy of the array. If the overhead of this is a
+ * concern, use GetValue() instead.
+ *
+ * @note The returned array is std::vector<int> instead of std::vector<bool>
+ * because std::vector<bool> is special-cased in C++. 0 is false, any
+ * non-zero value is true.
+ */
+ virtual std::vector<int> GetBooleanArray(
+ wpi::StringRef key, wpi::ArrayRef<int> defaultValue) const = 0;
+
+ /**
+ * Put a number array in the table
+ * @param key the key to be assigned to
+ * @param value the value that will be assigned
+ * @return False if the table key already exists with a different type
+ */
+ virtual bool PutNumberArray(wpi::StringRef key,
+ wpi::ArrayRef<double> value) = 0;
+
+ /**
+ * Gets the current value in the table, setting it if it does not exist.
+ * @param key the key
+ * @param defaultValue the default value to set if key doesn't exist.
+ * @returns False if the table key exists with a different type
+ */
+ virtual bool SetDefaultNumberArray(wpi::StringRef key,
+ wpi::ArrayRef<double> defaultValue) = 0;
+
+ /**
+ * Returns the number array the key maps to. If the key does not exist or is
+ * of different type, it will return the default value.
+ * @param key the key to look up
+ * @param defaultValue the value to be returned if no value is found
+ * @return the value associated with the given key or the given default value
+ * if there is no value associated with the key
+ *
+ * @note This makes a copy of the array. If the overhead of this is a
+ * concern, use GetValue() instead.
+ */
+ virtual std::vector<double> GetNumberArray(
+ wpi::StringRef key, wpi::ArrayRef<double> defaultValue) const = 0;
+
+ /**
+ * Put a string array in the table
+ * @param key the key to be assigned to
+ * @param value the value that will be assigned
+ * @return False if the table key already exists with a different type
+ */
+ virtual bool PutStringArray(wpi::StringRef key,
+ wpi::ArrayRef<std::string> value) = 0;
+
+ /**
+ * Gets the current value in the table, setting it if it does not exist.
+ * @param key the key
+ * @param defaultValue the default value to set if key doesn't exist.
+ * @returns False if the table key exists with a different type
+ */
+ virtual bool SetDefaultStringArray(
+ wpi::StringRef key, wpi::ArrayRef<std::string> defaultValue) = 0;
+
+ /**
+ * Returns the string array the key maps to. If the key does not exist or is
+ * of different type, it will return the default value.
+ * @param key the key to look up
+ * @param defaultValue the value to be returned if no value is found
+ * @return the value associated with the given key or the given default value
+ * if there is no value associated with the key
+ *
+ * @note This makes a copy of the array. If the overhead of this is a
+ * concern, use GetValue() instead.
+ */
+ virtual std::vector<std::string> GetStringArray(
+ wpi::StringRef key, wpi::ArrayRef<std::string> defaultValue) const = 0;
+
+ /**
+ * Put a raw value (byte array) in the table
+ * @param key the key to be assigned to
+ * @param value the value that will be assigned
+ * @return False if the table key already exists with a different type
+ */
+ virtual bool PutRaw(wpi::StringRef key, wpi::StringRef value) = 0;
+
+ /**
+ * Gets the current value in the table, setting it if it does not exist.
+ * @param key the key
+ * @param defaultValue the default value to set if key doesn't exist.
+ * @returns False if the table key exists with a different type
+ */
+ virtual bool SetDefaultRaw(wpi::StringRef key,
+ wpi::StringRef defaultValue) = 0;
+
+ /**
+ * Returns the raw value (byte array) the key maps to. If the key does not
+ * exist or is of different type, it will return the default value.
+ * @param key the key to look up
+ * @param defaultValue the value to be returned if no value is found
+ * @return the value associated with the given key or the given default value
+ * if there is no value associated with the key
+ *
+ * @note This makes a copy of the raw contents. If the overhead of this is a
+ * concern, use GetValue() instead.
+ */
+ virtual std::string GetRaw(wpi::StringRef key,
+ wpi::StringRef defaultValue) const = 0;
+
+ /**
+ * Add a listener for changes to the table
+ *
+ * @param listener the listener to add
+ */
+ virtual void AddTableListener(ITableListener* listener) = 0;
+
+ /**
+ * Add a listener for changes to the table
+ *
+ * @param listener the listener to add
+ * @param immediateNotify if true then this listener will be notified of all
+ * current entries (marked as new)
+ */
+ virtual void AddTableListener(ITableListener* listener,
+ bool immediateNotify) = 0;
+
+ /**
+ * Add a listener for changes to the table
+ *
+ * @param listener the listener to add
+ * @param immediateNotify if true then this listener will be notified of all
+ * current entries (marked as new)
+ * @param flags bitmask of NT_NotifyKind specifying desired notifications
+ */
+ virtual void AddTableListenerEx(ITableListener* listener,
+ unsigned int flags) = 0;
+
+ /**
+ * Add a listener for changes to a specific key the table
+ *
+ * @param key the key to listen for
+ * @param listener the listener to add
+ * @param immediateNotify if true then this listener will be notified of all
+ * current entries (marked as new)
+ */
+ virtual void AddTableListener(wpi::StringRef key, ITableListener* listener,
+ bool immediateNotify) = 0;
+
+ /**
+ * Add a listener for changes to a specific key the table
+ *
+ * @param key the key to listen for
+ * @param listener the listener to add
+ * @param immediateNotify if true then this listener will be notified of all
+ * current entries (marked as new)
+ * @param flags bitmask of NT_NotifyKind specifying desired notifications
+ */
+ virtual void AddTableListenerEx(wpi::StringRef key, ITableListener* listener,
+ unsigned int flags) = 0;
+
+ /**
+ * This will immediately notify the listener of all current sub tables
+ * @param listener the listener to add
+ */
+ virtual void AddSubTableListener(ITableListener* listener) = 0;
+
+ /**
+ * This will immediately notify the listener of all current sub tables
+ * @param listener the listener to add
+ * @param localNotify if true then this listener will be notified of all
+ * local changes in addition to all remote changes
+ */
+ virtual void AddSubTableListener(ITableListener* listener,
+ bool localNotify) = 0;
+
+ /**
+ * Remove a listener from receiving table events
+ *
+ * @param listener the listener to be removed
+ */
+ virtual void RemoveTableListener(ITableListener* listener) = 0;
+
+ /**
+ * Gets the full path of this table.
+ */
+ virtual wpi::StringRef GetPath() const = 0;
+};
+
+#endif // NTCORE_TABLES_ITABLE_H_
diff --git a/ntcore/src/main/native/include/tables/ITableListener.h b/ntcore/src/main/native/include/tables/ITableListener.h
new file mode 100644
index 0000000..e836b51
--- /dev/null
+++ b/ntcore/src/main/native/include/tables/ITableListener.h
@@ -0,0 +1,63 @@
+/*----------------------------------------------------------------------------*/
+/* Copyright (c) 2017-2018 FIRST. All Rights Reserved. */
+/* Open Source Software - may be modified and shared by FRC teams. The code */
+/* must be accompanied by the FIRST BSD license file in the root directory of */
+/* the project. */
+/*----------------------------------------------------------------------------*/
+
+#ifndef NTCORE_TABLES_ITABLELISTENER_H_
+#define NTCORE_TABLES_ITABLELISTENER_H_
+
+#include <memory>
+
+#include <wpi/StringRef.h>
+#include <wpi/deprecated.h>
+
+#include "networktables/NetworkTableValue.h"
+
+#ifdef __GNUC__
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
+#endif
+
+class ITable;
+
+/**
+ * A listener that listens to changes in values in a {@link ITable}
+ */
+class WPI_DEPRECATED(
+ "Use EntryListener, TableEntryListener, or TableListener as appropriate")
+ ITableListener {
+ public:
+ virtual ~ITableListener() = default;
+ /**
+ * Called when a key-value pair is changed in a {@link ITable}
+ * @param source the table the key-value pair exists in
+ * @param key the key associated with the value that changed
+ * @param value the new value
+ * @param isNew true if the key did not previously exist in the table,
+ * otherwise it is false
+ */
+ virtual void ValueChanged(ITable* source, wpi::StringRef key,
+ std::shared_ptr<nt::Value> value, bool isNew) = 0;
+
+ /**
+ * Extended version of ValueChanged. Called when a key-value pair is
+ * changed in a {@link ITable}. The default implementation simply calls
+ * ValueChanged(). If this is overridden, ValueChanged() will not be called.
+ * @param source the table the key-value pair exists in
+ * @param key the key associated with the value that changed
+ * @param value the new value
+ * @param flags update flags; for example, NT_NOTIFY_NEW if the key did not
+ * previously exist in the table
+ */
+ virtual void ValueChangedEx(ITable* source, wpi::StringRef key,
+ std::shared_ptr<nt::Value> value,
+ unsigned int flags);
+};
+
+#ifdef __GNUC__
+#pragma GCC diagnostic pop
+#endif
+
+#endif // NTCORE_TABLES_ITABLELISTENER_H_
diff --git a/ntcore/src/test/java/edu/wpi/first/networktables/ConnectionListenerTest.java b/ntcore/src/test/java/edu/wpi/first/networktables/ConnectionListenerTest.java
new file mode 100644
index 0000000..9628a0e
--- /dev/null
+++ b/ntcore/src/test/java/edu/wpi/first/networktables/ConnectionListenerTest.java
@@ -0,0 +1,160 @@
+/*----------------------------------------------------------------------------*/
+/* Copyright (c) 2017-2018 FIRST. All Rights Reserved. */
+/* Open Source Software - may be modified and shared by FRC teams. The code */
+/* must be accompanied by the FIRST BSD license file in the root directory of */
+/* the project. */
+/*----------------------------------------------------------------------------*/
+
+package edu.wpi.first.networktables;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.condition.DisabledOnOs;
+import org.junit.jupiter.api.condition.OS;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNotSame;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.fail;
+
+class ConnectionListenerTest {
+ private NetworkTableInstance m_serverInst;
+ private NetworkTableInstance m_clientInst;
+
+ @BeforeEach
+ void setUp() {
+ m_serverInst = NetworkTableInstance.create();
+ m_serverInst.setNetworkIdentity("server");
+
+ m_clientInst = NetworkTableInstance.create();
+ m_clientInst.setNetworkIdentity("client");
+ }
+
+ @AfterEach
+ void tearDown() {
+ m_clientInst.close();
+ m_serverInst.close();
+ }
+
+ /**
+ * Connect to the server.
+ */
+ @SuppressWarnings("PMD.AvoidUsingHardCodedIP")
+ private void connect() {
+ m_serverInst.startServer("connectionlistenertest.ini", "127.0.0.1", 10000);
+ m_clientInst.startClient("127.0.0.1", 10000);
+
+ // wait for client to report it's started, then wait another 0.1 sec
+ try {
+ while ((m_clientInst.getNetworkMode() & NetworkTableInstance.kNetModeStarting) != 0) {
+ Thread.sleep(100);
+ }
+ Thread.sleep(100);
+ } catch (InterruptedException ex) {
+ fail("interrupted while waiting for client to start");
+ }
+ }
+
+ @Test
+ @DisabledOnOs(OS.WINDOWS)
+ void testJNI() {
+ // set up the poller
+ int poller = NetworkTablesJNI.createConnectionListenerPoller(m_serverInst.getHandle());
+ assertNotSame(poller, 0, "bad poller handle");
+ int handle = NetworkTablesJNI.addPolledConnectionListener(poller, false);
+ assertNotSame(handle, 0, "bad listener handle");
+
+ // trigger a connect event
+ connect();
+
+ // get the event
+ assertTrue(m_serverInst.waitForConnectionListenerQueue(1.0));
+ ConnectionNotification[] events = null;
+ try {
+ events = NetworkTablesJNI.pollConnectionListenerTimeout(m_serverInst, poller, 0.0);
+ } catch (InterruptedException ex) {
+ Thread.currentThread().interrupt();
+ fail("unexpected interrupted exception" + ex);
+ }
+
+ assertNotNull(events);
+ assertEquals(1, events.length);
+ assertEquals(handle, events[0].listener);
+ assertTrue(events[0].connected);
+
+ // trigger a disconnect event
+ m_clientInst.stopClient();
+ try {
+ Thread.sleep(100);
+ } catch (InterruptedException ex) {
+ fail("interrupted while waiting for client to stop");
+ }
+
+ // get the event
+ assertTrue(m_serverInst.waitForConnectionListenerQueue(1.0));
+ try {
+ events = NetworkTablesJNI.pollConnectionListenerTimeout(m_serverInst, poller, 0.0);
+ } catch (InterruptedException ex) {
+ Thread.currentThread().interrupt();
+ fail("unexpected interrupted exception" + ex);
+ }
+
+ assertNotNull(events);
+ assertEquals(1, events.length);
+ assertEquals(handle, events[0].listener);
+ assertFalse(events[0].connected);
+
+ }
+
+ @ParameterizedTest
+ @DisabledOnOs(OS.WINDOWS)
+ @SuppressWarnings("PMD.AvoidUsingHardCodedIP")
+ @ValueSource(strings = { "127.0.0.1", "127.0.0.1 ", " 127.0.0.1 " })
+ void testThreaded(String address) {
+ m_serverInst.startServer("connectionlistenertest.ini", address, 10000);
+ List<ConnectionNotification> events = new ArrayList<>();
+ final int handle = m_serverInst.addConnectionListener(events::add, false);
+
+ // trigger a connect event
+ m_clientInst.startClient(address, 10000);
+
+ // wait for client to report it's started, then wait another 0.1 sec
+ try {
+ while ((m_clientInst.getNetworkMode() & NetworkTableInstance.kNetModeStarting) != 0) {
+ Thread.sleep(100);
+ }
+ Thread.sleep(100);
+ } catch (InterruptedException ex) {
+ fail("interrupted while waiting for client to start");
+ }
+ assertTrue(m_serverInst.waitForConnectionListenerQueue(1.0));
+
+ // get the event
+ assertEquals(1, events.size());
+ assertEquals(handle, events.get(0).listener);
+ assertTrue(events.get(0).connected);
+ events.clear();
+
+ // trigger a disconnect event
+ m_clientInst.stopClient();
+ try {
+ Thread.sleep(100);
+ } catch (InterruptedException ex) {
+ fail("interrupted while waiting for client to stop");
+ }
+
+ // get the event
+ assertTrue(m_serverInst.waitForConnectionListenerQueue(1.0));
+ assertEquals(1, events.size());
+ assertEquals(handle, events.get(0).listener);
+ assertFalse(events.get(0).connected);
+ }
+}
diff --git a/ntcore/src/test/java/edu/wpi/first/networktables/EntryListenerTest.java b/ntcore/src/test/java/edu/wpi/first/networktables/EntryListenerTest.java
new file mode 100644
index 0000000..80627a4
--- /dev/null
+++ b/ntcore/src/test/java/edu/wpi/first/networktables/EntryListenerTest.java
@@ -0,0 +1,91 @@
+/*----------------------------------------------------------------------------*/
+/* Copyright (c) 2017-2018 FIRST. All Rights Reserved. */
+/* Open Source Software - may be modified and shared by FRC teams. The code */
+/* must be accompanied by the FIRST BSD license file in the root directory of */
+/* the project. */
+/*----------------------------------------------------------------------------*/
+
+package edu.wpi.first.networktables;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertAll;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.fail;
+
+class EntryListenerTest {
+ private NetworkTableInstance m_serverInst;
+ private NetworkTableInstance m_clientInst;
+
+ @BeforeEach
+ void setUp() {
+ m_serverInst = NetworkTableInstance.create();
+ m_serverInst.setNetworkIdentity("server");
+
+ m_clientInst = NetworkTableInstance.create();
+ m_clientInst.setNetworkIdentity("client");
+ }
+
+ @AfterEach
+ void tearDown() {
+ m_clientInst.close();
+ m_serverInst.close();
+ }
+
+ @SuppressWarnings("PMD.AvoidUsingHardCodedIP")
+ private void connect() {
+ m_serverInst.startServer("connectionlistenertest.ini", "127.0.0.1", 10000);
+ m_clientInst.startClient("127.0.0.1", 10000);
+
+ // Use connection listener to ensure we've connected
+ int poller = NetworkTablesJNI.createConnectionListenerPoller(m_clientInst.getHandle());
+ NetworkTablesJNI.addPolledConnectionListener(poller, false);
+ try {
+ if (NetworkTablesJNI.pollConnectionListenerTimeout(m_clientInst, poller, 1.0).length == 0) {
+ fail("client didn't connect to server");
+ }
+ } catch (InterruptedException ex) {
+ Thread.currentThread().interrupt();
+ fail("interrupted while waiting for server connection");
+ }
+ }
+
+ /**
+ * Test prefix with a new remote.
+ */
+ @Test
+ void testPrefixNewRemote() {
+ connect();
+ List<EntryNotification> events = new ArrayList<>();
+ final int handle = m_serverInst.addEntryListener("/foo", events::add,
+ EntryListenerFlags.kNew);
+
+ // Trigger an event
+ m_clientInst.getEntry("/foo/bar").setDouble(1.0);
+ m_clientInst.getEntry("/baz").setDouble(1.0);
+ m_clientInst.flush();
+ try {
+ Thread.sleep(100);
+ } catch (InterruptedException ex) {
+ fail("interrupted while waiting for entries to update");
+ }
+
+ assertTrue(m_serverInst.waitForEntryListenerQueue(1.0));
+
+ // Check the event
+ assertAll("Event",
+ () -> assertEquals(1, events.size()),
+ () -> assertEquals(handle, events.get(0).listener),
+ () -> assertEquals(m_serverInst.getEntry("/foo/bar"), events.get(0).getEntry()),
+ () -> assertEquals("/foo/bar", events.get(0).name),
+ () -> assertEquals(NetworkTableValue.makeDouble(1.0), events.get(0).value),
+ () -> assertEquals(EntryListenerFlags.kNew, events.get(0).flags)
+ );
+ }
+}
diff --git a/ntcore/src/test/java/edu/wpi/first/networktables/JNITest.java b/ntcore/src/test/java/edu/wpi/first/networktables/JNITest.java
new file mode 100644
index 0000000..ef2b42b
--- /dev/null
+++ b/ntcore/src/test/java/edu/wpi/first/networktables/JNITest.java
@@ -0,0 +1,19 @@
+/*----------------------------------------------------------------------------*/
+/* Copyright (c) 2017-2018 FIRST. All Rights Reserved. */
+/* Open Source Software - may be modified and shared by FRC teams. The code */
+/* must be accompanied by the FIRST BSD license file in the root directory of */
+/* the project. */
+/*----------------------------------------------------------------------------*/
+
+package edu.wpi.first.networktables;
+
+import org.junit.jupiter.api.Test;
+
+class JNITest {
+ @Test
+ void jniLinkTest() {
+ // Test to verify that the JNI test link works correctly.
+ int inst = NetworkTablesJNI.getDefaultInstance();
+ NetworkTablesJNI.flush(inst);
+ }
+}
diff --git a/ntcore/src/test/java/edu/wpi/first/networktables/LoggerTest.java b/ntcore/src/test/java/edu/wpi/first/networktables/LoggerTest.java
new file mode 100644
index 0000000..420f9dc
--- /dev/null
+++ b/ntcore/src/test/java/edu/wpi/first/networktables/LoggerTest.java
@@ -0,0 +1,53 @@
+/*----------------------------------------------------------------------------*/
+/* Copyright (c) 2017-2018 FIRST. All Rights Reserved. */
+/* Open Source Software - may be modified and shared by FRC teams. The code */
+/* must be accompanied by the FIRST BSD license file in the root directory of */
+/* the project. */
+/*----------------------------------------------------------------------------*/
+
+package edu.wpi.first.networktables;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.fail;
+
+class LoggerTest {
+ private NetworkTableInstance m_clientInst;
+
+ @BeforeEach
+ protected void setUp() {
+ m_clientInst = NetworkTableInstance.create();
+ }
+
+ @AfterEach
+ protected void tearDown() {
+ m_clientInst.close();
+ }
+
+ @Test
+ @SuppressWarnings("PMD.AvoidUsingHardCodedIP")
+ void addMessageTest() {
+ List<LogMessage> msgs = new ArrayList<>();
+ m_clientInst.addLogger(msgs::add, LogMessage.kInfo, 100);
+
+ m_clientInst.startClient("127.0.0.1", 10000);
+
+ // wait for client to report it's started, then wait another 0.1 sec
+ try {
+ while ((m_clientInst.getNetworkMode() & NetworkTableInstance.kNetModeStarting) != 0) {
+ Thread.sleep(100);
+ }
+ Thread.sleep(100);
+ } catch (InterruptedException ex) {
+ fail("interrupted while waiting for client to start");
+ }
+
+ assertFalse(msgs.isEmpty());
+ }
+}
diff --git a/ntcore/src/test/java/edu/wpi/first/networktables/NetworkTableTest.java b/ntcore/src/test/java/edu/wpi/first/networktables/NetworkTableTest.java
new file mode 100644
index 0000000..e45f197
--- /dev/null
+++ b/ntcore/src/test/java/edu/wpi/first/networktables/NetworkTableTest.java
@@ -0,0 +1,80 @@
+/*----------------------------------------------------------------------------*/
+/* Copyright (c) 2017-2018 FIRST. All Rights Reserved. */
+/* Open Source Software - may be modified and shared by FRC teams. The code */
+/* must be accompanied by the FIRST BSD license file in the root directory of */
+/* the project. */
+/*----------------------------------------------------------------------------*/
+
+package edu.wpi.first.networktables;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Stream;
+
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+class NetworkTableTest {
+ private static Stream<Arguments> basenameKeyArguments() {
+ return Stream.of(
+ Arguments.of("simple", "simple"),
+ Arguments.of("simple", "one/two/many/simple"),
+ Arguments.of("simple", "//////an/////awful/key////simple")
+ );
+ }
+
+ @ParameterizedTest
+ @MethodSource("basenameKeyArguments")
+ void basenameKeyTest(final String expected, final String testString) {
+ assertEquals(expected, NetworkTable.basenameKey(testString));
+ }
+
+ private static Stream<Arguments> normalizeKeySlashArguments() {
+ return Stream.of(
+ Arguments.of("/", "///"),
+ Arguments.of("/no/normal/req", "/no/normal/req"),
+ Arguments.of("/no/leading/slash", "no/leading/slash"),
+ Arguments.of("/what/an/awful/key/", "//////what////an/awful/////key///")
+ );
+ }
+
+ @ParameterizedTest
+ @MethodSource("normalizeKeySlashArguments")
+ void normalizeKeySlashTest(final String expected, final String testString) {
+ assertEquals(expected, NetworkTable.normalizeKey(testString));
+ }
+
+ private static Stream<Arguments> normalizeKeyNoSlashArguments() {
+ return Stream.of(
+ Arguments.of("a", "a"),
+ Arguments.of("a", "///a"),
+ Arguments.of("leading/slash", "/leading/slash"),
+ Arguments.of("no/leading/slash", "no/leading/slash"),
+ Arguments.of("what/an/awful/key/", "//////what////an/awful/////key///")
+ );
+ }
+
+ @ParameterizedTest
+ @MethodSource("normalizeKeyNoSlashArguments")
+ void normalizeKeyNoSlashTest(final String expected, final String testString) {
+ assertEquals(expected, NetworkTable.normalizeKey(testString, false));
+ }
+
+ private static Stream<Arguments> getHierarchyArguments() {
+ return Stream.of(
+ Arguments.of(Arrays.asList("/"), ""),
+ Arguments.of(Arrays.asList("/"), "/"),
+ Arguments.of(Arrays.asList("/", "/foo", "/foo/bar", "/foo/bar/baz"), "/foo/bar/baz"),
+ Arguments.of(Arrays.asList("/", "/foo", "/foo/bar", "/foo/bar/"), "/foo/bar/")
+ );
+ }
+
+ @ParameterizedTest
+ @MethodSource("getHierarchyArguments")
+ void getHierarchyTest(final List<String> expected, final String testString) {
+ assertEquals(expected, NetworkTable.getHierarchy(testString));
+ }
+}
diff --git a/ntcore/src/test/native/cpp/ConnectionListenerTest.cpp b/ntcore/src/test/native/cpp/ConnectionListenerTest.cpp
new file mode 100644
index 0000000..a56e45c
--- /dev/null
+++ b/ntcore/src/test/native/cpp/ConnectionListenerTest.cpp
@@ -0,0 +1,108 @@
+/*----------------------------------------------------------------------------*/
+/* Copyright (c) 2017-2018 FIRST. All Rights Reserved. */
+/* Open Source Software - may be modified and shared by FRC teams. The code */
+/* must be accompanied by the FIRST BSD license file in the root directory of */
+/* the project. */
+/*----------------------------------------------------------------------------*/
+
+#include <chrono>
+#include <thread>
+
+#include "TestPrinters.h"
+#include "gtest/gtest.h"
+#include "ntcore_cpp.h"
+
+class ConnectionListenerTest : public ::testing::Test {
+ public:
+ ConnectionListenerTest()
+ : server_inst(nt::CreateInstance()), client_inst(nt::CreateInstance()) {
+ nt::SetNetworkIdentity(server_inst, "server");
+ nt::SetNetworkIdentity(client_inst, "client");
+ }
+
+ ~ConnectionListenerTest() override {
+ nt::DestroyInstance(server_inst);
+ nt::DestroyInstance(client_inst);
+ }
+
+ void Connect();
+
+ protected:
+ NT_Inst server_inst;
+ NT_Inst client_inst;
+};
+
+void ConnectionListenerTest::Connect() {
+ nt::StartServer(server_inst, "connectionlistenertest.ini", "127.0.0.1",
+ 10000);
+ nt::StartClient(client_inst, "127.0.0.1", 10000);
+
+ // wait for client to report it's started, then wait another 0.1 sec
+ while ((nt::GetNetworkMode(client_inst) & NT_NET_MODE_STARTING) != 0)
+ std::this_thread::sleep_for(std::chrono::milliseconds(100));
+ std::this_thread::sleep_for(std::chrono::milliseconds(100));
+}
+
+TEST_F(ConnectionListenerTest, Polled) {
+ // set up the poller
+ NT_ConnectionListenerPoller poller =
+ nt::CreateConnectionListenerPoller(server_inst);
+ ASSERT_NE(poller, 0u);
+ NT_ConnectionListener handle = nt::AddPolledConnectionListener(poller, false);
+ ASSERT_NE(handle, 0u);
+
+ // trigger a connect event
+ Connect();
+
+ // get the event
+ ASSERT_TRUE(nt::WaitForConnectionListenerQueue(server_inst, 1.0));
+ bool timed_out = false;
+ auto result = nt::PollConnectionListener(poller, 0.1, &timed_out);
+ EXPECT_FALSE(timed_out);
+ ASSERT_EQ(result.size(), 1u);
+ EXPECT_EQ(handle, result[0].listener);
+ EXPECT_TRUE(result[0].connected);
+
+ // trigger a disconnect event
+ nt::StopClient(client_inst);
+ std::this_thread::sleep_for(std::chrono::milliseconds(100));
+
+ // get the event
+ ASSERT_TRUE(nt::WaitForConnectionListenerQueue(server_inst, 1.0));
+ timed_out = false;
+ result = nt::PollConnectionListener(poller, 0.1, &timed_out);
+ EXPECT_FALSE(timed_out);
+ ASSERT_EQ(result.size(), 1u);
+ EXPECT_EQ(handle, result[0].listener);
+ EXPECT_FALSE(result[0].connected);
+
+ // trigger a disconnect event
+}
+
+TEST_F(ConnectionListenerTest, Threaded) {
+ std::vector<nt::ConnectionNotification> result;
+ auto handle = nt::AddConnectionListener(
+ server_inst,
+ [&](const nt::ConnectionNotification& event) { result.push_back(event); },
+ false);
+
+ // trigger a connect event
+ Connect();
+
+ ASSERT_TRUE(nt::WaitForConnectionListenerQueue(server_inst, 1.0));
+
+ // get the event
+ ASSERT_EQ(result.size(), 1u);
+ EXPECT_EQ(handle, result[0].listener);
+ EXPECT_TRUE(result[0].connected);
+ result.clear();
+
+ // trigger a disconnect event
+ nt::StopClient(client_inst);
+ std::this_thread::sleep_for(std::chrono::milliseconds(100));
+
+ // get the event
+ ASSERT_EQ(result.size(), 1u);
+ EXPECT_EQ(handle, result[0].listener);
+ EXPECT_FALSE(result[0].connected);
+}
diff --git a/ntcore/src/test/native/cpp/EntryListenerTest.cpp b/ntcore/src/test/native/cpp/EntryListenerTest.cpp
new file mode 100644
index 0000000..b7bf2f6
--- /dev/null
+++ b/ntcore/src/test/native/cpp/EntryListenerTest.cpp
@@ -0,0 +1,164 @@
+/*----------------------------------------------------------------------------*/
+/* Copyright (c) 2017-2018 FIRST. All Rights Reserved. */
+/* Open Source Software - may be modified and shared by FRC teams. The code */
+/* must be accompanied by the FIRST BSD license file in the root directory of */
+/* the project. */
+/*----------------------------------------------------------------------------*/
+
+#include <chrono>
+#include <thread>
+
+#include "TestPrinters.h"
+#include "ValueMatcher.h"
+#include "gtest/gtest.h"
+#include "ntcore_cpp.h"
+
+class EntryListenerTest : public ::testing::Test {
+ public:
+ EntryListenerTest()
+ : server_inst(nt::CreateInstance()), client_inst(nt::CreateInstance()) {
+ nt::SetNetworkIdentity(server_inst, "server");
+ nt::SetNetworkIdentity(client_inst, "client");
+#if 0
+ nt::AddLogger(server_inst,
+ [](const nt::LogMessage& msg) {
+ std::fprintf(stderr, "SERVER: %s\n", msg.message.c_str());
+ },
+ 0, UINT_MAX);
+ nt::AddLogger(client_inst,
+ [](const nt::LogMessage& msg) {
+ std::fprintf(stderr, "CLIENT: %s\n", msg.message.c_str());
+ },
+ 0, UINT_MAX);
+#endif
+ }
+
+ ~EntryListenerTest() override {
+ nt::DestroyInstance(server_inst);
+ nt::DestroyInstance(client_inst);
+ }
+
+ void Connect();
+
+ protected:
+ NT_Inst server_inst;
+ NT_Inst client_inst;
+};
+
+void EntryListenerTest::Connect() {
+ nt::StartServer(server_inst, "entrylistenertest.ini", "127.0.0.1", 10000);
+ nt::StartClient(client_inst, "127.0.0.1", 10000);
+
+ // Use connection listener to ensure we've connected
+ NT_ConnectionListenerPoller poller =
+ nt::CreateConnectionListenerPoller(server_inst);
+ nt::AddPolledConnectionListener(poller, false);
+ bool timed_out = false;
+ if (nt::PollConnectionListener(poller, 1.0, &timed_out).empty()) {
+ FAIL() << "client didn't connect to server";
+ }
+}
+
+TEST_F(EntryListenerTest, EntryNewLocal) {
+ std::vector<nt::EntryNotification> events;
+ auto handle = nt::AddEntryListener(
+ nt::GetEntry(server_inst, "/foo"),
+ [&](const nt::EntryNotification& event) { events.push_back(event); },
+ NT_NOTIFY_NEW | NT_NOTIFY_LOCAL);
+
+ // Trigger an event
+ nt::SetEntryValue(nt::GetEntry(server_inst, "/foo/bar"),
+ nt::Value::MakeDouble(2.0));
+ nt::SetEntryValue(nt::GetEntry(server_inst, "/foo"),
+ nt::Value::MakeDouble(1.0));
+
+ ASSERT_TRUE(nt::WaitForEntryListenerQueue(server_inst, 1.0));
+
+ // Check the event
+ ASSERT_EQ(events.size(), 1u);
+ ASSERT_EQ(events[0].listener, handle);
+ ASSERT_EQ(events[0].entry, nt::GetEntry(server_inst, "/foo"));
+ ASSERT_EQ(events[0].name, "/foo");
+ ASSERT_THAT(events[0].value, nt::ValueEq(nt::Value::MakeDouble(1.0)));
+ ASSERT_EQ(events[0].flags, (unsigned int)(NT_NOTIFY_NEW | NT_NOTIFY_LOCAL));
+}
+
+TEST_F(EntryListenerTest, DISABLED_EntryNewRemote) {
+ Connect();
+ if (HasFatalFailure()) return;
+ std::vector<nt::EntryNotification> events;
+ auto handle = nt::AddEntryListener(
+ nt::GetEntry(server_inst, "/foo"),
+ [&](const nt::EntryNotification& event) { events.push_back(event); },
+ NT_NOTIFY_NEW);
+
+ // Trigger an event
+ nt::SetEntryValue(nt::GetEntry(client_inst, "/foo/bar"),
+ nt::Value::MakeDouble(2.0));
+ nt::SetEntryValue(nt::GetEntry(client_inst, "/foo"),
+ nt::Value::MakeDouble(1.0));
+ nt::Flush(client_inst);
+ std::this_thread::sleep_for(std::chrono::milliseconds(100));
+
+ ASSERT_TRUE(nt::WaitForEntryListenerQueue(server_inst, 1.0));
+
+ // Check the event
+ ASSERT_EQ(events.size(), 1u);
+ ASSERT_EQ(events[0].listener, handle);
+ ASSERT_EQ(events[0].entry, nt::GetEntry(server_inst, "/foo"));
+ ASSERT_EQ(events[0].name, "/foo");
+ ASSERT_THAT(events[0].value, nt::ValueEq(nt::Value::MakeDouble(1.0)));
+ ASSERT_EQ(events[0].flags, NT_NOTIFY_NEW);
+}
+
+TEST_F(EntryListenerTest, PrefixNewLocal) {
+ std::vector<nt::EntryNotification> events;
+ auto handle = nt::AddEntryListener(
+ server_inst, "/foo",
+ [&](const nt::EntryNotification& event) { events.push_back(event); },
+ NT_NOTIFY_NEW | NT_NOTIFY_LOCAL);
+
+ // Trigger an event
+ nt::SetEntryValue(nt::GetEntry(server_inst, "/foo/bar"),
+ nt::Value::MakeDouble(1.0));
+ nt::SetEntryValue(nt::GetEntry(server_inst, "/baz"),
+ nt::Value::MakeDouble(1.0));
+
+ ASSERT_TRUE(nt::WaitForEntryListenerQueue(server_inst, 1.0));
+
+ // Check the event
+ ASSERT_EQ(events.size(), 1u);
+ ASSERT_EQ(events[0].listener, handle);
+ ASSERT_EQ(events[0].entry, nt::GetEntry(server_inst, "/foo/bar"));
+ ASSERT_EQ(events[0].name, "/foo/bar");
+ ASSERT_THAT(events[0].value, nt::ValueEq(nt::Value::MakeDouble(1.0)));
+ ASSERT_EQ(events[0].flags, (unsigned int)(NT_NOTIFY_NEW | NT_NOTIFY_LOCAL));
+}
+
+TEST_F(EntryListenerTest, DISABLED_PrefixNewRemote) {
+ Connect();
+ if (HasFatalFailure()) return;
+ std::vector<nt::EntryNotification> events;
+ auto handle = nt::AddEntryListener(
+ server_inst, "/foo",
+ [&](const nt::EntryNotification& event) { events.push_back(event); },
+ NT_NOTIFY_NEW);
+
+ // Trigger an event
+ nt::SetEntryValue(nt::GetEntry(client_inst, "/foo/bar"),
+ nt::Value::MakeDouble(1.0));
+ nt::SetEntryValue(nt::GetEntry(client_inst, "/baz"),
+ nt::Value::MakeDouble(1.0));
+ nt::Flush(client_inst);
+ std::this_thread::sleep_for(std::chrono::milliseconds(100));
+
+ ASSERT_TRUE(nt::WaitForEntryListenerQueue(server_inst, 1.0));
+
+ // Check the event
+ ASSERT_EQ(events.size(), 1u);
+ ASSERT_EQ(events[0].listener, handle);
+ ASSERT_EQ(events[0].entry, nt::GetEntry(server_inst, "/foo/bar"));
+ ASSERT_EQ(events[0].name, "/foo/bar");
+ ASSERT_THAT(events[0].value, nt::ValueEq(nt::Value::MakeDouble(1.0)));
+ ASSERT_EQ(events[0].flags, NT_NOTIFY_NEW);
+}
diff --git a/ntcore/src/test/native/cpp/EntryNotifierTest.cpp b/ntcore/src/test/native/cpp/EntryNotifierTest.cpp
new file mode 100644
index 0000000..4f1df77
--- /dev/null
+++ b/ntcore/src/test/native/cpp/EntryNotifierTest.cpp
@@ -0,0 +1,314 @@
+/*----------------------------------------------------------------------------*/
+/* Copyright (c) 2017-2018 FIRST. All Rights Reserved. */
+/* Open Source Software - may be modified and shared by FRC teams. The code */
+/* must be accompanied by the FIRST BSD license file in the root directory of */
+/* the project. */
+/*----------------------------------------------------------------------------*/
+
+#include <wpi/Logger.h>
+
+#include "EntryNotifier.h"
+#include "TestPrinters.h"
+#include "ValueMatcher.h"
+#include "gtest/gtest.h"
+
+using ::testing::AnyNumber;
+using ::testing::IsNull;
+using ::testing::Return;
+using ::testing::_;
+
+namespace nt {
+
+class EntryNotifierTest : public ::testing::Test {
+ public:
+ EntryNotifierTest() : notifier(1, logger) { notifier.Start(); }
+
+ void GenerateNotifications();
+
+ protected:
+ wpi::Logger logger;
+ EntryNotifier notifier;
+};
+
+void EntryNotifierTest::GenerateNotifications() {
+ // All flags combos that can be generated by Storage
+ static const unsigned int flags[] = {
+ // "normal" notifications
+ NT_NOTIFY_NEW, NT_NOTIFY_DELETE, NT_NOTIFY_UPDATE, NT_NOTIFY_FLAGS,
+ NT_NOTIFY_UPDATE | NT_NOTIFY_FLAGS,
+ // immediate notifications are always "new"
+ NT_NOTIFY_IMMEDIATE | NT_NOTIFY_NEW,
+ // local notifications can be of any flag combo
+ NT_NOTIFY_LOCAL | NT_NOTIFY_NEW, NT_NOTIFY_LOCAL | NT_NOTIFY_DELETE,
+ NT_NOTIFY_LOCAL | NT_NOTIFY_UPDATE, NT_NOTIFY_LOCAL | NT_NOTIFY_FLAGS,
+ NT_NOTIFY_LOCAL | NT_NOTIFY_UPDATE | NT_NOTIFY_FLAGS};
+ // Generate across keys
+ static const char* keys[] = {"/foo/bar", "/baz", "/boo"};
+
+ auto val = Value::MakeDouble(1);
+
+ // Provide unique key indexes for each key
+ unsigned int keyindex = 5;
+ for (auto key : keys) {
+ for (auto flag : flags) {
+ notifier.NotifyEntry(keyindex, key, val, flag);
+ }
+ ++keyindex;
+ }
+}
+
+TEST_F(EntryNotifierTest, PollEntryMultiple) {
+ auto poller1 = notifier.CreatePoller();
+ auto poller2 = notifier.CreatePoller();
+ auto poller3 = notifier.CreatePoller();
+ auto h1 = notifier.AddPolled(poller1, 6, NT_NOTIFY_NEW);
+ auto h2 = notifier.AddPolled(poller2, 6, NT_NOTIFY_NEW);
+ auto h3 = notifier.AddPolled(poller3, 6, NT_NOTIFY_UPDATE);
+
+ ASSERT_FALSE(notifier.local_notifiers());
+
+ GenerateNotifications();
+
+ ASSERT_TRUE(notifier.WaitForQueue(1.0));
+ bool timed_out = false;
+ auto results1 = notifier.Poll(poller1, 0, &timed_out);
+ ASSERT_FALSE(timed_out);
+ auto results2 = notifier.Poll(poller2, 0, &timed_out);
+ ASSERT_FALSE(timed_out);
+ auto results3 = notifier.Poll(poller3, 0, &timed_out);
+ ASSERT_FALSE(timed_out);
+
+ ASSERT_EQ(results1.size(), 2u);
+ for (const auto& result : results1) {
+ SCOPED_TRACE(::testing::PrintToString(result));
+ EXPECT_EQ(Handle{result.listener}.GetIndex(), (int)h1);
+ }
+
+ ASSERT_EQ(results2.size(), 2u);
+ for (const auto& result : results2) {
+ SCOPED_TRACE(::testing::PrintToString(result));
+ EXPECT_EQ(Handle{result.listener}.GetIndex(), (int)h2);
+ }
+
+ ASSERT_EQ(results3.size(), 2u);
+ for (const auto& result : results3) {
+ SCOPED_TRACE(::testing::PrintToString(result));
+ EXPECT_EQ(Handle{result.listener}.GetIndex(), (int)h3);
+ }
+}
+
+TEST_F(EntryNotifierTest, PollEntryBasic) {
+ auto poller = notifier.CreatePoller();
+ auto g1 = notifier.AddPolled(poller, 6, NT_NOTIFY_NEW);
+ auto g2 = notifier.AddPolled(poller, 6, NT_NOTIFY_DELETE);
+ auto g3 = notifier.AddPolled(poller, 6, NT_NOTIFY_UPDATE);
+ auto g4 = notifier.AddPolled(poller, 6, NT_NOTIFY_FLAGS);
+
+ ASSERT_FALSE(notifier.local_notifiers());
+
+ GenerateNotifications();
+
+ ASSERT_TRUE(notifier.WaitForQueue(1.0));
+ bool timed_out = false;
+ auto results = notifier.Poll(poller, 0, &timed_out);
+ ASSERT_FALSE(timed_out);
+
+ int g1count = 0;
+ int g2count = 0;
+ int g3count = 0;
+ int g4count = 0;
+ for (const auto& result : results) {
+ SCOPED_TRACE(::testing::PrintToString(result));
+ EXPECT_EQ(result.name, "/baz");
+ EXPECT_THAT(result.value, ValueEq(Value::MakeDouble(1)));
+ EXPECT_EQ(Handle{result.entry}.GetType(), Handle::kEntry);
+ EXPECT_EQ(Handle{result.entry}.GetInst(), 1);
+ EXPECT_EQ(Handle{result.entry}.GetIndex(), 6);
+ EXPECT_EQ(Handle{result.listener}.GetType(), Handle::kEntryListener);
+ EXPECT_EQ(Handle{result.listener}.GetInst(), 1);
+ if (Handle{result.listener}.GetIndex() == static_cast<int>(g1)) {
+ ++g1count;
+ EXPECT_TRUE((result.flags & NT_NOTIFY_NEW) != 0);
+ } else if (Handle{result.listener}.GetIndex() == static_cast<int>(g2)) {
+ ++g2count;
+ EXPECT_TRUE((result.flags & NT_NOTIFY_DELETE) != 0);
+ } else if (Handle{result.listener}.GetIndex() == static_cast<int>(g3)) {
+ ++g3count;
+ EXPECT_TRUE((result.flags & NT_NOTIFY_UPDATE) != 0);
+ } else if (Handle{result.listener}.GetIndex() == static_cast<int>(g4)) {
+ ++g4count;
+ EXPECT_TRUE((result.flags & NT_NOTIFY_FLAGS) != 0);
+ } else {
+ ADD_FAILURE() << "unknown listener index";
+ }
+ }
+ EXPECT_EQ(g1count, 2);
+ EXPECT_EQ(g2count, 1); // NT_NOTIFY_DELETE
+ EXPECT_EQ(g3count, 2);
+ EXPECT_EQ(g4count, 2);
+}
+
+TEST_F(EntryNotifierTest, PollEntryImmediate) {
+ auto poller = notifier.CreatePoller();
+ notifier.AddPolled(poller, 6, NT_NOTIFY_NEW | NT_NOTIFY_IMMEDIATE);
+ notifier.AddPolled(poller, 6, NT_NOTIFY_NEW);
+
+ ASSERT_FALSE(notifier.local_notifiers());
+
+ GenerateNotifications();
+
+ ASSERT_TRUE(notifier.WaitForQueue(1.0));
+ bool timed_out = false;
+ auto results = notifier.Poll(poller, 0, &timed_out);
+ ASSERT_FALSE(timed_out);
+ SCOPED_TRACE(::testing::PrintToString(results));
+ ASSERT_EQ(results.size(), 4u);
+}
+
+TEST_F(EntryNotifierTest, PollEntryLocal) {
+ auto poller = notifier.CreatePoller();
+ notifier.AddPolled(poller, 6, NT_NOTIFY_NEW | NT_NOTIFY_LOCAL);
+ notifier.AddPolled(poller, 6, NT_NOTIFY_NEW);
+
+ ASSERT_TRUE(notifier.local_notifiers());
+
+ GenerateNotifications();
+
+ ASSERT_TRUE(notifier.WaitForQueue(1.0));
+ bool timed_out = false;
+ auto results = notifier.Poll(poller, 0, &timed_out);
+ ASSERT_FALSE(timed_out);
+ SCOPED_TRACE(::testing::PrintToString(results));
+ ASSERT_EQ(results.size(), 6u);
+}
+
+TEST_F(EntryNotifierTest, PollPrefixMultiple) {
+ auto poller1 = notifier.CreatePoller();
+ auto poller2 = notifier.CreatePoller();
+ auto poller3 = notifier.CreatePoller();
+ auto h1 = notifier.AddPolled(poller1, "/foo", NT_NOTIFY_NEW);
+ auto h2 = notifier.AddPolled(poller2, "/foo", NT_NOTIFY_NEW);
+ auto h3 = notifier.AddPolled(poller3, "/foo", NT_NOTIFY_UPDATE);
+
+ ASSERT_FALSE(notifier.local_notifiers());
+
+ GenerateNotifications();
+
+ ASSERT_TRUE(notifier.WaitForQueue(1.0));
+ bool timed_out = false;
+ auto results1 = notifier.Poll(poller1, 0, &timed_out);
+ ASSERT_FALSE(timed_out);
+ auto results2 = notifier.Poll(poller2, 0, &timed_out);
+ ASSERT_FALSE(timed_out);
+ auto results3 = notifier.Poll(poller3, 0, &timed_out);
+ ASSERT_FALSE(timed_out);
+
+ ASSERT_EQ(results1.size(), 2u);
+ for (const auto& result : results1) {
+ SCOPED_TRACE(::testing::PrintToString(result));
+ EXPECT_EQ(Handle{result.listener}.GetIndex(), (int)h1);
+ }
+
+ ASSERT_EQ(results2.size(), 2u);
+ for (const auto& result : results2) {
+ SCOPED_TRACE(::testing::PrintToString(result));
+ EXPECT_EQ(Handle{result.listener}.GetIndex(), (int)h2);
+ }
+
+ ASSERT_EQ(results3.size(), 2u);
+ for (const auto& result : results3) {
+ SCOPED_TRACE(::testing::PrintToString(result));
+ EXPECT_EQ(Handle{result.listener}.GetIndex(), (int)h3);
+ }
+}
+
+TEST_F(EntryNotifierTest, PollPrefixBasic) {
+ auto poller = notifier.CreatePoller();
+ auto g1 = notifier.AddPolled(poller, "/foo", NT_NOTIFY_NEW);
+ auto g2 = notifier.AddPolled(poller, "/foo", NT_NOTIFY_DELETE);
+ auto g3 = notifier.AddPolled(poller, "/foo", NT_NOTIFY_UPDATE);
+ auto g4 = notifier.AddPolled(poller, "/foo", NT_NOTIFY_FLAGS);
+ notifier.AddPolled(poller, "/bar", NT_NOTIFY_NEW);
+ notifier.AddPolled(poller, "/bar", NT_NOTIFY_DELETE);
+ notifier.AddPolled(poller, "/bar", NT_NOTIFY_UPDATE);
+ notifier.AddPolled(poller, "/bar", NT_NOTIFY_FLAGS);
+
+ ASSERT_FALSE(notifier.local_notifiers());
+
+ GenerateNotifications();
+
+ ASSERT_TRUE(notifier.WaitForQueue(1.0));
+ bool timed_out = false;
+ auto results = notifier.Poll(poller, 0, &timed_out);
+ ASSERT_FALSE(timed_out);
+
+ int g1count = 0;
+ int g2count = 0;
+ int g3count = 0;
+ int g4count = 0;
+ for (const auto& result : results) {
+ SCOPED_TRACE(::testing::PrintToString(result));
+ EXPECT_TRUE(StringRef(result.name).startswith("/foo"));
+ EXPECT_THAT(result.value, ValueEq(Value::MakeDouble(1)));
+ EXPECT_EQ(Handle{result.entry}.GetType(), Handle::kEntry);
+ EXPECT_EQ(Handle{result.entry}.GetInst(), 1);
+ EXPECT_EQ(Handle{result.entry}.GetIndex(), 5);
+ EXPECT_EQ(Handle{result.listener}.GetType(), Handle::kEntryListener);
+ EXPECT_EQ(Handle{result.listener}.GetInst(), 1);
+ if (Handle{result.listener}.GetIndex() == static_cast<int>(g1)) {
+ ++g1count;
+ EXPECT_TRUE((result.flags & NT_NOTIFY_NEW) != 0);
+ } else if (Handle{result.listener}.GetIndex() == static_cast<int>(g2)) {
+ ++g2count;
+ EXPECT_TRUE((result.flags & NT_NOTIFY_DELETE) != 0);
+ } else if (Handle{result.listener}.GetIndex() == static_cast<int>(g3)) {
+ ++g3count;
+ EXPECT_TRUE((result.flags & NT_NOTIFY_UPDATE) != 0);
+ } else if (Handle{result.listener}.GetIndex() == static_cast<int>(g4)) {
+ ++g4count;
+ EXPECT_TRUE((result.flags & NT_NOTIFY_FLAGS) != 0);
+ } else {
+ ADD_FAILURE() << "unknown listener index";
+ }
+ }
+ EXPECT_EQ(g1count, 2);
+ EXPECT_EQ(g2count, 1); // NT_NOTIFY_DELETE
+ EXPECT_EQ(g3count, 2);
+ EXPECT_EQ(g4count, 2);
+}
+
+TEST_F(EntryNotifierTest, PollPrefixImmediate) {
+ auto poller = notifier.CreatePoller();
+ notifier.AddPolled(poller, "/foo", NT_NOTIFY_NEW | NT_NOTIFY_IMMEDIATE);
+ notifier.AddPolled(poller, "/foo", NT_NOTIFY_NEW);
+
+ ASSERT_FALSE(notifier.local_notifiers());
+
+ GenerateNotifications();
+
+ ASSERT_TRUE(notifier.WaitForQueue(1.0));
+ bool timed_out = false;
+ auto results = notifier.Poll(poller, 0, &timed_out);
+ ASSERT_FALSE(timed_out);
+ SCOPED_TRACE(::testing::PrintToString(results));
+ ASSERT_EQ(results.size(), 4u);
+}
+
+TEST_F(EntryNotifierTest, PollPrefixLocal) {
+ auto poller = notifier.CreatePoller();
+ notifier.AddPolled(poller, "/foo", NT_NOTIFY_NEW | NT_NOTIFY_LOCAL);
+ notifier.AddPolled(poller, "/foo", NT_NOTIFY_NEW);
+
+ ASSERT_TRUE(notifier.local_notifiers());
+
+ GenerateNotifications();
+
+ ASSERT_TRUE(notifier.WaitForQueue(1.0));
+ bool timed_out = false;
+ auto results = notifier.Poll(poller, 0, &timed_out);
+ ASSERT_FALSE(timed_out);
+ SCOPED_TRACE(::testing::PrintToString(results));
+ ASSERT_EQ(results.size(), 6u);
+}
+
+} // namespace nt
diff --git a/ntcore/src/test/native/cpp/MessageMatcher.cpp b/ntcore/src/test/native/cpp/MessageMatcher.cpp
new file mode 100644
index 0000000..35d4f8b
--- /dev/null
+++ b/ntcore/src/test/native/cpp/MessageMatcher.cpp
@@ -0,0 +1,52 @@
+/*----------------------------------------------------------------------------*/
+/* Copyright (c) 2017-2018 FIRST. All Rights Reserved. */
+/* Open Source Software - may be modified and shared by FRC teams. The code */
+/* must be accompanied by the FIRST BSD license file in the root directory of */
+/* the project. */
+/*----------------------------------------------------------------------------*/
+
+#include "MessageMatcher.h"
+
+namespace nt {
+
+bool MessageMatcher::MatchAndExplain(
+ std::shared_ptr<Message> msg,
+ ::testing::MatchResultListener* listener) const {
+ bool match = true;
+ if (!msg) return false;
+ 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
diff --git a/ntcore/src/test/native/cpp/MessageMatcher.h b/ntcore/src/test/native/cpp/MessageMatcher.h
new file mode 100644
index 0000000..5b14334
--- /dev/null
+++ b/ntcore/src/test/native/cpp/MessageMatcher.h
@@ -0,0 +1,42 @@
+/*----------------------------------------------------------------------------*/
+/* Copyright (c) 2017-2018 FIRST. All Rights Reserved. */
+/* Open Source Software - may be modified and shared by FRC teams. The code */
+/* must be accompanied by the FIRST BSD license file in the root directory of */
+/* the project. */
+/*----------------------------------------------------------------------------*/
+
+#ifndef NTCORE_MESSAGEMATCHER_H_
+#define NTCORE_MESSAGEMATCHER_H_
+
+#include <memory>
+#include <ostream>
+
+#include "Message.h"
+#include "TestPrinters.h"
+#include "gmock/gmock.h"
+
+namespace nt {
+
+class MessageMatcher
+ : public ::testing::MatcherInterface<std::shared_ptr<Message>> {
+ public:
+ explicit MessageMatcher(std::shared_ptr<Message> goodmsg_)
+ : goodmsg(goodmsg_) {}
+
+ bool MatchAndExplain(std::shared_ptr<Message> msg,
+ ::testing::MatchResultListener* listener) const override;
+ void DescribeTo(::std::ostream* os) const override;
+ void DescribeNegationTo(::std::ostream* os) const override;
+
+ private:
+ std::shared_ptr<Message> goodmsg;
+};
+
+inline ::testing::Matcher<std::shared_ptr<Message>> MessageEq(
+ std::shared_ptr<Message> goodmsg) {
+ return ::testing::MakeMatcher(new MessageMatcher(goodmsg));
+}
+
+} // namespace nt
+
+#endif // NTCORE_MESSAGEMATCHER_H_
diff --git a/ntcore/src/test/native/cpp/MockConnectionNotifier.h b/ntcore/src/test/native/cpp/MockConnectionNotifier.h
new file mode 100644
index 0000000..ddf8b2e
--- /dev/null
+++ b/ntcore/src/test/native/cpp/MockConnectionNotifier.h
@@ -0,0 +1,30 @@
+/*----------------------------------------------------------------------------*/
+/* Copyright (c) 2017-2018 FIRST. All Rights Reserved. */
+/* Open Source Software - may be modified and shared by FRC teams. The code */
+/* must be accompanied by the FIRST BSD license file in the root directory of */
+/* the project. */
+/*----------------------------------------------------------------------------*/
+
+#ifndef NTCORE_MOCKCONNECTIONNOTIFIER_H_
+#define NTCORE_MOCKCONNECTIONNOTIFIER_H_
+
+#include "IConnectionNotifier.h"
+#include "gmock/gmock.h"
+
+namespace nt {
+
+class MockConnectionNotifier : public IConnectionNotifier {
+ public:
+ MOCK_METHOD1(
+ Add,
+ unsigned int(
+ std::function<void(const ConnectionNotification& event)> callback));
+ MOCK_METHOD1(AddPolled, unsigned int(unsigned int poller_uid));
+ MOCK_METHOD3(NotifyConnection,
+ void(bool connected, const ConnectionInfo& conn_info,
+ unsigned int only_listener));
+};
+
+} // namespace nt
+
+#endif // NTCORE_MOCKCONNECTIONNOTIFIER_H_
diff --git a/ntcore/src/test/native/cpp/MockDispatcher.h b/ntcore/src/test/native/cpp/MockDispatcher.h
new file mode 100644
index 0000000..10af839
--- /dev/null
+++ b/ntcore/src/test/native/cpp/MockDispatcher.h
@@ -0,0 +1,27 @@
+/*----------------------------------------------------------------------------*/
+/* Copyright (c) 2017-2018 FIRST. All Rights Reserved. */
+/* Open Source Software - may be modified and shared by FRC teams. The code */
+/* must be accompanied by the FIRST BSD license file in the root directory of */
+/* the project. */
+/*----------------------------------------------------------------------------*/
+
+#ifndef NTCORE_MOCKDISPATCHER_H_
+#define NTCORE_MOCKDISPATCHER_H_
+
+#include <memory>
+
+#include "IDispatcher.h"
+#include "gmock/gmock.h"
+
+namespace nt {
+
+class MockDispatcher : public IDispatcher {
+ public:
+ MOCK_METHOD3(QueueOutgoing,
+ void(std::shared_ptr<Message> msg, INetworkConnection* only,
+ INetworkConnection* except));
+};
+
+} // namespace nt
+
+#endif // NTCORE_MOCKDISPATCHER_H_
diff --git a/ntcore/src/test/native/cpp/MockEntryNotifier.h b/ntcore/src/test/native/cpp/MockEntryNotifier.h
new file mode 100644
index 0000000..2f078cb
--- /dev/null
+++ b/ntcore/src/test/native/cpp/MockEntryNotifier.h
@@ -0,0 +1,43 @@
+/*----------------------------------------------------------------------------*/
+/* Copyright (c) 2017-2018 FIRST. All Rights Reserved. */
+/* Open Source Software - may be modified and shared by FRC teams. The code */
+/* must be accompanied by the FIRST BSD license file in the root directory of */
+/* the project. */
+/*----------------------------------------------------------------------------*/
+
+#ifndef NTCORE_MOCKENTRYNOTIFIER_H_
+#define NTCORE_MOCKENTRYNOTIFIER_H_
+
+#include <memory>
+
+#include "IEntryNotifier.h"
+#include "gmock/gmock.h"
+
+namespace nt {
+
+class MockEntryNotifier : public IEntryNotifier {
+ public:
+ MOCK_CONST_METHOD0(local_notifiers, bool());
+ MOCK_METHOD3(
+ Add,
+ unsigned int(std::function<void(const EntryNotification& event)> callback,
+ wpi::StringRef prefix, unsigned int flags));
+ MOCK_METHOD3(
+ Add,
+ unsigned int(std::function<void(const EntryNotification& event)> callback,
+ unsigned int local_id, unsigned int flags));
+ MOCK_METHOD3(AddPolled,
+ unsigned int(unsigned int poller_uid, wpi::StringRef prefix,
+ unsigned int flags));
+ MOCK_METHOD3(AddPolled,
+ unsigned int(unsigned int poller_uid, unsigned int local_id,
+ unsigned int flags));
+ MOCK_METHOD5(NotifyEntry,
+ void(unsigned int local_id, StringRef name,
+ std::shared_ptr<Value> value, unsigned int flags,
+ unsigned int only_listener));
+};
+
+} // namespace nt
+
+#endif // NTCORE_MOCKENTRYNOTIFIER_H_
diff --git a/ntcore/src/test/native/cpp/MockNetworkConnection.h b/ntcore/src/test/native/cpp/MockNetworkConnection.h
new file mode 100644
index 0000000..52c917d
--- /dev/null
+++ b/ntcore/src/test/native/cpp/MockNetworkConnection.h
@@ -0,0 +1,34 @@
+/*----------------------------------------------------------------------------*/
+/* Copyright (c) 2017-2018 FIRST. All Rights Reserved. */
+/* Open Source Software - may be modified and shared by FRC teams. The code */
+/* must be accompanied by the FIRST BSD license file in the root directory of */
+/* the project. */
+/*----------------------------------------------------------------------------*/
+
+#ifndef NTCORE_MOCKNETWORKCONNECTION_H_
+#define NTCORE_MOCKNETWORKCONNECTION_H_
+
+#include <memory>
+
+#include "INetworkConnection.h"
+#include "gmock/gmock.h"
+
+namespace nt {
+
+class MockNetworkConnection : public INetworkConnection {
+ public:
+ MOCK_CONST_METHOD0(info, ConnectionInfo());
+
+ MOCK_METHOD1(QueueOutgoing, void(std::shared_ptr<Message> msg));
+ MOCK_METHOD1(PostOutgoing, void(bool keep_alive));
+
+ MOCK_CONST_METHOD0(proto_rev, unsigned int());
+ MOCK_METHOD1(set_proto_rev, void(unsigned int proto_rev));
+
+ MOCK_CONST_METHOD0(state, State());
+ MOCK_METHOD1(set_state, void(State state));
+};
+
+} // namespace nt
+
+#endif // NTCORE_MOCKNETWORKCONNECTION_H_
diff --git a/ntcore/src/test/native/cpp/MockRpcServer.h b/ntcore/src/test/native/cpp/MockRpcServer.h
new file mode 100644
index 0000000..6e9d970
--- /dev/null
+++ b/ntcore/src/test/native/cpp/MockRpcServer.h
@@ -0,0 +1,29 @@
+/*----------------------------------------------------------------------------*/
+/* Copyright (c) 2017-2018 FIRST. All Rights Reserved. */
+/* Open Source Software - may be modified and shared by FRC teams. The code */
+/* must be accompanied by the FIRST BSD license file in the root directory of */
+/* the project. */
+/*----------------------------------------------------------------------------*/
+
+#ifndef NTCORE_MOCKRPCSERVER_H_
+#define NTCORE_MOCKRPCSERVER_H_
+
+#include "IRpcServer.h"
+#include "gmock/gmock.h"
+
+namespace nt {
+
+class MockRpcServer : public IRpcServer {
+ public:
+ MOCK_METHOD0(Start, void());
+ MOCK_METHOD1(RemoveRpc, void(unsigned int rpc_uid));
+ MOCK_METHOD7(ProcessRpc,
+ void(unsigned int local_id, unsigned int call_uid,
+ StringRef name, StringRef params,
+ const ConnectionInfo& conn, SendResponseFunc send_response,
+ unsigned int rpc_uid));
+};
+
+} // namespace nt
+
+#endif // NTCORE_MOCKRPCSERVER_H_
diff --git a/ntcore/src/test/native/cpp/NetworkTableTest.cpp b/ntcore/src/test/native/cpp/NetworkTableTest.cpp
new file mode 100644
index 0000000..d9a2743
--- /dev/null
+++ b/ntcore/src/test/native/cpp/NetworkTableTest.cpp
@@ -0,0 +1,91 @@
+/*----------------------------------------------------------------------------*/
+/* Copyright (c) 2015-2018 FIRST. All Rights Reserved. */
+/* Open Source Software - may be modified and shared by FRC teams. The code */
+/* must be accompanied by the FIRST BSD license file in the root directory of */
+/* the project. */
+/*----------------------------------------------------------------------------*/
+
+#include "TestPrinters.h"
+#include "gtest/gtest.h"
+#include "networktables/NetworkTable.h"
+#include "networktables/NetworkTableInstance.h"
+
+class NetworkTableTest : public ::testing::Test {};
+
+TEST_F(NetworkTableTest, BasenameKey) {
+ EXPECT_EQ("simple", NetworkTable::BasenameKey("simple"));
+ EXPECT_EQ("simple", NetworkTable::BasenameKey("one/two/many/simple"));
+ EXPECT_EQ("simple",
+ NetworkTable::BasenameKey("//////an/////awful/key////simple"));
+}
+
+TEST_F(NetworkTableTest, NormalizeKeySlash) {
+ EXPECT_EQ("/", NetworkTable::NormalizeKey("///"));
+ EXPECT_EQ("/no/normal/req", NetworkTable::NormalizeKey("/no/normal/req"));
+ EXPECT_EQ("/no/leading/slash",
+ NetworkTable::NormalizeKey("no/leading/slash"));
+ EXPECT_EQ("/what/an/awful/key/",
+ NetworkTable::NormalizeKey("//////what////an/awful/////key///"));
+}
+
+TEST_F(NetworkTableTest, NormalizeKeyNoSlash) {
+ EXPECT_EQ("a", NetworkTable::NormalizeKey("a", false));
+ EXPECT_EQ("a", NetworkTable::NormalizeKey("///a", false));
+ EXPECT_EQ("leading/slash",
+ NetworkTable::NormalizeKey("/leading/slash", false));
+ EXPECT_EQ("no/leading/slash",
+ NetworkTable::NormalizeKey("no/leading/slash", false));
+ EXPECT_EQ(
+ "what/an/awful/key/",
+ NetworkTable::NormalizeKey("//////what////an/awful/////key///", false));
+}
+
+TEST_F(NetworkTableTest, GetHierarchyEmpty) {
+ std::vector<std::string> expected{"/"};
+ ASSERT_EQ(expected, NetworkTable::GetHierarchy(""));
+}
+
+TEST_F(NetworkTableTest, GetHierarchyRoot) {
+ std::vector<std::string> expected{"/"};
+ ASSERT_EQ(expected, NetworkTable::GetHierarchy("/"));
+}
+
+TEST_F(NetworkTableTest, GetHierarchyNormal) {
+ std::vector<std::string> expected{"/", "/foo", "/foo/bar", "/foo/bar/baz"};
+ ASSERT_EQ(expected, NetworkTable::GetHierarchy("/foo/bar/baz"));
+}
+
+TEST_F(NetworkTableTest, GetHierarchyTrailingSlash) {
+ std::vector<std::string> expected{"/", "/foo", "/foo/bar", "/foo/bar/"};
+ ASSERT_EQ(expected, NetworkTable::GetHierarchy("/foo/bar/"));
+}
+
+TEST_F(NetworkTableTest, ContainsKey) {
+ auto inst = nt::NetworkTableInstance::Create();
+ auto nt = inst.GetTable("containskey");
+ ASSERT_FALSE(nt->ContainsKey("testkey"));
+ nt->PutNumber("testkey", 5);
+ ASSERT_TRUE(nt->ContainsKey("testkey"));
+ ASSERT_TRUE(inst.GetEntry("/containskey/testkey").Exists());
+ ASSERT_FALSE(inst.GetEntry("containskey/testkey").Exists());
+}
+
+TEST_F(NetworkTableTest, LeadingSlash) {
+ auto inst = nt::NetworkTableInstance::Create();
+ auto nt = inst.GetTable("leadingslash");
+ auto nt2 = inst.GetTable("/leadingslash");
+ ASSERT_FALSE(nt->ContainsKey("testkey"));
+ nt2->PutNumber("testkey", 5);
+ ASSERT_TRUE(nt->ContainsKey("testkey"));
+ ASSERT_TRUE(inst.GetEntry("/leadingslash/testkey").Exists());
+}
+
+TEST_F(NetworkTableTest, EmptyOrNoSlash) {
+ auto inst = nt::NetworkTableInstance::Create();
+ auto nt = inst.GetTable("/");
+ auto nt2 = inst.GetTable("");
+ ASSERT_FALSE(nt->ContainsKey("testkey"));
+ nt2->PutNumber("testkey", 5);
+ ASSERT_TRUE(nt->ContainsKey("testkey"));
+ ASSERT_TRUE(inst.GetEntry("/testkey").Exists());
+}
diff --git a/ntcore/src/test/native/cpp/StorageTest.cpp b/ntcore/src/test/native/cpp/StorageTest.cpp
new file mode 100644
index 0000000..5a6982b
--- /dev/null
+++ b/ntcore/src/test/native/cpp/StorageTest.cpp
@@ -0,0 +1,987 @@
+/*----------------------------------------------------------------------------*/
+/* Copyright (c) 2015-2018 FIRST. All Rights Reserved. */
+/* Open Source Software - may be modified and shared by FRC teams. The code */
+/* must be accompanied by the FIRST BSD license file in the root directory of */
+/* the project. */
+/*----------------------------------------------------------------------------*/
+
+#include "StorageTest.h"
+
+#include <wpi/raw_istream.h>
+#include <wpi/raw_ostream.h>
+
+#include "MessageMatcher.h"
+#include "MockNetworkConnection.h"
+#include "Storage.h"
+#include "TestPrinters.h"
+#include "ValueMatcher.h"
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+
+using ::testing::AnyNumber;
+using ::testing::IsNull;
+using ::testing::Return;
+using ::testing::_;
+
+namespace nt {
+
+class StorageTestEmpty : public StorageTest,
+ public ::testing::TestWithParam<bool> {
+ public:
+ StorageTestEmpty() {
+ HookOutgoing(GetParam());
+ EXPECT_CALL(notifier, local_notifiers())
+ .Times(AnyNumber())
+ .WillRepeatedly(Return(true));
+ }
+};
+
+class StorageTestPopulateOne : public StorageTestEmpty {
+ public:
+ StorageTestPopulateOne() {
+ EXPECT_CALL(dispatcher, QueueOutgoing(_, _, _)).Times(AnyNumber());
+ EXPECT_CALL(notifier, NotifyEntry(_, _, _, _, _)).Times(AnyNumber());
+ EXPECT_CALL(notifier, local_notifiers())
+ .Times(AnyNumber())
+ .WillRepeatedly(Return(false));
+ storage.SetEntryTypeValue("foo", Value::MakeBoolean(true));
+ ::testing::Mock::VerifyAndClearExpectations(&dispatcher);
+ ::testing::Mock::VerifyAndClearExpectations(¬ifier);
+ EXPECT_CALL(notifier, local_notifiers())
+ .Times(AnyNumber())
+ .WillRepeatedly(Return(true));
+ }
+};
+
+class StorageTestPopulated : public StorageTestEmpty {
+ public:
+ StorageTestPopulated() {
+ EXPECT_CALL(dispatcher, QueueOutgoing(_, _, _)).Times(AnyNumber());
+ EXPECT_CALL(notifier, NotifyEntry(_, _, _, _, _)).Times(AnyNumber());
+ EXPECT_CALL(notifier, local_notifiers())
+ .Times(AnyNumber())
+ .WillRepeatedly(Return(false));
+ storage.SetEntryTypeValue("foo", Value::MakeBoolean(true));
+ storage.SetEntryTypeValue("foo2", Value::MakeDouble(0.0));
+ storage.SetEntryTypeValue("bar", Value::MakeDouble(1.0));
+ storage.SetEntryTypeValue("bar2", Value::MakeBoolean(false));
+ ::testing::Mock::VerifyAndClearExpectations(&dispatcher);
+ ::testing::Mock::VerifyAndClearExpectations(¬ifier);
+ EXPECT_CALL(notifier, local_notifiers())
+ .Times(AnyNumber())
+ .WillRepeatedly(Return(true));
+ }
+};
+
+class StorageTestPersistent : public StorageTestEmpty {
+ public:
+ StorageTestPersistent() {
+ EXPECT_CALL(dispatcher, QueueOutgoing(_, _, _)).Times(AnyNumber());
+ EXPECT_CALL(notifier, NotifyEntry(_, _, _, _, _)).Times(AnyNumber());
+ EXPECT_CALL(notifier, local_notifiers())
+ .Times(AnyNumber())
+ .WillRepeatedly(Return(false));
+ storage.SetEntryTypeValue("boolean/true", Value::MakeBoolean(true));
+ storage.SetEntryTypeValue("boolean/false", Value::MakeBoolean(false));
+ storage.SetEntryTypeValue("double/neg", Value::MakeDouble(-1.5));
+ storage.SetEntryTypeValue("double/zero", Value::MakeDouble(0.0));
+ storage.SetEntryTypeValue("double/big", Value::MakeDouble(1.3e8));
+ storage.SetEntryTypeValue("string/empty", Value::MakeString(""));
+ storage.SetEntryTypeValue("string/normal", Value::MakeString("hello"));
+ storage.SetEntryTypeValue("string/special",
+ Value::MakeString(StringRef("\0\3\5\n", 4)));
+ storage.SetEntryTypeValue("raw/empty", Value::MakeRaw(""));
+ storage.SetEntryTypeValue("raw/normal", Value::MakeRaw("hello"));
+ storage.SetEntryTypeValue("raw/special",
+ Value::MakeRaw(StringRef("\0\3\5\n", 4)));
+ storage.SetEntryTypeValue("booleanarr/empty",
+ Value::MakeBooleanArray(std::vector<int>{}));
+ storage.SetEntryTypeValue("booleanarr/one",
+ Value::MakeBooleanArray(std::vector<int>{1}));
+ storage.SetEntryTypeValue("booleanarr/two",
+ Value::MakeBooleanArray(std::vector<int>{1, 0}));
+ storage.SetEntryTypeValue("doublearr/empty",
+ Value::MakeDoubleArray(std::vector<double>{}));
+ storage.SetEntryTypeValue("doublearr/one",
+ Value::MakeDoubleArray(std::vector<double>{0.5}));
+ storage.SetEntryTypeValue(
+ "doublearr/two",
+ Value::MakeDoubleArray(std::vector<double>{0.5, -0.25}));
+ storage.SetEntryTypeValue(
+ "stringarr/empty", Value::MakeStringArray(std::vector<std::string>{}));
+ storage.SetEntryTypeValue(
+ "stringarr/one",
+ Value::MakeStringArray(std::vector<std::string>{"hello"}));
+ storage.SetEntryTypeValue(
+ "stringarr/two",
+ Value::MakeStringArray(std::vector<std::string>{"hello", "world\n"}));
+ storage.SetEntryTypeValue(StringRef("\0\3\5\n", 4),
+ Value::MakeBoolean(true));
+ storage.SetEntryTypeValue("=", Value::MakeBoolean(true));
+ ::testing::Mock::VerifyAndClearExpectations(&dispatcher);
+ ::testing::Mock::VerifyAndClearExpectations(¬ifier);
+ EXPECT_CALL(notifier, local_notifiers())
+ .Times(AnyNumber())
+ .WillRepeatedly(Return(true));
+ }
+};
+
+class MockLoadWarn {
+ public:
+ MOCK_METHOD2(Warn, void(size_t line, wpi::StringRef msg));
+};
+
+TEST_P(StorageTestEmpty, Construct) {
+ EXPECT_TRUE(entries().empty());
+ EXPECT_TRUE(idmap().empty());
+}
+
+TEST_P(StorageTestEmpty, StorageEntryInit) {
+ auto entry = GetEntry("foo");
+ EXPECT_FALSE(entry->value);
+ EXPECT_EQ(0u, entry->flags);
+ EXPECT_EQ("foobar", entry->name); // since GetEntry uses the tmp_entry.
+ EXPECT_EQ(0xffffu, entry->id);
+ EXPECT_EQ(SequenceNumber(), entry->seq_num);
+}
+
+TEST_P(StorageTestEmpty, GetEntryValueNotExist) {
+ EXPECT_FALSE(storage.GetEntryValue("foo"));
+ EXPECT_TRUE(entries().empty());
+ EXPECT_TRUE(idmap().empty());
+}
+
+TEST_P(StorageTestEmpty, GetEntryValueExist) {
+ auto value = Value::MakeBoolean(true);
+ EXPECT_CALL(dispatcher, QueueOutgoing(_, IsNull(), IsNull()));
+ EXPECT_CALL(notifier, NotifyEntry(_, _, _, _, _));
+ storage.SetEntryTypeValue("foo", value);
+ EXPECT_EQ(value, storage.GetEntryValue("foo"));
+}
+
+TEST_P(StorageTestEmpty, SetEntryTypeValueAssignNew) {
+ // brand new entry
+ auto value = Value::MakeBoolean(true);
+ // id assigned if server
+ EXPECT_CALL(dispatcher,
+ QueueOutgoing(MessageEq(Message::EntryAssign(
+ "foo", GetParam() ? 0 : 0xffff, 1, value, 0)),
+ IsNull(), IsNull()));
+ EXPECT_CALL(notifier, NotifyEntry(0, StringRef("foo"), value,
+ NT_NOTIFY_NEW | NT_NOTIFY_LOCAL, UINT_MAX));
+
+ storage.SetEntryTypeValue("foo", value);
+ EXPECT_EQ(value, GetEntry("foo")->value);
+ if (GetParam()) {
+ ASSERT_EQ(1u, idmap().size());
+ EXPECT_EQ(value, idmap()[0]->value);
+ } else {
+ EXPECT_TRUE(idmap().empty());
+ }
+}
+
+TEST_P(StorageTestPopulateOne, SetEntryTypeValueAssignTypeChange) {
+ // update with different type results in assignment message
+ auto value = Value::MakeDouble(0.0);
+
+ // id assigned if server; seq_num incremented
+ EXPECT_CALL(dispatcher,
+ QueueOutgoing(MessageEq(Message::EntryAssign(
+ "foo", GetParam() ? 0 : 0xffff, 2, value, 0)),
+ IsNull(), IsNull()));
+ EXPECT_CALL(notifier,
+ NotifyEntry(0, StringRef("foo"), value,
+ NT_NOTIFY_UPDATE | NT_NOTIFY_LOCAL, UINT_MAX));
+
+ storage.SetEntryTypeValue("foo", value);
+ EXPECT_EQ(value, GetEntry("foo")->value);
+}
+
+TEST_P(StorageTestPopulateOne, SetEntryTypeValueEqualValue) {
+ // update with same type and same value: change value contents but no update
+ // message is issued (minimizing bandwidth usage)
+ auto value = Value::MakeBoolean(true);
+ storage.SetEntryTypeValue("foo", value);
+ EXPECT_EQ(value, GetEntry("foo")->value);
+}
+
+TEST_P(StorageTestPopulated, SetEntryTypeValueDifferentValue) {
+ // update with same type and different value results in value update message
+ auto value = Value::MakeDouble(1.0);
+
+ // client shouldn't send an update as id not assigned yet
+ if (GetParam()) {
+ // id assigned if server; seq_num incremented
+ EXPECT_CALL(dispatcher,
+ QueueOutgoing(MessageEq(Message::EntryUpdate(1, 2, value)),
+ IsNull(), IsNull()));
+ }
+ EXPECT_CALL(notifier,
+ NotifyEntry(1, StringRef("foo2"), value,
+ NT_NOTIFY_UPDATE | NT_NOTIFY_LOCAL, UINT_MAX));
+ storage.SetEntryTypeValue("foo2", value);
+ EXPECT_EQ(value, GetEntry("foo2")->value);
+
+ if (!GetParam()) {
+ // seq_num should still be incremented
+ EXPECT_EQ(2u, GetEntry("foo2")->seq_num.value());
+ }
+}
+
+TEST_P(StorageTestEmpty, SetEntryTypeValueEmptyName) {
+ auto value = Value::MakeBoolean(true);
+ storage.SetEntryTypeValue("", value);
+ EXPECT_TRUE(entries().empty());
+ EXPECT_TRUE(idmap().empty());
+}
+
+TEST_P(StorageTestEmpty, SetEntryTypeValueEmptyValue) {
+ storage.SetEntryTypeValue("foo", nullptr);
+ EXPECT_TRUE(entries().empty());
+ EXPECT_TRUE(idmap().empty());
+}
+
+TEST_P(StorageTestEmpty, SetEntryValueAssignNew) {
+ // brand new entry
+ auto value = Value::MakeBoolean(true);
+
+ // id assigned if server
+ EXPECT_CALL(dispatcher,
+ QueueOutgoing(MessageEq(Message::EntryAssign(
+ "foo", GetParam() ? 0 : 0xffff, 1, value, 0)),
+ IsNull(), IsNull()));
+ EXPECT_CALL(notifier, NotifyEntry(0, StringRef("foo"), value,
+ NT_NOTIFY_NEW | NT_NOTIFY_LOCAL, UINT_MAX));
+
+ EXPECT_TRUE(storage.SetEntryValue("foo", value));
+ EXPECT_EQ(value, GetEntry("foo")->value);
+}
+
+TEST_P(StorageTestPopulateOne, SetEntryValueAssignTypeChange) {
+ // update with different type results in error and no message or notification
+ auto value = Value::MakeDouble(0.0);
+ EXPECT_FALSE(storage.SetEntryValue("foo", value));
+ auto entry = GetEntry("foo");
+ EXPECT_NE(value, entry->value);
+}
+
+TEST_P(StorageTestPopulateOne, SetEntryValueEqualValue) {
+ // update with same type and same value: change value contents but no update
+ // message is issued (minimizing bandwidth usage)
+ auto value = Value::MakeBoolean(true);
+ EXPECT_TRUE(storage.SetEntryValue("foo", value));
+ auto entry = GetEntry("foo");
+ EXPECT_EQ(value, entry->value);
+}
+
+TEST_P(StorageTestPopulated, SetEntryValueDifferentValue) {
+ // update with same type and different value results in value update message
+ auto value = Value::MakeDouble(1.0);
+
+ // client shouldn't send an update as id not assigned yet
+ if (GetParam()) {
+ // id assigned if server; seq_num incremented
+ EXPECT_CALL(dispatcher,
+ QueueOutgoing(MessageEq(Message::EntryUpdate(1, 2, value)),
+ IsNull(), IsNull()));
+ }
+ EXPECT_CALL(notifier,
+ NotifyEntry(1, StringRef("foo2"), value,
+ NT_NOTIFY_UPDATE | NT_NOTIFY_LOCAL, UINT_MAX));
+
+ EXPECT_TRUE(storage.SetEntryValue("foo2", value));
+ auto entry = GetEntry("foo2");
+ EXPECT_EQ(value, entry->value);
+
+ if (!GetParam()) {
+ // seq_num should still be incremented
+ EXPECT_EQ(2u, GetEntry("foo2")->seq_num.value());
+ }
+}
+
+TEST_P(StorageTestEmpty, SetEntryValueEmptyName) {
+ auto value = Value::MakeBoolean(true);
+ EXPECT_TRUE(storage.SetEntryValue("", value));
+ EXPECT_TRUE(entries().empty());
+ EXPECT_TRUE(idmap().empty());
+}
+
+TEST_P(StorageTestEmpty, SetEntryValueEmptyValue) {
+ EXPECT_TRUE(storage.SetEntryValue("foo", nullptr));
+ EXPECT_TRUE(entries().empty());
+ EXPECT_TRUE(idmap().empty());
+}
+
+TEST_P(StorageTestEmpty, SetDefaultEntryAssignNew) {
+ // brand new entry
+ auto value = Value::MakeBoolean(true);
+
+ // id assigned if server
+ EXPECT_CALL(dispatcher,
+ QueueOutgoing(MessageEq(Message::EntryAssign(
+ "foo", GetParam() ? 0 : 0xffff, 1, value, 0)),
+ IsNull(), IsNull()));
+ EXPECT_CALL(notifier, NotifyEntry(0, StringRef("foo"), value,
+ NT_NOTIFY_NEW | NT_NOTIFY_LOCAL, UINT_MAX));
+
+ auto ret_val = storage.SetDefaultEntryValue("foo", value);
+ EXPECT_TRUE(ret_val);
+ EXPECT_EQ(value, GetEntry("foo")->value);
+}
+
+TEST_P(StorageTestPopulateOne, SetDefaultEntryExistsSameType) {
+ // existing entry
+ auto value = Value::MakeBoolean(true);
+ auto ret_val = storage.SetDefaultEntryValue("foo", value);
+ EXPECT_TRUE(ret_val);
+ EXPECT_NE(value, GetEntry("foo")->value);
+}
+
+TEST_P(StorageTestPopulateOne, SetDefaultEntryExistsDifferentType) {
+ // existing entry is boolean
+ auto value = Value::MakeDouble(2.0);
+ auto ret_val = storage.SetDefaultEntryValue("foo", value);
+ EXPECT_FALSE(ret_val);
+ // should not have updated value in table if it already existed.
+ EXPECT_NE(value, GetEntry("foo")->value);
+}
+
+TEST_P(StorageTestEmpty, SetDefaultEntryEmptyName) {
+ auto value = Value::MakeBoolean(true);
+ auto ret_val = storage.SetDefaultEntryValue("", value);
+ EXPECT_FALSE(ret_val);
+ auto entry = GetEntry("foo");
+ EXPECT_FALSE(entry->value);
+ EXPECT_EQ(0u, entry->flags);
+ EXPECT_EQ("foobar", entry->name); // since GetEntry uses the tmp_entry.
+ EXPECT_EQ(0xffffu, entry->id);
+ EXPECT_EQ(SequenceNumber(), entry->seq_num);
+ EXPECT_TRUE(entries().empty());
+ EXPECT_TRUE(idmap().empty());
+}
+
+TEST_P(StorageTestEmpty, SetDefaultEntryEmptyValue) {
+ auto value = Value::MakeBoolean(true);
+ auto ret_val = storage.SetDefaultEntryValue("", nullptr);
+ EXPECT_FALSE(ret_val);
+ auto entry = GetEntry("foo");
+ EXPECT_FALSE(entry->value);
+ EXPECT_EQ(0u, entry->flags);
+ EXPECT_EQ("foobar", entry->name); // since GetEntry uses the tmp_entry.
+ EXPECT_EQ(0xffffu, entry->id);
+ EXPECT_EQ(SequenceNumber(), entry->seq_num);
+ EXPECT_TRUE(entries().empty());
+ EXPECT_TRUE(idmap().empty());
+}
+
+TEST_P(StorageTestPopulated, SetDefaultEntryEmptyName) {
+ auto value = Value::MakeBoolean(true);
+ auto ret_val = storage.SetDefaultEntryValue("", value);
+ EXPECT_FALSE(ret_val);
+ // assert that no entries get added
+ EXPECT_EQ(4u, entries().size());
+ if (GetParam())
+ EXPECT_EQ(4u, idmap().size());
+ else
+ EXPECT_EQ(0u, idmap().size());
+}
+
+TEST_P(StorageTestPopulated, SetDefaultEntryEmptyValue) {
+ auto value = Value::MakeBoolean(true);
+ auto ret_val = storage.SetDefaultEntryValue("", nullptr);
+ EXPECT_FALSE(ret_val);
+ // assert that no entries get added
+ EXPECT_EQ(4u, entries().size());
+ if (GetParam())
+ EXPECT_EQ(4u, idmap().size());
+ else
+ EXPECT_EQ(0u, idmap().size());
+}
+
+TEST_P(StorageTestEmpty, SetEntryFlagsNew) {
+ // flags setting doesn't create an entry
+ storage.SetEntryFlags("foo", 0u);
+ EXPECT_TRUE(entries().empty());
+ EXPECT_TRUE(idmap().empty());
+}
+
+TEST_P(StorageTestPopulateOne, SetEntryFlagsEqualValue) {
+ // update with same value: no update message is issued (minimizing bandwidth
+ // usage)
+ storage.SetEntryFlags("foo", 0u);
+ auto entry = GetEntry("foo");
+ EXPECT_EQ(0u, entry->flags);
+}
+
+TEST_P(StorageTestPopulated, SetEntryFlagsDifferentValue) {
+ // update with different value results in flags update message
+ // client shouldn't send an update as id not assigned yet
+ if (GetParam()) {
+ // id assigned as this is the server
+ EXPECT_CALL(dispatcher, QueueOutgoing(MessageEq(Message::FlagsUpdate(1, 1)),
+ IsNull(), IsNull()));
+ }
+ EXPECT_CALL(notifier,
+ NotifyEntry(1, StringRef("foo2"), _,
+ NT_NOTIFY_FLAGS | NT_NOTIFY_LOCAL, UINT_MAX));
+ storage.SetEntryFlags("foo2", 1u);
+ EXPECT_EQ(1u, GetEntry("foo2")->flags);
+}
+
+TEST_P(StorageTestEmpty, SetEntryFlagsEmptyName) {
+ storage.SetEntryFlags("", 0u);
+ EXPECT_TRUE(entries().empty());
+ EXPECT_TRUE(idmap().empty());
+}
+
+TEST_P(StorageTestEmpty, GetEntryFlagsNotExist) {
+ EXPECT_EQ(0u, storage.GetEntryFlags("foo"));
+ EXPECT_TRUE(entries().empty());
+ EXPECT_TRUE(idmap().empty());
+}
+
+TEST_P(StorageTestPopulateOne, GetEntryFlagsExist) {
+ EXPECT_CALL(dispatcher, QueueOutgoing(_, _, _)).Times(AnyNumber());
+ EXPECT_CALL(notifier, NotifyEntry(_, _, _, _, _));
+ storage.SetEntryFlags("foo", 1u);
+ ::testing::Mock::VerifyAndClearExpectations(&dispatcher);
+ EXPECT_EQ(1u, storage.GetEntryFlags("foo"));
+}
+
+TEST_P(StorageTestEmpty, DeleteEntryNotExist) { storage.DeleteEntry("foo"); }
+
+TEST_P(StorageTestPopulated, DeleteEntryExist) {
+ // client shouldn't send an update as id not assigned yet
+ if (GetParam()) {
+ // id assigned as this is the server
+ EXPECT_CALL(dispatcher, QueueOutgoing(MessageEq(Message::EntryDelete(1)),
+ IsNull(), IsNull()));
+ }
+ EXPECT_CALL(notifier,
+ NotifyEntry(1, StringRef("foo2"), ValueEq(Value::MakeDouble(0)),
+ NT_NOTIFY_DELETE | NT_NOTIFY_LOCAL, UINT_MAX));
+
+ storage.DeleteEntry("foo2");
+ ASSERT_EQ(1u, entries().count("foo2"));
+ EXPECT_EQ(nullptr, entries()["foo2"]->value);
+ EXPECT_EQ(0xffffu, entries()["foo2"]->id);
+ EXPECT_FALSE(entries()["foo2"]->local_write);
+ if (GetParam()) {
+ ASSERT_TRUE(idmap().size() >= 2);
+ EXPECT_FALSE(idmap()[1]);
+ }
+}
+
+TEST_P(StorageTestEmpty, DeleteAllEntriesEmpty) {
+ storage.DeleteAllEntries();
+ ASSERT_TRUE(entries().empty());
+}
+
+TEST_P(StorageTestPopulated, DeleteAllEntries) {
+ EXPECT_CALL(dispatcher, QueueOutgoing(MessageEq(Message::ClearEntries()),
+ IsNull(), IsNull()));
+ EXPECT_CALL(notifier, NotifyEntry(_, _, _, NT_NOTIFY_DELETE | NT_NOTIFY_LOCAL,
+ UINT_MAX))
+ .Times(4);
+
+ storage.DeleteAllEntries();
+ ASSERT_EQ(1u, entries().count("foo2"));
+ EXPECT_EQ(nullptr, entries()["foo2"]->value);
+}
+
+TEST_P(StorageTestPopulated, DeleteAllEntriesPersistent) {
+ GetEntry("foo2")->flags = NT_PERSISTENT;
+
+ EXPECT_CALL(dispatcher, QueueOutgoing(MessageEq(Message::ClearEntries()),
+ IsNull(), IsNull()));
+ EXPECT_CALL(notifier, NotifyEntry(_, _, _, NT_NOTIFY_DELETE | NT_NOTIFY_LOCAL,
+ UINT_MAX))
+ .Times(3);
+
+ storage.DeleteAllEntries();
+ ASSERT_EQ(1u, entries().count("foo2"));
+ EXPECT_NE(nullptr, entries()["foo2"]->value);
+}
+
+TEST_P(StorageTestPopulated, GetEntryInfoAll) {
+ auto info = storage.GetEntryInfo(0, "", 0u);
+ ASSERT_EQ(4u, info.size());
+}
+
+TEST_P(StorageTestPopulated, GetEntryInfoPrefix) {
+ auto info = storage.GetEntryInfo(0, "foo", 0u);
+ ASSERT_EQ(2u, info.size());
+ if (info[0].name == "foo") {
+ EXPECT_EQ("foo", info[0].name);
+ EXPECT_EQ(NT_BOOLEAN, info[0].type);
+ EXPECT_EQ("foo2", info[1].name);
+ EXPECT_EQ(NT_DOUBLE, info[1].type);
+ } else {
+ EXPECT_EQ("foo2", info[0].name);
+ EXPECT_EQ(NT_DOUBLE, info[0].type);
+ EXPECT_EQ("foo", info[1].name);
+ EXPECT_EQ(NT_BOOLEAN, info[1].type);
+ }
+}
+
+TEST_P(StorageTestPopulated, GetEntryInfoTypes) {
+ auto info = storage.GetEntryInfo(0, "", NT_DOUBLE);
+ ASSERT_EQ(2u, info.size());
+ EXPECT_EQ(NT_DOUBLE, info[0].type);
+ EXPECT_EQ(NT_DOUBLE, info[1].type);
+ if (info[0].name == "foo2") {
+ EXPECT_EQ("foo2", info[0].name);
+ EXPECT_EQ("bar", info[1].name);
+ } else {
+ EXPECT_EQ("bar", info[0].name);
+ EXPECT_EQ("foo2", info[1].name);
+ }
+}
+
+TEST_P(StorageTestPopulated, GetEntryInfoPrefixTypes) {
+ auto info = storage.GetEntryInfo(0, "bar", NT_BOOLEAN);
+ ASSERT_EQ(1u, info.size());
+ EXPECT_EQ("bar2", info[0].name);
+ EXPECT_EQ(NT_BOOLEAN, info[0].type);
+}
+
+TEST_P(StorageTestPersistent, SavePersistentEmpty) {
+ wpi::SmallString<256> buf;
+ wpi::raw_svector_ostream oss(buf);
+ storage.SavePersistent(oss, false);
+ ASSERT_EQ("[NetworkTables Storage 3.0]\n", oss.str());
+}
+
+TEST_P(StorageTestPersistent, SavePersistent) {
+ for (auto& i : entries()) i.getValue()->flags = NT_PERSISTENT;
+ wpi::SmallString<256> buf;
+ wpi::raw_svector_ostream oss(buf);
+ storage.SavePersistent(oss, false);
+ wpi::StringRef out = oss.str();
+ // std::fputs(out.c_str(), stderr);
+ wpi::StringRef line, rem = out;
+ std::tie(line, rem) = rem.split('\n');
+ ASSERT_EQ("[NetworkTables Storage 3.0]", line);
+ std::tie(line, rem) = rem.split('\n');
+ ASSERT_EQ("boolean \"\\x00\\x03\\x05\\n\"=true", line);
+ std::tie(line, rem) = rem.split('\n');
+ ASSERT_EQ("boolean \"\\x3D\"=true", line);
+ std::tie(line, rem) = rem.split('\n');
+ ASSERT_EQ("boolean \"boolean/false\"=false", line);
+ std::tie(line, rem) = rem.split('\n');
+ ASSERT_EQ("boolean \"boolean/true\"=true", line);
+ std::tie(line, rem) = rem.split('\n');
+ ASSERT_EQ("array boolean \"booleanarr/empty\"=", line);
+ std::tie(line, rem) = rem.split('\n');
+ ASSERT_EQ("array boolean \"booleanarr/one\"=true", line);
+ std::tie(line, rem) = rem.split('\n');
+ ASSERT_EQ("array boolean \"booleanarr/two\"=true,false", line);
+ std::tie(line, rem) = rem.split('\n');
+ ASSERT_EQ("double \"double/big\"=1.3e+08", line);
+ std::tie(line, rem) = rem.split('\n');
+ ASSERT_EQ("double \"double/neg\"=-1.5", line);
+ std::tie(line, rem) = rem.split('\n');
+ ASSERT_EQ("double \"double/zero\"=0", line);
+ std::tie(line, rem) = rem.split('\n');
+ ASSERT_EQ("array double \"doublearr/empty\"=", line);
+ std::tie(line, rem) = rem.split('\n');
+ ASSERT_EQ("array double \"doublearr/one\"=0.5", line);
+ std::tie(line, rem) = rem.split('\n');
+ ASSERT_EQ("array double \"doublearr/two\"=0.5,-0.25", line);
+ std::tie(line, rem) = rem.split('\n');
+ ASSERT_EQ("raw \"raw/empty\"=", line);
+ std::tie(line, rem) = rem.split('\n');
+ ASSERT_EQ("raw \"raw/normal\"=aGVsbG8=", line);
+ std::tie(line, rem) = rem.split('\n');
+ ASSERT_EQ("raw \"raw/special\"=AAMFCg==", line);
+ std::tie(line, rem) = rem.split('\n');
+ ASSERT_EQ("string \"string/empty\"=\"\"", line);
+ std::tie(line, rem) = rem.split('\n');
+ ASSERT_EQ("string \"string/normal\"=\"hello\"", line);
+ std::tie(line, rem) = rem.split('\n');
+ ASSERT_EQ("string \"string/special\"=\"\\x00\\x03\\x05\\n\"", line);
+ std::tie(line, rem) = rem.split('\n');
+ ASSERT_EQ("array string \"stringarr/empty\"=", line);
+ std::tie(line, rem) = rem.split('\n');
+ ASSERT_EQ("array string \"stringarr/one\"=\"hello\"", line);
+ std::tie(line, rem) = rem.split('\n');
+ ASSERT_EQ("array string \"stringarr/two\"=\"hello\",\"world\\n\"", line);
+ std::tie(line, rem) = rem.split('\n');
+ ASSERT_EQ("", line);
+}
+
+TEST_P(StorageTestEmpty, LoadPersistentBadHeader) {
+ MockLoadWarn warn;
+ auto warn_func = [&](size_t line, const char* msg) { warn.Warn(line, msg); };
+
+ wpi::raw_mem_istream iss("");
+ EXPECT_CALL(
+ warn,
+ Warn(1, wpi::StringRef("header line mismatch, ignoring rest of file")));
+ EXPECT_FALSE(storage.LoadEntries(iss, "", true, warn_func));
+
+ wpi::raw_mem_istream iss2("[NetworkTables");
+ EXPECT_CALL(
+ warn,
+ Warn(1, wpi::StringRef("header line mismatch, ignoring rest of file")));
+
+ EXPECT_FALSE(storage.LoadEntries(iss2, "", true, warn_func));
+ EXPECT_TRUE(entries().empty());
+ EXPECT_TRUE(idmap().empty());
+}
+
+TEST_P(StorageTestEmpty, LoadPersistentCommentHeader) {
+ MockLoadWarn warn;
+ auto warn_func = [&](size_t line, const char* msg) { warn.Warn(line, msg); };
+
+ wpi::raw_mem_istream iss(
+ "\n; comment\n# comment\n[NetworkTables Storage 3.0]\n");
+ EXPECT_TRUE(storage.LoadEntries(iss, "", true, warn_func));
+ EXPECT_TRUE(entries().empty());
+ EXPECT_TRUE(idmap().empty());
+}
+
+TEST_P(StorageTestEmpty, LoadPersistentEmptyName) {
+ MockLoadWarn warn;
+ auto warn_func = [&](size_t line, const char* msg) { warn.Warn(line, msg); };
+
+ wpi::raw_mem_istream iss("[NetworkTables Storage 3.0]\nboolean \"\"=true\n");
+ EXPECT_TRUE(storage.LoadEntries(iss, "", true, warn_func));
+ EXPECT_TRUE(entries().empty());
+ EXPECT_TRUE(idmap().empty());
+}
+
+TEST_P(StorageTestEmpty, LoadPersistentAssign) {
+ MockLoadWarn warn;
+ auto warn_func = [&](size_t line, const char* msg) { warn.Warn(line, msg); };
+
+ auto value = Value::MakeBoolean(true);
+
+ // id assigned if server
+ EXPECT_CALL(dispatcher, QueueOutgoing(MessageEq(Message::EntryAssign(
+ "foo", GetParam() ? 0 : 0xffff, 1,
+ value, NT_PERSISTENT)),
+ IsNull(), IsNull()));
+ EXPECT_CALL(notifier, NotifyEntry(0, StringRef("foo"),
+ ValueEq(Value::MakeBoolean(true)),
+ NT_NOTIFY_NEW | NT_NOTIFY_LOCAL, UINT_MAX));
+
+ wpi::raw_mem_istream iss(
+ "[NetworkTables Storage 3.0]\nboolean \"foo\"=true\n");
+ EXPECT_TRUE(storage.LoadEntries(iss, "", true, warn_func));
+ auto entry = GetEntry("foo");
+ EXPECT_EQ(*value, *entry->value);
+ EXPECT_EQ(NT_PERSISTENT, entry->flags);
+}
+
+TEST_P(StorageTestPopulated, LoadPersistentUpdateFlags) {
+ MockLoadWarn warn;
+ auto warn_func = [&](size_t line, const char* msg) { warn.Warn(line, msg); };
+
+ // client shouldn't send an update as id not assigned yet
+ if (GetParam()) {
+ // id assigned as this is server
+ EXPECT_CALL(dispatcher,
+ QueueOutgoing(MessageEq(Message::FlagsUpdate(1, NT_PERSISTENT)),
+ IsNull(), IsNull()));
+ }
+ EXPECT_CALL(notifier,
+ NotifyEntry(1, StringRef("foo2"), ValueEq(Value::MakeDouble(0)),
+ NT_NOTIFY_FLAGS | NT_NOTIFY_LOCAL, UINT_MAX));
+
+ wpi::raw_mem_istream iss(
+ "[NetworkTables Storage 3.0]\ndouble \"foo2\"=0.0\n");
+ EXPECT_TRUE(storage.LoadEntries(iss, "", true, warn_func));
+ auto entry = GetEntry("foo2");
+ EXPECT_EQ(*Value::MakeDouble(0.0), *entry->value);
+ EXPECT_EQ(NT_PERSISTENT, entry->flags);
+}
+
+TEST_P(StorageTestPopulated, LoadPersistentUpdateValue) {
+ MockLoadWarn warn;
+ auto warn_func = [&](size_t line, const char* msg) { warn.Warn(line, msg); };
+
+ GetEntry("foo2")->flags = NT_PERSISTENT;
+
+ auto value = Value::MakeDouble(1.0);
+
+ // client shouldn't send an update as id not assigned yet
+ if (GetParam()) {
+ // id assigned as this is the server; seq_num incremented
+ EXPECT_CALL(dispatcher,
+ QueueOutgoing(MessageEq(Message::EntryUpdate(1, 2, value)),
+ IsNull(), IsNull()));
+ }
+ EXPECT_CALL(notifier,
+ NotifyEntry(1, StringRef("foo2"), ValueEq(Value::MakeDouble(1)),
+ NT_NOTIFY_UPDATE | NT_NOTIFY_LOCAL, UINT_MAX));
+
+ wpi::raw_mem_istream iss(
+ "[NetworkTables Storage 3.0]\ndouble \"foo2\"=1.0\n");
+ EXPECT_TRUE(storage.LoadEntries(iss, "", true, warn_func));
+ auto entry = GetEntry("foo2");
+ EXPECT_EQ(*value, *entry->value);
+ EXPECT_EQ(NT_PERSISTENT, entry->flags);
+
+ if (!GetParam()) {
+ // seq_num should still be incremented
+ EXPECT_EQ(2u, GetEntry("foo2")->seq_num.value());
+ }
+}
+
+TEST_P(StorageTestPopulated, LoadPersistentUpdateValueFlags) {
+ MockLoadWarn warn;
+ auto warn_func = [&](size_t line, const char* msg) { warn.Warn(line, msg); };
+
+ auto value = Value::MakeDouble(1.0);
+
+ // client shouldn't send an update as id not assigned yet
+ if (GetParam()) {
+ // id assigned as this is the server; seq_num incremented
+ EXPECT_CALL(dispatcher,
+ QueueOutgoing(MessageEq(Message::EntryUpdate(1, 2, value)),
+ IsNull(), IsNull()));
+ EXPECT_CALL(dispatcher,
+ QueueOutgoing(MessageEq(Message::FlagsUpdate(1, NT_PERSISTENT)),
+ IsNull(), IsNull()));
+ }
+ EXPECT_CALL(notifier,
+ NotifyEntry(1, StringRef("foo2"), ValueEq(Value::MakeDouble(1)),
+ NT_NOTIFY_FLAGS | NT_NOTIFY_UPDATE | NT_NOTIFY_LOCAL,
+ UINT_MAX));
+
+ wpi::raw_mem_istream iss(
+ "[NetworkTables Storage 3.0]\ndouble \"foo2\"=1.0\n");
+ EXPECT_TRUE(storage.LoadEntries(iss, "", true, warn_func));
+ auto entry = GetEntry("foo2");
+ EXPECT_EQ(*value, *entry->value);
+ EXPECT_EQ(NT_PERSISTENT, entry->flags);
+
+ if (!GetParam()) {
+ // seq_num should still be incremented
+ EXPECT_EQ(2u, GetEntry("foo2")->seq_num.value());
+ }
+}
+
+TEST_P(StorageTestEmpty, LoadPersistent) {
+ MockLoadWarn warn;
+ auto warn_func = [&](size_t line, const char* msg) { warn.Warn(line, msg); };
+
+ std::string in = "[NetworkTables Storage 3.0]\n";
+ in += "boolean \"\\x00\\x03\\x05\\n\"=true\n";
+ in += "boolean \"\\x3D\"=true\n";
+ in += "boolean \"boolean/false\"=false\n";
+ in += "boolean \"boolean/true\"=true\n";
+ in += "array boolean \"booleanarr/empty\"=\n";
+ in += "array boolean \"booleanarr/one\"=true\n";
+ in += "array boolean \"booleanarr/two\"=true,false\n";
+ in += "double \"double/big\"=1.3e+08\n";
+ in += "double \"double/neg\"=-1.5\n";
+ in += "double \"double/zero\"=0\n";
+ in += "array double \"doublearr/empty\"=\n";
+ in += "array double \"doublearr/one\"=0.5\n";
+ in += "array double \"doublearr/two\"=0.5,-0.25\n";
+ in += "raw \"raw/empty\"=\n";
+ in += "raw \"raw/normal\"=aGVsbG8=\n";
+ in += "raw \"raw/special\"=AAMFCg==\n";
+ in += "string \"string/empty\"=\"\"\n";
+ in += "string \"string/normal\"=\"hello\"\n";
+ in += "string \"string/special\"=\"\\x00\\x03\\x05\\n\"\n";
+ in += "array string \"stringarr/empty\"=\n";
+ in += "array string \"stringarr/one\"=\"hello\"\n";
+ in += "array string \"stringarr/two\"=\"hello\",\"world\\n\"\n";
+
+ EXPECT_CALL(dispatcher, QueueOutgoing(_, _, _)).Times(22);
+ EXPECT_CALL(notifier,
+ NotifyEntry(_, _, _, NT_NOTIFY_NEW | NT_NOTIFY_LOCAL, UINT_MAX))
+ .Times(22);
+
+ wpi::raw_mem_istream iss(in);
+ EXPECT_TRUE(storage.LoadEntries(iss, "", true, warn_func));
+ ASSERT_EQ(22u, entries().size());
+
+ EXPECT_EQ(*Value::MakeBoolean(true), *storage.GetEntryValue("boolean/true"));
+ EXPECT_EQ(*Value::MakeBoolean(false),
+ *storage.GetEntryValue("boolean/false"));
+ EXPECT_EQ(*Value::MakeDouble(-1.5), *storage.GetEntryValue("double/neg"));
+ EXPECT_EQ(*Value::MakeDouble(0.0), *storage.GetEntryValue("double/zero"));
+ EXPECT_EQ(*Value::MakeDouble(1.3e8), *storage.GetEntryValue("double/big"));
+ EXPECT_EQ(*Value::MakeString(""), *storage.GetEntryValue("string/empty"));
+ EXPECT_EQ(*Value::MakeString("hello"),
+ *storage.GetEntryValue("string/normal"));
+ EXPECT_EQ(*Value::MakeString(StringRef("\0\3\5\n", 4)),
+ *storage.GetEntryValue("string/special"));
+ EXPECT_EQ(*Value::MakeRaw(""), *storage.GetEntryValue("raw/empty"));
+ EXPECT_EQ(*Value::MakeRaw("hello"), *storage.GetEntryValue("raw/normal"));
+ EXPECT_EQ(*Value::MakeRaw(StringRef("\0\3\5\n", 4)),
+ *storage.GetEntryValue("raw/special"));
+ EXPECT_EQ(*Value::MakeBooleanArray(std::vector<int>{}),
+ *storage.GetEntryValue("booleanarr/empty"));
+ EXPECT_EQ(*Value::MakeBooleanArray(std::vector<int>{1}),
+ *storage.GetEntryValue("booleanarr/one"));
+ EXPECT_EQ(*Value::MakeBooleanArray(std::vector<int>{1, 0}),
+ *storage.GetEntryValue("booleanarr/two"));
+ EXPECT_EQ(*Value::MakeDoubleArray(std::vector<double>{}),
+ *storage.GetEntryValue("doublearr/empty"));
+ EXPECT_EQ(*Value::MakeDoubleArray(std::vector<double>{0.5}),
+ *storage.GetEntryValue("doublearr/one"));
+ EXPECT_EQ(*Value::MakeDoubleArray(std::vector<double>{0.5, -0.25}),
+ *storage.GetEntryValue("doublearr/two"));
+ EXPECT_EQ(*Value::MakeStringArray(std::vector<std::string>{}),
+ *storage.GetEntryValue("stringarr/empty"));
+ EXPECT_EQ(*Value::MakeStringArray(std::vector<std::string>{"hello"}),
+ *storage.GetEntryValue("stringarr/one"));
+ EXPECT_EQ(
+ *Value::MakeStringArray(std::vector<std::string>{"hello", "world\n"}),
+ *storage.GetEntryValue("stringarr/two"));
+ EXPECT_EQ(*Value::MakeBoolean(true),
+ *storage.GetEntryValue(StringRef("\0\3\5\n", 4)));
+ EXPECT_EQ(*Value::MakeBoolean(true), *storage.GetEntryValue("="));
+}
+
+TEST_P(StorageTestEmpty, LoadPersistentWarn) {
+ MockLoadWarn warn;
+ auto warn_func = [&](size_t line, const char* msg) { warn.Warn(line, msg); };
+
+ wpi::raw_mem_istream iss(
+ "[NetworkTables Storage 3.0]\nboolean \"foo\"=foo\n");
+ EXPECT_CALL(
+ warn, Warn(2, wpi::StringRef(
+ "unrecognized boolean value, not 'true' or 'false'")));
+ EXPECT_TRUE(storage.LoadEntries(iss, "", true, warn_func));
+
+ EXPECT_TRUE(entries().empty());
+ EXPECT_TRUE(idmap().empty());
+}
+
+TEST_P(StorageTestEmpty, ProcessIncomingEntryAssign) {
+ auto conn = std::make_shared<MockNetworkConnection>();
+ auto value = Value::MakeDouble(1.0);
+ if (GetParam()) {
+ // id assign message reply generated on the server; sent to everyone
+ EXPECT_CALL(
+ dispatcher,
+ QueueOutgoing(MessageEq(Message::EntryAssign("foo", 0, 0, value, 0)),
+ IsNull(), IsNull()));
+ }
+ EXPECT_CALL(notifier, NotifyEntry(0, StringRef("foo"), ValueEq(value),
+ NT_NOTIFY_NEW, UINT_MAX));
+
+ storage.ProcessIncoming(
+ Message::EntryAssign("foo", GetParam() ? 0xffff : 0, 0, value, 0),
+ conn.get(), conn);
+}
+
+TEST_P(StorageTestPopulateOne, ProcessIncomingEntryAssign) {
+ auto conn = std::make_shared<MockNetworkConnection>();
+ auto value = Value::MakeDouble(1.0);
+ EXPECT_CALL(*conn, proto_rev()).WillRepeatedly(Return(0x0300u));
+ if (GetParam()) {
+ // server broadcasts new value to all *other* connections
+ EXPECT_CALL(
+ dispatcher,
+ QueueOutgoing(MessageEq(Message::EntryAssign("foo", 0, 1, value, 0)),
+ IsNull(), conn.get()));
+ }
+ EXPECT_CALL(notifier, NotifyEntry(0, StringRef("foo"), ValueEq(value),
+ NT_NOTIFY_UPDATE, UINT_MAX));
+
+ storage.ProcessIncoming(Message::EntryAssign("foo", 0, 1, value, 0),
+ conn.get(), conn);
+}
+
+TEST_P(StorageTestPopulateOne, ProcessIncomingEntryAssignIgnore) {
+ auto conn = std::make_shared<MockNetworkConnection>();
+ auto value = Value::MakeDouble(1.0);
+ storage.ProcessIncoming(Message::EntryAssign("foo", 0xffff, 1, value, 0),
+ conn.get(), conn);
+}
+
+TEST_P(StorageTestPopulateOne, ProcessIncomingEntryAssignWithFlags) {
+ auto conn = std::make_shared<MockNetworkConnection>();
+ auto value = Value::MakeDouble(1.0);
+ EXPECT_CALL(*conn, proto_rev()).WillRepeatedly(Return(0x0300u));
+ if (GetParam()) {
+ // server broadcasts new value/flags to all *other* connections
+ EXPECT_CALL(
+ dispatcher,
+ QueueOutgoing(MessageEq(Message::EntryAssign("foo", 0, 1, value, 0x2)),
+ IsNull(), conn.get()));
+ EXPECT_CALL(notifier,
+ NotifyEntry(0, StringRef("foo"), ValueEq(value),
+ NT_NOTIFY_UPDATE | NT_NOTIFY_FLAGS, UINT_MAX));
+ } else {
+ // client forces flags back when an assign message is received for an
+ // existing entry with different flags
+ EXPECT_CALL(dispatcher, QueueOutgoing(MessageEq(Message::FlagsUpdate(0, 0)),
+ IsNull(), IsNull()));
+ EXPECT_CALL(notifier, NotifyEntry(0, StringRef("foo"), ValueEq(value),
+ NT_NOTIFY_UPDATE, UINT_MAX));
+ }
+
+ storage.ProcessIncoming(Message::EntryAssign("foo", 0, 1, value, 0x2),
+ conn.get(), conn);
+}
+
+TEST_P(StorageTestPopulateOne, DeleteCheckHandle) {
+ EXPECT_CALL(dispatcher, QueueOutgoing(_, _, _)).Times(AnyNumber());
+ EXPECT_CALL(notifier, NotifyEntry(_, _, _, _, _)).Times(AnyNumber());
+ auto handle = storage.GetEntry("foo");
+ storage.DeleteEntry("foo");
+ storage.SetEntryTypeValue("foo", Value::MakeBoolean(true));
+ ::testing::Mock::VerifyAndClearExpectations(&dispatcher);
+ ::testing::Mock::VerifyAndClearExpectations(¬ifier);
+
+ auto handle2 = storage.GetEntry("foo");
+ ASSERT_EQ(handle, handle2);
+}
+
+TEST_P(StorageTestPopulateOne, DeletedEntryFlags) {
+ EXPECT_CALL(dispatcher, QueueOutgoing(_, _, _)).Times(AnyNumber());
+ EXPECT_CALL(notifier, NotifyEntry(_, _, _, _, _)).Times(AnyNumber());
+ auto handle = storage.GetEntry("foo");
+ storage.SetEntryFlags("foo", 2);
+ storage.DeleteEntry("foo");
+ ::testing::Mock::VerifyAndClearExpectations(&dispatcher);
+ ::testing::Mock::VerifyAndClearExpectations(¬ifier);
+
+ EXPECT_EQ(storage.GetEntryFlags("foo"), 0u);
+ EXPECT_EQ(storage.GetEntryFlags(handle), 0u);
+ storage.SetEntryFlags("foo", 4);
+ storage.SetEntryFlags(handle, 4);
+ EXPECT_EQ(storage.GetEntryFlags("foo"), 0u);
+ EXPECT_EQ(storage.GetEntryFlags(handle), 0u);
+}
+
+TEST_P(StorageTestPopulateOne, DeletedDeleteAllEntries) {
+ EXPECT_CALL(dispatcher, QueueOutgoing(_, _, _)).Times(AnyNumber());
+ EXPECT_CALL(notifier, NotifyEntry(_, _, _, _, _)).Times(AnyNumber());
+ storage.DeleteEntry("foo");
+ ::testing::Mock::VerifyAndClearExpectations(&dispatcher);
+ ::testing::Mock::VerifyAndClearExpectations(¬ifier);
+
+ EXPECT_CALL(dispatcher, QueueOutgoing(MessageEq(Message::ClearEntries()),
+ IsNull(), IsNull()));
+ storage.DeleteAllEntries();
+}
+
+TEST_P(StorageTestPopulateOne, DeletedGetEntries) {
+ EXPECT_CALL(dispatcher, QueueOutgoing(_, _, _)).Times(AnyNumber());
+ EXPECT_CALL(notifier, NotifyEntry(_, _, _, _, _)).Times(AnyNumber());
+ storage.DeleteEntry("foo");
+ ::testing::Mock::VerifyAndClearExpectations(&dispatcher);
+ ::testing::Mock::VerifyAndClearExpectations(¬ifier);
+
+ EXPECT_TRUE(storage.GetEntries("", 0).empty());
+}
+
+INSTANTIATE_TEST_CASE_P(StorageTestsEmpty, StorageTestEmpty,
+ ::testing::Bool(), );
+INSTANTIATE_TEST_CASE_P(StorageTestsPopulateOne, StorageTestPopulateOne,
+ ::testing::Bool(), );
+INSTANTIATE_TEST_CASE_P(StorageTestsPopulated, StorageTestPopulated,
+ ::testing::Bool(), );
+INSTANTIATE_TEST_CASE_P(StorageTestsPersistent, StorageTestPersistent,
+ ::testing::Bool(), );
+
+} // namespace nt
diff --git a/ntcore/src/test/native/cpp/StorageTest.h b/ntcore/src/test/native/cpp/StorageTest.h
new file mode 100644
index 0000000..1bb8a8c
--- /dev/null
+++ b/ntcore/src/test/native/cpp/StorageTest.h
@@ -0,0 +1,47 @@
+/*----------------------------------------------------------------------------*/
+/* Copyright (c) 2015-2018 FIRST. All Rights Reserved. */
+/* Open Source Software - may be modified and shared by FRC teams. The code */
+/* must be accompanied by the FIRST BSD license file in the root directory of */
+/* the project. */
+/*----------------------------------------------------------------------------*/
+
+#ifndef NTCORE_STORAGETEST_H_
+#define NTCORE_STORAGETEST_H_
+
+#include <functional>
+#include <memory>
+#include <vector>
+
+#include "Log.h"
+#include "MockDispatcher.h"
+#include "MockEntryNotifier.h"
+#include "MockRpcServer.h"
+#include "Storage.h"
+
+namespace nt {
+
+class StorageTest {
+ public:
+ StorageTest() : storage(notifier, rpc_server, logger), tmp_entry("foobar") {}
+
+ Storage::EntriesMap& entries() { return storage.m_entries; }
+ Storage::IdMap& idmap() { return storage.m_idmap; }
+
+ Storage::Entry* GetEntry(StringRef name) {
+ auto i = storage.m_entries.find(name);
+ return i == storage.m_entries.end() ? &tmp_entry : i->getValue();
+ }
+
+ void HookOutgoing(bool server) { storage.SetDispatcher(&dispatcher, server); }
+
+ wpi::Logger logger;
+ ::testing::StrictMock<MockEntryNotifier> notifier;
+ ::testing::StrictMock<MockRpcServer> rpc_server;
+ ::testing::StrictMock<MockDispatcher> dispatcher;
+ Storage storage;
+ Storage::Entry tmp_entry;
+};
+
+} // namespace nt
+
+#endif // NTCORE_STORAGETEST_H_
diff --git a/ntcore/src/test/native/cpp/TestPrinters.cpp b/ntcore/src/test/native/cpp/TestPrinters.cpp
new file mode 100644
index 0000000..3368b8c
--- /dev/null
+++ b/ntcore/src/test/native/cpp/TestPrinters.cpp
@@ -0,0 +1,156 @@
+/*----------------------------------------------------------------------------*/
+/* Copyright (c) 2017-2018 FIRST. All Rights Reserved. */
+/* Open Source Software - may be modified and shared by FRC teams. The code */
+/* must be accompanied by the FIRST BSD license file in the root directory of */
+/* the project. */
+/*----------------------------------------------------------------------------*/
+
+#include "TestPrinters.h"
+
+#include "Handle.h"
+#include "Message.h"
+#include "networktables/NetworkTableValue.h"
+#include "ntcore_cpp.h"
+
+namespace nt {
+
+void PrintTo(const EntryNotification& event, std::ostream* os) {
+ *os << "EntryNotification{listener=";
+ PrintTo(Handle{event.listener}, os);
+ *os << ", entry=";
+ PrintTo(Handle{event.entry}, os);
+ *os << ", name=\"" << event.name << "\", flags=" << event.flags << ", value=";
+ PrintTo(event.value, os);
+ *os << '}';
+}
+
+void PrintTo(const Handle& handle, std::ostream* os) {
+ *os << "Handle{";
+ switch (handle.GetType()) {
+ case Handle::kConnectionListener:
+ *os << "kConnectionListener";
+ break;
+ case Handle::kConnectionListenerPoller:
+ *os << "kConnectionListenerPoller";
+ break;
+ case Handle::kEntry:
+ *os << "kEntry";
+ break;
+ case Handle::kEntryListener:
+ *os << "kEntryListener";
+ break;
+ case Handle::kEntryListenerPoller:
+ *os << "kEntryListenerPoller";
+ break;
+ case Handle::kInstance:
+ *os << "kInstance";
+ break;
+ case Handle::kLogger:
+ *os << "kLogger";
+ break;
+ case Handle::kLoggerPoller:
+ *os << "kLoggerPoller";
+ break;
+ case Handle::kRpcCall:
+ *os << "kRpcCall";
+ break;
+ case Handle::kRpcCallPoller:
+ *os << "kRpcCallPoller";
+ break;
+ default:
+ *os << "UNKNOWN";
+ break;
+ }
+ *os << ", " << handle.GetInst() << ", " << handle.GetIndex() << '}';
+}
+
+void PrintTo(const Message& msg, std::ostream* os) {
+ *os << "Message{";
+ switch (msg.type()) {
+ case Message::kKeepAlive:
+ *os << "kKeepAlive";
+ break;
+ case Message::kClientHello:
+ *os << "kClientHello";
+ break;
+ case Message::kProtoUnsup:
+ *os << "kProtoUnsup";
+ break;
+ case Message::kServerHelloDone:
+ *os << "kServerHelloDone";
+ break;
+ case Message::kServerHello:
+ *os << "kServerHello";
+ break;
+ case Message::kClientHelloDone:
+ *os << "kClientHelloDone";
+ break;
+ case Message::kEntryAssign:
+ *os << "kEntryAssign";
+ break;
+ case Message::kEntryUpdate:
+ *os << "kEntryUpdate";
+ break;
+ case Message::kFlagsUpdate:
+ *os << "kFlagsUpdate";
+ break;
+ case Message::kEntryDelete:
+ *os << "kEntryDelete";
+ break;
+ case Message::kClearEntries:
+ *os << "kClearEntries";
+ break;
+ case Message::kExecuteRpc:
+ *os << "kExecuteRpc";
+ break;
+ case Message::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()) {
+ case NT_UNASSIGNED:
+ break;
+ case NT_BOOLEAN:
+ *os << (value.GetBoolean() ? "true" : "false");
+ break;
+ case NT_DOUBLE:
+ *os << value.GetDouble();
+ break;
+ case NT_STRING:
+ *os << '"' << value.GetString().str() << '"';
+ break;
+ case NT_RAW:
+ *os << ::testing::PrintToString(value.GetRaw());
+ break;
+ case NT_BOOLEAN_ARRAY:
+ *os << ::testing::PrintToString(value.GetBooleanArray());
+ break;
+ case NT_DOUBLE_ARRAY:
+ *os << ::testing::PrintToString(value.GetDoubleArray());
+ break;
+ case NT_STRING_ARRAY:
+ *os << ::testing::PrintToString(value.GetStringArray());
+ break;
+ case NT_RPC:
+ *os << ::testing::PrintToString(value.GetRpc());
+ break;
+ default:
+ *os << "UNKNOWN TYPE " << value.type();
+ break;
+ }
+ *os << '}';
+}
+
+} // namespace nt
diff --git a/ntcore/src/test/native/cpp/TestPrinters.h b/ntcore/src/test/native/cpp/TestPrinters.h
new file mode 100644
index 0000000..2976c1c
--- /dev/null
+++ b/ntcore/src/test/native/cpp/TestPrinters.h
@@ -0,0 +1,54 @@
+/*----------------------------------------------------------------------------*/
+/* Copyright (c) 2017-2018 FIRST. All Rights Reserved. */
+/* Open Source Software - may be modified and shared by FRC teams. The code */
+/* must be accompanied by the FIRST BSD license file in the root directory of */
+/* the project. */
+/*----------------------------------------------------------------------------*/
+
+#ifndef NTCORE_TESTPRINTERS_H_
+#define NTCORE_TESTPRINTERS_H_
+
+#include <memory>
+#include <ostream>
+
+#include <wpi/StringRef.h>
+
+#include "gtest/gtest.h"
+
+namespace wpi {
+
+inline void PrintTo(StringRef str, ::std::ostream* os) {
+ ::testing::internal::PrintStringTo(str.str(), os);
+}
+
+} // namespace wpi
+
+namespace nt {
+
+class EntryNotification;
+class Handle;
+class Message;
+class Value;
+
+void PrintTo(const EntryNotification& event, std::ostream* os);
+void PrintTo(const Handle& handle, std::ostream* os);
+
+void PrintTo(const Message& msg, std::ostream* os);
+
+inline void PrintTo(std::shared_ptr<Message> msg, std::ostream* os) {
+ *os << "shared_ptr{";
+ if (msg) PrintTo(*msg, os);
+ *os << '}';
+}
+
+void PrintTo(const Value& value, std::ostream* os);
+
+inline void PrintTo(std::shared_ptr<Value> value, std::ostream* os) {
+ *os << "shared_ptr{";
+ if (value) PrintTo(*value, os);
+ *os << '}';
+}
+
+} // namespace nt
+
+#endif // NTCORE_TESTPRINTERS_H_
diff --git a/ntcore/src/test/native/cpp/ValueMatcher.cpp b/ntcore/src/test/native/cpp/ValueMatcher.cpp
new file mode 100644
index 0000000..45b09ed
--- /dev/null
+++ b/ntcore/src/test/native/cpp/ValueMatcher.cpp
@@ -0,0 +1,33 @@
+/*----------------------------------------------------------------------------*/
+/* Copyright (c) 2017-2018 FIRST. All Rights Reserved. */
+/* Open Source Software - may be modified and shared by FRC teams. The code */
+/* must be accompanied by the FIRST BSD license file in the root directory of */
+/* the project. */
+/*----------------------------------------------------------------------------*/
+
+#include "ValueMatcher.h"
+
+#include "TestPrinters.h"
+
+namespace nt {
+
+bool ValueMatcher::MatchAndExplain(
+ std::shared_ptr<Value> val,
+ ::testing::MatchResultListener* listener) const {
+ if ((!val && goodval) || (val && !goodval) ||
+ (val && goodval && *val != *goodval)) {
+ return false;
+ }
+ return true;
+}
+
+void ValueMatcher::DescribeTo(::std::ostream* os) const {
+ PrintTo(goodval, os);
+}
+
+void ValueMatcher::DescribeNegationTo(::std::ostream* os) const {
+ *os << "is not equal to ";
+ PrintTo(goodval, os);
+}
+
+} // namespace nt
diff --git a/ntcore/src/test/native/cpp/ValueMatcher.h b/ntcore/src/test/native/cpp/ValueMatcher.h
new file mode 100644
index 0000000..5c417d7
--- /dev/null
+++ b/ntcore/src/test/native/cpp/ValueMatcher.h
@@ -0,0 +1,40 @@
+/*----------------------------------------------------------------------------*/
+/* Copyright (c) 2017-2018 FIRST. All Rights Reserved. */
+/* Open Source Software - may be modified and shared by FRC teams. The code */
+/* must be accompanied by the FIRST BSD license file in the root directory of */
+/* the project. */
+/*----------------------------------------------------------------------------*/
+
+#ifndef NTCORE_VALUEMATCHER_H_
+#define NTCORE_VALUEMATCHER_H_
+
+#include <memory>
+#include <ostream>
+
+#include "gmock/gmock.h"
+#include "networktables/NetworkTableValue.h"
+
+namespace nt {
+
+class ValueMatcher
+ : public ::testing::MatcherInterface<std::shared_ptr<Value>> {
+ public:
+ explicit ValueMatcher(std::shared_ptr<Value> goodval_) : goodval(goodval_) {}
+
+ bool MatchAndExplain(std::shared_ptr<Value> msg,
+ ::testing::MatchResultListener* listener) const override;
+ void DescribeTo(::std::ostream* os) const override;
+ void DescribeNegationTo(::std::ostream* os) const override;
+
+ private:
+ std::shared_ptr<Value> goodval;
+};
+
+inline ::testing::Matcher<std::shared_ptr<Value>> ValueEq(
+ std::shared_ptr<Value> goodval) {
+ return ::testing::MakeMatcher(new ValueMatcher(goodval));
+}
+
+} // namespace nt
+
+#endif // NTCORE_VALUEMATCHER_H_
diff --git a/ntcore/src/test/native/cpp/ValueTest.cpp b/ntcore/src/test/native/cpp/ValueTest.cpp
new file mode 100644
index 0000000..818cdac
--- /dev/null
+++ b/ntcore/src/test/native/cpp/ValueTest.cpp
@@ -0,0 +1,365 @@
+/*----------------------------------------------------------------------------*/
+/* Copyright (c) 2015-2018 FIRST. All Rights Reserved. */
+/* Open Source Software - may be modified and shared by FRC teams. The code */
+/* must be accompanied by the FIRST BSD license file in the root directory of */
+/* the project. */
+/*----------------------------------------------------------------------------*/
+
+#include "TestPrinters.h"
+#include "Value_internal.h"
+#include "gtest/gtest.h"
+#include "networktables/NetworkTableValue.h"
+
+namespace nt {
+
+class ValueTest : public ::testing::Test {};
+
+typedef ValueTest ValueDeathTest;
+
+TEST_F(ValueTest, ConstructEmpty) {
+ Value v;
+ ASSERT_EQ(NT_UNASSIGNED, v.type());
+}
+
+TEST_F(ValueTest, Boolean) {
+ auto v = Value::MakeBoolean(false);
+ ASSERT_EQ(NT_BOOLEAN, v->type());
+ ASSERT_FALSE(v->GetBoolean());
+ NT_Value cv;
+ NT_InitValue(&cv);
+ ConvertToC(*v, &cv);
+ ASSERT_EQ(NT_BOOLEAN, cv.type);
+ ASSERT_EQ(0, cv.data.v_boolean);
+
+ v = Value::MakeBoolean(true);
+ ASSERT_EQ(NT_BOOLEAN, v->type());
+ ASSERT_TRUE(v->GetBoolean());
+ ConvertToC(*v, &cv);
+ ASSERT_EQ(NT_BOOLEAN, cv.type);
+ ASSERT_EQ(1, cv.data.v_boolean);
+
+ NT_DisposeValue(&cv);
+}
+
+TEST_F(ValueTest, Double) {
+ auto v = Value::MakeDouble(0.5);
+ ASSERT_EQ(NT_DOUBLE, v->type());
+ ASSERT_EQ(0.5, v->GetDouble());
+ NT_Value cv;
+ NT_InitValue(&cv);
+ ConvertToC(*v, &cv);
+ ASSERT_EQ(NT_DOUBLE, cv.type);
+ ASSERT_EQ(0.5, cv.data.v_double);
+
+ v = Value::MakeDouble(0.25);
+ ASSERT_EQ(NT_DOUBLE, v->type());
+ ASSERT_EQ(0.25, v->GetDouble());
+ ConvertToC(*v, &cv);
+ ASSERT_EQ(NT_DOUBLE, cv.type);
+ ASSERT_EQ(0.25, cv.data.v_double);
+
+ NT_DisposeValue(&cv);
+}
+
+TEST_F(ValueTest, String) {
+ auto v = Value::MakeString("hello");
+ ASSERT_EQ(NT_STRING, v->type());
+ ASSERT_EQ("hello", v->GetString());
+ NT_Value cv;
+ NT_InitValue(&cv);
+ ConvertToC(*v, &cv);
+ ASSERT_EQ(NT_STRING, cv.type);
+ ASSERT_EQ(wpi::StringRef("hello"), cv.data.v_string.str);
+ ASSERT_EQ(5u, cv.data.v_string.len);
+
+ v = Value::MakeString("goodbye");
+ ASSERT_EQ(NT_STRING, v->type());
+ ASSERT_EQ("goodbye", v->GetString());
+ ConvertToC(*v, &cv);
+ ASSERT_EQ(NT_STRING, cv.type);
+ ASSERT_EQ(wpi::StringRef("goodbye"), cv.data.v_string.str);
+ ASSERT_EQ(7u, cv.data.v_string.len);
+
+ NT_DisposeValue(&cv);
+}
+
+TEST_F(ValueTest, Raw) {
+ auto v = Value::MakeRaw("hello");
+ ASSERT_EQ(NT_RAW, v->type());
+ ASSERT_EQ("hello", v->GetRaw());
+ NT_Value cv;
+ NT_InitValue(&cv);
+ ConvertToC(*v, &cv);
+ ASSERT_EQ(NT_RAW, cv.type);
+ ASSERT_EQ(wpi::StringRef("hello"), cv.data.v_string.str);
+ ASSERT_EQ(5u, cv.data.v_string.len);
+
+ v = Value::MakeRaw("goodbye");
+ ASSERT_EQ(NT_RAW, v->type());
+ ASSERT_EQ("goodbye", v->GetRaw());
+ ConvertToC(*v, &cv);
+ ASSERT_EQ(NT_RAW, cv.type);
+ ASSERT_EQ(wpi::StringRef("goodbye"), cv.data.v_string.str);
+ ASSERT_EQ(7u, cv.data.v_string.len);
+
+ NT_DisposeValue(&cv);
+}
+
+TEST_F(ValueTest, BooleanArray) {
+ std::vector<int> vec{1, 0, 1};
+ auto v = Value::MakeBooleanArray(vec);
+ ASSERT_EQ(NT_BOOLEAN_ARRAY, v->type());
+ ASSERT_EQ(wpi::ArrayRef<int>(vec), v->GetBooleanArray());
+ NT_Value cv;
+ NT_InitValue(&cv);
+ ConvertToC(*v, &cv);
+ ASSERT_EQ(NT_BOOLEAN_ARRAY, cv.type);
+ ASSERT_EQ(3u, cv.data.arr_boolean.size);
+ ASSERT_EQ(vec[0], cv.data.arr_boolean.arr[0]);
+ ASSERT_EQ(vec[1], cv.data.arr_boolean.arr[1]);
+ ASSERT_EQ(vec[2], cv.data.arr_boolean.arr[2]);
+
+ // assign with same size
+ vec = {0, 1, 0};
+ v = Value::MakeBooleanArray(vec);
+ ASSERT_EQ(NT_BOOLEAN_ARRAY, v->type());
+ ASSERT_EQ(wpi::ArrayRef<int>(vec), v->GetBooleanArray());
+ ConvertToC(*v, &cv);
+ ASSERT_EQ(NT_BOOLEAN_ARRAY, cv.type);
+ ASSERT_EQ(3u, cv.data.arr_boolean.size);
+ ASSERT_EQ(vec[0], cv.data.arr_boolean.arr[0]);
+ ASSERT_EQ(vec[1], cv.data.arr_boolean.arr[1]);
+ ASSERT_EQ(vec[2], cv.data.arr_boolean.arr[2]);
+
+ // assign with different size
+ vec = {1, 0};
+ v = Value::MakeBooleanArray(vec);
+ ASSERT_EQ(NT_BOOLEAN_ARRAY, v->type());
+ ASSERT_EQ(wpi::ArrayRef<int>(vec), v->GetBooleanArray());
+ ConvertToC(*v, &cv);
+ ASSERT_EQ(NT_BOOLEAN_ARRAY, cv.type);
+ ASSERT_EQ(2u, cv.data.arr_boolean.size);
+ ASSERT_EQ(vec[0], cv.data.arr_boolean.arr[0]);
+ ASSERT_EQ(vec[1], cv.data.arr_boolean.arr[1]);
+
+ NT_DisposeValue(&cv);
+}
+
+TEST_F(ValueTest, DoubleArray) {
+ std::vector<double> vec{0.5, 0.25, 0.5};
+ auto v = Value::MakeDoubleArray(vec);
+ ASSERT_EQ(NT_DOUBLE_ARRAY, v->type());
+ ASSERT_EQ(wpi::ArrayRef<double>(vec), v->GetDoubleArray());
+ NT_Value cv;
+ NT_InitValue(&cv);
+ ConvertToC(*v, &cv);
+ ASSERT_EQ(NT_DOUBLE_ARRAY, cv.type);
+ ASSERT_EQ(3u, cv.data.arr_double.size);
+ ASSERT_EQ(vec[0], cv.data.arr_double.arr[0]);
+ ASSERT_EQ(vec[1], cv.data.arr_double.arr[1]);
+ ASSERT_EQ(vec[2], cv.data.arr_double.arr[2]);
+
+ // assign with same size
+ vec = {0.25, 0.5, 0.25};
+ v = Value::MakeDoubleArray(vec);
+ ASSERT_EQ(NT_DOUBLE_ARRAY, v->type());
+ ASSERT_EQ(wpi::ArrayRef<double>(vec), v->GetDoubleArray());
+ ConvertToC(*v, &cv);
+ ASSERT_EQ(NT_DOUBLE_ARRAY, cv.type);
+ ASSERT_EQ(3u, cv.data.arr_double.size);
+ ASSERT_EQ(vec[0], cv.data.arr_double.arr[0]);
+ ASSERT_EQ(vec[1], cv.data.arr_double.arr[1]);
+ ASSERT_EQ(vec[2], cv.data.arr_double.arr[2]);
+
+ // assign with different size
+ vec = {0.5, 0.25};
+ v = Value::MakeDoubleArray(vec);
+ ASSERT_EQ(NT_DOUBLE_ARRAY, v->type());
+ ASSERT_EQ(wpi::ArrayRef<double>(vec), v->GetDoubleArray());
+ ConvertToC(*v, &cv);
+ ASSERT_EQ(NT_DOUBLE_ARRAY, cv.type);
+ ASSERT_EQ(2u, cv.data.arr_double.size);
+ ASSERT_EQ(vec[0], cv.data.arr_double.arr[0]);
+ ASSERT_EQ(vec[1], cv.data.arr_double.arr[1]);
+
+ NT_DisposeValue(&cv);
+}
+
+TEST_F(ValueTest, StringArray) {
+ std::vector<std::string> vec;
+ vec.push_back("hello");
+ vec.push_back("goodbye");
+ vec.push_back("string");
+ auto v = Value::MakeStringArray(std::move(vec));
+ ASSERT_EQ(NT_STRING_ARRAY, v->type());
+ ASSERT_EQ(3u, v->GetStringArray().size());
+ ASSERT_EQ(wpi::StringRef("hello"), v->GetStringArray()[0]);
+ ASSERT_EQ(wpi::StringRef("goodbye"), v->GetStringArray()[1]);
+ ASSERT_EQ(wpi::StringRef("string"), v->GetStringArray()[2]);
+ NT_Value cv;
+ NT_InitValue(&cv);
+ ConvertToC(*v, &cv);
+ ASSERT_EQ(NT_STRING_ARRAY, cv.type);
+ ASSERT_EQ(3u, cv.data.arr_string.size);
+ ASSERT_EQ(wpi::StringRef("hello"), cv.data.arr_string.arr[0].str);
+ ASSERT_EQ(wpi::StringRef("goodbye"), cv.data.arr_string.arr[1].str);
+ ASSERT_EQ(wpi::StringRef("string"), cv.data.arr_string.arr[2].str);
+
+ // assign with same size
+ vec.clear();
+ vec.push_back("s1");
+ vec.push_back("str2");
+ vec.push_back("string3");
+ v = Value::MakeStringArray(vec);
+ ASSERT_EQ(NT_STRING_ARRAY, v->type());
+ ASSERT_EQ(3u, v->GetStringArray().size());
+ ASSERT_EQ(wpi::StringRef("s1"), v->GetStringArray()[0]);
+ ASSERT_EQ(wpi::StringRef("str2"), v->GetStringArray()[1]);
+ ASSERT_EQ(wpi::StringRef("string3"), v->GetStringArray()[2]);
+ ConvertToC(*v, &cv);
+ ASSERT_EQ(NT_STRING_ARRAY, cv.type);
+ ASSERT_EQ(3u, cv.data.arr_string.size);
+ ASSERT_EQ(wpi::StringRef("s1"), cv.data.arr_string.arr[0].str);
+ ASSERT_EQ(wpi::StringRef("str2"), cv.data.arr_string.arr[1].str);
+ ASSERT_EQ(wpi::StringRef("string3"), cv.data.arr_string.arr[2].str);
+
+ // assign with different size
+ vec.clear();
+ vec.push_back("short");
+ vec.push_back("er");
+ v = Value::MakeStringArray(std::move(vec));
+ ASSERT_EQ(NT_STRING_ARRAY, v->type());
+ ASSERT_EQ(2u, v->GetStringArray().size());
+ ASSERT_EQ(wpi::StringRef("short"), v->GetStringArray()[0]);
+ ASSERT_EQ(wpi::StringRef("er"), v->GetStringArray()[1]);
+ ConvertToC(*v, &cv);
+ ASSERT_EQ(NT_STRING_ARRAY, cv.type);
+ ASSERT_EQ(2u, cv.data.arr_string.size);
+ ASSERT_EQ(wpi::StringRef("short"), cv.data.arr_string.arr[0].str);
+ ASSERT_EQ(wpi::StringRef("er"), cv.data.arr_string.arr[1].str);
+
+ NT_DisposeValue(&cv);
+}
+
+TEST_F(ValueDeathTest, GetAssertions) {
+ Value v;
+ ASSERT_DEATH((void)v.GetBoolean(), "type == NT_BOOLEAN");
+ ASSERT_DEATH((void)v.GetDouble(), "type == NT_DOUBLE");
+ ASSERT_DEATH((void)v.GetString(), "type == NT_STRING");
+ ASSERT_DEATH((void)v.GetRaw(), "type == NT_RAW");
+ ASSERT_DEATH((void)v.GetBooleanArray(), "type == NT_BOOLEAN_ARRAY");
+ ASSERT_DEATH((void)v.GetDoubleArray(), "type == NT_DOUBLE_ARRAY");
+ ASSERT_DEATH((void)v.GetStringArray(), "type == NT_STRING_ARRAY");
+}
+
+TEST_F(ValueTest, UnassignedComparison) {
+ Value v1, v2;
+ ASSERT_EQ(v1, v2);
+}
+
+TEST_F(ValueTest, MixedComparison) {
+ Value v1;
+ auto v2 = Value::MakeBoolean(true);
+ ASSERT_NE(v1, *v2); // unassigned vs boolean
+ auto v3 = Value::MakeDouble(0.5);
+ ASSERT_NE(*v2, *v3); // boolean vs double
+}
+
+TEST_F(ValueTest, BooleanComparison) {
+ auto v1 = Value::MakeBoolean(true);
+ auto v2 = Value::MakeBoolean(true);
+ ASSERT_EQ(*v1, *v2);
+ v2 = Value::MakeBoolean(false);
+ ASSERT_NE(*v1, *v2);
+}
+
+TEST_F(ValueTest, DoubleComparison) {
+ auto v1 = Value::MakeDouble(0.25);
+ auto v2 = Value::MakeDouble(0.25);
+ ASSERT_EQ(*v1, *v2);
+ v2 = Value::MakeDouble(0.5);
+ ASSERT_NE(*v1, *v2);
+}
+
+TEST_F(ValueTest, StringComparison) {
+ auto v1 = Value::MakeString("hello");
+ auto v2 = Value::MakeString("hello");
+ ASSERT_EQ(*v1, *v2);
+ v2 = Value::MakeString("world"); // different contents
+ ASSERT_NE(*v1, *v2);
+ v2 = Value::MakeString("goodbye"); // different size
+ ASSERT_NE(*v1, *v2);
+}
+
+TEST_F(ValueTest, BooleanArrayComparison) {
+ std::vector<int> vec{1, 0, 1};
+ auto v1 = Value::MakeBooleanArray(vec);
+ auto v2 = Value::MakeBooleanArray(vec);
+ ASSERT_EQ(*v1, *v2);
+
+ // different contents
+ vec = {1, 1, 1};
+ v2 = Value::MakeBooleanArray(vec);
+ ASSERT_NE(*v1, *v2);
+
+ // different size
+ vec = {1, 0};
+ v2 = Value::MakeBooleanArray(vec);
+ ASSERT_NE(*v1, *v2);
+}
+
+TEST_F(ValueTest, DoubleArrayComparison) {
+ std::vector<double> vec{0.5, 0.25, 0.5};
+ auto v1 = Value::MakeDoubleArray(vec);
+ auto v2 = Value::MakeDoubleArray(vec);
+ ASSERT_EQ(*v1, *v2);
+
+ // different contents
+ vec = {0.5, 0.5, 0.5};
+ v2 = Value::MakeDoubleArray(vec);
+ ASSERT_NE(*v1, *v2);
+
+ // different size
+ vec = {0.5, 0.25};
+ v2 = Value::MakeDoubleArray(vec);
+ ASSERT_NE(*v1, *v2);
+}
+
+TEST_F(ValueTest, StringArrayComparison) {
+ std::vector<std::string> vec;
+ vec.push_back("hello");
+ vec.push_back("goodbye");
+ vec.push_back("string");
+ auto v1 = Value::MakeStringArray(vec);
+ vec.clear();
+ vec.push_back("hello");
+ vec.push_back("goodbye");
+ vec.push_back("string");
+ auto v2 = Value::MakeStringArray(std::move(vec));
+ ASSERT_EQ(*v1, *v2);
+
+ // different contents
+ vec.clear();
+ vec.push_back("hello");
+ vec.push_back("goodby2");
+ vec.push_back("string");
+ v2 = Value::MakeStringArray(std::move(vec));
+ ASSERT_NE(*v1, *v2);
+
+ // different sized contents
+ vec.clear();
+ vec.push_back("hello");
+ vec.push_back("goodbye2");
+ vec.push_back("string");
+ v2 = Value::MakeStringArray(vec);
+ ASSERT_NE(*v1, *v2);
+
+ // different size
+ vec.clear();
+ vec.push_back("hello");
+ vec.push_back("goodbye");
+ v2 = Value::MakeStringArray(std::move(vec));
+ ASSERT_NE(*v1, *v2);
+}
+
+} // namespace nt
diff --git a/ntcore/src/test/native/cpp/WireDecoderTest.cpp b/ntcore/src/test/native/cpp/WireDecoderTest.cpp
new file mode 100644
index 0000000..a13fa7a
--- /dev/null
+++ b/ntcore/src/test/native/cpp/WireDecoderTest.cpp
@@ -0,0 +1,643 @@
+/*----------------------------------------------------------------------------*/
+/* Copyright (c) 2015-2018 FIRST. All Rights Reserved. */
+/* Open Source Software - may be modified and shared by FRC teams. The code */
+/* must be accompanied by the FIRST BSD license file in the root directory of */
+/* the project. */
+/*----------------------------------------------------------------------------*/
+
+#include <stdint.h>
+
+#include <cfloat>
+#include <climits>
+#include <string>
+
+#include <wpi/StringRef.h>
+
+#include "TestPrinters.h"
+#include "WireDecoder.h"
+#include "gtest/gtest.h"
+
+namespace nt {
+
+class WireDecoderTest : public ::testing::Test {
+ protected:
+ WireDecoderTest() {
+ v_boolean = Value::MakeBoolean(true);
+ v_double = Value::MakeDouble(1.0);
+ v_string = Value::MakeString(wpi::StringRef("hello"));
+ v_raw = Value::MakeRaw(wpi::StringRef("hello"));
+ v_boolean_array = Value::MakeBooleanArray(std::vector<int>{0, 1, 0});
+ v_boolean_array_big = Value::MakeBooleanArray(std::vector<int>(255));
+ v_double_array = Value::MakeDoubleArray(std::vector<double>{0.5, 0.25});
+ v_double_array_big = Value::MakeDoubleArray(std::vector<double>(255));
+
+ std::vector<std::string> sa;
+ sa.push_back("hello");
+ sa.push_back("goodbye");
+ v_string_array = Value::MakeStringArray(std::move(sa));
+
+ sa.clear();
+ for (int i = 0; i < 255; ++i) sa.push_back("h");
+ v_string_array_big = Value::MakeStringArray(std::move(sa));
+
+ s_normal = std::string("hello");
+
+ s_long.clear();
+ s_long.append(127, '*');
+ s_long.push_back('x');
+
+ s_big2.clear();
+ s_big2.append(65534, '*');
+ s_big2.push_back('x');
+
+ s_big3.clear();
+ s_big3.append(65534, '*');
+ s_big3.append(3, 'x');
+ }
+
+ std::shared_ptr<Value> v_boolean, v_double, v_string, v_raw;
+ std::shared_ptr<Value> v_boolean_array, v_boolean_array_big;
+ std::shared_ptr<Value> v_double_array, v_double_array_big;
+ std::shared_ptr<Value> v_string_array, v_string_array_big;
+
+ std::string s_normal, s_long, s_big2, s_big3;
+};
+
+TEST_F(WireDecoderTest, Construct) {
+ wpi::raw_mem_istream is("", 0);
+ wpi::Logger logger;
+ WireDecoder d(is, 0x0300u, logger);
+ EXPECT_EQ(nullptr, d.error());
+ EXPECT_EQ(0x0300u, d.proto_rev());
+}
+
+TEST_F(WireDecoderTest, SetProtoRev) {
+ wpi::raw_mem_istream is("", 0);
+ wpi::Logger logger;
+ WireDecoder d(is, 0x0300u, logger);
+ d.set_proto_rev(0x0200u);
+ EXPECT_EQ(0x0200u, d.proto_rev());
+}
+
+TEST_F(WireDecoderTest, Read8) {
+ wpi::raw_mem_istream is("\x05\x01\x00", 3);
+ wpi::Logger logger;
+ WireDecoder d(is, 0x0300u, logger);
+ unsigned int val;
+ ASSERT_TRUE(d.Read8(&val));
+ EXPECT_EQ(5u, val);
+ ASSERT_TRUE(d.Read8(&val));
+ EXPECT_EQ(1u, val);
+ ASSERT_TRUE(d.Read8(&val));
+ EXPECT_EQ(0u, val);
+ ASSERT_FALSE(d.Read8(&val));
+ ASSERT_EQ(nullptr, d.error());
+}
+
+TEST_F(WireDecoderTest, Read16) {
+ wpi::raw_mem_istream is("\x00\x05\x00\x01\x45\x67\x00\x00", 8);
+ wpi::Logger logger;
+ WireDecoder d(is, 0x0300u, logger);
+ unsigned int val;
+ ASSERT_TRUE(d.Read16(&val));
+ EXPECT_EQ(5u, val);
+ ASSERT_TRUE(d.Read16(&val));
+ EXPECT_EQ(1u, val);
+ ASSERT_TRUE(d.Read16(&val));
+ EXPECT_EQ(0x4567u, val);
+ ASSERT_TRUE(d.Read16(&val));
+ EXPECT_EQ(0u, val);
+ ASSERT_FALSE(d.Read16(&val));
+ ASSERT_EQ(nullptr, d.error());
+}
+
+TEST_F(WireDecoderTest, Read32) {
+ wpi::raw_mem_istream is(
+ "\x00\x00\x00\x05\x00\x00\x00\x01\x00\x00\xab\xcd"
+ "\x12\x34\x56\x78\x00\x00\x00\x00",
+ 20);
+ wpi::Logger logger;
+ WireDecoder d(is, 0x0300u, logger);
+ uint32_t val;
+ ASSERT_TRUE(d.Read32(&val));
+ EXPECT_EQ(5ul, val);
+ ASSERT_TRUE(d.Read32(&val));
+ EXPECT_EQ(1ul, val);
+ ASSERT_TRUE(d.Read32(&val));
+ EXPECT_EQ(0xabcdul, val);
+ ASSERT_TRUE(d.Read32(&val));
+ EXPECT_EQ(0x12345678ul, val);
+ ASSERT_TRUE(d.Read32(&val));
+ EXPECT_EQ(0ul, val);
+ ASSERT_FALSE(d.Read32(&val));
+ ASSERT_EQ(nullptr, d.error());
+}
+
+TEST_F(WireDecoderTest, ReadDouble) {
+ // values except min and max from
+ // http://www.binaryconvert.com/result_double.html
+ wpi::raw_mem_istream is(
+ "\x00\x00\x00\x00\x00\x00\x00\x00"
+ "\x41\x0c\x13\x80\x00\x00\x00\x00"
+ "\x7f\xf0\x00\x00\x00\x00\x00\x00"
+ "\x00\x10\x00\x00\x00\x00\x00\x00"
+ "\x7f\xef\xff\xff\xff\xff\xff\xff",
+ 40);
+ wpi::Logger logger;
+ WireDecoder d(is, 0x0300u, logger);
+ double val;
+ ASSERT_TRUE(d.ReadDouble(&val));
+ EXPECT_EQ(0.0, val);
+ ASSERT_TRUE(d.ReadDouble(&val));
+ EXPECT_EQ(2.3e5, val);
+ ASSERT_TRUE(d.ReadDouble(&val));
+ EXPECT_EQ(std::numeric_limits<double>::infinity(), val);
+ ASSERT_TRUE(d.ReadDouble(&val));
+ EXPECT_EQ(DBL_MIN, val);
+ ASSERT_TRUE(d.ReadDouble(&val));
+ EXPECT_EQ(DBL_MAX, val);
+ ASSERT_FALSE(d.ReadDouble(&val));
+ ASSERT_EQ(nullptr, d.error());
+}
+
+TEST_F(WireDecoderTest, ReadUleb128) {
+ wpi::raw_mem_istream is("\x00\x7f\x80\x01\x80", 5);
+ wpi::Logger logger;
+ WireDecoder d(is, 0x0300u, logger);
+ uint64_t val;
+ ASSERT_TRUE(d.ReadUleb128(&val));
+ EXPECT_EQ(0ul, val);
+ ASSERT_TRUE(d.ReadUleb128(&val));
+ EXPECT_EQ(0x7ful, val);
+ ASSERT_TRUE(d.ReadUleb128(&val));
+ EXPECT_EQ(0x80ul, val);
+ ASSERT_FALSE(d.ReadUleb128(&val)); // partial
+ ASSERT_EQ(nullptr, d.error());
+}
+
+TEST_F(WireDecoderTest, ReadType) {
+ wpi::raw_mem_istream is("\x00\x01\x02\x03\x10\x11\x12\x20", 8);
+ wpi::Logger logger;
+ WireDecoder d(is, 0x0300u, logger);
+ NT_Type val;
+ ASSERT_TRUE(d.ReadType(&val));
+ EXPECT_EQ(NT_BOOLEAN, val);
+ ASSERT_TRUE(d.ReadType(&val));
+ EXPECT_EQ(NT_DOUBLE, val);
+ ASSERT_TRUE(d.ReadType(&val));
+ EXPECT_EQ(NT_STRING, val);
+ ASSERT_TRUE(d.ReadType(&val));
+ EXPECT_EQ(NT_RAW, val);
+ ASSERT_TRUE(d.ReadType(&val));
+ EXPECT_EQ(NT_BOOLEAN_ARRAY, val);
+ ASSERT_TRUE(d.ReadType(&val));
+ EXPECT_EQ(NT_DOUBLE_ARRAY, val);
+ ASSERT_TRUE(d.ReadType(&val));
+ EXPECT_EQ(NT_STRING_ARRAY, val);
+ ASSERT_TRUE(d.ReadType(&val));
+ EXPECT_EQ(NT_RPC, val);
+ ASSERT_FALSE(d.ReadType(&val));
+ ASSERT_EQ(nullptr, d.error());
+}
+
+TEST_F(WireDecoderTest, ReadTypeError) {
+ wpi::raw_mem_istream is("\x30", 1);
+ wpi::Logger logger;
+ WireDecoder d(is, 0x0200u, logger);
+ NT_Type val;
+ ASSERT_FALSE(d.ReadType(&val));
+ EXPECT_EQ(NT_UNASSIGNED, val);
+ ASSERT_NE(nullptr, d.error());
+}
+
+TEST_F(WireDecoderTest, Reset) {
+ wpi::raw_mem_istream is("\x30", 1);
+ wpi::Logger logger;
+ WireDecoder d(is, 0x0200u, logger);
+ NT_Type val;
+ ASSERT_FALSE(d.ReadType(&val));
+ EXPECT_EQ(NT_UNASSIGNED, val);
+ ASSERT_NE(nullptr, d.error());
+ d.Reset();
+ EXPECT_EQ(nullptr, d.error());
+}
+
+TEST_F(WireDecoderTest, ReadBooleanValue2) {
+ wpi::raw_mem_istream is("\x01\x00", 2);
+ wpi::Logger logger;
+ WireDecoder d(is, 0x0200u, logger);
+ auto val = d.ReadValue(NT_BOOLEAN);
+ ASSERT_TRUE(static_cast<bool>(val));
+ EXPECT_EQ(*v_boolean, *val);
+
+ auto v_false = Value::MakeBoolean(false);
+ val = d.ReadValue(NT_BOOLEAN);
+ ASSERT_TRUE(static_cast<bool>(val));
+ EXPECT_EQ(*v_false, *val);
+
+ ASSERT_FALSE(d.ReadValue(NT_BOOLEAN));
+ ASSERT_EQ(nullptr, d.error());
+}
+
+TEST_F(WireDecoderTest, ReadDoubleValue2) {
+ wpi::raw_mem_istream is(
+ "\x3f\xf0\x00\x00\x00\x00\x00\x00"
+ "\x3f\xf0\x00\x00\x00\x00\x00\x00",
+ 16);
+ wpi::Logger logger;
+ WireDecoder d(is, 0x0200u, logger);
+ auto val = d.ReadValue(NT_DOUBLE);
+ ASSERT_TRUE(static_cast<bool>(val));
+ EXPECT_EQ(*v_double, *val);
+
+ val = d.ReadValue(NT_DOUBLE);
+ ASSERT_TRUE(static_cast<bool>(val));
+ EXPECT_EQ(*v_double, *val);
+
+ ASSERT_FALSE(d.ReadValue(NT_DOUBLE));
+ ASSERT_EQ(nullptr, d.error());
+}
+
+TEST_F(WireDecoderTest, ReadStringValue2) {
+ wpi::raw_mem_istream is(
+ "\x00\x05hello\x00\x03"
+ "bye\x55",
+ 13);
+ wpi::Logger logger;
+ WireDecoder d(is, 0x0200u, logger);
+ auto val = d.ReadValue(NT_STRING);
+ ASSERT_TRUE(static_cast<bool>(val));
+ EXPECT_EQ(*v_string, *val);
+
+ auto v_bye = Value::MakeString(wpi::StringRef("bye"));
+ val = d.ReadValue(NT_STRING);
+ ASSERT_TRUE(static_cast<bool>(val));
+ EXPECT_EQ(*v_bye, *val);
+
+ unsigned int b;
+ ASSERT_TRUE(d.Read8(&b));
+ EXPECT_EQ(0x55u, b);
+
+ ASSERT_FALSE(d.ReadValue(NT_STRING));
+ ASSERT_EQ(nullptr, d.error());
+}
+
+TEST_F(WireDecoderTest, ReadBooleanArrayValue2) {
+ wpi::raw_mem_istream is("\x03\x00\x01\x00\x02\x01\x00\xff", 8);
+ wpi::Logger logger;
+ WireDecoder d(is, 0x0200u, logger);
+ auto val = d.ReadValue(NT_BOOLEAN_ARRAY);
+ ASSERT_TRUE(static_cast<bool>(val));
+ EXPECT_EQ(*v_boolean_array, *val);
+
+ auto v_boolean_array2 = Value::MakeBooleanArray(std::vector<int>{1, 0});
+ val = d.ReadValue(NT_BOOLEAN_ARRAY);
+ ASSERT_TRUE(static_cast<bool>(val));
+ EXPECT_EQ(*v_boolean_array2, *val);
+
+ ASSERT_FALSE(d.ReadValue(NT_BOOLEAN_ARRAY));
+ ASSERT_EQ(nullptr, d.error());
+}
+
+TEST_F(WireDecoderTest, ReadBooleanArrayBigValue2) {
+ std::string s;
+ s.push_back('\xff');
+ s.append(255, '\x00');
+ wpi::raw_mem_istream is(s.data(), s.size());
+ wpi::Logger logger;
+ WireDecoder d(is, 0x0200u, logger);
+ auto val = d.ReadValue(NT_BOOLEAN_ARRAY);
+ ASSERT_TRUE(static_cast<bool>(val));
+ EXPECT_EQ(*v_boolean_array_big, *val);
+
+ ASSERT_FALSE(d.ReadValue(NT_BOOLEAN_ARRAY));
+ ASSERT_EQ(nullptr, d.error());
+}
+
+TEST_F(WireDecoderTest, ReadDoubleArrayValue2) {
+ wpi::raw_mem_istream is(
+ "\x02\x3f\xe0\x00\x00\x00\x00\x00\x00"
+ "\x3f\xd0\x00\x00\x00\x00\x00\x00\x55",
+ 18);
+ wpi::Logger logger;
+ WireDecoder d(is, 0x0200u, logger);
+ auto val = d.ReadValue(NT_DOUBLE_ARRAY);
+ ASSERT_TRUE(static_cast<bool>(val));
+ EXPECT_EQ(*v_double_array, *val);
+
+ unsigned int b;
+ ASSERT_TRUE(d.Read8(&b));
+ EXPECT_EQ(0x55u, b);
+
+ ASSERT_FALSE(d.ReadValue(NT_DOUBLE_ARRAY));
+ ASSERT_EQ(nullptr, d.error());
+}
+
+TEST_F(WireDecoderTest, ReadDoubleArrayBigValue2) {
+ std::string s;
+ s.push_back('\xff');
+ s.append(255 * 8, '\x00');
+ wpi::raw_mem_istream is(s.data(), s.size());
+ wpi::Logger logger;
+ WireDecoder d(is, 0x0200u, logger);
+ auto val = d.ReadValue(NT_DOUBLE_ARRAY);
+ ASSERT_TRUE(static_cast<bool>(val));
+ EXPECT_EQ(*v_double_array_big, *val);
+
+ ASSERT_FALSE(d.ReadValue(NT_DOUBLE_ARRAY));
+ ASSERT_EQ(nullptr, d.error());
+}
+
+TEST_F(WireDecoderTest, ReadStringArrayValue2) {
+ wpi::raw_mem_istream is("\x02\x00\x05hello\x00\x07goodbye\x55", 18);
+ wpi::Logger logger;
+ WireDecoder d(is, 0x0200u, logger);
+ auto val = d.ReadValue(NT_STRING_ARRAY);
+ ASSERT_TRUE(static_cast<bool>(val));
+ EXPECT_EQ(*v_string_array, *val);
+
+ unsigned int b;
+ ASSERT_TRUE(d.Read8(&b));
+ EXPECT_EQ(0x55u, b);
+
+ ASSERT_FALSE(d.ReadValue(NT_STRING_ARRAY));
+ ASSERT_EQ(nullptr, d.error());
+}
+
+TEST_F(WireDecoderTest, ReadStringArrayBigValue2) {
+ std::string s;
+ s.push_back('\xff');
+ for (int i = 0; i < 255; ++i) s.append("\x00\x01h", 3);
+ wpi::raw_mem_istream is(s.data(), s.size());
+ wpi::Logger logger;
+ WireDecoder d(is, 0x0200u, logger);
+ auto val = d.ReadValue(NT_STRING_ARRAY);
+ ASSERT_TRUE(static_cast<bool>(val));
+ EXPECT_EQ(*v_string_array_big, *val);
+
+ ASSERT_FALSE(d.ReadValue(NT_STRING_ARRAY));
+ ASSERT_EQ(nullptr, d.error());
+}
+
+TEST_F(WireDecoderTest, ReadValueError2) {
+ wpi::raw_mem_istream is("", 0);
+ wpi::Logger logger;
+ WireDecoder d(is, 0x0200u, logger);
+ ASSERT_FALSE(d.ReadValue(NT_UNASSIGNED)); // unassigned
+ ASSERT_NE(nullptr, d.error());
+
+ d.Reset();
+ ASSERT_FALSE(d.ReadValue(NT_RAW)); // not supported
+ ASSERT_NE(nullptr, d.error());
+
+ d.Reset();
+ ASSERT_FALSE(d.ReadValue(NT_RPC)); // not supported
+ ASSERT_NE(nullptr, d.error());
+}
+
+TEST_F(WireDecoderTest, ReadBooleanValue3) {
+ wpi::raw_mem_istream is("\x01\x00", 2);
+ wpi::Logger logger;
+ WireDecoder d(is, 0x0300u, logger);
+ auto val = d.ReadValue(NT_BOOLEAN);
+ ASSERT_TRUE(static_cast<bool>(val));
+ EXPECT_EQ(*v_boolean, *val);
+
+ auto v_false = Value::MakeBoolean(false);
+ val = d.ReadValue(NT_BOOLEAN);
+ ASSERT_TRUE(static_cast<bool>(val));
+ EXPECT_EQ(*v_false, *val);
+
+ ASSERT_FALSE(d.ReadValue(NT_BOOLEAN));
+ ASSERT_EQ(nullptr, d.error());
+}
+
+TEST_F(WireDecoderTest, ReadDoubleValue3) {
+ wpi::raw_mem_istream is(
+ "\x3f\xf0\x00\x00\x00\x00\x00\x00"
+ "\x3f\xf0\x00\x00\x00\x00\x00\x00",
+ 16);
+ wpi::Logger logger;
+ WireDecoder d(is, 0x0300u, logger);
+ auto val = d.ReadValue(NT_DOUBLE);
+ ASSERT_TRUE(static_cast<bool>(val));
+ EXPECT_EQ(*v_double, *val);
+
+ val = d.ReadValue(NT_DOUBLE);
+ ASSERT_TRUE(static_cast<bool>(val));
+ EXPECT_EQ(*v_double, *val);
+
+ ASSERT_FALSE(d.ReadValue(NT_DOUBLE));
+ ASSERT_EQ(nullptr, d.error());
+}
+
+TEST_F(WireDecoderTest, ReadStringValue3) {
+ wpi::raw_mem_istream is(
+ "\x05hello\x03"
+ "bye\x55",
+ 11);
+ wpi::Logger logger;
+ WireDecoder d(is, 0x0300u, logger);
+ auto val = d.ReadValue(NT_STRING);
+ ASSERT_TRUE(static_cast<bool>(val));
+ EXPECT_EQ(*v_string, *val);
+
+ auto v_bye = Value::MakeString(wpi::StringRef("bye"));
+ val = d.ReadValue(NT_STRING);
+ ASSERT_TRUE(static_cast<bool>(val));
+ EXPECT_EQ(*v_bye, *val);
+
+ unsigned int b;
+ ASSERT_TRUE(d.Read8(&b));
+ EXPECT_EQ(0x55u, b);
+
+ ASSERT_FALSE(d.ReadValue(NT_STRING));
+ ASSERT_EQ(nullptr, d.error());
+}
+
+TEST_F(WireDecoderTest, ReadRawValue3) {
+ wpi::raw_mem_istream is(
+ "\x05hello\x03"
+ "bye\x55",
+ 11);
+ wpi::Logger logger;
+ WireDecoder d(is, 0x0300u, logger);
+ auto val = d.ReadValue(NT_RAW);
+ ASSERT_TRUE(static_cast<bool>(val));
+ EXPECT_EQ(*v_raw, *val);
+
+ auto v_bye = Value::MakeRaw(wpi::StringRef("bye"));
+ val = d.ReadValue(NT_RAW);
+ ASSERT_TRUE(static_cast<bool>(val));
+ EXPECT_EQ(*v_bye, *val);
+
+ unsigned int b;
+ ASSERT_TRUE(d.Read8(&b));
+ EXPECT_EQ(0x55u, b);
+
+ ASSERT_FALSE(d.ReadValue(NT_RAW));
+ ASSERT_EQ(nullptr, d.error());
+}
+
+TEST_F(WireDecoderTest, ReadBooleanArrayValue3) {
+ wpi::raw_mem_istream is("\x03\x00\x01\x00\x02\x01\x00\xff", 8);
+ wpi::Logger logger;
+ WireDecoder d(is, 0x0300u, logger);
+ auto val = d.ReadValue(NT_BOOLEAN_ARRAY);
+ ASSERT_TRUE(static_cast<bool>(val));
+ EXPECT_EQ(*v_boolean_array, *val);
+
+ auto v_boolean_array2 = Value::MakeBooleanArray(std::vector<int>{1, 0});
+ val = d.ReadValue(NT_BOOLEAN_ARRAY);
+ ASSERT_TRUE(static_cast<bool>(val));
+ EXPECT_EQ(*v_boolean_array2, *val);
+
+ ASSERT_FALSE(d.ReadValue(NT_BOOLEAN_ARRAY));
+ ASSERT_EQ(nullptr, d.error());
+}
+
+TEST_F(WireDecoderTest, ReadBooleanArrayBigValue3) {
+ std::string s;
+ s.push_back('\xff');
+ s.append(255, '\x00');
+ wpi::raw_mem_istream is(s.data(), s.size());
+ wpi::Logger logger;
+ WireDecoder d(is, 0x0300u, logger);
+ auto val = d.ReadValue(NT_BOOLEAN_ARRAY);
+ ASSERT_TRUE(static_cast<bool>(val));
+ EXPECT_EQ(*v_boolean_array_big, *val);
+
+ ASSERT_FALSE(d.ReadValue(NT_BOOLEAN_ARRAY));
+ ASSERT_EQ(nullptr, d.error());
+}
+
+TEST_F(WireDecoderTest, ReadDoubleArrayValue3) {
+ wpi::raw_mem_istream is(
+ "\x02\x3f\xe0\x00\x00\x00\x00\x00\x00"
+ "\x3f\xd0\x00\x00\x00\x00\x00\x00\x55",
+ 18);
+ wpi::Logger logger;
+ WireDecoder d(is, 0x0300u, logger);
+ auto val = d.ReadValue(NT_DOUBLE_ARRAY);
+ ASSERT_TRUE(static_cast<bool>(val));
+ EXPECT_EQ(*v_double_array, *val);
+
+ unsigned int b;
+ ASSERT_TRUE(d.Read8(&b));
+ EXPECT_EQ(0x55u, b);
+
+ ASSERT_FALSE(d.ReadValue(NT_DOUBLE_ARRAY));
+ ASSERT_EQ(nullptr, d.error());
+}
+
+TEST_F(WireDecoderTest, ReadDoubleArrayBigValue3) {
+ std::string s;
+ s.push_back('\xff');
+ s.append(255 * 8, '\x00');
+ wpi::raw_mem_istream is(s.data(), s.size());
+ wpi::Logger logger;
+ WireDecoder d(is, 0x0300u, logger);
+ auto val = d.ReadValue(NT_DOUBLE_ARRAY);
+ ASSERT_TRUE(static_cast<bool>(val));
+ EXPECT_EQ(*v_double_array_big, *val);
+
+ ASSERT_FALSE(d.ReadValue(NT_DOUBLE_ARRAY));
+ ASSERT_EQ(nullptr, d.error());
+}
+
+TEST_F(WireDecoderTest, ReadStringArrayValue3) {
+ wpi::raw_mem_istream is("\x02\x05hello\x07goodbye\x55", 16);
+ wpi::Logger logger;
+ WireDecoder d(is, 0x0300u, logger);
+ auto val = d.ReadValue(NT_STRING_ARRAY);
+ ASSERT_TRUE(static_cast<bool>(val));
+ EXPECT_EQ(*v_string_array, *val);
+
+ unsigned int b;
+ ASSERT_TRUE(d.Read8(&b));
+ EXPECT_EQ(0x55u, b);
+
+ ASSERT_FALSE(d.ReadValue(NT_STRING_ARRAY));
+ ASSERT_EQ(nullptr, d.error());
+}
+
+TEST_F(WireDecoderTest, ReadStringArrayBigValue3) {
+ std::string s;
+ s.push_back('\xff');
+ for (int i = 0; i < 255; ++i) s.append("\x01h", 2);
+ wpi::raw_mem_istream is(s.data(), s.size());
+ wpi::Logger logger;
+ WireDecoder d(is, 0x0300u, logger);
+ auto val = d.ReadValue(NT_STRING_ARRAY);
+ ASSERT_TRUE(static_cast<bool>(val));
+ EXPECT_EQ(*v_string_array_big, *val);
+
+ ASSERT_FALSE(d.ReadValue(NT_STRING_ARRAY));
+ ASSERT_EQ(nullptr, d.error());
+}
+
+TEST_F(WireDecoderTest, ReadValueError3) {
+ wpi::raw_mem_istream is("", 0);
+ wpi::Logger logger;
+ WireDecoder d(is, 0x0200u, logger);
+ ASSERT_FALSE(d.ReadValue(NT_UNASSIGNED)); // unassigned
+ ASSERT_NE(nullptr, d.error());
+}
+
+TEST_F(WireDecoderTest, ReadString2) {
+ std::string s;
+ s.append("\x00\x05", 2);
+ s += s_normal;
+ s.append("\x00\x80", 2);
+ s += s_long;
+ s.append("\xff\xff", 2);
+ s += s_big2;
+ s.push_back('\x55');
+ wpi::raw_mem_istream is(s.data(), s.size());
+ wpi::Logger logger;
+ WireDecoder d(is, 0x0200u, logger);
+ std::string outs;
+ ASSERT_TRUE(d.ReadString(&outs));
+ EXPECT_EQ(s_normal, outs);
+ ASSERT_TRUE(d.ReadString(&outs));
+ EXPECT_EQ(s_long, outs);
+ ASSERT_TRUE(d.ReadString(&outs));
+ EXPECT_EQ(s_big2, outs);
+
+ unsigned int b;
+ ASSERT_TRUE(d.Read8(&b));
+ EXPECT_EQ(0x55u, b);
+
+ ASSERT_FALSE(d.ReadString(&outs));
+ ASSERT_EQ(nullptr, d.error());
+}
+
+TEST_F(WireDecoderTest, ReadString3) {
+ std::string s;
+ s.push_back('\x05');
+ s += s_normal;
+ s.append("\x80\x01", 2);
+ s += s_long;
+ s.append("\x81\x80\x04", 3);
+ s += s_big3;
+ s.push_back('\x55');
+ wpi::raw_mem_istream is(s.data(), s.size());
+ wpi::Logger logger;
+ WireDecoder d(is, 0x0300u, logger);
+ std::string outs;
+ ASSERT_TRUE(d.ReadString(&outs));
+ EXPECT_EQ(s_normal, outs);
+ ASSERT_TRUE(d.ReadString(&outs));
+ EXPECT_EQ(s_long, outs);
+ ASSERT_TRUE(d.ReadString(&outs));
+ EXPECT_EQ(s_big3, outs);
+
+ unsigned int b;
+ ASSERT_TRUE(d.Read8(&b));
+ EXPECT_EQ(0x55u, b);
+
+ ASSERT_FALSE(d.ReadString(&outs));
+ ASSERT_EQ(nullptr, d.error());
+}
+
+} // namespace nt
diff --git a/ntcore/src/test/native/cpp/WireEncoderTest.cpp b/ntcore/src/test/native/cpp/WireEncoderTest.cpp
new file mode 100644
index 0000000..664344f
--- /dev/null
+++ b/ntcore/src/test/native/cpp/WireEncoderTest.cpp
@@ -0,0 +1,493 @@
+/*----------------------------------------------------------------------------*/
+/* Copyright (c) 2015-2018 FIRST. All Rights Reserved. */
+/* Open Source Software - may be modified and shared by FRC teams. The code */
+/* must be accompanied by the FIRST BSD license file in the root directory of */
+/* the project. */
+/*----------------------------------------------------------------------------*/
+
+#include <cfloat>
+#include <climits>
+#include <string>
+
+#include <wpi/StringRef.h>
+
+#include "TestPrinters.h"
+#include "WireEncoder.h"
+#include "gtest/gtest.h"
+
+#define BUFSIZE 1024
+
+namespace nt {
+
+class WireEncoderTest : public ::testing::Test {
+ protected:
+ WireEncoderTest() {
+ v_empty = std::make_shared<Value>();
+ v_boolean = Value::MakeBoolean(true);
+ v_double = Value::MakeDouble(1.0);
+ v_string = Value::MakeString(wpi::StringRef("hello"));
+ v_raw = Value::MakeRaw(wpi::StringRef("hello"));
+ v_boolean_array = Value::MakeBooleanArray(std::vector<int>{0, 1, 0});
+ v_boolean_array_big = Value::MakeBooleanArray(std::vector<int>(256));
+ v_double_array = Value::MakeDoubleArray(std::vector<double>{0.5, 0.25});
+ v_double_array_big = Value::MakeDoubleArray(std::vector<double>(256));
+
+ std::vector<std::string> sa;
+ sa.push_back("hello");
+ sa.push_back("goodbye");
+ v_string_array = Value::MakeStringArray(std::move(sa));
+
+ sa.clear();
+ for (int i = 0; i < 256; ++i) sa.push_back("h");
+ v_string_array_big = Value::MakeStringArray(std::move(sa));
+
+ s_normal = "hello";
+
+ s_long.clear();
+ s_long.append(127, '*');
+ s_long.push_back('x');
+
+ s_big.clear();
+ s_big.append(65534, '*');
+ s_big.append(3, 'x');
+ }
+
+ std::shared_ptr<Value> v_empty;
+ std::shared_ptr<Value> v_boolean, v_double, v_string, v_raw;
+ std::shared_ptr<Value> v_boolean_array, v_boolean_array_big;
+ std::shared_ptr<Value> v_double_array, v_double_array_big;
+ std::shared_ptr<Value> v_string_array, v_string_array_big;
+
+ std::string s_normal, s_long, s_big;
+};
+
+TEST_F(WireEncoderTest, Construct) {
+ WireEncoder e(0x0300u);
+ EXPECT_EQ(0u, e.size());
+ EXPECT_EQ(nullptr, e.error());
+ EXPECT_EQ(0x0300u, e.proto_rev());
+}
+
+TEST_F(WireEncoderTest, SetProtoRev) {
+ WireEncoder e(0x0300u);
+ e.set_proto_rev(0x0200u);
+ EXPECT_EQ(0x0200u, e.proto_rev());
+}
+
+TEST_F(WireEncoderTest, Write8) {
+ size_t off = BUFSIZE - 1;
+ WireEncoder e(0x0300u);
+ for (size_t i = 0; i < off; ++i) e.Write8(0u); // test across Reserve()
+ e.Write8(5u);
+ e.Write8(0x101u); // should be truncated
+ e.Write8(0u);
+ ASSERT_EQ(3u, e.size() - off);
+ ASSERT_EQ(wpi::StringRef("\x05\x01\x00", 3),
+ wpi::StringRef(e.data(), e.size()).substr(off));
+}
+
+TEST_F(WireEncoderTest, Write16) {
+ size_t off = BUFSIZE - 2;
+ WireEncoder e(0x0300u);
+ for (size_t i = 0; i < off; ++i) e.Write8(0u); // test across Reserve()
+ e.Write16(5u);
+ e.Write16(0x10001u); // should be truncated
+ e.Write16(0x4567u);
+ e.Write16(0u);
+ ASSERT_EQ(8u, e.size() - off);
+ ASSERT_EQ(wpi::StringRef("\x00\x05\x00\x01\x45\x67\x00\x00", 8),
+ wpi::StringRef(e.data(), e.size()).substr(off));
+}
+
+TEST_F(WireEncoderTest, Write32) {
+ size_t off = BUFSIZE - 4;
+ WireEncoder e(0x0300u);
+ for (size_t i = 0; i < off; ++i) e.Write8(0u); // test across Reserve()
+ e.Write32(5ul);
+ e.Write32(1ul);
+ e.Write32(0xabcdul);
+ e.Write32(0x12345678ul);
+ e.Write32(0ul);
+ ASSERT_EQ(20u, e.size() - off);
+ ASSERT_EQ(wpi::StringRef("\x00\x00\x00\x05\x00\x00\x00\x01\x00\x00\xab\xcd"
+ "\x12\x34\x56\x78\x00\x00\x00\x00",
+ 20),
+ wpi::StringRef(e.data(), e.size()).substr(off));
+}
+
+TEST_F(WireEncoderTest, WriteDouble) {
+ size_t off = BUFSIZE - 8;
+ WireEncoder e(0x0300u);
+ for (size_t i = 0; i < off; ++i) e.Write8(0u); // test across Reserve()
+ e.WriteDouble(0.0);
+ e.WriteDouble(2.3e5);
+ e.WriteDouble(std::numeric_limits<double>::infinity());
+ e.WriteDouble(DBL_MIN);
+ e.WriteDouble(DBL_MAX);
+ ASSERT_EQ(40u, e.size() - off);
+ // golden values except min and max from
+ // http://www.binaryconvert.com/result_double.html
+ ASSERT_EQ(wpi::StringRef("\x00\x00\x00\x00\x00\x00\x00\x00"
+ "\x41\x0c\x13\x80\x00\x00\x00\x00"
+ "\x7f\xf0\x00\x00\x00\x00\x00\x00"
+ "\x00\x10\x00\x00\x00\x00\x00\x00"
+ "\x7f\xef\xff\xff\xff\xff\xff\xff",
+ 40),
+ wpi::StringRef(e.data(), e.size()).substr(off));
+}
+
+TEST_F(WireEncoderTest, WriteUleb128) {
+ size_t off = BUFSIZE - 2;
+ WireEncoder e(0x0300u);
+ for (size_t i = 0; i < off; ++i) e.Write8(0u); // test across Reserve()
+ e.WriteUleb128(0ul);
+ e.WriteUleb128(0x7ful);
+ e.WriteUleb128(0x80ul);
+ ASSERT_EQ(4u, e.size() - off);
+ ASSERT_EQ(wpi::StringRef("\x00\x7f\x80\x01", 4),
+ wpi::StringRef(e.data(), e.size()).substr(off));
+}
+
+TEST_F(WireEncoderTest, WriteType) {
+ size_t off = BUFSIZE - 1;
+ WireEncoder e(0x0300u);
+ for (size_t i = 0; i < off; ++i) e.Write8(0u); // test across Reserve()
+ e.WriteType(NT_BOOLEAN);
+ e.WriteType(NT_DOUBLE);
+ e.WriteType(NT_STRING);
+ e.WriteType(NT_RAW);
+ e.WriteType(NT_BOOLEAN_ARRAY);
+ e.WriteType(NT_DOUBLE_ARRAY);
+ e.WriteType(NT_STRING_ARRAY);
+ e.WriteType(NT_RPC);
+ ASSERT_EQ(nullptr, e.error());
+ ASSERT_EQ(8u, e.size() - off);
+ ASSERT_EQ(wpi::StringRef("\x00\x01\x02\x03\x10\x11\x12\x20", 8),
+ wpi::StringRef(e.data(), e.size()).substr(off));
+}
+
+TEST_F(WireEncoderTest, WriteTypeError) {
+ WireEncoder e(0x0200u);
+ e.WriteType(NT_UNASSIGNED);
+ EXPECT_EQ(0u, e.size());
+ EXPECT_EQ(std::string("unrecognized type"), e.error());
+
+ e.Reset();
+ e.WriteType(NT_RAW);
+ EXPECT_EQ(0u, e.size());
+ EXPECT_EQ(std::string("raw type not supported in protocol < 3.0"), e.error());
+
+ e.Reset();
+ e.WriteType(NT_RPC);
+ EXPECT_EQ(0u, e.size());
+ EXPECT_EQ(std::string("RPC type not supported in protocol < 3.0"), e.error());
+}
+
+TEST_F(WireEncoderTest, Reset) {
+ WireEncoder e(0x0300u);
+ e.WriteType(NT_UNASSIGNED);
+ EXPECT_NE(nullptr, e.error());
+ e.Reset();
+ EXPECT_EQ(nullptr, e.error());
+
+ e.Write8(0u);
+ EXPECT_EQ(1u, e.size());
+ e.Reset();
+ EXPECT_EQ(0u, e.size());
+}
+
+TEST_F(WireEncoderTest, GetValueSize2) {
+ WireEncoder e(0x0200u);
+ EXPECT_EQ(0u, e.GetValueSize(*v_empty)); // empty
+ EXPECT_EQ(1u, e.GetValueSize(*v_boolean));
+ EXPECT_EQ(8u, e.GetValueSize(*v_double));
+ EXPECT_EQ(7u, e.GetValueSize(*v_string));
+ EXPECT_EQ(0u, e.GetValueSize(*v_raw)); // not supported
+
+ EXPECT_EQ(1u + 3u, e.GetValueSize(*v_boolean_array));
+ // truncated
+ EXPECT_EQ(1u + 255u, e.GetValueSize(*v_boolean_array_big));
+
+ EXPECT_EQ(1u + 2u * 8u, e.GetValueSize(*v_double_array));
+ // truncated
+ EXPECT_EQ(1u + 255u * 8u, e.GetValueSize(*v_double_array_big));
+
+ EXPECT_EQ(1u + 7u + 9u, e.GetValueSize(*v_string_array));
+ // truncated
+ EXPECT_EQ(1u + 255u * 3u, e.GetValueSize(*v_string_array_big));
+}
+
+TEST_F(WireEncoderTest, WriteBooleanValue2) {
+ WireEncoder e(0x0200u);
+ e.WriteValue(*v_boolean);
+ auto v_false = Value::MakeBoolean(false);
+ e.WriteValue(*v_false);
+ ASSERT_EQ(nullptr, e.error());
+ ASSERT_EQ(2u, e.size());
+ ASSERT_EQ(wpi::StringRef("\x01\x00", 2), wpi::StringRef(e.data(), e.size()));
+}
+
+TEST_F(WireEncoderTest, WriteDoubleValue2) {
+ WireEncoder e(0x0200u);
+ e.WriteValue(*v_double);
+ ASSERT_EQ(nullptr, e.error());
+ ASSERT_EQ(8u, e.size());
+ ASSERT_EQ(wpi::StringRef("\x3f\xf0\x00\x00\x00\x00\x00\x00", 8),
+ wpi::StringRef(e.data(), e.size()));
+}
+
+TEST_F(WireEncoderTest, WriteStringValue2) {
+ WireEncoder e(0x0200u);
+ e.WriteValue(*v_string);
+ ASSERT_EQ(nullptr, e.error());
+ ASSERT_EQ(7u, e.size());
+ ASSERT_EQ(wpi::StringRef("\x00\x05hello", 7),
+ wpi::StringRef(e.data(), e.size()));
+}
+
+TEST_F(WireEncoderTest, WriteBooleanArrayValue2) {
+ WireEncoder e(0x0200u);
+ e.WriteValue(*v_boolean_array);
+ ASSERT_EQ(nullptr, e.error());
+ ASSERT_EQ(1u + 3u, e.size());
+ ASSERT_EQ(wpi::StringRef("\x03\x00\x01\x00", 4),
+ wpi::StringRef(e.data(), e.size()));
+
+ // truncated
+ e.Reset();
+ e.WriteValue(*v_boolean_array_big);
+ ASSERT_EQ(nullptr, e.error());
+ ASSERT_EQ(1u + 255u, e.size());
+ ASSERT_EQ(wpi::StringRef("\xff\x00", 2), wpi::StringRef(e.data(), 2));
+}
+
+TEST_F(WireEncoderTest, WriteDoubleArrayValue2) {
+ WireEncoder e(0x0200u);
+ e.WriteValue(*v_double_array);
+ ASSERT_EQ(nullptr, e.error());
+ ASSERT_EQ(1u + 2u * 8u, e.size());
+ ASSERT_EQ(wpi::StringRef("\x02\x3f\xe0\x00\x00\x00\x00\x00\x00"
+ "\x3f\xd0\x00\x00\x00\x00\x00\x00",
+ 17),
+ wpi::StringRef(e.data(), e.size()));
+
+ // truncated
+ e.Reset();
+ e.WriteValue(*v_double_array_big);
+ ASSERT_EQ(nullptr, e.error());
+ ASSERT_EQ(1u + 255u * 8u, e.size());
+ ASSERT_EQ(wpi::StringRef("\xff\x00", 2), wpi::StringRef(e.data(), 2));
+}
+
+TEST_F(WireEncoderTest, WriteStringArrayValue2) {
+ WireEncoder e(0x0200u);
+ e.WriteValue(*v_string_array);
+ ASSERT_EQ(nullptr, e.error());
+ ASSERT_EQ(1u + 7u + 9u, e.size());
+ ASSERT_EQ(wpi::StringRef("\x02\x00\x05hello\x00\x07goodbye", 17),
+ wpi::StringRef(e.data(), e.size()));
+
+ // truncated
+ e.Reset();
+ e.WriteValue(*v_string_array_big);
+ ASSERT_EQ(nullptr, e.error());
+ ASSERT_EQ(1u + 255u * 3u, e.size());
+ ASSERT_EQ(wpi::StringRef("\xff\x00\x01", 3), wpi::StringRef(e.data(), 3));
+}
+
+TEST_F(WireEncoderTest, WriteValueError2) {
+ WireEncoder e(0x0200u);
+ e.WriteValue(*v_empty); // empty
+ ASSERT_EQ(0u, e.size());
+ ASSERT_NE(nullptr, e.error());
+
+ e.Reset();
+ e.WriteValue(*v_raw); // not supported
+ ASSERT_EQ(0u, e.size());
+ ASSERT_NE(nullptr, e.error());
+}
+
+TEST_F(WireEncoderTest, GetValueSize3) {
+ WireEncoder e(0x0300u);
+ EXPECT_EQ(0u, e.GetValueSize(*v_empty)); // empty
+ EXPECT_EQ(1u, e.GetValueSize(*v_boolean));
+ EXPECT_EQ(8u, e.GetValueSize(*v_double));
+ EXPECT_EQ(6u, e.GetValueSize(*v_string));
+ EXPECT_EQ(6u, e.GetValueSize(*v_raw));
+
+ EXPECT_EQ(1u + 3u, e.GetValueSize(*v_boolean_array));
+ // truncated
+ EXPECT_EQ(1u + 255u, e.GetValueSize(*v_boolean_array_big));
+
+ EXPECT_EQ(1u + 2u * 8u, e.GetValueSize(*v_double_array));
+ // truncated
+ EXPECT_EQ(1u + 255u * 8u, e.GetValueSize(*v_double_array_big));
+
+ EXPECT_EQ(1u + 6u + 8u, e.GetValueSize(*v_string_array));
+ // truncated
+ EXPECT_EQ(1u + 255u * 2u, e.GetValueSize(*v_string_array_big));
+}
+
+TEST_F(WireEncoderTest, WriteBooleanValue3) {
+ WireEncoder e(0x0300u);
+ e.WriteValue(*v_boolean);
+ auto v_false = Value::MakeBoolean(false);
+ e.WriteValue(*v_false);
+ ASSERT_EQ(nullptr, e.error());
+ ASSERT_EQ(2u, e.size());
+ ASSERT_EQ(wpi::StringRef("\x01\x00", 2), wpi::StringRef(e.data(), e.size()));
+}
+
+TEST_F(WireEncoderTest, WriteDoubleValue3) {
+ WireEncoder e(0x0300u);
+ e.WriteValue(*v_double);
+ ASSERT_EQ(nullptr, e.error());
+ ASSERT_EQ(8u, e.size());
+ ASSERT_EQ(wpi::StringRef("\x3f\xf0\x00\x00\x00\x00\x00\x00", 8),
+ wpi::StringRef(e.data(), e.size()));
+}
+
+TEST_F(WireEncoderTest, WriteStringValue3) {
+ WireEncoder e(0x0300u);
+ e.WriteValue(*v_string);
+ ASSERT_EQ(nullptr, e.error());
+ ASSERT_EQ(6u, e.size());
+ ASSERT_EQ(wpi::StringRef("\x05hello", 6), wpi::StringRef(e.data(), e.size()));
+}
+
+TEST_F(WireEncoderTest, WriteRawValue3) {
+ WireEncoder e(0x0300u);
+ e.WriteValue(*v_raw);
+ ASSERT_EQ(nullptr, e.error());
+ ASSERT_EQ(6u, e.size());
+ ASSERT_EQ(wpi::StringRef("\x05hello", 6), wpi::StringRef(e.data(), e.size()));
+}
+
+TEST_F(WireEncoderTest, WriteBooleanArrayValue3) {
+ WireEncoder e(0x0300u);
+ e.WriteValue(*v_boolean_array);
+ ASSERT_EQ(nullptr, e.error());
+ ASSERT_EQ(1u + 3u, e.size());
+ ASSERT_EQ(wpi::StringRef("\x03\x00\x01\x00", 4),
+ wpi::StringRef(e.data(), e.size()));
+
+ // truncated
+ e.Reset();
+ e.WriteValue(*v_boolean_array_big);
+ ASSERT_EQ(nullptr, e.error());
+ ASSERT_EQ(1u + 255u, e.size());
+ ASSERT_EQ(wpi::StringRef("\xff\x00", 2), wpi::StringRef(e.data(), 2));
+}
+
+TEST_F(WireEncoderTest, WriteDoubleArrayValue3) {
+ WireEncoder e(0x0300u);
+ e.WriteValue(*v_double_array);
+ ASSERT_EQ(nullptr, e.error());
+ ASSERT_EQ(1u + 2u * 8u, e.size());
+ ASSERT_EQ(wpi::StringRef("\x02\x3f\xe0\x00\x00\x00\x00\x00\x00"
+ "\x3f\xd0\x00\x00\x00\x00\x00\x00",
+ 17),
+ wpi::StringRef(e.data(), e.size()));
+
+ // truncated
+ e.Reset();
+ e.WriteValue(*v_double_array_big);
+ ASSERT_EQ(nullptr, e.error());
+ ASSERT_EQ(1u + 255u * 8u, e.size());
+ ASSERT_EQ(wpi::StringRef("\xff\x00", 2), wpi::StringRef(e.data(), 2));
+}
+
+TEST_F(WireEncoderTest, WriteStringArrayValue3) {
+ WireEncoder e(0x0300u);
+ e.WriteValue(*v_string_array);
+ ASSERT_EQ(nullptr, e.error());
+ ASSERT_EQ(1u + 6u + 8u, e.size());
+ ASSERT_EQ(wpi::StringRef("\x02\x05hello\x07goodbye", 15),
+ wpi::StringRef(e.data(), e.size()));
+
+ // truncated
+ e.Reset();
+ e.WriteValue(*v_string_array_big);
+ ASSERT_EQ(nullptr, e.error());
+ ASSERT_EQ(1u + 255u * 2u, e.size());
+ ASSERT_EQ(wpi::StringRef("\xff\x01", 2), wpi::StringRef(e.data(), 2));
+}
+
+TEST_F(WireEncoderTest, WriteValueError3) {
+ WireEncoder e(0x0300u);
+ e.WriteValue(*v_empty); // empty
+ ASSERT_EQ(0u, e.size());
+ ASSERT_NE(nullptr, e.error());
+}
+
+TEST_F(WireEncoderTest, GetStringSize2) {
+ // 2-byte length
+ WireEncoder e(0x0200u);
+ EXPECT_EQ(7u, e.GetStringSize(s_normal));
+ EXPECT_EQ(130u, e.GetStringSize(s_long));
+ // truncated
+ EXPECT_EQ(65537u, e.GetStringSize(s_big));
+}
+
+TEST_F(WireEncoderTest, WriteString2) {
+ WireEncoder e(0x0200u);
+ e.WriteString(s_normal);
+ EXPECT_EQ(nullptr, e.error());
+ EXPECT_EQ(7u, e.size());
+ EXPECT_EQ(wpi::StringRef("\x00\x05hello", 7),
+ wpi::StringRef(e.data(), e.size()));
+
+ e.Reset();
+ e.WriteString(s_long);
+ EXPECT_EQ(nullptr, e.error());
+ ASSERT_EQ(130u, e.size());
+ EXPECT_EQ(wpi::StringRef("\x00\x80**", 4), wpi::StringRef(e.data(), 4));
+ EXPECT_EQ('*', e.data()[128]);
+ EXPECT_EQ('x', e.data()[129]);
+
+ // truncated
+ e.Reset();
+ e.WriteString(s_big);
+ EXPECT_EQ(nullptr, e.error());
+ ASSERT_EQ(65537u, e.size());
+ EXPECT_EQ(wpi::StringRef("\xff\xff**", 4), wpi::StringRef(e.data(), 4));
+ EXPECT_EQ('*', e.data()[65535]);
+ EXPECT_EQ('x', e.data()[65536]);
+}
+
+TEST_F(WireEncoderTest, GetStringSize3) {
+ // leb128-encoded length
+ WireEncoder e(0x0300u);
+ EXPECT_EQ(6u, e.GetStringSize(s_normal));
+ EXPECT_EQ(130u, e.GetStringSize(s_long));
+ EXPECT_EQ(65540u, e.GetStringSize(s_big));
+}
+
+TEST_F(WireEncoderTest, WriteString3) {
+ WireEncoder e(0x0300u);
+ e.WriteString(s_normal);
+ EXPECT_EQ(nullptr, e.error());
+ EXPECT_EQ(6u, e.size());
+ EXPECT_EQ(wpi::StringRef("\x05hello", 6), wpi::StringRef(e.data(), e.size()));
+
+ e.Reset();
+ e.WriteString(s_long);
+ EXPECT_EQ(nullptr, e.error());
+ ASSERT_EQ(130u, e.size());
+ EXPECT_EQ(wpi::StringRef("\x80\x01**", 4), wpi::StringRef(e.data(), 4));
+ EXPECT_EQ('*', e.data()[128]);
+ EXPECT_EQ('x', e.data()[129]);
+
+ // NOT truncated
+ e.Reset();
+ e.WriteString(s_big);
+ EXPECT_EQ(nullptr, e.error());
+ ASSERT_EQ(65540u, e.size());
+ EXPECT_EQ(wpi::StringRef("\x81\x80\x04*", 4), wpi::StringRef(e.data(), 4));
+ EXPECT_EQ('*', e.data()[65536]);
+ EXPECT_EQ('x', e.data()[65537]);
+ EXPECT_EQ('x', e.data()[65538]);
+ EXPECT_EQ('x', e.data()[65539]);
+}
+
+} // namespace nt
diff --git a/ntcore/src/test/native/cpp/main.cpp b/ntcore/src/test/native/cpp/main.cpp
new file mode 100644
index 0000000..d0b0e3c
--- /dev/null
+++ b/ntcore/src/test/native/cpp/main.cpp
@@ -0,0 +1,23 @@
+/*----------------------------------------------------------------------------*/
+/* Copyright (c) 2015-2018 FIRST. All Rights Reserved. */
+/* Open Source Software - may be modified and shared by FRC teams. The code */
+/* must be accompanied by the FIRST BSD license file in the root directory of */
+/* the project. */
+/*----------------------------------------------------------------------------*/
+
+#include <climits>
+
+#include "gmock/gmock.h"
+#include "ntcore.h"
+
+int main(int argc, char** argv) {
+ nt::AddLogger(nt::GetDefaultInstance(),
+ [](const nt::LogMessage& msg) {
+ std::fputs(msg.message.c_str(), stderr);
+ std::fputc('\n', stderr);
+ },
+ 0, UINT_MAX);
+ ::testing::InitGoogleMock(&argc, argv);
+ int ret = RUN_ALL_TESTS();
+ return ret;
+}