Squashed 'third_party/allwpilib/' changes from e4b91005cf..83f1860047

83f1860047 [wpilib] Add/update documentation to PneumaticBase and subclasses (NFC) (#4881)
9872e676d8 [commands] Make Subsystem destructor virtual (#4892)
25db20e49d [hal] Fix segfault in various HAL functions (#4891)
b0c6724eed [glass] Add hamburger menu icon to titlebars (#4874)
f0fa8205ac Add missing compiler flags and fix warnings (#4889)
42fc4cb6bc [wpiutil] SafeThread: Provide start/stop hooks (#4880)
cc166c98d2 [templates] Add Command-based skeleton template (#4861)
3f51f10ad3 [build] Update to 2023v3 image (#4886)
1562eae74a [ntcore] Refactor meta-topic decoding from glass (#4809)
b632b288a3 Fix usages of std::min and std::max to be windows safe (#4887)
c11bd2720f [wpilibc] Add internal function to reset Shuffleboard instance (#4884)
f1151d375f [ntcore] Add method to get server time offset (#4847)
fe1b62647f [hal,wpilib] Update documentation for getComments (NFC) (#4879)
c49a45abbd [build] Fix examples linking in incorrect jni library (#4873)
bc3d01a721 [build] Add platform check to doxygen plugin (#4862)
bc473240ae Add Jetbrains Fleet folder to .gitignore (#4872)
2121bd5fb8 [wpimath] Remove RKF45 (#4870)
835f8470d6 [build] Fix roborio cross-compiler on arm hosts (#4864)
6cfe5de00d [ntcore] Don't deadlock server on early destroy (#4863)
2ac41f3edc [hal, wpilib] Add RobotController.getComments()  (#4463)
26bdbf3d41 Java optimization and formatting fixes (#4857)
92149efa11 Spelling and grammar cleanups (#4849)
176fddeb4c [commands] Add functions to HID classes to allow use of axes as BooleanEvents/Triggers (#4762)
87a34af367 [templates] Add bindings to command-based template (#4838)
4534e75787 [examples] Remove redundant MotorControl example (#4837)
1cbebaa2f7 [commands] Remove final semicolon from test macro definition (#4859)
6efb9ee405 [commands] Add constructor for SwerveControllerCommand that takes a HolonomicDriveController (#4785)
1e7fcd5637 [cscore] Change run loop functions to not be mac specific (#4854)
1f940e2b60 [apriltag] Add C++ wrappers, rewrite Java/JNI to match (#4842)
a6d127aedf [build] Add missing task dependency in wpilibjExamples (#4852)
b893b3d6d3 [cscore] Add support for USB cameras on macOS (#4846)
1696a490fa [glass] Add support for alternate NT ports (#4848)
40a22d69bc [glass] Add support for alternate NT ports (#4848)
e84dbfede0 [wpilib] GenericHID: Add rumble both option (#4843)
8aa9dbfa90 [examples] Link apriltag package in examples build.gradle (#4845)
eda2fa8a17 [build] Update Spotless (#4840)
d20594db0d Fix typos (#4839)
dd8ecfdd54 [commands] Fix typo in waitUntil docs (NFC) (#4841)
17ceebfff4 [apriltag] Clean up apriltag JNI (#4823)
8b74ab389d [examples] RapidReactCommandBot: Fix array indices (#4833)
1aad3489c2 [sim] Implement PD total current and power (#4830)
2744991771 [wpimath] Fix docs in SwerveModulePosition (#4825)
ffbf6a1fa2 [commands] Disable regularly failing unit test (#4824)
fbabd0ef15 [commands] Enhance Command Sendable implementations (#4822)
7713f68772 [hal] Use atomic rather then mutex for DS Data updates (#4787)
701995d6cc [examples] Update Command-based starter project (#4778)
bf7068ac27 [wpilibc] Add missing PPS implementation for C++ (#4821)
aae0f52ca6 [ntcore] NetworkTable: fix visibility of get/set value (#4820)
ee02fb7ba7 [hal] Add support for Pulse-Per-Second signal (#4819)
518916ba02 [wpilib] Fix DS mode thread event being manual reset accidentally (#4818)
3997c6635b [hal] Update to new image, use new TCP notify callback and new duty cycle API (#4774)
cc8675a4e5 [examples] Add comment on how to view elevator sim (NFC) (#4482)
fb2c170b6e [ntcore] Simplify local startup (#4803)
7ba8a9ee1f [wpimath] ProfiledPIDController: Add to SendableRegistry (#4656)
c569d8e523 [wpilib] Joystick.getMagnitude(): use hypot() function (#4816)
2a5e89fa97 [apriltag] Improve description of pose coordinates (NFC) (#4810)
cc003c6c38 [apriltag] Fix AprilTagFieldLayout JSON name (#4814)
5522916123 [commands] CommandXBoxController bumper documentation fix (NFC) (#4815)
967b30de3a [glass] Fix NT view UpdateClients() bug (#4808)
3270d4fc86 [wpimath] Rewrite pose estimator docs (#4807)
be39678447 [apriltag] Add test to ensure apriltagjni loads (#4805)
61c75deb2a [commands] Test no-op behavior of scheduling a scheduled command (#4806)
a865f48e96 [ntcore] Pass pub/sub options as a unified PubSubOptions struct (#4794)
f66a667321 [commands] Fix incorrect Trigger docs (NFC) (#4792)
f8d4e9866e [ntcore] Clean up ntcore_test.h (#4804)
7e84ea891f [wpimath] Fix ComputerVisionUtil transform example in parameter docs (NFC) (#4800)
da3ec1be10 [wpimath] Change terminology for ArmFeedforward gravity gain (NFC) (#4791)
944dd7265d [wpilibc] Add C++ Notifier error handling, update java notifier error message (#4795)
6948cea67a [wpiutil] Fix MemoryBuffer initialization (#4797)
a31459bce6 [wpiutil] Fix UnescapeCString overflow when inputSize < 2 (#4796)
4a0ad6b48c [wpimath] Rotation2d: Add reference to angleModulus in docs (NFC) (#4786)
e6552d272e [ntcore] Remove table multi-subscriber (#4789)
bde383f763 [hal] Replace const char* with std::string_view in Driver Station sim functions (#4532)
5a52b51443 [hal] Add RobotController.getSerialNumber() (#4783)
69a66ec5ec [wpilib] Fix multiple motor safety issues (#4784)
989c9fb29a [wpimath] Revert Rotation2D change that limits angles (#4781)
0f5b08ec69 [wpigui] Update imgui to 1.89.1+ (#4780)
fba191099c [examples] AddressableLED: Add unit test (#4779)
b390cad095 [wpilibj] Consistently use ErrorMessages.requireNonNullParam (#4776)
b9772214d9 [wpilib] Sendable: Don't call setter for getter changes
342c375a71 [ntcore] Add subscriber option to exclude single publisher
b0e4053087 [ntcore] Use int for options instead of double
f3e666b7bb [cscore] Convert YUYV and UYVY directly to grayscale (#4777)
b300518bd1 [hal] Add CAN Stream API to Java through JNI bindings (#4193)
be27171236 [wpilibj] Shuffleboard: Check for null sendable (#4772)
4bbdbdfb48 [commands] Move GroupedCommands to CommandScheduler (#4728)
f18fd41ac3 [wpimath] Remove broken and obsoleted ComputerVisionUtil functions (#4775)
2d0faecf4f [glass] DataSource: Add spinlock to protect value (#4771)
348bd107fc [hal] Add CANManufacturer for The Thrifty Bot (#4773)
3149dc64b8 [examples] HatchbotInlined: Use Subsystem factories (#4765)
8618dd4160 [glass, wpilib] Replace remaining references to Speed Controller with Motor Controller (#4769)
72e21a1ed1 [apriltag] Use wpilibsuite fork of apriltag (#4764)
eab0d929e6 [commands] CommandGenericHID POV methods: Fix docs (NFC) (#4760)
6789869663 [wpilib] Call set(0) rather than disable for stopMotor (#4763)
c9dea2968d [cscore] Emit warning that USB Camera isn't supported on OSX (#4766)
8f402645f5 [commands] Fix PIDSubsystem setSetpoint behavior (#4759)
f24ad1d715 [build] Upgrade to googletest 1.12.1 (#4752)
ff88756864 [wpimath] Add new DCMotor functions for alternative calculations and reduction calculation (#4749)
f58873db8e [wpimath] Remove extra terms in matrix for pose estimator docs (#4756)
37e969b41a [wpimath] Add constructors to pose estimators with default standard deviations (#4754)
13cdc29382 [ci] Rename comment command from "/wpiformat" to "/format" (#4755)
6e23985ae6 [examples] Add main include directory to test builds (#4751)
66bb0ffb2c [examples] Add unit testing infrastructure (#4646)
74cc86c4c5 [wpimath] Make transform tests use pose/transform equality operators (#4675)
e22d8cc343 [wpimath] Use Odometry for internal state in Pose Estimation (#4668)
68dba92630 [ci] Update mac and windows builds to Java 17 (#4750)
23bfc2d9ab [sim] Remove unmaintained Gazebo support (#4736)
1f1461e254 [wpilib] Add method to enable/disable LiveWindow in test mode (#4678)
eae68fc165 [wpimath] Add tolerance for Rotation3d rotation matrix special orthogonality (#4744)
4c4545fb4b [apriltag] Suppress warning (#4743)
16ffaa754d [docs] Generate docs for apriltag subproject (#4745)
5e74ff26d8 [apriltag, build] Update native utils, add apriltag impl and JNI (#4733)
53875419a1 [hal] Allow overriding stderr printing by HAL_SendError (#4742)
aa6499e920 [ntcore] Fix special topic multi-subscriber handling (#4740)
df70351107 [build] Fix cmake install of thirdparty includes (#4741)
e9bd50ff9b [glass] NT view: clear meta-topic info on disconnect (#4732)
9b319fd56b [ntcore] Add sub option for local vs remote changes (#4731)
18d28ec5e3 [ntcore] Remove duplicate value checking from ClientImpl
bdfb625211 [ntcore] Send duplicate values to network if necessary
21003e34eb [commands] Update Subsystem factories and example to return CommandBase (#4729)
70080457d5 [commands] Refactor ProxyScheduleCommand, SelectCommand into ProxyCommand (#4534)
e82cd5147b [wpilib] Tweak Color HSV formula and use in AddressableLED (#4724)
ec124bb662 [commands] Allow unsetting a subsystem's default command (#4621)
2b2aa8eef7 [examples] Update all examples to use NWU coordinate conventions (#4725)
cb38bacfe8 [commands] Revert to original Trigger implementation (#4673)
15561338d5 [commands] Remove one more default command isFinished check (#4727)
ca35a2e097 Add simgui files to .gitignore (#4726)
20dbae0cee [examples] Renovate command-based examples (#4409)
1a59737f40 [commands] Add convenience factories (#4460)
42b6d4e3f7 Use defaulted comparison operators in C++ (#4723)
135c13958f [wpigui] Add FontAwesome (#4713)
ffbfc61532 [ntcore] Add NetworkTable table-specific listeners (#4640)
8958b2a4da [commands] Add property tests for command compositions (#4715)
e4ac09077c [wpilib] Add link to MotorSafety article (#4720)
f40de0c120 [commands] Add C++ factory templates (#4686)
51fa3e851f [build] cmake: Use FetchContent instead of ExternalProject (#4714)
1da84b2255 [wpigui] Reload fonts to scale rather than preloading (#4712)
e43e2fbc84 [wpiutil] StringExtras: Add UnescapeCString (#4707)
5804d8fa84 [ntcore] Server: Properly handle multiple subscribers (#4717)
169ef5fabf [glass] Update NT view for topicsOnly and sendAll changes (#4718)
148759ef54 [examples] CANPDP: Expand properties shown (#4687)
58ed112b51 [commands] RepeatCommand: restart on following iteration (#4706)
dd1da77d20 [readme] Fix broken CI badge (#4710)
7cda85df20 [build] Check Gradle plugin repo last to fix CI (#4711)
7ed9b13277 [build] Bump version plugin to fix null tag (#4705)
6b4f26225d [apriltag] Fix pluralization of apriltag artifacts (#4671)
b2d2924b72 [cscore] Add Y16 image support (#4702)
34ec89c041 [wpilibc] Shuffleboard SimpleWidget: Return pointer instead of reference (#4703)
e15200068d [ci] Disable HW testbench runs (#4704)
d5200db6cd [wpimath] Rename HolonomicDriveController.calculate params (#4683)
2ee3d86de4 [wpimath] Clarify Rotation3d roll-pitch-yaw direction (#4699)
9f0a8b930f [cscore] Use MFVideoFormat_L8 for Gray on Windows (#4701)
2bca43779e [cscore] Add UYVY image support (#4700)
4307d0ee8b [glass] Plot: allow for more than 11 plots (#4685)
3fe8d355a1 [examples] StateSpaceDifferentialDriveSimulation: Use encoder reversed constants (#4682)
b44034dadc [ntcore] Allow duplicate client IDs on server (#4676)
52d2c53888 [commands] Rename Java factory wait() to waitSeconds() (#4684)
76e918f71e [build] Fix JNI artifacts linking to incorrect libraries (#4680)
0bee875aff [commands] Change C++ CommandPtr to use CommandBase (#4677)
98e922313b [glass] Don't check IsConnected for NT widgets (#4674)
9a36373b8f [apriltag] Switch 2022 apriltag layout length and width values (#4670)
cf8faa9e67 [wpilib] Update values on controllable sendables (#4667)
5ec067c1f8 [ntcore] Implement keep duplicates pub/sub flag (#4666)
e962fd2916 [ntcore] Allow numeric-compatible value sets (#4620)
88bd67e7de [ci] Update clang repositories to jammy (#4665)
902e8686d3 [wpimath] Rework odometry APIs to improve feature parity (#4645)
e2d49181da Update to native utils 2023.8.0 (#4664)
149bac55b1 [cscore] Add Arducam OV9281 exposure quirk (#4663)
88f7a3ccb9 [wpimath] Fix Pose relativeTo documentation (#4661)
8acce443f0 [examples] Fix swerve examples to use getDistance for turning encoder (#4652)
295a1f8f3b [ntcore] Fix WaitForListenerQueue (#4662)
388e7a4265 [ntcore] Provide mechanism to reset internals of NT instance (#4653)
13aceea8dc [apriltag] Fix FieldDimensions argument order (#4659)
c203f3f0a9 [apriltag] Fix documentation for AprilTagFieldLayout (#4657)
f54d495c90 Fix non initialized hal functionality during motor safety init (#4658)
e6392a1570 [cmd] Change factories return type to CommandBase (#4655)
53904e7cf4 [apriltag] Split AprilTag functionality to a separate library (#4578)
2e88a496c2 [wpimath] Add support for swerve joystick normalization (#4516)
ce4c45df13 [wpimath] Rework function signatures for Pose Estimation / Odometry (#4642)
0401597d3b [readme] Add wpinet to MavenArtifacts.md (#4651)
2e5f9e45bb [wpimath] Remove encoder reset comments on Swerve, Mecanum Odometry and Pose Estimation (#4643)
e4b5795fc7 [docs] Disable Doxygen for memory to fix search (#4636)
03d0ea188c [build] cmake: Add missing wpinet to installed config file (#4637)
3082bd236b [build] Move version file to its own source set (#4638)
b7ca860417 [build] Use build cache for sign step (#4635)
64838e6367 [commands] Remove unsafe default command isFinished check (#4411)
1269d2b901 [myRobot] Disable spotbugs (#4565)
14d8506b72 [wpimath] Fix units docs for LinearSystemId::IdentifyDrivetrainSystem() (#4600)
d1d458db2b [wpimath] Constrain Rotation2d range to -pi to pi (#4611)
f656e99245 [readme] Add links to development build documentation (#4481)
6dd937cef7 [commands] Fix Trigger API docs (NFC) (#4599)
49047c85b9 [commands] Report error on C++ CommandPtr use-after-move (#4575)
d07267fed1 [ci] Upgrade containers to Ubuntu 22.04 and remove libclang installation (#4633)
b53ce1d3f0 [build, wpiutil] Switch macos to universal binaries (#4628)
5a320c326b [upstream_util, wpiutil] Refactor python scripts (#4614)
c4e526d315 [glass] Fix NT Mechanism2D (#4626)
d122e4254f [ci] Run spotlessApply after wpiformat in comment command (#4623)
5a1e7ea036 [wpilibj] FieldObject2d: Add null check to close() (#4619)
179f569113 [ntcore] Notify locally on SetDefault (#4617)
b0f6dc199d [wpilibc] ShuffleboardComponent.WithProperties: Update type (#4615)
7836f661cd [wpimath] Add missing open curly brace to units/base.h (#4613)
dbcc1de37f [wpimath] Add DifferentialDriveFeedforward classes which wrap LinearPlantInversionFeedforward (#4598)
93890c528b [wpimath] Add additional angular acceleration units (#4610)
3d8d5936f9 [wpimath] Add macro for disabling units fmt support (#4609)
2b04159dec [wpimath] Update units/base.h license header (#4608)
2764004fad [wpinet] Fix incorrect jni definitions (#4605)
85f1bb8f2b [wpiutil] Reenable jni check task (#4606)
231ae2c353 [glass] Plot: Fix Y-axis not being saved (#4594)
e92b6dd5f9 [wpilib] Fix AprilTagFieldLayout JSON property name typos (#4597)
2a8e0e1cc8 Update all dependencies that use grgit (#4596)
7d06e517e9 [commands] Move SelectCommand factory impl to header (#4581)
323524fed6 [wpimath] Remove deprecated units/units.h header (#4572)
d426873ed1 [commands] Add missing PS4 triangle methods (#4576)
5be5869b2f [apriltags] Use map as internal data model (#4577)
b1b4c1e9e7 [wpimath] Fix Pose3d transformBy rotation type (#4545)
a4054d702f [commands] Allow composing two triggers directly (#4580)
0190301e09 [wpilibc] Explicitly mark EventLoop as non-copyable/non-movable (#4579)
9d1ce6a6d9 [ntcore] Catch file open error when saving preferences (#4571)
5005e2ca04 [ntcore] Change Java event mask to EnumSet (#4564)
fa44a07938 [upstream-utils][mpack] Add upstream util for mpack (#4500)
4ba16db645 [ntcore] Various fixes and cleanups (#4544)
837415abfd [hal] Fix joysticks either crashing or returning 0 (#4570)
2c20fd0d09 [wpilib] SingleJointedArmSim: Check angle equals limit on wouldHit (#4567)
64a7136e08 [wpimath] SwerveDrivePoseEstimator: Restore comment about encoder reset (#4569)
b2b473b24a [wpilib] Add AprilTag and AprilTagFieldLayout (#4421)
7aab8fa93a [build] Update to Native Utils 2023.6.0 (#4563)
12c2851856 [commands] WrapperCommand: inherit from CommandBase (#4561)
0da169dd84 [wpimath] Remove template argument from ElevatorFeedforward (#4554)
2416827c25 [wpimath] Fix docs for pose estimator local measurement models (#4558)
1177a3522e [wpilib] Fix Xbox/PS4 POV sim for port number constructors (#4548)
102344e27a [commands] HID classes: Add missing methods, tweak return types (#4557)
1831ef3e19 [wpilib] Fix Shuffleboard SuppliedValueWidget (#4559)
a9606ce870 [wpilib] Fix Xbox/PS4 POV sim (#4546)
6c80d5eab3 [wpimath] Remove unused SymbolExports.h include from units/base.h (#4541)
b114006543 [ntcore] Unify listeners (#4536)
32fbfb7da6 [build] cmake: Install ntcore generated include files (#4540)
02465920fb [build] Update native utils to 2023.4.0 (#4539)
3a5a376465 [wpimath] Increase constexpr support in geometry data types (#4231)
1c3c86e9f1 [ntcore] Cache GetEntry(name) values (#4531)
dcda09f90a [command] Rename trigger methods (#4210)
66157397c1 [wpilib] Make drive classes follow NWU axes convention (#4079)
9e22ffbebf [ntcore] Fix null deref in NT3 client (#4530)
648ab6115c [wpigui,dlt,glass,ov] Support arm in GUI tools (#4527)
8bc3b04f5b [wpimath] Make ComputerVisionUtil use 3D geometry classes (#4528)
cfb84a6083 [wpilibc] Don't hang waiting for NT server to start (#4524)
02c47726e1 [wpimath] Remove unused odometry instance from DifferentialDrivePoseEstimator test (#4522)
b2a0093294 [ci] Revert upgrade of github-pages-deploy-action (#4521)
2a98d6b5d7 [wpimath] PIDController: Add getters for position & velocity tolerances (#4458)
9f36301dc8 [ci] Write wpiformat patch to job summary (#4519)
901fc555f4 [wpimath] Position Delta Odometry for Mecanum (#4514)
4170ec6107 [wpimath] Position Delta Odometry for Swerve (#4493)
fe400f68c5 [docs] Add wpinet to docs build (#4517)
794669b346 [ntcore] Revamp listeners (#4511)
dcfa85a5d5 [ci] Build sanitizers with clang-14 (#4518)
15ad855f1d [ntcore] Add UnitTopic<T> (C++ only) (#4497)
11244a49d9 [wpilib] Add IsConnected function to all gyros (#4465)
1d2e8eb153 [build] Update myRobot deployment (#4515)
ad53fb19b4 [hal] Use new HMB api for addressable LED (#4479)
ba850bac3b [hal] Add more shutdown checks and motor safety shutdown (#4510)
023a5989f8 [ntcore] Fix typo in NetworkServer client connect message (#4512)
c970011ccc [docs] Add Doxygen aliases used by Foonathan memory (#4509)
07a43c3d9a [readme] Document clang-format version and /wpiformat (#4503)
a05b212b04 [ci] Revert changes to wpiformat task from #4501 (#4508)
09faf31b67 [commands] Replace Command HID inheritance with delegation (#4470)
9e1f9c1133 [commands] Add command factories (#4476)
f19d2b9b84 [ci] Add NUMBER environment variable to comment command commit script (#4507)
a28f93863c [ci] Push comment command commit directly to PR (#4506)
c9f61669b8 [ci] Fix comment command commit push (#4505)
dcce5ad3b3 [ci] Update github-script API usage (#4504)
6836e5923d [wpilibc] Restore get duty cycle scale factor (#4502)
335188c652 [dlt] Add deselect/select all buttons to download view (#4499)
60a29dcb99 [glass] Field2D: Add "hidden" option for objects (#4498)
b55d5b3034 [ci] Update deprecated github actions (#4501)
10ed4b3969 [ntcore] Various NT4 fixes (#4474)
4a401b89d7 [hal, wpilib] New DS thread model and implementation (#3787)
c195b4fc46 [wpimath] Clean up PoseEstimator nominal dt docs (#4496)
8f2e34c6a3 [build] Remove wpilib prefix from CMake flat install (#4492)
150d692df7 [wpimath] Remove unused private PoseEstimator function (#4495)
3e5bfff1b5 [wpimath] FromFieldRelativeSpeeds: Add ChassisSpeeds overload (#4494)
9c7e66a27d [commands] C++: Add CommandPtr overload for SetDefaultCommand (#4488)
0ca274866b [build] Fix CMake system library opt-ins (#4487)
dc037f8d41 [commands] Remove EndlessCommand (#4483)
16cdc741cf [wpimath] Add Pose3d(Pose2d) constructor (#4485)
9d5055176d [build] cmake: Allow disabling ntcore build (#4486)
d1e66e1296 [build] Compile all java code with inline string concatenation (#4490)
1fc098e696 Enable log macros to work with no args (#4475)
878cc8defb [wpilib] LiveWindow: Add enableAllTelemetry() (#4480)
8153911160 [build] Fix MSVC runtime archiver to grab default runtime (#4478)
fbdc810887 Upgrade to C++20 (#4239)
396143004c [ntcore] Add ntcoreffi binary (#4471)
1f45732700 [build] Update to 2023.2.4 native-utils and new dependencies (#4473)
574cb41c18 [ntcore] Various fixes (#4469)
d9d6c425e7 [build] Force Java 11 source compatibility (#4472)
58b6484dbe Switch away from NI interrupt manager to custom implementation (#3705)
ca43fe2798 [wpimath] Use Units conversions in ComputerVisionUtil docs (NFC) (#4464)
87a64ccedc [hal] Convert DutyCycle Raw output to be a high time measurement (#4466)
89a3d00297 [commands] Add FinallyDo and HandleInterrupt decorators (#4412)
1497665f96 [commands] Add C++ versions of Java-only decorators (#4457)
27b173374e [wpimath] Add minLinearAccel parameter to DifferentialDriveAccelerationLimiter (#4422)
2a13dba8ac [wpilib] TrajectoryUtil: Fix ambiguous documentation (NFC) (#4461)
77301b126c [ntcore] NetworkTables 4 (#3217)
90cfa00115 [build] cmake: Fix libssh include directory order (#4459)
5cf961edb9 [commands] Refactor lambda-based commands to inherit FunctionalCommand (#4451)
b2276e47de [wpimath] Enable continuous angle input for HolonomicDriveController (#4453)
893b46139a [fieldImages] Add utilities to simplify loading of fields (#4456)
60e29627c0 [commands] C++ unique_ptr migration (#4319)
3b81cf6c35 [wpilib] Improve Color.toString (#4450)
5c067d30a0 [wpinet] WebSocket: Add SendFrames() (#4445)
ceaf493811 [wpiutil] MakeJByteArray: Use span<uint8> instead of string_view (#4446)
10e04e2b13 [examples] FrisbeeBot: Fix reference capture (#4449)
726f67c64b [build] Add exeSplitSetup (#4444)
c7b7624c1c [wpiutil] Add MessagePack utility functions (#4448)
d600529ec0 [wpinet] uv::Async: Add UnsafeSend() (#4447)
b53b3526a2 [wpimath] Add CoordinateSystem conversion for Transform3d (#4443)
38bb23eb18 [wpimath] Add scalar multiply and divide operators to all geometry classes (#4438)
3937ff8221 [wpilib] Remove deprecated Controller class (#4440)
abbfe244b5 [wpilib] Improve Color FromHSV (#4439)
4ddb8aa0dd [sim] Provide function that resets all simulation data (#4016)
a791470de7 Clean up Java warning suppressions (#4433)
17f504f548 [hal,wpilib] Fix SPI Mode Setting (#4434)
773198537c [wpiutil] Add wpi::scope_exit (#4432)
5ac658c8f0 [wpiutil] Logger: Conditionalize around WPI_LOG (#4431)
8767e4a941 [wpiutil] DataLog: Fix SetMetadata output (#4430)
8c4af073f4 [wpiutil] Synchronization: shutdown race protection (#4429)
c79f38584a [build] Fix Java integration tests (#4428)
36c08dd97c [build] Fix cmake install of fmtlib (#4426)
69b7b3dd7d [ci] Remove the Windows cmake job (#4425)
738c75fed8 [readme] Fix formatting/linting link (#4423)
4eb1d03fb3 [wpimath] Document C++ LinearFilter exception (#4417)
ba4ec6c967 [build] Fix clang-tidy false positive on Linux (#4406)
97836f0e55 [commands] Fix ProfiledPIDSubsystem setGoal behavior (#4414)
fdfb85f695 [wpimath] Remove Java LQR constructor that takes a controller gain matrix (#4419)
ab1baf4832 [wpimath] Add rotation matrix constructor to Rotation3d (#4413)
9730032866 [wpimath] Document LQR and KalmanFilter exceptions (#4418)
5b656eecf6 [wpimath] Fix HTML5 entity (#4420)
9ae38eaa7c [commands] Add owning overload to ProxyScheduleCommand (#4405)
cb33bd71df [commands] deprecate withInterrupt decorator (#4407)
d9b4e7b8bf [commands] Revert "Change grouping decorator impl to flatten nested group structures (#3335)" (#4402)
0389bf5214 [hal] REVPH: Improve handling of disconnected CAN Bus (#4169)
4267fa08d1 [wpilibc] ADIS IMUs: Fix memory leak (#4170)
65c8fbd452 [wpilib] MotorControllerGroup: Override setVoltage (#4403)
f36162fddc [wpimath] Improve Discretization internal docs (#4400)
5149f7d894 [wpimath] Add two-vector Rotation3d constructor (#4398)
20b5bed1cb [wpimath] Clean up Java Quaternion class (#4399)
f18dd1905d [build] Include all thirdparty sources in distribution (#4397)
aa9d7f1cdc [wpiutil] Import foonathan memory (#4306)
2742662254 [ci] Remove a couple of obsolete clang-tidy checks (#4396)
a5df391166 [hal, wpilib] Fix up DIO pulse API (#4387)
59e6706b75 [glass] Turn on docking by default
8461bb1e03 [glass] Add support for saving docking info
b873e208b4 [wpigui] Add support for imgui config flags
873e72df8c [build] Update imgui to 1.88 docking branch
c8bd6fc5b4 [ci] Fix comment-command (take 2) (#4395)
fed68b83b4 [ci] Fix comment-command action not running runners (#4393)
0ef8a4e1df [wpimath] Support formatting more Eigen types (#4391)
c393b3b367 [build] Update to native utils 2023.1.0 and Gradle 7.5.1 (#4392)
b5a17f762c [wpimath] Add direction to slew rate limiter (#4377)
fafc81ed1a [wpiutil] Upgrade to fmt 9.1.0 (#4389)
cc56bdc787 [wpiutil] SafeThread: Add Synchronization object variant (#4382)
4254438d8d [commands] Mark command group lifecycle methods as final (#4385)
97c15af238 [wpimath] LinearSystemId: Fix docs, move C++ impls out of header (#4388)
d22ff8a158 [wpiutil] Add JNI access to C++ stderr (#4381)
fdb5a2791f [wpiutil] jni_util: Add Mac-friendly MakeJLongArray/JArrayRef (#4383)
c3a93fb995 [commands] Revamp Interruptible (#4192)
f2a8d38d2a [commands] Rename Command.repeat to repeatedly (#4379)
9e24c6eac0 [wpiutil] Logger: paren-protect instance usage in macro (#4384)
fe4d12ce22 [wpimath] Add LTV controller derivations and make enums private (#4380)
eb08486039 [build] Fix MacOS binary rpath generation (#4376)
ccf83c634a [build] Use native-utils platform names instead of raw strings (#4375)
3fd69749e7 [docs] Upgrade to doxygen 1.9.4 (#4370)
594df5fc08 [wpinet] uv/util.h: Pull in ws2_32.lib on Windows for ntohs (#4371)
539070820d [ci] Enable asan for wpinet and wpiutil (#4369)
564a56d99b [wpinet] Fix memory leak in WorkerThreadTest (#4368)
5adf50d93c [upstream_utils] Refactor upstream_utils scripts (#4367)
d80e8039d7 [wpiutil] Suppress fmtlib clang-tidy warning in C++20 consteval contexts (#4364)
0e6d67b23b [upstream_utils] Remove yapf format disable comment (#4366)
be5270697a [build] Suppress enum-enum deprecation warning in OpenCV (#4365)
8d28851263 Add Rosetta install command to build requirements (#4363)
3d2115c93e [wpinet] include-what-you-use in MulticastTest (#4360)
91002ae3cc [wpimath] Upgrade to Drake 1.6.0 (#4361)
148c18e658 [wpinet] Upgrade to libuv 1.44.2 (#4362)
a2a5c926b6 Fix clang-tidy warnings (#4359)
ea6b1d8449 [wpiutil] Remove unused ManagedStatic class (#4358)
ac9be78e27 Use stricter C++ type conversions (#4357)
151dabb2af [wpiutil] Upgrade to fmt 9.0.0 (#4337)
340465c929 [ci] Upgrade to clang-format and clang-tidy 14 (NFC) (#4347)
d45bcddd15 [examples] Add comments to StateSpaceDifferentialDrive (#4341)
0e0786331a Update LLVM libraries to 14.0.6 (#4350)
c5db23f296 [wpimath] Add Eigen sparse matrix and iterative solver support (#4349)
44abc8dfa6 [upstream_utils] Remove git version from upstream patches (#4351)
3fdb2f767d [wpimath] Add comments with Ramsete equations (#4348)
0485f05da9 [wpilibjExamples] Upgrade jacoco to match allwpilib (#4346)
0a5eb65231 [wpinet] Handle empty txt block for mdns announcer (#4072)
19ffebaf3e [wpilib] Add reference to I2C Lockup to API Docs (NFC) (#4340)
ce1a90d639 [hal] Replace SerialHelper "goto done" with continue (#4342)
d25af48797 [ci] Make upstream_utils CI fail on untracked files (#4339)
ebb836dacb [examples] Fix negations in event loop examples (#4334)
d83e202f00 [upstream_utils] Update paths in update_fmt.py (#4338)
3ccf806064 [wpimath] Remove redundant LinearFilter.finiteDifference() argument (#4335)
6f1e01f8bd [wpimath] Document example of online filtering for LinearFilter.finiteDifference() (#4336)
1023c34b1c [readme] Update location of ni-libraries (#4333)
faa29d596c [wpilib] Improve Notifier docs (NFC) (#4326)
add00a96ed [wpimath] Improve DifferentialDriveAccelerationLimiter docs (NFC) (#4323)
82fac41244 [wpimath] Better document trackwidth parameters (NFC) (#4324)
5eb44e22a9 Format Python scripts with black (NFC) (#4325)
2e09fa7325 [build] Fix mpack cmake (#4322)
fe3c24b1ee [command] Add ignoringDisable decorator (#4305)
aa221597bc [build] Add M1 builds, change arm name, update to 2023 deps (#4315)
579a8ee229 [ci] Use one worker for Windows release Gradle build (#4318)
5105c5eab6 [wpilibj] Change "final" to "exit" in the IterativeRobotBase JavaDoc (NFC) (#4317)
787fe6e7a5 [wpiutil] Separate third party libraries (#4190)
6671f8d099 [wpigui] Update portable file dialogs (#4316)
9ac9b69aa2 [command] Reorder Scheduler operations (#4261)
e61028cb18 [build] halsim_gui: Add wpinet dependency (#4313)
661d23eaf5 [glass] Add precision setting for NetworkTable view (#4311)
666040e3e5 [hal] Throw exceptions for invalid sizes in I2C and SPI JNI (#4312)
aebc272449 [build] Upgrade to spotbugs Gradle plugin 5.0.8 (#4310)
fd884581e4 [wpilib] Add BooleanEvent/Trigger factories on HID classes (#4247)
9b1bf5c7f1 [wpimath] Move Drake and Eigen to thirdparty folders (#4307)
c9e620a920 [wpilibc] Change EventLoop data structure to vector (#4304)
41d40dd62f [wpinet] Fix libuv unused variable warning on Mac (#4299)
30f5b68264 [wpinet] Fix JNI loading error (#4295)
f7b3f4b90e [examples] Getting Started: Change Joystick to XboxController (#4194)
a99c11c14c [wpimath] Replace UKF implementation with square root form (#4168)
45b7fc445b [wpilib] Add EventLoop (#4104)
16a4888c52 [wpilib] Default off LiveWindow telemetry (#4301)
17752f1337 [ci] Split debug and release Windows builds (#4277)
abb45a68db [commands] Remove custom test wrappers (#4296)
1280a54ef3 [upstream_utils]: Make work with Python 3.8 (#4298)
f2d243fa68 [build] Change defaults for Java lints (#4300)
a4787130f4 Update using development build to work with 2023 gradlerio (#4294)
af7985e46c [wpiutil] Use invoke_result_t instead of result_of in future.h (#4293)
e9d1b5c2d0 [hal] Remove deprecated SimDevice functions (#4209)
45b598d236 [wpilibj] Add toString() methods to Color and Color8Bit (#4286)
fc37265da5 [wpimath] Add angle measurement convention to ArmFeedforward docs (NFC) (#4285)
a4ec13eb0e [wpilibjexamples] Remove unnecessary voltage desaturation
2fa52007af [wpilibc] Use GetBatteryVoltage() in MotorController::SetVoltage
d9f9cd1140 [wpimath] Reset prev_time on pose estimator reset (#4283)
8b6df88783 [wpilibj] Tachometer.getFrequency(): Fix bug (#4281)
345cff08c0 [wpiutil] Make wpi::array constexpr (#4278)
57428112ac [wpimath] Upgrade to Drake v1.3.0 (#4279)
a18d4ff154 [build] Fix tools not being copied when built with -Ponly* (#4276)
d1cd07b9f3 [wpigui] Add OpenURL (#4273)
e67f8e917a [glass] Use glfwSetKeyCallback for Enter key remap (#4275)
be2fedfe50 [wpimath] Add stdexcept include for std::invalid_argument (IWYU) (#4274)
7ad2be172e [build] Update native-utils to 2023.0.1 (#4272)
abc605c9c9 [ci] Update workflows to 20.04 base image (#4271)
3e94805220 [wpiutil] Reduce llvm collections patches (#4268)
db2e1d170e [upstream_utils] Document how to update thirdparty libraries (#4253)
96ebdcaf16 [wpimath] Remove unused Eigen AutoDiff module (#4267)
553b2a3b12 [upstream_utils] Fix stackwalker (#4265)
3e13ef42eb [wpilibc] Add missing std::array #include (include-what-you-use) (#4266)
d651a1fcec Fix internal deprecation warnings (#4257)
b193b318c1 [commands] Add unless() decorator (#4244)
ef3714223b [commands] Remove docs reference to obsolete interrupted() method (NFC) (#4262)
3d8dbbbac3 [readme] Add quickstart (#4225)
013efdde25 [wpinet] Wrap a number of newer libuv features (#4260)
816aa4e465 [wpilib] Add Pneumatics sim classes (#4033)
046c2c8972 [wpilibc] Rename SpeedControllerGroupTest.cpp (#4258)
d80e9cdf64 [upstream_utils] Use shallow clones for thirdparty repos (#4255)
7576136b4a [upstream_utils] Make update_llvm.py executable (#4254)
c3b223ce60 [wpiutil] Vendor llvm and update to 13.0.0 (#4224)
5aa67f56e6 [wpimath] Clean up math comments (#4252)
fff4d1f44e [wpimath] Extend Eigen warning suppression to GCC 12 (#4251)
0d9956273c [wpimath] Add CoordinateSystem.convert() translation and rotation overloads (#4227)
3fada4e0b4 [wpinet] Update to libuv 1.44.1 (#4232)
65b23ac45e [wpilibc] Fix return value of DriverStation::GetJoystickAxisType() (#4230)
4ac34c0141 [upstream_utils] Cleanup update_libuv.py (#4249)
8bd614bb1e [upstream_utils] Use "git am" instead of "git apply" for patches (#4248)
4253d6d5f0 [upstream_utils] Apply "git am" patches individually (#4250)
6a4752dcdc Fix GCC 12.1 warning false positives (#4246)
5876b40f08 [wpimath] Memoize CoordinateSystem and CoordinateAxis statics (#4241)
5983434a70 [cameraserver] Replace IterativeRobot in comment sample code with TimedRobot (#4238)
a3d44a1e69 [wpimath] Add Translation2d.getAngle() (#4217)
d364bbd5a7 [upstream_utils] Give vendor update scripts execute permissions (#4226)
f341e1b2be [wpimath] Document standard coordinate systems better (NFC) (#4228)
9af389b200 [wpinet] AddrToName: Initialize name (#4229)
2ae4adf2d7 [ci] Add wpiformat command to PRs (#4223)
178b2a1e88 Contributing.md: Correct version of clang-format used (#4222)
18db343cdc [wpiutil, wpinet] Vendor libuv, stack walker (#4219)
f0c821282a [build] Use artifactory mirror (#4220)
d673ead481 [wpinet] Move network portions of wpiutil into new wpinet library (#4077)
b33715db15 [wpimath] Add CoordinateSystem class (#4214)
99424ad562 [sim] Allow creating a PWMSim object from a PWMMotorController (#4039)
dc6f641fd2 [wpimath] PIDController: Reset position and velocity error when reset() is called. (#4064)
f20a20f3f1 [wpimath] Add 3D geometry classes (#4175)
708a4bc3bc [wpimath] Conserve previously calculated swerve module angles when updating states for stationary ChassisSpeeds (#4208)
ef7ed21a9d [wpimath] Improve accuracy of ComputerVisionUtil.calculateDistanceToTarget() (#4215)
b1abf455c1 [wpimath] LTVUnicycleController: Use LUT, provide default hyperparameters (#4213)
d5456cf278 [wpimath] LTVDifferentialDriveController: Remove unused variable (#4212)
99343d40ba [command] Remove old command-based framework (#4211)
ee03a7ad3b Remove most 2022 deprecations (#4205)
ce1a7d698a [wpimath] Refactor WheelVoltages inner class to a separate file (#4203)
87bf70fa8e [wpimath] Add LTV controllers (#4094)
ebd2a303bf [wpimath] Remove deprecated MakeMatrix() function (#4202)
e28776d361 [wpimath] LinearSystemLoop: Add extern templates for common cases
dac1429aa9 [wpimath] LQR: Use extern template instead of Impl class
e767605e94 [wpimath] Add typedefs for common types
97c493241f [wpimath] UnscentedKalmanFilter: Move implementation out-of-line
8ea90d8bc9 [wpimath] ExtendedKalmanFilter: Move implementation out-of-line
ae7b1851ec [wpimath] KalmanFilter: Use extern template instead of Impl class
e3d62c22d3 [wpimath] Add extern templates for common cases
7200c4951d [wpiutil] SymbolExports: Add WPILIB_IMPORTS for dllimport
84056c9347 [wpiutil] SymbolExports: Add EXPORT_TEMPLATE_DECLARE/DEFINE
09cf6eeecb [wpimath] ApplyDeadband: add a scale param (#3865)
03230fc842 [build,ci] Enable artifactory build cache (#4200)
63cf3aaa3f [examples] Don't square ArcadeDrive inputs in auto (#4201)
18ff694f02 [wpimath] Add Rotation2d.fromRadians factory (#4178)
4f79ceedd9 [wpilibc] Add missing #include (#4198)
f7ca72fb41 [command] Rename PerpetualCommand to EndlessCommand (#4177)
a06b3f0307 [hal] Correct documentation on updateNotifierAlarm (#4156)
d926dd1610 [wpimath] Fix pose estimator performance (#4111)
51bc893bc5 [wpiutil] CircularBuffer: Change Java package-private methods to public (#4181)
fbe761f7f6 [build] Increase Gradle JVM heap size (#4172)
5ebe911933 [wpimath] Add DifferentialDriveAccelerationLimiter (#4091)
3919250da2 [wpilibj] Remove finalizers (#4158)
b3aee28388 [commands] Allow BooleanSupplier for Trigger operations (#4103)
9d20ab3024 [wpilib] Allow disabling ElevatorSim gravity (#4145)
aaa69f6717 [ci] Remove 32-bit Windows builds (#4078)
355a11a414 Update Java linters and fix new PMD errors (#4157)
ffc69d406c [examples] Reduce suggested acceleration in Ramsete example (#4171)
922d50079a [wpimath] Units: fix comment in degreesToRotations (NFC) (#4159)
dd163b62ae [wpimath] Rotation2d: Add factory method that uses rotations (#4166)
bd80e220b9 [ci] Upgrade CMake actions (#4161)
aef4b16d4c [wpimath] Remove unnecessary NOLINT in LinearPlantInversionFeedforward (NFC) (#4155)
975171609e [wpilib] Compressor: Rename enabled to isEnabled (#4147)
5bf46a9093 [wpimath] Add ComputerVisionUtil (#4124)
f27a1f9bfb [commands] Fix JoystickButton.getAsBoolean (#4131)
1b26e2d5da [commands] Add RepeatCommand (#4009)
88222daa3d [hal] Fix misspelling in AnalogInput/Output docs (NFC) (#4153)
81c5b41ce1 [wpilibj] Document MechanismLigament2d angle unit (NFC) (#4142)
9650e6733e [wpiutil] DataLog: Document finish and thread safety (NFC) (#4140)
c8905ec29a [wpimath] Remove ImplicitModelFollower dt argument (#4119)
b4620f01f9 [wpimath] Fix Rotation2d interpolation in Java (#4125)
2e462a19d3 [wpimath] Constexprify units unary operators (#4138)
069f932e59 [build] Fix gl3w cmake build (#4139)
126e3de91a [wpilibc] Remove unused SetPriority() call from Ultrasonic (#4123)
ba0dccaae4 [wpimath] Fix reference to Rotation2d.fromRadians() (#4118)
e1b6e5f212 [wpilib] Improve MotorSafety documentation (NFC) (#4120)
8d79dc8738 [wpimath] Add ImplicitModelFollower (#4056)
78108c2aba [wpimath] Fix PIDController having incorrect error after calling SetSetpoint() (#4070)
cdafc723fb [examples] Remove unused LinearPlantInversionFeedforward includes (#4069)
0d70884dce [wpimath] Add InterpolatedTreeMap (#4073)
765efa325e [wpimath] Remove redundant column index from vectors (#4116)
89ffcbbe41 [wpimath] Update TrapezoidProfile class name in comment (NFC) (#4107)
95ae23b0e7 [wpimath] Improve EKF numerical stability (#4093)
d5cb6fed67 [wpimath] Support zero cost entries in MakeCostMatrix() (#4100)
d0fef18378 [wpimath] Remove redundant `this.` from ExtendedKalmanFilter.java (#4115)
d640c0f41f [wpimath] Fix pose estimator local measurement standard deviation docs (NFC) (#4113)
a2fa5e3ff7 [wpilibc] BatterySim: Provide non-initializer list versions of Calculate (#4076)
a3eea9958e [hal] Add link to FRC CAN Spec (NFC) (#4086)
db27331d7b [wpilib] Update DifferentialDrive docs (NFC) (#4085)
fdfb31f164 [dlt] Export boolean[] values (#4082)
f93c3331b3 [wpigui] disable changing directory when initializing on MacOS (#4092)
ab7ac4fbb9 [build] Fix various warnings in cmake builds (#4081)
bc39a1a293 [wpilibc] Fix moved pneumatics objects not destructing properly (#4068)
2668130e70 [wpimath] Remove SwerveDrivePoseEstimator encoder reset warning (#4066)
d27ed3722b [ci] Set actions workflow concurrency (#4060)
dae18308c9 [wpimath] Minor fixes to Rotation2d docs (NFC) (#4055)
d66555e42f [datalogtool] Add datalogtool
9f52d8a3b1 [wpilib] DriverStation: Add DataLog support for modes and joystick data
757ea91932 [wpilib] Add DataLogManager
02a804f1c5 [ntcore] Add DataLog support
9b500df0d9 [wpiutil] Add high speed data logging
5a89575b3a [wpiutil] Import customized LLVM MemoryBuffer
b8c4d7527b [wpiutil] Add MappedFileRegion
ac5d46cfa7 [wpilibc] Fix ProfiledPID SetTolerance default velocity value (#4054)
bc9e96e86f [wpilib] Absolute Encoder API and behavior fixes (#4052)
f88c435dd0 [hal] Add mechanism to cancel all periodic callbacks (#4049)

Change-Id: I49aa5b08abbefc7a045e99e19d48ce2cd8fc4d1b
git-subtree-dir: third_party/allwpilib
git-subtree-split: 83f1860047c86aa3330fcb41caf3b2047e074804
Signed-off-by: James Kuszmaul <jabukuszmaul+collab@gmail.com>
diff --git a/wpiutil/src/main/native/cpp/Base64.cpp b/wpiutil/src/main/native/cpp/Base64.cpp
index 8f1f810..bc1d3ba 100644
--- a/wpiutil/src/main/native/cpp/Base64.cpp
+++ b/wpiutil/src/main/native/cpp/Base64.cpp
@@ -138,8 +138,8 @@
   return Base64Decode(os, encoded);
 }
 
-span<uint8_t> Base64Decode(std::string_view encoded, size_t* num_read,
-                           SmallVectorImpl<uint8_t>& buf) {
+std::span<uint8_t> Base64Decode(std::string_view encoded, size_t* num_read,
+                                SmallVectorImpl<uint8_t>& buf) {
   buf.clear();
   raw_usvector_ostream os(buf);
   *num_read = Base64Decode(os, encoded);
@@ -193,19 +193,19 @@
   return os.str();
 }
 
-void Base64Encode(raw_ostream& os, span<const uint8_t> plain) {
+void Base64Encode(raw_ostream& os, std::span<const uint8_t> plain) {
   Base64Encode(os, std::string_view{reinterpret_cast<const char*>(plain.data()),
                                     plain.size()});
 }
 
-void Base64Encode(span<const uint8_t> plain, std::string* encoded) {
+void Base64Encode(std::span<const uint8_t> plain, std::string* encoded) {
   encoded->resize(0);
   raw_string_ostream os(*encoded);
   Base64Encode(os, plain);
   os.flush();
 }
 
-std::string_view Base64Encode(span<const uint8_t> plain,
+std::string_view Base64Encode(std::span<const uint8_t> plain,
                               SmallVectorImpl<char>& buf) {
   buf.clear();
   raw_svector_ostream os(buf);
diff --git a/wpiutil/src/main/native/cpp/DataLog.cpp b/wpiutil/src/main/native/cpp/DataLog.cpp
new file mode 100644
index 0000000..7009628
--- /dev/null
+++ b/wpiutil/src/main/native/cpp/DataLog.cpp
@@ -0,0 +1,821 @@
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
+
+#include "wpi/DataLog.h"
+
+#include "wpi/Synchronization.h"
+
+#ifndef _WIN32
+#include <unistd.h>
+#endif
+
+#ifdef _WIN32
+#ifndef WIN32_LEAN_AND_MEAN
+#define WIN32_LEAN_AND_MEAN
+#endif
+
+#include <windows.h>  // NOLINT(build/include_order)
+
+#endif
+
+#include <atomic>
+#include <cstdio>
+#include <cstdlib>
+#include <cstring>
+#include <random>
+#include <vector>
+
+#include "fmt/format.h"
+#include "wpi/Endian.h"
+#include "wpi/Logger.h"
+#include "wpi/MathExtras.h"
+#include "wpi/fs.h"
+#include "wpi/timestamp.h"
+
+using namespace wpi::log;
+
+static constexpr size_t kBlockSize = 16 * 1024;
+static constexpr size_t kRecordMaxHeaderSize = 17;
+
+template <typename T>
+static unsigned int WriteVarInt(uint8_t* buf, T val) {
+  unsigned int len = 0;
+  do {
+    *buf++ = static_cast<unsigned int>(val) & 0xff;
+    ++len;
+    val >>= 8;
+  } while (val != 0);
+  return len;
+}
+
+// min size: 4, max size: 17
+static unsigned int WriteRecordHeader(uint8_t* buf, uint32_t entry,
+                                      uint64_t timestamp,
+                                      uint32_t payloadSize) {
+  uint8_t* origbuf = buf++;
+
+  unsigned int entryLen = WriteVarInt(buf, entry);
+  buf += entryLen;
+  unsigned int payloadLen = WriteVarInt(buf, payloadSize);
+  buf += payloadLen;
+  unsigned int timestampLen =
+      WriteVarInt(buf, timestamp == 0 ? wpi::Now() : timestamp);
+  buf += timestampLen;
+  *origbuf =
+      ((timestampLen - 1) << 4) | ((payloadLen - 1) << 2) | (entryLen - 1);
+  return buf - origbuf;
+}
+
+class DataLog::Buffer {
+ public:
+  explicit Buffer(size_t alloc = kBlockSize)
+      : m_buf{new uint8_t[alloc]}, m_maxLen{alloc} {}
+  ~Buffer() { delete[] m_buf; }
+
+  Buffer(const Buffer&) = delete;
+  Buffer& operator=(const Buffer&) = delete;
+
+  Buffer(Buffer&& oth)
+      : m_buf{oth.m_buf}, m_len{oth.m_len}, m_maxLen{oth.m_maxLen} {
+    oth.m_buf = nullptr;
+    oth.m_len = 0;
+    oth.m_maxLen = 0;
+  }
+
+  Buffer& operator=(Buffer&& oth) {
+    if (m_buf) {
+      delete[] m_buf;
+    }
+    m_buf = oth.m_buf;
+    m_len = oth.m_len;
+    m_maxLen = oth.m_maxLen;
+    oth.m_buf = nullptr;
+    oth.m_len = 0;
+    oth.m_maxLen = 0;
+    return *this;
+  }
+
+  uint8_t* Reserve(size_t size) {
+    assert(size <= GetRemaining());
+    uint8_t* rv = m_buf + m_len;
+    m_len += size;
+    return rv;
+  }
+
+  void Unreserve(size_t size) { m_len -= size; }
+
+  void Clear() { m_len = 0; }
+
+  size_t GetRemaining() const { return m_maxLen - m_len; }
+
+  std::span<uint8_t> GetData() { return {m_buf, m_len}; }
+  std::span<const uint8_t> GetData() const { return {m_buf, m_len}; }
+
+ private:
+  uint8_t* m_buf;
+  size_t m_len = 0;
+  size_t m_maxLen;
+};
+
+static void DefaultLog(unsigned int level, const char* file, unsigned int line,
+                       const char* msg) {
+  if (level > wpi::WPI_LOG_INFO) {
+    fmt::print(stderr, "DataLog: {}\n", msg);
+  } else if (level == wpi::WPI_LOG_INFO) {
+    fmt::print("DataLog: {}\n", msg);
+  }
+}
+
+static wpi::Logger defaultMessageLog{DefaultLog};
+
+DataLog::DataLog(std::string_view dir, std::string_view filename, double period,
+                 std::string_view extraHeader)
+    : DataLog{defaultMessageLog, dir, filename, period, extraHeader} {}
+
+DataLog::DataLog(wpi::Logger& msglog, std::string_view dir,
+                 std::string_view filename, double period,
+                 std::string_view extraHeader)
+    : m_msglog{msglog},
+      m_period{period},
+      m_extraHeader{extraHeader},
+      m_newFilename{filename},
+      m_thread{[this, dir = std::string{dir}] { WriterThreadMain(dir); }} {}
+
+DataLog::DataLog(std::function<void(std::span<const uint8_t> data)> write,
+                 double period, std::string_view extraHeader)
+    : DataLog{defaultMessageLog, std::move(write), period, extraHeader} {}
+
+DataLog::DataLog(wpi::Logger& msglog,
+                 std::function<void(std::span<const uint8_t> data)> write,
+                 double period, std::string_view extraHeader)
+    : m_msglog{msglog},
+      m_period{period},
+      m_extraHeader{extraHeader},
+      m_thread{[this, write = std::move(write)] {
+        WriterThreadMain(std::move(write));
+      }} {}
+
+DataLog::~DataLog() {
+  {
+    std::scoped_lock lock{m_mutex};
+    m_active = false;
+    m_doFlush = true;
+  }
+  m_cond.notify_all();
+  m_thread.join();
+}
+
+void DataLog::SetFilename(std::string_view filename) {
+  {
+    std::scoped_lock lock{m_mutex};
+    m_newFilename = filename;
+  }
+  m_cond.notify_all();
+}
+
+void DataLog::Flush() {
+  {
+    std::scoped_lock lock{m_mutex};
+    m_doFlush = true;
+  }
+  m_cond.notify_all();
+}
+
+void DataLog::Pause() {
+  std::scoped_lock lock{m_mutex};
+  m_paused = true;
+}
+
+void DataLog::Resume() {
+  std::scoped_lock lock{m_mutex};
+  m_paused = false;
+}
+
+static void WriteToFile(fs::file_t f, std::span<const uint8_t> data,
+                        std::string_view filename, wpi::Logger& msglog) {
+  do {
+#ifdef _WIN32
+    DWORD ret;
+    if (!WriteFile(f, data.data(), data.size(), &ret, nullptr)) {
+      WPI_ERROR(msglog, "Error writing to log file '{}': {}", filename,
+                GetLastError());
+      break;
+    }
+#else
+    ssize_t ret = ::write(f, data.data(), data.size());
+    if (ret < 0) {
+      // If it's a recoverable error, swallow it and retry the write
+      if (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK) {
+        continue;
+      }
+
+      // Otherwise it's a non-recoverable error; quit trying
+      WPI_ERROR(msglog, "Error writing to log file '{}': {}", filename,
+                std::strerror(errno));
+      break;
+    }
+#endif
+
+    // The write may have written some or all of the data
+    data = data.subspan(ret);
+  } while (data.size() > 0);
+}
+
+static std::string MakeRandomFilename() {
+  // build random filename
+  static std::random_device dev;
+  static std::mt19937 rng(dev());
+  std::uniform_int_distribution<int> dist(0, 15);
+  const char* v = "0123456789abcdef";
+  std::string filename = "wpilog_";
+  for (int i = 0; i < 16; i++) {
+    filename += v[dist(rng)];
+  }
+  filename += ".wpilog";
+  return filename;
+}
+
+void DataLog::WriterThreadMain(std::string_view dir) {
+  std::chrono::duration<double> periodTime{m_period};
+
+  std::error_code ec;
+  fs::path dirPath{dir};
+  std::string filename;
+
+  {
+    std::scoped_lock lock{m_mutex};
+    filename = std::move(m_newFilename);
+    m_newFilename.clear();
+  }
+
+  if (filename.empty()) {
+    filename = MakeRandomFilename();
+  }
+
+  // try preferred filename, or randomize it a few times, before giving up
+  fs::file_t f;
+  for (int i = 0; i < 5; ++i) {
+    // open file for append
+#ifdef _WIN32
+    // WIN32 doesn't allow combination of CreateNew and Append
+    f = fs::OpenFileForWrite(dirPath / filename, ec, fs::CD_CreateNew,
+                             fs::OF_None);
+#else
+    f = fs::OpenFileForWrite(dirPath / filename, ec, fs::CD_CreateNew,
+                             fs::OF_Append);
+#endif
+    if (ec) {
+      WPI_ERROR(m_msglog, "Could not open log file '{}': {}",
+                (dirPath / filename).string(), ec.message());
+      // try again with random filename
+      filename = MakeRandomFilename();
+    } else {
+      break;
+    }
+  }
+
+  if (f == fs::kInvalidFile) {
+    WPI_ERROR(m_msglog, "Could not open log file, no log being saved");
+  } else {
+    WPI_INFO(m_msglog, "Logging to '{}'", (dirPath / filename).string());
+  }
+
+  // write header (version 1.0)
+  if (f != fs::kInvalidFile) {
+    const uint8_t header[] = {'W', 'P', 'I', 'L', 'O', 'G', 0, 1};
+    WriteToFile(f, header, filename, m_msglog);
+    uint8_t extraLen[4];
+    support::endian::write32le(extraLen, m_extraHeader.size());
+    WriteToFile(f, extraLen, filename, m_msglog);
+    if (m_extraHeader.size() > 0) {
+      WriteToFile(f,
+                  {reinterpret_cast<const uint8_t*>(m_extraHeader.data()),
+                   m_extraHeader.size()},
+                  filename, m_msglog);
+    }
+  }
+
+  std::vector<Buffer> toWrite;
+
+  std::unique_lock lock{m_mutex};
+  while (m_active) {
+    bool doFlush = false;
+    auto timeoutTime = std::chrono::steady_clock::now() + periodTime;
+    if (m_cond.wait_until(lock, timeoutTime) == std::cv_status::timeout) {
+      doFlush = true;
+    }
+
+    if (!m_newFilename.empty()) {
+      auto newFilename = std::move(m_newFilename);
+      m_newFilename.clear();
+      lock.unlock();
+      // rename
+      if (filename != newFilename) {
+        fs::rename(dirPath / filename, dirPath / newFilename, ec);
+      }
+      if (ec) {
+        WPI_ERROR(m_msglog, "Could not rename log file from '{}' to '{}': {}",
+                  filename, newFilename, ec.message());
+      } else {
+        WPI_INFO(m_msglog, "Renamed log file from '{}' to '{}'", filename,
+                 newFilename);
+      }
+      filename = std::move(newFilename);
+      lock.lock();
+    }
+
+    if (doFlush || m_doFlush) {
+      // flush to file
+      m_doFlush = false;
+      if (m_outgoing.empty()) {
+        continue;
+      }
+      // swap outgoing with empty vector
+      toWrite.swap(m_outgoing);
+
+      if (f != fs::kInvalidFile) {
+        lock.unlock();
+        // write buffers to file
+        for (auto&& buf : toWrite) {
+          WriteToFile(f, buf.GetData(), filename, m_msglog);
+        }
+
+        // sync to storage
+#if defined(__linux__)
+        ::fdatasync(f);
+#elif defined(__APPLE__)
+        ::fsync(f);
+#endif
+        lock.lock();
+      }
+
+      // release buffers back to free list
+      for (auto&& buf : toWrite) {
+        buf.Clear();
+        m_free.emplace_back(std::move(buf));
+      }
+      toWrite.resize(0);
+    }
+  }
+
+  if (f != fs::kInvalidFile) {
+    fs::CloseFile(f);
+  }
+}
+
+void DataLog::WriterThreadMain(
+    std::function<void(std::span<const uint8_t> data)> write) {
+  std::chrono::duration<double> periodTime{m_period};
+
+  // write header (version 1.0)
+  {
+    const uint8_t header[] = {'W', 'P', 'I', 'L', 'O', 'G', 0, 1};
+    write(header);
+    uint8_t extraLen[4];
+    support::endian::write32le(extraLen, m_extraHeader.size());
+    write(extraLen);
+    if (m_extraHeader.size() > 0) {
+      write({reinterpret_cast<const uint8_t*>(m_extraHeader.data()),
+             m_extraHeader.size()});
+    }
+  }
+
+  std::vector<Buffer> toWrite;
+
+  std::unique_lock lock{m_mutex};
+  while (m_active) {
+    bool doFlush = false;
+    auto timeoutTime = std::chrono::steady_clock::now() + periodTime;
+    if (m_cond.wait_until(lock, timeoutTime) == std::cv_status::timeout) {
+      doFlush = true;
+    }
+
+    if (doFlush || m_doFlush) {
+      // flush to file
+      m_doFlush = false;
+      if (m_outgoing.empty()) {
+        continue;
+      }
+      // swap outgoing with empty vector
+      toWrite.swap(m_outgoing);
+
+      lock.unlock();
+      // write buffers
+      for (auto&& buf : toWrite) {
+        if (!buf.GetData().empty()) {
+          write(buf.GetData());
+        }
+      }
+      lock.lock();
+
+      // release buffers back to free list
+      for (auto&& buf : toWrite) {
+        buf.Clear();
+        m_free.emplace_back(std::move(buf));
+      }
+      toWrite.resize(0);
+    }
+  }
+
+  write({});  // indicate EOF
+}
+
+// Control records use the following format:
+// 1-byte type
+// 4-byte entry
+// rest of data (depending on type)
+
+int DataLog::Start(std::string_view name, std::string_view type,
+                   std::string_view metadata, int64_t timestamp) {
+  std::scoped_lock lock{m_mutex};
+  auto& entryInfo = m_entries[name];
+  if (entryInfo.id == 0) {
+    entryInfo.id = ++m_lastId;
+  }
+  auto& savedCount = m_entryCounts[entryInfo.id];
+  ++savedCount;
+  if (savedCount > 1) {
+    if (entryInfo.type != type) {
+      WPI_ERROR(m_msglog,
+                "type mismatch for '{}': was '{}', requested '{}'; ignoring",
+                name, entryInfo.type, type);
+      return 0;
+    }
+    return entryInfo.id;
+  }
+  entryInfo.type = type;
+  size_t strsize = name.size() + type.size() + metadata.size();
+  uint8_t* buf = StartRecord(0, timestamp, 5 + 12 + strsize, 5);
+  *buf++ = impl::kControlStart;
+  wpi::support::endian::write32le(buf, entryInfo.id);
+  AppendStringImpl(name);
+  AppendStringImpl(type);
+  AppendStringImpl(metadata);
+
+  return entryInfo.id;
+}
+
+void DataLog::Finish(int entry, int64_t timestamp) {
+  if (entry <= 0) {
+    return;
+  }
+  std::scoped_lock lock{m_mutex};
+  auto& savedCount = m_entryCounts[entry];
+  if (savedCount == 0) {
+    return;
+  }
+  --savedCount;
+  if (savedCount != 0) {
+    return;
+  }
+  m_entryCounts.erase(entry);
+  uint8_t* buf = StartRecord(0, timestamp, 5, 5);
+  *buf++ = impl::kControlFinish;
+  wpi::support::endian::write32le(buf, entry);
+}
+
+void DataLog::SetMetadata(int entry, std::string_view metadata,
+                          int64_t timestamp) {
+  if (entry <= 0) {
+    return;
+  }
+  std::scoped_lock lock{m_mutex};
+  uint8_t* buf = StartRecord(0, timestamp, 5 + 4 + metadata.size(), 5);
+  *buf++ = impl::kControlSetMetadata;
+  wpi::support::endian::write32le(buf, entry);
+  AppendStringImpl(metadata);
+}
+
+uint8_t* DataLog::Reserve(size_t size) {
+  assert(size <= kBlockSize);
+  if (m_outgoing.empty() || size > m_outgoing.back().GetRemaining()) {
+    if (m_free.empty()) {
+      m_outgoing.emplace_back();
+    } else {
+      m_outgoing.emplace_back(std::move(m_free.back()));
+      m_free.pop_back();
+    }
+  }
+  return m_outgoing.back().Reserve(size);
+}
+
+uint8_t* DataLog::StartRecord(uint32_t entry, uint64_t timestamp,
+                              uint32_t payloadSize, size_t reserveSize) {
+  uint8_t* buf = Reserve(kRecordMaxHeaderSize + reserveSize);
+  auto headerLen = WriteRecordHeader(buf, entry, timestamp, payloadSize);
+  m_outgoing.back().Unreserve(kRecordMaxHeaderSize - headerLen);
+  buf += headerLen;
+  return buf;
+}
+
+void DataLog::AppendImpl(std::span<const uint8_t> data) {
+  while (data.size() > kBlockSize) {
+    uint8_t* buf = Reserve(kBlockSize);
+    std::memcpy(buf, data.data(), kBlockSize);
+    data = data.subspan(kBlockSize);
+  }
+  uint8_t* buf = Reserve(data.size());
+  std::memcpy(buf, data.data(), data.size());
+}
+
+void DataLog::AppendStringImpl(std::string_view str) {
+  uint8_t* buf = Reserve(4);
+  wpi::support::endian::write32le(buf, str.size());
+  AppendImpl({reinterpret_cast<const uint8_t*>(str.data()), str.size()});
+}
+
+void DataLog::AppendRaw(int entry, std::span<const uint8_t> data,
+                        int64_t timestamp) {
+  if (entry <= 0) {
+    return;
+  }
+  std::scoped_lock lock{m_mutex};
+  if (m_paused) {
+    return;
+  }
+  StartRecord(entry, timestamp, data.size(), 0);
+  AppendImpl(data);
+}
+
+void DataLog::AppendRaw2(int entry,
+                         std::span<const std::span<const uint8_t>> data,
+                         int64_t timestamp) {
+  if (entry <= 0) {
+    return;
+  }
+  std::scoped_lock lock{m_mutex};
+  if (m_paused) {
+    return;
+  }
+  size_t size = 0;
+  for (auto&& chunk : data) {
+    size += chunk.size();
+  }
+  StartRecord(entry, timestamp, size, 0);
+  for (auto chunk : data) {
+    AppendImpl(chunk);
+  }
+}
+
+void DataLog::AppendBoolean(int entry, bool value, int64_t timestamp) {
+  if (entry <= 0) {
+    return;
+  }
+  std::scoped_lock lock{m_mutex};
+  if (m_paused) {
+    return;
+  }
+  uint8_t* buf = StartRecord(entry, timestamp, 1, 1);
+  buf[0] = value ? 1 : 0;
+}
+
+void DataLog::AppendInteger(int entry, int64_t value, int64_t timestamp) {
+  if (entry <= 0) {
+    return;
+  }
+  std::scoped_lock lock{m_mutex};
+  if (m_paused) {
+    return;
+  }
+  uint8_t* buf = StartRecord(entry, timestamp, 8, 8);
+  wpi::support::endian::write64le(buf, value);
+}
+
+void DataLog::AppendFloat(int entry, float value, int64_t timestamp) {
+  if (entry <= 0) {
+    return;
+  }
+  std::scoped_lock lock{m_mutex};
+  if (m_paused) {
+    return;
+  }
+  uint8_t* buf = StartRecord(entry, timestamp, 4, 4);
+  if constexpr (wpi::support::endian::system_endianness() ==
+                wpi::support::little) {
+    std::memcpy(buf, &value, 4);
+  } else {
+    wpi::support::endian::write32le(buf, wpi::FloatToBits(value));
+  }
+}
+
+void DataLog::AppendDouble(int entry, double value, int64_t timestamp) {
+  if (entry <= 0) {
+    return;
+  }
+  std::scoped_lock lock{m_mutex};
+  if (m_paused) {
+    return;
+  }
+  uint8_t* buf = StartRecord(entry, timestamp, 8, 8);
+  if constexpr (wpi::support::endian::system_endianness() ==
+                wpi::support::little) {
+    std::memcpy(buf, &value, 8);
+  } else {
+    wpi::support::endian::write64le(buf, wpi::DoubleToBits(value));
+  }
+}
+
+void DataLog::AppendString(int entry, std::string_view value,
+                           int64_t timestamp) {
+  AppendRaw(entry,
+            {reinterpret_cast<const uint8_t*>(value.data()), value.size()},
+            timestamp);
+}
+
+void DataLog::AppendBooleanArray(int entry, std::span<const bool> arr,
+                                 int64_t timestamp) {
+  if (entry <= 0) {
+    return;
+  }
+  std::scoped_lock lock{m_mutex};
+  if (m_paused) {
+    return;
+  }
+  StartRecord(entry, timestamp, arr.size(), 0);
+  uint8_t* buf;
+  while (arr.size() > kBlockSize) {
+    buf = Reserve(kBlockSize);
+    for (auto val : arr.subspan(0, kBlockSize)) {
+      *buf++ = val ? 1 : 0;
+    }
+    arr = arr.subspan(kBlockSize);
+  }
+  buf = Reserve(arr.size());
+  for (auto val : arr) {
+    *buf++ = val ? 1 : 0;
+  }
+}
+
+void DataLog::AppendBooleanArray(int entry, std::span<const int> arr,
+                                 int64_t timestamp) {
+  if (entry <= 0) {
+    return;
+  }
+  std::scoped_lock lock{m_mutex};
+  if (m_paused) {
+    return;
+  }
+  StartRecord(entry, timestamp, arr.size(), 0);
+  uint8_t* buf;
+  while (arr.size() > kBlockSize) {
+    buf = Reserve(kBlockSize);
+    for (auto val : arr.subspan(0, kBlockSize)) {
+      *buf++ = val & 1;
+    }
+    arr = arr.subspan(kBlockSize);
+  }
+  buf = Reserve(arr.size());
+  for (auto val : arr) {
+    *buf++ = val & 1;
+  }
+}
+
+void DataLog::AppendBooleanArray(int entry, std::span<const uint8_t> arr,
+                                 int64_t timestamp) {
+  AppendRaw(entry, arr, timestamp);
+}
+
+void DataLog::AppendIntegerArray(int entry, std::span<const int64_t> arr,
+                                 int64_t timestamp) {
+  if constexpr (wpi::support::endian::system_endianness() ==
+                wpi::support::little) {
+    AppendRaw(entry,
+              {reinterpret_cast<const uint8_t*>(arr.data()), arr.size() * 8},
+              timestamp);
+  } else {
+    if (entry <= 0) {
+      return;
+    }
+    std::scoped_lock lock{m_mutex};
+    if (m_paused) {
+      return;
+    }
+    StartRecord(entry, timestamp, arr.size() * 8, 0);
+    uint8_t* buf;
+    while ((arr.size() * 8) > kBlockSize) {
+      buf = Reserve(kBlockSize);
+      for (auto val : arr.subspan(0, kBlockSize / 8)) {
+        wpi::support::endian::write64le(buf, val);
+        buf += 8;
+      }
+      arr = arr.subspan(kBlockSize / 8);
+    }
+    buf = Reserve(arr.size() * 8);
+    for (auto val : arr) {
+      wpi::support::endian::write64le(buf, val);
+      buf += 8;
+    }
+  }
+}
+
+void DataLog::AppendFloatArray(int entry, std::span<const float> arr,
+                               int64_t timestamp) {
+  if constexpr (wpi::support::endian::system_endianness() ==
+                wpi::support::little) {
+    AppendRaw(entry,
+              {reinterpret_cast<const uint8_t*>(arr.data()), arr.size() * 4},
+              timestamp);
+  } else {
+    if (entry <= 0) {
+      return;
+    }
+    std::scoped_lock lock{m_mutex};
+    if (m_paused) {
+      return;
+    }
+    StartRecord(entry, timestamp, arr.size() * 4, 0);
+    uint8_t* buf;
+    while ((arr.size() * 4) > kBlockSize) {
+      buf = Reserve(kBlockSize);
+      for (auto val : arr.subspan(0, kBlockSize / 4)) {
+        wpi::support::endian::write32le(buf, wpi::FloatToBits(val));
+        buf += 4;
+      }
+      arr = arr.subspan(kBlockSize / 4);
+    }
+    buf = Reserve(arr.size() * 4);
+    for (auto val : arr) {
+      wpi::support::endian::write32le(buf, wpi::FloatToBits(val));
+      buf += 4;
+    }
+  }
+}
+
+void DataLog::AppendDoubleArray(int entry, std::span<const double> arr,
+                                int64_t timestamp) {
+  if constexpr (wpi::support::endian::system_endianness() ==
+                wpi::support::little) {
+    AppendRaw(entry,
+              {reinterpret_cast<const uint8_t*>(arr.data()), arr.size() * 8},
+              timestamp);
+  } else {
+    if (entry <= 0) {
+      return;
+    }
+    std::scoped_lock lock{m_mutex};
+    if (m_paused) {
+      return;
+    }
+    StartRecord(entry, timestamp, arr.size() * 8, 0);
+    uint8_t* buf;
+    while ((arr.size() * 8) > kBlockSize) {
+      buf = Reserve(kBlockSize);
+      for (auto val : arr.subspan(0, kBlockSize / 8)) {
+        wpi::support::endian::write64le(buf, wpi::DoubleToBits(val));
+        buf += 8;
+      }
+      arr = arr.subspan(kBlockSize / 8);
+    }
+    buf = Reserve(arr.size() * 8);
+    for (auto val : arr) {
+      wpi::support::endian::write64le(buf, wpi::DoubleToBits(val));
+      buf += 8;
+    }
+  }
+}
+
+void DataLog::AppendStringArray(int entry, std::span<const std::string> arr,
+                                int64_t timestamp) {
+  if (entry <= 0) {
+    return;
+  }
+  // storage: 4-byte array length, each string prefixed by 4-byte length
+  // calculate total size
+  size_t size = 4;
+  for (auto&& str : arr) {
+    size += 4 + str.size();
+  }
+  std::scoped_lock lock{m_mutex};
+  if (m_paused) {
+    return;
+  }
+  uint8_t* buf = StartRecord(entry, timestamp, size, 4);
+  wpi::support::endian::write32le(buf, arr.size());
+  for (auto&& str : arr) {
+    AppendStringImpl(str);
+  }
+}
+
+void DataLog::AppendStringArray(int entry,
+                                std::span<const std::string_view> arr,
+                                int64_t timestamp) {
+  if (entry <= 0) {
+    return;
+  }
+  // storage: 4-byte array length, each string prefixed by 4-byte length
+  // calculate total size
+  size_t size = 4;
+  for (auto&& str : arr) {
+    size += 4 + str.size();
+  }
+  std::scoped_lock lock{m_mutex};
+  if (m_paused) {
+    return;
+  }
+  uint8_t* buf = StartRecord(entry, timestamp, size, 4);
+  wpi::support::endian::write32le(buf, arr.size());
+  for (auto sv : arr) {
+    AppendStringImpl(sv);
+  }
+}
diff --git a/wpiutil/src/main/native/cpp/DataLogReader.cpp b/wpiutil/src/main/native/cpp/DataLogReader.cpp
new file mode 100644
index 0000000..96f6689
--- /dev/null
+++ b/wpiutil/src/main/native/cpp/DataLogReader.cpp
@@ -0,0 +1,307 @@
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
+
+#include "wpi/DataLogReader.h"
+
+#include "wpi/DataLog.h"
+#include "wpi/Endian.h"
+#include "wpi/MathExtras.h"
+
+using namespace wpi::log;
+
+static bool ReadString(std::span<const uint8_t>* buf, std::string_view* str) {
+  if (buf->size() < 4) {
+    *str = {};
+    return false;
+  }
+  uint32_t len = wpi::support::endian::read32le(buf->data());
+  if (len > (buf->size() - 4)) {
+    *str = {};
+    return false;
+  }
+  *str = {reinterpret_cast<const char*>(buf->data() + 4), len};
+  *buf = buf->subspan(len + 4);
+  return true;
+}
+
+bool DataLogRecord::IsStart() const {
+  return m_entry == 0 && m_data.size() >= 17 &&
+         m_data[0] == impl::kControlStart;
+}
+
+bool DataLogRecord::IsFinish() const {
+  return m_entry == 0 && m_data.size() == 5 &&
+         m_data[0] == impl::kControlFinish;
+}
+
+bool DataLogRecord::IsSetMetadata() const {
+  return m_entry == 0 && m_data.size() >= 9 &&
+         m_data[0] == impl::kControlSetMetadata;
+}
+
+bool DataLogRecord::GetStartData(StartRecordData* out) const {
+  if (!IsStart()) {
+    return false;
+  }
+  out->entry = wpi::support::endian::read32le(&m_data[1]);
+  auto buf = m_data.subspan(5);
+  if (!ReadString(&buf, &out->name)) {
+    return false;
+  }
+  if (!ReadString(&buf, &out->type)) {
+    return false;
+  }
+  if (!ReadString(&buf, &out->metadata)) {
+    return false;
+  }
+  return true;
+}
+
+bool DataLogRecord::GetFinishEntry(int* out) const {
+  if (!IsFinish()) {
+    return false;
+  }
+  *out = wpi::support::endian::read32le(&m_data[1]);
+  return true;
+}
+
+bool DataLogRecord::GetSetMetadataData(MetadataRecordData* out) const {
+  if (!IsSetMetadata()) {
+    return false;
+  }
+  out->entry = wpi::support::endian::read32le(&m_data[1]);
+  auto buf = m_data.subspan(5);
+  return ReadString(&buf, &out->metadata);
+}
+
+bool DataLogRecord::GetBoolean(bool* value) const {
+  if (m_data.size() != 1) {
+    return false;
+  }
+  *value = m_data[0] != 0;
+  return true;
+}
+
+bool DataLogRecord::GetInteger(int64_t* value) const {
+  if (m_data.size() != 8) {
+    return false;
+  }
+  *value = wpi::support::endian::read64le(m_data.data());
+  return true;
+}
+
+bool DataLogRecord::GetFloat(float* value) const {
+  if (m_data.size() != 4) {
+    return false;
+  }
+  *value = wpi::BitsToFloat(wpi::support::endian::read32le(m_data.data()));
+  return true;
+}
+
+bool DataLogRecord::GetDouble(double* value) const {
+  if (m_data.size() != 8) {
+    return false;
+  }
+  *value = wpi::BitsToDouble(wpi::support::endian::read64le(m_data.data()));
+  return true;
+}
+
+bool DataLogRecord::GetString(std::string_view* value) const {
+  *value = {reinterpret_cast<const char*>(m_data.data()), m_data.size()};
+  return true;
+}
+
+bool DataLogRecord::GetBooleanArray(std::vector<int>* arr) const {
+  arr->clear();
+  arr->reserve(m_data.size());
+  for (auto v : m_data) {
+    arr->push_back(v);
+  }
+  return true;
+}
+
+bool DataLogRecord::GetIntegerArray(std::vector<int64_t>* arr) const {
+  arr->clear();
+  if ((m_data.size() % 8) != 0) {
+    return false;
+  }
+  arr->reserve(m_data.size() / 8);
+  for (size_t pos = 0; pos < m_data.size(); pos += 8) {
+    arr->push_back(wpi::support::endian::read64le(&m_data[pos]));
+  }
+  return true;
+}
+
+bool DataLogRecord::GetFloatArray(std::vector<float>* arr) const {
+  arr->clear();
+  if ((m_data.size() % 4) != 0) {
+    return false;
+  }
+  arr->reserve(m_data.size() / 4);
+  for (size_t pos = 0; pos < m_data.size(); pos += 4) {
+    arr->push_back(
+        wpi::BitsToFloat(wpi::support::endian::read32le(&m_data[pos])));
+  }
+  return true;
+}
+
+bool DataLogRecord::GetDoubleArray(std::vector<double>* arr) const {
+  arr->clear();
+  if ((m_data.size() % 8) != 0) {
+    return false;
+  }
+  arr->reserve(m_data.size() / 8);
+  for (size_t pos = 0; pos < m_data.size(); pos += 8) {
+    arr->push_back(
+        wpi::BitsToDouble(wpi::support::endian::read64le(&m_data[pos])));
+  }
+  return true;
+}
+
+bool DataLogRecord::GetStringArray(std::vector<std::string_view>* arr) const {
+  arr->clear();
+  if (m_data.size() < 4) {
+    return false;
+  }
+  uint32_t size = wpi::support::endian::read32le(m_data.data());
+  // sanity check size
+  if (size > ((m_data.size() - 4) / 4)) {
+    return false;
+  }
+  auto buf = m_data.subspan(4);
+  arr->reserve(size);
+  for (uint32_t i = 0; i < size; ++i) {
+    std::string_view str;
+    if (!ReadString(&buf, &str)) {
+      arr->clear();
+      return false;
+    }
+    arr->push_back(str);
+  }
+  // any left over?  treat as corrupt
+  if (!buf.empty()) {
+    arr->clear();
+    return false;
+  }
+  return true;
+}
+
+DataLogReader::DataLogReader(std::unique_ptr<MemoryBuffer> buffer)
+    : m_buf{std::move(buffer)} {}
+
+bool DataLogReader::IsValid() const {
+  if (!m_buf) {
+    return false;
+  }
+  auto buf = m_buf->GetBuffer();
+  return buf.size() >= 12 &&
+         std::string_view{reinterpret_cast<const char*>(buf.data()), 6} ==
+             "WPILOG" &&
+         wpi::support::endian::read16le(&buf[6]) >= 0x0100;
+}
+
+uint16_t DataLogReader::GetVersion() const {
+  if (!m_buf) {
+    return 0;
+  }
+  auto buf = m_buf->GetBuffer();
+  if (buf.size() < 12) {
+    return 0;
+  }
+  return wpi::support::endian::read16le(&buf[6]);
+}
+
+std::string_view DataLogReader::GetExtraHeader() const {
+  if (!m_buf) {
+    return {};
+  }
+  auto buf = m_buf->GetBuffer();
+  if (buf.size() < 8) {
+    return {};
+  }
+  std::string_view rv;
+  buf = buf.subspan(8);
+  ReadString(&buf, &rv);
+  return rv;
+}
+
+DataLogReader::iterator DataLogReader::begin() const {
+  if (!m_buf) {
+    return end();
+  }
+  auto buf = m_buf->GetBuffer();
+  if (buf.size() < 12) {
+    return end();
+  }
+  uint32_t size = wpi::support::endian::read32le(&buf[8]);
+  if (buf.size() < (12 + size)) {
+    return end();
+  }
+  return DataLogIterator{this, 12 + size};
+}
+
+static uint64_t ReadVarInt(std::span<const uint8_t> buf) {
+  uint64_t val = 0;
+  int shift = 0;
+  for (auto v : buf) {
+    val |= static_cast<uint64_t>(v) << shift;
+    shift += 8;
+  }
+  return val;
+}
+
+bool DataLogReader::GetRecord(size_t* pos, DataLogRecord* out) const {
+  if (!m_buf) {
+    return false;
+  }
+  auto buf = m_buf->GetBuffer();
+  if (*pos >= buf.size()) {
+    return false;
+  }
+  buf = buf.subspan(*pos);
+  if (buf.size() < 4) {  // minimum header length
+    return false;
+  }
+  unsigned int entryLen = (buf[0] & 0x3) + 1;
+  unsigned int sizeLen = ((buf[0] >> 2) & 0x3) + 1;
+  unsigned int timestampLen = ((buf[0] >> 4) & 0x7) + 1;
+  unsigned int headerLen = 1 + entryLen + sizeLen + timestampLen;
+  if (buf.size() < headerLen) {
+    return false;
+  }
+  int entry = ReadVarInt(buf.subspan(1, entryLen));
+  uint32_t size = ReadVarInt(buf.subspan(1 + entryLen, sizeLen));
+  if (size > (buf.size() - headerLen)) {
+    return false;
+  }
+  int64_t timestamp =
+      ReadVarInt(buf.subspan(1 + entryLen + sizeLen, timestampLen));
+  *out = DataLogRecord{entry, timestamp, buf.subspan(headerLen, size)};
+  *pos += headerLen + size;
+  return true;
+}
+
+bool DataLogReader::GetNextRecord(size_t* pos) const {
+  if (!m_buf) {
+    return false;
+  }
+  auto buf = m_buf->GetBuffer();
+  if (buf.size() < (*pos + 4)) {  // minimum header length
+    return false;
+  }
+  unsigned int entryLen = (buf[*pos] & 0x3) + 1;
+  unsigned int sizeLen = ((buf[*pos] >> 2) & 0x3) + 1;
+  unsigned int timestampLen = ((buf[*pos] >> 4) & 0x7) + 1;
+  unsigned int headerLen = 1 + entryLen + sizeLen + timestampLen;
+  if (buf.size() < (*pos + headerLen)) {
+    return false;
+  }
+  uint32_t size = ReadVarInt(buf.subspan(*pos + 1 + entryLen, sizeLen));
+  // check this way to avoid overflow
+  if (size >= (buf.size() - *pos - headerLen)) {
+    return false;
+  }
+  *pos += headerLen + size;
+  return true;
+}
diff --git a/wpiutil/src/main/native/cpp/DsClient.cpp b/wpiutil/src/main/native/cpp/DsClient.cpp
deleted file mode 100644
index 455f10c..0000000
--- a/wpiutil/src/main/native/cpp/DsClient.cpp
+++ /dev/null
@@ -1,107 +0,0 @@
-// Copyright (c) FIRST and other WPILib contributors.
-// Open Source Software; you can modify and/or share it under the terms of
-// the WPILib BSD license file in the root directory of this project.
-
-#include "wpi/DsClient.h"
-
-#include <fmt/format.h>
-#include <wpi/StringExtras.h>
-#include <wpi/json.h>
-#include <wpi/uv/Tcp.h>
-#include <wpi/uv/Timer.h>
-
-#include "wpi/Logger.h"
-
-using namespace wpi;
-
-static constexpr uv::Timer::Time kReconnectTime{500};
-
-DsClient::DsClient(wpi::uv::Loop& loop, wpi::Logger& logger,
-                   const private_init&)
-    : m_logger{logger},
-      m_tcp{uv::Tcp::Create(loop)},
-      m_timer{uv::Timer::Create(loop)} {
-  m_tcp->end.connect([this] {
-    WPI_DEBUG4(m_logger, "{}", "DS connection closed");
-    clearIp();
-    // try to connect again
-    m_tcp->Reuse([this] { m_timer->Start(kReconnectTime); });
-  });
-  m_tcp->data.connect([this](wpi::uv::Buffer buf, size_t len) {
-    HandleIncoming({buf.base, len});
-  });
-  m_timer->timeout.connect([this] { Connect(); });
-  Connect();
-}
-
-DsClient::~DsClient() = default;
-
-void DsClient::Close() {
-  m_tcp->Close();
-  m_timer->Close();
-  clearIp();
-}
-
-void DsClient::Connect() {
-  auto connreq = std::make_shared<uv::TcpConnectReq>();
-  connreq->connected.connect([this] {
-    m_json.clear();
-    m_tcp->StopRead();
-    m_tcp->StartRead();
-  });
-
-  connreq->error = [this](uv::Error err) {
-    WPI_DEBUG4(m_logger, "DS connect failure: {}", err.str());
-    // try to connect again
-    m_tcp->Reuse([this] { m_timer->Start(kReconnectTime); });
-  };
-
-  WPI_DEBUG4(m_logger, "{}", "Starting DS connection attempt");
-  m_tcp->Connect("127.0.0.1", 1742, connreq);
-}
-
-void DsClient::HandleIncoming(std::string_view in) {
-  // this is very bare-bones, as there are never nested {} in these messages
-  while (!in.empty()) {
-    // if json is empty, look for the first { (and discard)
-    if (m_json.empty()) {
-      auto start = in.find('{');
-      in = wpi::slice(in, start, std::string_view::npos);
-    }
-
-    // look for the terminating } (and save)
-    auto end = in.find('}');
-    if (end == std::string_view::npos) {
-      m_json.append(in);
-      return;  // nothing left to read
-    }
-
-    // have complete json message
-    ++end;
-    m_json.append(wpi::slice(in, 0, end));
-    in = wpi::slice(in, end, std::string_view::npos);
-    ParseJson();
-    m_json.clear();
-  }
-}
-
-void DsClient::ParseJson() {
-  WPI_DEBUG4(m_logger, "DsClient JSON: {}", m_json);
-  unsigned int ip = 0;
-  try {
-    ip = wpi::json::parse(m_json).at("robotIP").get<unsigned int>();
-  } catch (wpi::json::exception& e) {
-    WPI_INFO(m_logger, "DsClient JSON error: {}", e.what());
-    return;
-  }
-
-  if (ip == 0) {
-    clearIp();
-  } else {
-    // Convert number into dotted quad
-    auto newip = fmt::format("{}.{}.{}.{}", (ip >> 24) & 0xff,
-                             (ip >> 16) & 0xff, (ip >> 8) & 0xff, ip & 0xff);
-    WPI_INFO(m_logger, "DS received server IP: {}", newip);
-    setIp(newip);
-  }
-}
diff --git a/wpiutil/src/main/native/cpp/EventLoopRunner.cpp b/wpiutil/src/main/native/cpp/EventLoopRunner.cpp
deleted file mode 100644
index c86176e..0000000
--- a/wpiutil/src/main/native/cpp/EventLoopRunner.cpp
+++ /dev/null
@@ -1,90 +0,0 @@
-// Copyright (c) FIRST and other WPILib contributors.
-// Open Source Software; you can modify and/or share it under the terms of
-// the WPILib BSD license file in the root directory of this project.
-
-#include "wpi/EventLoopRunner.h"
-
-#include "wpi/SmallVector.h"
-#include "wpi/condition_variable.h"
-#include "wpi/mutex.h"
-#include "wpi/uv/AsyncFunction.h"
-#include "wpi/uv/Loop.h"
-
-using namespace wpi;
-
-class EventLoopRunner::Thread : public SafeThread {
- public:
-  using UvExecFunc = uv::AsyncFunction<void(LoopFunc)>;
-
-  Thread() : m_loop(uv::Loop::Create()) {
-    // set up async handles
-    if (!m_loop) {
-      return;
-    }
-
-    // run function
-    m_doExec = UvExecFunc::Create(
-        m_loop, [loop = m_loop.get()](auto out, LoopFunc func) {
-          func(*loop);
-          out.set_value();
-        });
-  }
-
-  void Main() override {
-    if (m_loop) {
-      m_loop->Run();
-    }
-  }
-
-  // the loop
-  std::shared_ptr<uv::Loop> m_loop;
-
-  // run function
-  std::weak_ptr<UvExecFunc> m_doExec;
-};
-
-EventLoopRunner::EventLoopRunner() {
-  m_owner.Start();
-}
-
-EventLoopRunner::~EventLoopRunner() {
-  Stop();
-}
-
-void EventLoopRunner::Stop() {
-  ExecAsync([](uv::Loop& loop) {
-    // close all handles; this will (eventually) stop the loop
-    loop.Walk([](uv::Handle& h) {
-      h.SetLoopClosing(true);
-      h.Close();
-    });
-  });
-  m_owner.Join();
-}
-
-void EventLoopRunner::ExecAsync(LoopFunc func) {
-  if (auto thr = m_owner.GetThread()) {
-    if (auto doExec = thr->m_doExec.lock()) {
-      doExec->Call(std::move(func));
-    }
-  }
-}
-
-void EventLoopRunner::ExecSync(LoopFunc func) {
-  wpi::future<void> f;
-  if (auto thr = m_owner.GetThread()) {
-    if (auto doExec = thr->m_doExec.lock()) {
-      f = doExec->Call(std::move(func));
-    }
-  }
-  if (f.valid()) {
-    f.wait();
-  }
-}
-
-std::shared_ptr<uv::Loop> EventLoopRunner::GetLoop() {
-  if (auto thr = m_owner.GetThread()) {
-    return thr->m_loop;
-  }
-  return nullptr;
-}
diff --git a/wpiutil/src/main/native/cpp/HttpParser.cpp b/wpiutil/src/main/native/cpp/HttpParser.cpp
deleted file mode 100644
index 3c18e0f..0000000
--- a/wpiutil/src/main/native/cpp/HttpParser.cpp
+++ /dev/null
@@ -1,193 +0,0 @@
-// Copyright (c) FIRST and other WPILib contributors.
-// Open Source Software; you can modify and/or share it under the terms of
-// the WPILib BSD license file in the root directory of this project.
-
-#include "wpi/HttpParser.h"
-
-using namespace wpi;
-
-uint32_t HttpParser::GetParserVersion() {
-  return static_cast<uint32_t>(http_parser_version());
-}
-
-HttpParser::HttpParser(Type type) {
-  http_parser_init(&m_parser,
-                   static_cast<http_parser_type>(static_cast<int>(type)));
-  m_parser.data = this;
-
-  http_parser_settings_init(&m_settings);
-
-  // Unlike the underlying http_parser library, we don't perform callbacks
-  // (other than body) with partial data; instead we buffer and call the user
-  // callback only when the data is complete.
-
-  // on_message_begin: initialize our state, call user callback
-  m_settings.on_message_begin = [](http_parser* p) -> int {
-    auto& self = *static_cast<HttpParser*>(p->data);
-    self.m_urlBuf.clear();
-    self.m_state = kStart;
-    self.messageBegin();
-    return self.m_aborted;
-  };
-
-  // on_url: collect into buffer
-  m_settings.on_url = [](http_parser* p, const char* at, size_t length) -> int {
-    auto& self = *static_cast<HttpParser*>(p->data);
-    // append to buffer
-    if ((self.m_urlBuf.size() + length) > self.m_maxLength) {
-      return 1;
-    }
-    self.m_urlBuf += std::string_view{at, length};
-    self.m_state = kUrl;
-    return 0;
-  };
-
-  // on_status: collect into buffer, call user URL callback
-  m_settings.on_status = [](http_parser* p, const char* at,
-                            size_t length) -> int {
-    auto& self = *static_cast<HttpParser*>(p->data);
-    // use valueBuf for the status
-    if ((self.m_valueBuf.size() + length) > self.m_maxLength) {
-      return 1;
-    }
-    self.m_valueBuf += std::string_view{at, length};
-    self.m_state = kStatus;
-    return 0;
-  };
-
-  // on_header_field: collect into buffer, call user header/status callback
-  m_settings.on_header_field = [](http_parser* p, const char* at,
-                                  size_t length) -> int {
-    auto& self = *static_cast<HttpParser*>(p->data);
-
-    // once we're in header, we know the URL is complete
-    if (self.m_state == kUrl) {
-      self.url(self.m_urlBuf);
-      if (self.m_aborted) {
-        return 1;
-      }
-    }
-
-    // once we're in header, we know the status is complete
-    if (self.m_state == kStatus) {
-      self.status(self.m_valueBuf);
-      if (self.m_aborted) {
-        return 1;
-      }
-    }
-
-    // if we previously were in value state, that means we finished a header
-    if (self.m_state == kValue) {
-      self.header(self.m_fieldBuf, self.m_valueBuf);
-      if (self.m_aborted) {
-        return 1;
-      }
-    }
-
-    // clear field and value when we enter this state
-    if (self.m_state != kField) {
-      self.m_state = kField;
-      self.m_fieldBuf.clear();
-      self.m_valueBuf.clear();
-    }
-
-    // append data to field buffer
-    if ((self.m_fieldBuf.size() + length) > self.m_maxLength) {
-      return 1;
-    }
-    self.m_fieldBuf += std::string_view{at, length};
-    return 0;
-  };
-
-  // on_header_field: collect into buffer
-  m_settings.on_header_value = [](http_parser* p, const char* at,
-                                  size_t length) -> int {
-    auto& self = *static_cast<HttpParser*>(p->data);
-
-    // if we weren't previously in value state, clear the buffer
-    if (self.m_state != kValue) {
-      self.m_state = kValue;
-      self.m_valueBuf.clear();
-    }
-
-    // append data to value buffer
-    if ((self.m_valueBuf.size() + length) > self.m_maxLength) {
-      return 1;
-    }
-    self.m_valueBuf += std::string_view{at, length};
-    return 0;
-  };
-
-  // on_headers_complete: call user status/header/complete callback
-  m_settings.on_headers_complete = [](http_parser* p) -> int {
-    auto& self = *static_cast<HttpParser*>(p->data);
-
-    // if we previously were in url state, that means we finished the url
-    if (self.m_state == kUrl) {
-      self.url(self.m_urlBuf);
-      if (self.m_aborted) {
-        return 1;
-      }
-    }
-
-    // if we previously were in status state, that means we finished the status
-    if (self.m_state == kStatus) {
-      self.status(self.m_valueBuf);
-      if (self.m_aborted) {
-        return 1;
-      }
-    }
-
-    // if we previously were in value state, that means we finished a header
-    if (self.m_state == kValue) {
-      self.header(self.m_fieldBuf, self.m_valueBuf);
-      if (self.m_aborted) {
-        return 1;
-      }
-    }
-
-    self.headersComplete(self.ShouldKeepAlive());
-    return self.m_aborted;
-  };
-
-  // on_body: call user callback
-  m_settings.on_body = [](http_parser* p, const char* at,
-                          size_t length) -> int {
-    auto& self = *static_cast<HttpParser*>(p->data);
-    self.body(std::string_view{at, length}, self.IsBodyFinal());
-    return self.m_aborted;
-  };
-
-  // on_message_complete: call user callback
-  m_settings.on_message_complete = [](http_parser* p) -> int {
-    auto& self = *static_cast<HttpParser*>(p->data);
-    self.messageComplete(self.ShouldKeepAlive());
-    return self.m_aborted;
-  };
-
-  // on_chunk_header: call user callback
-  m_settings.on_chunk_header = [](http_parser* p) -> int {
-    auto& self = *static_cast<HttpParser*>(p->data);
-    self.chunkHeader(p->content_length);
-    return self.m_aborted;
-  };
-
-  // on_chunk_complete: call user callback
-  m_settings.on_chunk_complete = [](http_parser* p) -> int {
-    auto& self = *static_cast<HttpParser*>(p->data);
-    self.chunkComplete();
-    return self.m_aborted;
-  };
-}
-
-void HttpParser::Reset(Type type) {
-  http_parser_init(&m_parser,
-                   static_cast<http_parser_type>(static_cast<int>(type)));
-  m_parser.data = this;
-  m_maxLength = 1024;
-  m_state = kStart;
-  m_urlBuf.clear();
-  m_fieldBuf.clear();
-  m_valueBuf.clear();
-  m_aborted = false;
-}
diff --git a/wpiutil/src/main/native/cpp/HttpServerConnection.cpp b/wpiutil/src/main/native/cpp/HttpServerConnection.cpp
deleted file mode 100644
index 716d2af..0000000
--- a/wpiutil/src/main/native/cpp/HttpServerConnection.cpp
+++ /dev/null
@@ -1,178 +0,0 @@
-// Copyright (c) FIRST and other WPILib contributors.
-// Open Source Software; you can modify and/or share it under the terms of
-// the WPILib BSD license file in the root directory of this project.
-
-#include "wpi/HttpServerConnection.h"
-
-#include "fmt/format.h"
-#include "wpi/SmallString.h"
-#include "wpi/SmallVector.h"
-#include "wpi/SpanExtras.h"
-#include "wpi/StringExtras.h"
-#include "wpi/fmt/raw_ostream.h"
-#include "wpi/raw_uv_ostream.h"
-
-using namespace wpi;
-
-HttpServerConnection::HttpServerConnection(std::shared_ptr<uv::Stream> stream)
-    : m_stream(*stream) {
-  // process HTTP messages
-  m_messageCompleteConn =
-      m_request.messageComplete.connect_connection([this](bool keepAlive) {
-        m_keepAlive = keepAlive;
-        ProcessRequest();
-      });
-
-  // look for Accept-Encoding headers to determine if gzip is acceptable
-  m_request.messageBegin.connect([this] { m_acceptGzip = false; });
-  m_request.header.connect(
-      [this](std::string_view name, std::string_view value) {
-        if (wpi::equals_lower(name, "accept-encoding") &&
-            wpi::contains(value, "gzip")) {
-          m_acceptGzip = true;
-        }
-      });
-
-  // pass incoming data to HTTP parser
-  m_dataConn =
-      stream->data.connect_connection([this](uv::Buffer& buf, size_t size) {
-        m_request.Execute({buf.base, size});
-        if (m_request.HasError()) {
-          // could not parse; just close the connection
-          m_stream.Close();
-        }
-      });
-
-  // close when remote side closes
-  m_endConn =
-      stream->end.connect_connection([h = stream.get()] { h->Close(); });
-
-  // start reading
-  stream->StartRead();
-}
-
-void HttpServerConnection::BuildCommonHeaders(raw_ostream& os) {
-  os << "Server: WebServer/1.0\r\n"
-        "Cache-Control: no-store, no-cache, must-revalidate, pre-check=0, "
-        "post-check=0, max-age=0\r\n"
-        "Pragma: no-cache\r\n"
-        "Expires: Mon, 3 Jan 2000 12:34:56 GMT\r\n";
-}
-
-void HttpServerConnection::BuildHeader(raw_ostream& os, int code,
-                                       std::string_view codeText,
-                                       std::string_view contentType,
-                                       uint64_t contentLength,
-                                       std::string_view extra) {
-  fmt::print(os, "HTTP/{}.{} {} {}\r\n", m_request.GetMajor(),
-             m_request.GetMinor(), code, codeText);
-  if (contentLength == 0) {
-    m_keepAlive = false;
-  }
-  if (!m_keepAlive) {
-    os << "Connection: close\r\n";
-  }
-  BuildCommonHeaders(os);
-  os << "Content-Type: " << contentType << "\r\n";
-  if (contentLength != 0) {
-    fmt::print(os, "Content-Length: {}\r\n", contentLength);
-  }
-  os << "Access-Control-Allow-Origin: *\r\nAccess-Control-Allow-Methods: *\r\n";
-  if (!extra.empty()) {
-    os << extra;
-  }
-  os << "\r\n";  // header ends with a blank line
-}
-
-void HttpServerConnection::SendData(span<const uv::Buffer> bufs,
-                                    bool closeAfter) {
-  m_stream.Write(bufs, [closeAfter, stream = &m_stream](auto bufs, uv::Error) {
-    for (auto&& buf : bufs) {
-      buf.Deallocate();
-    }
-    if (closeAfter) {
-      stream->Close();
-    }
-  });
-}
-
-void HttpServerConnection::SendResponse(int code, std::string_view codeText,
-                                        std::string_view contentType,
-                                        std::string_view content,
-                                        std::string_view extraHeader) {
-  SmallVector<uv::Buffer, 4> toSend;
-  raw_uv_ostream os{toSend, 4096};
-  BuildHeader(os, code, codeText, contentType, content.size(), extraHeader);
-  os << content;
-  // close after write completes if we aren't keeping alive
-  SendData(os.bufs(), !m_keepAlive);
-}
-
-void HttpServerConnection::SendStaticResponse(
-    int code, std::string_view codeText, std::string_view contentType,
-    std::string_view content, bool gzipped, std::string_view extraHeader) {
-  // TODO: handle remote side not accepting gzip (very rare)
-
-  std::string_view contentEncodingHeader;
-  if (gzipped /* && m_acceptGzip*/) {
-    contentEncodingHeader = "Content-Encoding: gzip\r\n";
-  }
-
-  SmallVector<uv::Buffer, 4> bufs;
-  raw_uv_ostream os{bufs, 4096};
-  BuildHeader(os, code, codeText, contentType, content.size(),
-              fmt::format("{}{}", extraHeader, contentEncodingHeader));
-  // can send content without copying
-  bufs.emplace_back(content);
-
-  m_stream.Write(bufs, [closeAfter = !m_keepAlive, stream = &m_stream](
-                           auto bufs, uv::Error) {
-    // don't deallocate the static content
-    for (auto&& buf : wpi::drop_back(bufs)) {
-      buf.Deallocate();
-    }
-    if (closeAfter) {
-      stream->Close();
-    }
-  });
-}
-
-void HttpServerConnection::SendError(int code, std::string_view message) {
-  std::string_view codeText, extra, baseMessage;
-  switch (code) {
-    case 401:
-      codeText = "Unauthorized";
-      extra = "WWW-Authenticate: Basic realm=\"CameraServer\"";
-      baseMessage = "401: Not Authenticated!";
-      break;
-    case 404:
-      codeText = "Not Found";
-      baseMessage = "404: Not Found!";
-      break;
-    case 500:
-      codeText = "Internal Server Error";
-      baseMessage = "500: Internal Server Error!";
-      break;
-    case 400:
-      codeText = "Bad Request";
-      baseMessage = "400: Not Found!";
-      break;
-    case 403:
-      codeText = "Forbidden";
-      baseMessage = "403: Forbidden!";
-      break;
-    case 503:
-      codeText = "Service Unavailable";
-      baseMessage = "503: Service Unavailable";
-      break;
-    default:
-      code = 501;
-      codeText = "Not Implemented";
-      baseMessage = "501: Not Implemented!";
-      break;
-  }
-  SmallString<256> content{baseMessage};
-  content += "\r\n";
-  content += message;
-  SendResponse(code, codeText, "text/plain", content.str(), extra);
-}
diff --git a/wpiutil/src/main/native/cpp/HttpUtil.cpp b/wpiutil/src/main/native/cpp/HttpUtil.cpp
deleted file mode 100644
index b8b7cc8..0000000
--- a/wpiutil/src/main/native/cpp/HttpUtil.cpp
+++ /dev/null
@@ -1,493 +0,0 @@
-// Copyright (c) FIRST and other WPILib contributors.
-// Open Source Software; you can modify and/or share it under the terms of
-// the WPILib BSD license file in the root directory of this project.
-
-#include "wpi/HttpUtil.h"
-
-#include <cctype>
-
-#include "fmt/format.h"
-#include "wpi/Base64.h"
-#include "wpi/StringExtras.h"
-#include "wpi/TCPConnector.h"
-#include "wpi/raw_ostream.h"
-
-namespace wpi {
-
-std::string_view UnescapeURI(std::string_view str, SmallVectorImpl<char>& buf,
-                             bool* error) {
-  buf.clear();
-  for (auto i = str.begin(), end = str.end(); i != end; ++i) {
-    // pass non-escaped characters to output
-    if (*i != '%') {
-      // decode + to space
-      if (*i == '+') {
-        buf.push_back(' ');
-      } else {
-        buf.push_back(*i);
-      }
-      continue;
-    }
-
-    // are there enough characters left?
-    if (i + 2 >= end) {
-      *error = true;
-      return {};
-    }
-
-    // replace %xx with the corresponding character
-    unsigned val1 = hexDigitValue(*++i);
-    if (val1 == -1U) {
-      *error = true;
-      return {};
-    }
-    unsigned val2 = hexDigitValue(*++i);
-    if (val2 == -1U) {
-      *error = true;
-      return {};
-    }
-    buf.push_back((val1 << 4) | val2);
-  }
-
-  *error = false;
-  return {buf.data(), buf.size()};
-}
-
-std::string_view EscapeURI(std::string_view str, SmallVectorImpl<char>& buf,
-                           bool spacePlus) {
-  static const char* const hexLut = "0123456789ABCDEF";
-
-  buf.clear();
-  for (auto i = str.begin(), end = str.end(); i != end; ++i) {
-    // pass unreserved characters to output
-    if (std::isalnum(*i) || *i == '-' || *i == '_' || *i == '.' || *i == '~') {
-      buf.push_back(*i);
-      continue;
-    }
-
-    // encode space to +
-    if (spacePlus && *i == ' ') {
-      buf.push_back('+');
-      continue;
-    }
-
-    // convert others to %xx
-    buf.push_back('%');
-    buf.push_back(hexLut[((*i) >> 4) & 0x0f]);
-    buf.push_back(hexLut[(*i) & 0x0f]);
-  }
-
-  return {buf.data(), buf.size()};
-}
-
-HttpQueryMap::HttpQueryMap(std::string_view query) {
-  SmallVector<std::string_view, 16> queryElems;
-  split(query, queryElems, '&', 100, false);
-  for (auto elem : queryElems) {
-    auto [nameEsc, valueEsc] = split(elem, '=');
-    SmallString<64> nameBuf;
-    bool err = false;
-    auto name = wpi::UnescapeURI(nameEsc, nameBuf, &err);
-    // note: ignores duplicates
-    if (!err) {
-      m_elems.try_emplace(name, valueEsc);
-    }
-  }
-}
-
-std::optional<std::string_view> HttpQueryMap::Get(
-    std::string_view name, wpi::SmallVectorImpl<char>& buf) const {
-  auto it = m_elems.find(name);
-  if (it == m_elems.end()) {
-    return {};
-  }
-  bool err = false;
-  auto val = wpi::UnescapeURI(it->second, buf, &err);
-  if (err) {
-    return {};
-  }
-  return val;
-}
-
-HttpPath::HttpPath(std::string_view path) {
-  // special-case root path to be a single empty element
-  if (path == "/") {
-    m_pathEnds.emplace_back(0);
-    return;
-  }
-  wpi::SmallVector<std::string_view, 16> pathElems;
-  split(path, pathElems, '/', 100, false);
-  for (auto elem : pathElems) {
-    SmallString<64> buf;
-    bool err = false;
-    auto val = wpi::UnescapeURI(elem, buf, &err);
-    if (err) {
-      m_pathEnds.clear();
-      return;
-    }
-    m_pathBuf += val;
-    m_pathEnds.emplace_back(m_pathBuf.size());
-  }
-}
-
-bool HttpPath::startswith(size_t start,
-                          span<const std::string_view> match) const {
-  if (m_pathEnds.size() < (start + match.size())) {
-    return false;
-  }
-  bool first = start == 0;
-  auto p = m_pathEnds.begin() + start;
-  for (auto m : match) {
-    auto val = slice(m_pathBuf, first ? 0 : *(p - 1), *p);
-    if (val != m) {
-      return false;
-    }
-    first = false;
-    ++p;
-  }
-  return true;
-}
-
-std::string_view HttpPath::operator[](size_t n) const {
-  return slice(m_pathBuf, n == 0 ? 0 : m_pathEnds[n - 1], m_pathEnds[n]);
-}
-
-bool ParseHttpHeaders(raw_istream& is, SmallVectorImpl<char>* contentType,
-                      SmallVectorImpl<char>* contentLength) {
-  if (contentType) {
-    contentType->clear();
-  }
-  if (contentLength) {
-    contentLength->clear();
-  }
-
-  bool inContentType = false;
-  bool inContentLength = false;
-  SmallString<64> lineBuf;
-  for (;;) {
-    std::string_view line = rtrim(is.getline(lineBuf, 1024));
-    if (is.has_error()) {
-      return false;
-    }
-    if (line.empty()) {
-      return true;  // empty line signals end of headers
-    }
-
-    // header fields start at the beginning of the line
-    if (!std::isspace(line[0])) {
-      inContentType = false;
-      inContentLength = false;
-      std::string_view field;
-      std::tie(field, line) = split(line, ':');
-      field = rtrim(field);
-      if (equals_lower(field, "content-type")) {
-        inContentType = true;
-      } else if (equals_lower(field, "content-length")) {
-        inContentLength = true;
-      } else {
-        continue;  // ignore other fields
-      }
-    }
-
-    // collapse whitespace
-    line = ltrim(line);
-
-    // save field data
-    if (inContentType && contentType) {
-      contentType->append(line.begin(), line.end());
-    } else if (inContentLength && contentLength) {
-      contentLength->append(line.begin(), line.end());
-    }
-  }
-}
-
-bool FindMultipartBoundary(raw_istream& is, std::string_view boundary,
-                           std::string* saveBuf) {
-  SmallString<64> searchBuf;
-  searchBuf.resize(boundary.size() + 2);
-  size_t searchPos = 0;
-
-  // Per the spec, the --boundary should be preceded by \r\n, so do a first
-  // pass of 1-byte reads to throw those away (common case) and keep the
-  // last non-\r\n character in searchBuf.
-  if (!saveBuf) {
-    do {
-      is.read(searchBuf.data(), 1);
-      if (is.has_error()) {
-        return false;
-      }
-    } while (searchBuf[0] == '\r' || searchBuf[0] == '\n');
-    searchPos = 1;
-  }
-
-  // Look for --boundary.  Read boundarysize+2 bytes at a time
-  // during the search to speed up the reads, then fast-scan for -,
-  // and only then match the entire boundary.  This will be slow if
-  // there's a bunch of continuous -'s in the output, but that's unlikely.
-  for (;;) {
-    is.read(searchBuf.data() + searchPos, searchBuf.size() - searchPos);
-    if (is.has_error()) {
-      return false;
-    }
-
-    // Did we find the boundary?
-    if (searchBuf[0] == '-' && searchBuf[1] == '-' &&
-        wpi::substr(searchBuf, 2) == boundary) {
-      return true;
-    }
-
-    // Fast-scan for '-'
-    size_t pos = searchBuf.find('-', searchBuf[0] == '-' ? 1 : 0);
-    if (pos == std::string_view::npos) {
-      if (saveBuf) {
-        saveBuf->append(searchBuf.data(), searchBuf.size());
-      }
-    } else {
-      if (saveBuf) {
-        saveBuf->append(searchBuf.data(), pos);
-      }
-
-      // move '-' and following to start of buffer (next read will fill)
-      std::memmove(searchBuf.data(), searchBuf.data() + pos,
-                   searchBuf.size() - pos);
-      searchPos = searchBuf.size() - pos;
-    }
-  }
-}
-
-HttpLocation::HttpLocation(std::string_view url_, bool* error,
-                           std::string* errorMsg)
-    : url{url_} {
-  // Split apart into components
-  std::string_view query{url};
-
-  // scheme:
-  std::string_view scheme;
-  std::tie(scheme, query) = split(query, ':');
-  if (!equals_lower(scheme, "http")) {
-    *errorMsg = "only supports http URLs";
-    *error = true;
-    return;
-  }
-
-  // "//"
-  if (!starts_with(query, "//")) {
-    *errorMsg = "expected http://...";
-    *error = true;
-    return;
-  }
-  query.remove_prefix(2);
-
-  // user:password@host:port/
-  std::string_view authority;
-  std::tie(authority, query) = split(query, '/');
-
-  auto [userpass, hostport] = split(authority, '@');
-  // split leaves the RHS empty if the split char isn't present...
-  if (hostport.empty()) {
-    hostport = userpass;
-    userpass = {};
-  }
-
-  if (!userpass.empty()) {
-    auto [rawUser, rawPassword] = split(userpass, ':');
-    SmallString<64> userBuf, passBuf;
-    user = UnescapeURI(rawUser, userBuf, error);
-    if (*error) {
-      *errorMsg = fmt::format("could not unescape user \"{}\"", rawUser);
-      return;
-    }
-    password = UnescapeURI(rawPassword, passBuf, error);
-    if (*error) {
-      *errorMsg =
-          fmt::format("could not unescape password \"{}\"", rawPassword);
-      return;
-    }
-  }
-
-  std::string_view portStr;
-  std::tie(host, portStr) = rsplit(hostport, ':');
-  if (host.empty()) {
-    *errorMsg = "host is empty";
-    *error = true;
-    return;
-  }
-  if (portStr.empty()) {
-    port = 80;
-  } else if (auto p = parse_integer<int>(portStr, 10)) {
-    port = p.value();
-  } else {
-    *errorMsg = fmt::format("port \"{}\" is not an integer", portStr);
-    *error = true;
-    return;
-  }
-
-  // path?query#fragment
-  std::tie(query, fragment) = split(query, '#');
-  std::tie(path, query) = split(query, '?');
-
-  // Split query string into parameters
-  while (!query.empty()) {
-    // split out next param and value
-    std::string_view rawParam, rawValue;
-    std::tie(rawParam, query) = split(query, '&');
-    if (rawParam.empty()) {
-      continue;  // ignore "&&"
-    }
-    std::tie(rawParam, rawValue) = split(rawParam, '=');
-
-    // unescape param
-    *error = false;
-    SmallString<64> paramBuf;
-    std::string_view param = UnescapeURI(rawParam, paramBuf, error);
-    if (*error) {
-      *errorMsg = fmt::format("could not unescape parameter \"{}\"", rawParam);
-      return;
-    }
-
-    // unescape value
-    SmallString<64> valueBuf;
-    std::string_view value = UnescapeURI(rawValue, valueBuf, error);
-    if (*error) {
-      *errorMsg = fmt::format("could not unescape value \"{}\"", rawValue);
-      return;
-    }
-
-    params.emplace_back(std::make_pair(param, value));
-  }
-
-  *error = false;
-}
-
-void HttpRequest::SetAuth(const HttpLocation& loc) {
-  if (!loc.user.empty()) {
-    SmallString<64> userpass;
-    userpass += loc.user;
-    userpass += ':';
-    userpass += loc.password;
-    Base64Encode(userpass.str(), &auth);
-  }
-}
-
-bool HttpConnection::Handshake(const HttpRequest& request,
-                               std::string* warnMsg) {
-  // send GET request
-  os << "GET /" << request.path << " HTTP/1.1\r\n";
-  os << "Host: " << request.host << "\r\n";
-  if (!request.auth.empty()) {
-    os << "Authorization: Basic " << request.auth << "\r\n";
-  }
-  os << "\r\n";
-  os.flush();
-
-  // read first line of response
-  SmallString<64> lineBuf;
-  std::string_view line = rtrim(is.getline(lineBuf, 1024));
-  if (is.has_error()) {
-    *warnMsg = "disconnected before response";
-    return false;
-  }
-
-  // see if we got a HTTP 200 response
-  std::string_view httpver, code, codeText;
-  std::tie(httpver, line) = split(line, ' ');
-  std::tie(code, codeText) = split(line, ' ');
-  if (!starts_with(httpver, "HTTP")) {
-    *warnMsg = "did not receive HTTP response";
-    return false;
-  }
-  if (code != "200") {
-    *warnMsg = fmt::format("received {} {} response", code, codeText);
-    return false;
-  }
-
-  // Parse headers
-  if (!ParseHttpHeaders(is, &contentType, &contentLength)) {
-    *warnMsg = "disconnected during headers";
-    return false;
-  }
-
-  return true;
-}
-
-void HttpMultipartScanner::SetBoundary(std::string_view boundary) {
-  m_boundaryWith = "\n--";
-  m_boundaryWith += boundary;
-  m_boundaryWithout = "\n";
-  m_boundaryWithout += boundary;
-  m_dashes = kUnknown;
-}
-
-void HttpMultipartScanner::Reset(bool saveSkipped) {
-  m_saveSkipped = saveSkipped;
-  m_state = kBoundary;
-  m_posWith = 0;
-  m_posWithout = 0;
-  m_buf.resize(0);
-}
-
-std::string_view HttpMultipartScanner::Execute(std::string_view in) {
-  if (m_state == kDone) {
-    Reset(m_saveSkipped);
-  }
-  if (m_saveSkipped) {
-    m_buf += in;
-  }
-
-  size_t pos = 0;
-  if (m_state == kBoundary) {
-    for (char ch : in) {
-      ++pos;
-      if (m_dashes != kWithout) {
-        if (ch == m_boundaryWith[m_posWith]) {
-          ++m_posWith;
-          if (m_posWith == m_boundaryWith.size()) {
-            // Found the boundary; transition to padding
-            m_state = kPadding;
-            m_dashes = kWith;  // no longer accept plain 'boundary'
-            break;
-          }
-        } else if (ch == m_boundaryWith[0]) {
-          m_posWith = 1;
-        } else {
-          m_posWith = 0;
-        }
-      }
-
-      if (m_dashes != kWith) {
-        if (ch == m_boundaryWithout[m_posWithout]) {
-          ++m_posWithout;
-          if (m_posWithout == m_boundaryWithout.size()) {
-            // Found the boundary; transition to padding
-            m_state = kPadding;
-            m_dashes = kWithout;  // no longer accept '--boundary'
-            break;
-          }
-        } else if (ch == m_boundaryWithout[0]) {
-          m_posWithout = 1;
-        } else {
-          m_posWithout = 0;
-        }
-      }
-    }
-  }
-
-  if (m_state == kPadding) {
-    for (char ch : drop_front(in, pos)) {
-      ++pos;
-      if (ch == '\n') {
-        // Found the LF; return remaining input buffer (following it)
-        m_state = kDone;
-        if (m_saveSkipped) {
-          m_buf.resize(m_buf.size() - in.size() + pos);
-        }
-        return drop_front(in, pos);
-      }
-    }
-  }
-
-  // We consumed the entire input
-  return {};
-}
-
-}  // namespace wpi
diff --git a/wpiutil/src/main/native/cpp/MappedFileRegion.cpp b/wpiutil/src/main/native/cpp/MappedFileRegion.cpp
new file mode 100644
index 0000000..006e395
--- /dev/null
+++ b/wpiutil/src/main/native/cpp/MappedFileRegion.cpp
@@ -0,0 +1,133 @@
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
+
+#include "wpi/MappedFileRegion.h"
+
+#include <sys/types.h>
+
+#ifdef _WIN32
+#ifndef WIN32_LEAN_AND_MEAN
+#define WIN32_LEAN_AND_MEAN
+#endif
+
+#include <windows.h>  // NOLINT(build/include_order)
+
+#include <memoryapi.h>
+#include <sysinfoapi.h>
+
+#else  // _WIN32
+
+#include <sys/mman.h>
+#include <unistd.h>
+
+#endif  // _WIN32
+
+#ifdef _MSC_VER
+#include <io.h>
+#endif
+
+#ifdef _WIN32
+#include "wpi/WindowsError.h"
+#endif
+
+using namespace wpi;
+
+MappedFileRegion::MappedFileRegion(fs::file_t f, uint64_t length,
+                                   uint64_t offset, MapMode mapMode,
+                                   std::error_code& ec)
+    : m_size(length) {
+#ifdef _WIN32
+  if (f == INVALID_HANDLE_VALUE) {
+    ec = std::make_error_code(std::errc::bad_file_descriptor);
+    return;
+  }
+
+  HANDLE fileMappingHandle = ::CreateFileMappingW(
+      f, 0, mapMode == kReadOnly ? PAGE_READONLY : PAGE_READWRITE, length >> 32,
+      length & 0xffffffff, 0);
+  if (fileMappingHandle == nullptr) {
+    ec = wpi::mapWindowsError(GetLastError());
+    return;
+  }
+
+  DWORD dwDesiredAccess = 0;
+  switch (mapMode) {
+    case kReadOnly:
+      dwDesiredAccess = FILE_MAP_READ;
+      break;
+    case kReadWrite:
+      dwDesiredAccess = FILE_MAP_WRITE;
+      break;
+    case kPriv:
+      dwDesiredAccess = FILE_MAP_WRITE | FILE_MAP_COPY;
+      break;
+  }
+  m_mapping = ::MapViewOfFile(fileMappingHandle, dwDesiredAccess, offset >> 32,
+                              offset & 0xffffffff, length);
+  if (m_mapping == nullptr) {
+    ec = wpi::mapWindowsError(GetLastError());
+    ::CloseHandle(fileMappingHandle);
+    return;
+  }
+
+  // Close the file mapping handle, as it's kept alive by the file mapping. But
+  // neither the file mapping nor the file mapping handle keep the file handle
+  // alive, so we need to keep a reference to the file in case all other handles
+  // are closed and the file is deleted, which may cause invalid data to be read
+  // from the file.
+  ::CloseHandle(fileMappingHandle);
+  if (!::DuplicateHandle(::GetCurrentProcess(), f, ::GetCurrentProcess(),
+                         &m_fileHandle, 0, 0, DUPLICATE_SAME_ACCESS)) {
+    ec = wpi::mapWindowsError(GetLastError());
+    ::UnmapViewOfFile(m_mapping);
+    m_mapping = nullptr;
+    return;
+  }
+#else
+  m_mapping =
+      ::mmap(nullptr, length,
+             mapMode == kReadOnly ? PROT_READ : (PROT_READ | PROT_WRITE),
+             mapMode == kPriv ? MAP_PRIVATE : MAP_SHARED, f, offset);
+  if (m_mapping == MAP_FAILED) {
+    ec = std::error_code(errno, std::generic_category());
+    m_mapping = nullptr;
+  }
+#endif
+}
+
+void MappedFileRegion::Flush() {
+#ifdef _WIN32
+  ::FlushViewOfFile(m_mapping, 0);
+  ::FlushFileBuffers(m_fileHandle);
+#else
+  ::msync(m_mapping, m_size, MS_ASYNC);
+#endif
+}
+
+void MappedFileRegion::Unmap() {
+  if (!m_mapping) {
+    return;
+  }
+#ifdef _WIN32
+  ::UnmapViewOfFile(m_mapping);
+  ::CloseHandle(m_fileHandle);
+#else
+  ::munmap(m_mapping, m_size);
+#endif
+  m_mapping = nullptr;
+}
+
+size_t MappedFileRegion::GetAlignment() {
+#ifdef _WIN32
+  SYSTEM_INFO SysInfo;
+  ::GetSystemInfo(&SysInfo);
+  return SysInfo.dwAllocationGranularity;
+#else
+  static long pageSize = ::getpagesize();  // NOLINT
+  if (pageSize < 0) {
+    pageSize = 4096;
+  }
+  return pageSize;
+#endif
+}
diff --git a/wpiutil/src/main/native/cpp/MessagePack.cpp b/wpiutil/src/main/native/cpp/MessagePack.cpp
new file mode 100644
index 0000000..60a2f01
--- /dev/null
+++ b/wpiutil/src/main/native/cpp/MessagePack.cpp
@@ -0,0 +1,45 @@
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
+
+#include "wpi/MessagePack.h"
+
+using namespace mpack;
+
+mpack_error_t mpack::mpack_expect_str(mpack_reader_t* reader, std::string* out,
+                                      uint32_t maxLen) {
+  uint32_t count = mpack_expect_str_max(reader, maxLen);
+  mpack_error_t err = mpack_reader_error(reader);
+  if (err != mpack_ok) {
+    return err;
+  }
+  const char* bytes = mpack_read_bytes_inplace(reader, count);
+  if (bytes) {
+    out->assign(bytes, count);
+  } else {
+    return mpack_reader_error(reader);
+  }
+  mpack_done_str(reader);
+  return mpack_ok;
+}
+
+mpack_error_t mpack::mpack_read_str(mpack_reader_t* reader, mpack_tag_t* tag,
+                                    std::string* out, uint32_t maxLen) {
+  uint32_t count = mpack_tag_str_length(tag);
+  mpack_error_t err = mpack_reader_error(reader);
+  if (err != mpack_ok) {
+    return err;
+  }
+  if (count > maxLen) {
+    mpack_reader_flag_error(reader, mpack_error_too_big);
+    return mpack_error_too_big;
+  }
+  const char* bytes = mpack_read_bytes_inplace(reader, count);
+  if (bytes) {
+    out->assign(bytes, count);
+  } else {
+    return mpack_reader_error(reader);
+  }
+  mpack_done_str(reader);
+  return mpack_ok;
+}
diff --git a/wpiutil/src/main/native/cpp/MimeTypes.cpp b/wpiutil/src/main/native/cpp/MimeTypes.cpp
deleted file mode 100644
index 5f5bf59..0000000
--- a/wpiutil/src/main/native/cpp/MimeTypes.cpp
+++ /dev/null
@@ -1,69 +0,0 @@
-// Copyright (c) FIRST and other WPILib contributors.
-// Open Source Software; you can modify and/or share it under the terms of
-// the WPILib BSD license file in the root directory of this project.
-
-#include "wpi/MimeTypes.h"
-
-#include "wpi/StringExtras.h"
-#include "wpi/StringMap.h"
-
-namespace wpi {
-
-// derived partially from
-// https://github.com/DEGoodmanWilson/libmime/blob/stable/0.1.2/mime/mime.cpp
-std::string_view MimeTypeFromPath(std::string_view path) {
-  // https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types
-  static StringMap<const char*> mimeTypes{
-      // text
-      {"css", "text/css"},
-      {"csv", "text/csv"},
-      {"htm", "text/html"},
-      {"html", "text/html"},
-      {"js", "text/javascript"},
-      {"json", "application/json"},
-      {"map", "application/json"},
-      {"md", "text/markdown"},
-      {"txt", "text/plain"},
-      {"xml", "text/xml"},
-
-      // images
-      {"apng", "image/apng"},
-      {"bmp", "image/bmp"},
-      {"gif", "image/gif"},
-      {"cur", "image/x-icon"},
-      {"ico", "image/x-icon"},
-      {"jpg", "image/jpeg"},
-      {"jpeg", "image/jpeg"},
-      {"png", "image/png"},
-      {"svg", "image/svg+xml"},
-      {"tif", "image/tiff"},
-      {"tiff", "image/tiff"},
-      {"webp", "image/webp"},
-
-      // fonts
-      {"otf", "font/otf"},
-      {"ttf", "font/ttf"},
-      {"woff", "font/woff"},
-
-      // misc
-      {"pdf", "application/pdf"},
-      {"zip", "application/zip"},
-  };
-
-  static const char* defaultType = "application/octet-stream";
-
-  auto pos = path.find_last_of("/");
-  if (pos != std::string_view::npos) {
-    path = wpi::substr(path, pos + 1);
-  }
-  auto dot_pos = path.find_last_of(".");
-  if (dot_pos > 0 && dot_pos != std::string_view::npos) {
-    auto type = mimeTypes.find(wpi::substr(path, dot_pos + 1));
-    if (type != mimeTypes.end()) {
-      return type->getValue();
-    }
-  }
-  return defaultType;
-}
-
-}  // namespace wpi
diff --git a/wpiutil/src/main/native/cpp/MulticastHandleManager.cpp b/wpiutil/src/main/native/cpp/MulticastHandleManager.cpp
deleted file mode 100644
index d249a1c..0000000
--- a/wpiutil/src/main/native/cpp/MulticastHandleManager.cpp
+++ /dev/null
@@ -1,12 +0,0 @@
-// Copyright (c) FIRST and other WPILib contributors.
-// Open Source Software; you can modify and/or share it under the terms of
-// the WPILib BSD license file in the root directory of this project.
-
-#include "MulticastHandleManager.h"
-
-using namespace wpi;
-
-MulticastHandleManager& wpi::GetMulticastManager() {
-  static MulticastHandleManager manager;
-  return manager;
-}
diff --git a/wpiutil/src/main/native/cpp/MulticastHandleManager.h b/wpiutil/src/main/native/cpp/MulticastHandleManager.h
deleted file mode 100644
index be8d061..0000000
--- a/wpiutil/src/main/native/cpp/MulticastHandleManager.h
+++ /dev/null
@@ -1,25 +0,0 @@
-// Copyright (c) FIRST and other WPILib contributors.
-// Open Source Software; you can modify and/or share it under the terms of
-// the WPILib BSD license file in the root directory of this project.
-
-#pragma once
-
-#include <memory>
-
-#include "wpi/DenseMap.h"
-#include "wpi/MulticastServiceAnnouncer.h"
-#include "wpi/MulticastServiceResolver.h"
-#include "wpi/UidVector.h"
-
-namespace wpi {
-struct MulticastHandleManager {
-  wpi::mutex mutex;
-  wpi::UidVector<int, 8> handleIds;
-  wpi::DenseMap<size_t, std::unique_ptr<wpi::MulticastServiceResolver>>
-      resolvers;
-  wpi::DenseMap<size_t, std::unique_ptr<wpi::MulticastServiceAnnouncer>>
-      announcers;
-};
-
-MulticastHandleManager& GetMulticastManager();
-}  // namespace wpi
diff --git a/wpiutil/src/main/native/cpp/MulticastServiceAnnouncer.cpp b/wpiutil/src/main/native/cpp/MulticastServiceAnnouncer.cpp
deleted file mode 100644
index 736a03d..0000000
--- a/wpiutil/src/main/native/cpp/MulticastServiceAnnouncer.cpp
+++ /dev/null
@@ -1,67 +0,0 @@
-// Copyright (c) FIRST and other WPILib contributors.
-// Open Source Software; you can modify and/or share it under the terms of
-// the WPILib BSD license file in the root directory of this project.
-
-#include "wpi/MulticastServiceAnnouncer.h"
-
-#include <wpi/SmallVector.h>
-
-#include "MulticastHandleManager.h"
-
-extern "C" {
-WPI_MulticastServiceAnnouncerHandle WPI_CreateMulticastServiceAnnouncer(
-    const char* serviceName, const char* serviceType, int32_t port,
-    int32_t txtCount, const char** keys, const char** values)
-
-{
-  auto& manager = wpi::GetMulticastManager();
-  std::scoped_lock lock{manager.mutex};
-
-  wpi::SmallVector<std::pair<std::string_view, std::string_view>, 8> txts;
-
-  for (int32_t i = 0; i < txtCount; i++) {
-    txts.emplace_back(
-        std::pair<std::string_view, std::string_view>{keys[i], values[i]});
-  }
-
-  auto announcer = std::make_unique<wpi::MulticastServiceAnnouncer>(
-      serviceName, serviceType, port, txts);
-
-  size_t index = manager.handleIds.emplace_back(3);
-  manager.announcers[index] = std::move(announcer);
-
-  return index;
-}
-
-void WPI_FreeMulticastServiceAnnouncer(
-    WPI_MulticastServiceAnnouncerHandle handle) {
-  auto& manager = wpi::GetMulticastManager();
-  std::scoped_lock lock{manager.mutex};
-  manager.announcers[handle] = nullptr;
-  manager.handleIds.erase(handle);
-}
-
-void WPI_StartMulticastServiceAnnouncer(
-    WPI_MulticastServiceAnnouncerHandle handle) {
-  auto& manager = wpi::GetMulticastManager();
-  std::scoped_lock lock{manager.mutex};
-  auto& announcer = manager.announcers[handle];
-  announcer->Start();
-}
-
-void WPI_StopMulticastServiceAnnouncer(
-    WPI_MulticastServiceAnnouncerHandle handle) {
-  auto& manager = wpi::GetMulticastManager();
-  std::scoped_lock lock{manager.mutex};
-  auto& announcer = manager.announcers[handle];
-  announcer->Stop();
-}
-
-int32_t WPI_GetMulticastServiceAnnouncerHasImplementation(
-    WPI_MulticastServiceAnnouncerHandle handle) {
-  auto& manager = wpi::GetMulticastManager();
-  std::scoped_lock lock{manager.mutex};
-  auto& announcer = manager.announcers[handle];
-  return announcer->HasImplementation();
-}
-}  // extern "C"
diff --git a/wpiutil/src/main/native/cpp/MulticastServiceResolver.cpp b/wpiutil/src/main/native/cpp/MulticastServiceResolver.cpp
deleted file mode 100644
index b834f17..0000000
--- a/wpiutil/src/main/native/cpp/MulticastServiceResolver.cpp
+++ /dev/null
@@ -1,148 +0,0 @@
-// Copyright (c) FIRST and other WPILib contributors.
-// Open Source Software; you can modify and/or share it under the terms of
-// the WPILib BSD license file in the root directory of this project.
-
-#include "wpi/MulticastServiceResolver.h"
-
-#include "MulticastHandleManager.h"
-#include "wpi/MemAlloc.h"
-
-extern "C" {
-WPI_MulticastServiceResolverHandle WPI_CreateMulticastServiceResolver(
-    const char* serviceType)
-
-{
-  auto& manager = wpi::GetMulticastManager();
-  std::scoped_lock lock{manager.mutex};
-
-  auto resolver = std::make_unique<wpi::MulticastServiceResolver>(serviceType);
-
-  size_t index = manager.handleIds.emplace_back(2);
-  manager.resolvers[index] = std::move(resolver);
-
-  return index;
-}
-
-void WPI_FreeMulticastServiceResolver(
-    WPI_MulticastServiceResolverHandle handle) {
-  auto& manager = wpi::GetMulticastManager();
-  std::scoped_lock lock{manager.mutex};
-  manager.resolvers[handle] = nullptr;
-  manager.handleIds.erase(handle);
-}
-
-void WPI_StartMulticastServiceResolver(
-    WPI_MulticastServiceResolverHandle handle) {
-  auto& manager = wpi::GetMulticastManager();
-  std::scoped_lock lock{manager.mutex};
-  auto& resolver = manager.resolvers[handle];
-  resolver->Start();
-}
-
-void WPI_StopMulticastServiceResolver(
-    WPI_MulticastServiceResolverHandle handle) {
-  auto& manager = wpi::GetMulticastManager();
-  std::scoped_lock lock{manager.mutex};
-  auto& resolver = manager.resolvers[handle];
-  resolver->Stop();
-}
-
-int32_t WPI_GetMulticastServiceResolverHasImplementation(
-    WPI_MulticastServiceResolverHandle handle) {
-  auto& manager = wpi::GetMulticastManager();
-  std::scoped_lock lock{manager.mutex};
-  auto& resolver = manager.resolvers[handle];
-  return resolver->HasImplementation();
-}
-
-WPI_EventHandle WPI_GetMulticastServiceResolverEventHandle(
-    WPI_MulticastServiceResolverHandle handle) {
-  auto& manager = wpi::GetMulticastManager();
-  std::scoped_lock lock{manager.mutex};
-  auto& resolver = manager.resolvers[handle];
-  return resolver->GetEventHandle();
-}
-
-WPI_ServiceData* WPI_GetMulticastServiceResolverData(
-    WPI_MulticastServiceResolverHandle handle, int32_t* dataCount) {
-  std::vector<wpi::MulticastServiceResolver::ServiceData> allData;
-  {
-    auto& manager = wpi::GetMulticastManager();
-    std::scoped_lock lock{manager.mutex};
-    auto& resolver = manager.resolvers[handle];
-    allData = resolver->GetData();
-  }
-  if (allData.empty()) {
-    *dataCount = 0;
-    return nullptr;
-  }
-  size_t allocSize = sizeof(WPI_ServiceData) * allData.size();
-
-  for (auto&& data : allData) {
-    // Include space for hostName and serviceType (+ terminators)
-    allocSize += data.hostName.size() + data.serviceName.size() + 2;
-
-    size_t keysTotalLength = 0;
-    size_t valuesTotalLength = 0;
-    // Include space for all keys and values, and pointer array
-    for (auto&& t : data.txt) {
-      allocSize += sizeof(const char*);
-      keysTotalLength += (t.first.size() + 1);
-      allocSize += sizeof(const char*);
-      valuesTotalLength += (t.second.size() + 1);
-    }
-    allocSize += keysTotalLength;
-    allocSize += valuesTotalLength;
-  }
-
-  uint8_t* cDataRaw = reinterpret_cast<uint8_t*>(wpi::safe_malloc(allocSize));
-  if (!cDataRaw) {
-    return nullptr;
-  }
-  WPI_ServiceData* rootArray = reinterpret_cast<WPI_ServiceData*>(cDataRaw);
-  cDataRaw += (sizeof(WPI_ServiceData) + allData.size());
-  WPI_ServiceData* currentData = rootArray;
-
-  for (auto&& data : allData) {
-    currentData->ipv4Address = data.ipv4Address;
-    currentData->port = data.port;
-    currentData->txtCount = data.txt.size();
-
-    std::memcpy(cDataRaw, data.hostName.c_str(), data.hostName.size() + 1);
-    currentData->hostName = reinterpret_cast<const char*>(cDataRaw);
-    cDataRaw += data.hostName.size() + 1;
-
-    std::memcpy(cDataRaw, data.serviceName.c_str(),
-                data.serviceName.size() + 1);
-    currentData->serviceName = reinterpret_cast<const char*>(cDataRaw);
-    cDataRaw += data.serviceName.size() + 1;
-
-    char** valuesPtrArr = reinterpret_cast<char**>(cDataRaw);
-    cDataRaw += (sizeof(char**) * data.txt.size());
-    char** keysPtrArr = reinterpret_cast<char**>(cDataRaw);
-    cDataRaw += (sizeof(char**) * data.txt.size());
-
-    currentData->txtKeys = const_cast<const char**>(keysPtrArr);
-    currentData->txtValues = const_cast<const char**>(valuesPtrArr);
-
-    for (size_t i = 0; i < data.txt.size(); i++) {
-      keysPtrArr[i] = reinterpret_cast<char*>(cDataRaw);
-      std::memcpy(keysPtrArr[i], data.txt[i].first.c_str(),
-                  data.txt[i].first.size() + 1);
-      cDataRaw += (data.txt[i].first.size() + 1);
-
-      valuesPtrArr[i] = reinterpret_cast<char*>(cDataRaw);
-      std::memcpy(valuesPtrArr[i], data.txt[i].second.c_str(),
-                  data.txt[i].second.size() + 1);
-      cDataRaw += (data.txt[i].second.size() + 1);
-    }
-    currentData++;
-  }
-
-  return rootArray;
-}
-
-void WPI_FreeServiceData(WPI_ServiceData* serviceData, int32_t length) {
-  std::free(serviceData);
-}
-}  // extern "C"
diff --git a/wpiutil/src/main/native/cpp/ParallelTcpConnector.cpp b/wpiutil/src/main/native/cpp/ParallelTcpConnector.cpp
deleted file mode 100644
index 5a8394a..0000000
--- a/wpiutil/src/main/native/cpp/ParallelTcpConnector.cpp
+++ /dev/null
@@ -1,177 +0,0 @@
-// Copyright (c) FIRST and other WPILib contributors.
-// Open Source Software; you can modify and/or share it under the terms of
-// the WPILib BSD license file in the root directory of this project.
-
-#include "wpi/ParallelTcpConnector.h"
-
-#include <fmt/format.h>
-
-#include "wpi/Logger.h"
-#include "wpi/uv/GetAddrInfo.h"
-#include "wpi/uv/Loop.h"
-#include "wpi/uv/Tcp.h"
-#include "wpi/uv/Timer.h"
-#include "wpi/uv/util.h"
-
-using namespace wpi;
-
-ParallelTcpConnector::ParallelTcpConnector(
-    wpi::uv::Loop& loop, wpi::uv::Timer::Time reconnectRate,
-    wpi::Logger& logger, std::function<void(wpi::uv::Tcp& tcp)> connected,
-    const private_init&)
-    : m_loop{loop},
-      m_logger{logger},
-      m_reconnectRate{reconnectRate},
-      m_connected{std::move(connected)},
-      m_reconnectTimer{uv::Timer::Create(loop)} {
-  m_reconnectTimer->timeout.connect([this] {
-    if (!IsConnected()) {
-      WPI_DEBUG1(m_logger, "{}", "timed out, reconnecting");
-      Connect();
-    }
-  });
-}
-
-ParallelTcpConnector::~ParallelTcpConnector() = default;
-
-void ParallelTcpConnector::Close() {
-  CancelAll();
-  m_reconnectTimer->Close();
-}
-
-void ParallelTcpConnector::SetServers(
-    wpi::span<const std::pair<std::string, unsigned int>> servers) {
-  m_servers.assign(servers.begin(), servers.end());
-  if (!IsConnected()) {
-    Connect();
-  }
-}
-
-void ParallelTcpConnector::Disconnected() {
-  if (m_isConnected) {
-    m_isConnected = false;
-    Connect();
-  }
-}
-
-void ParallelTcpConnector::Succeeded(uv::Tcp& tcp) {
-  if (!m_isConnected) {
-    m_isConnected = true;
-    m_reconnectTimer->Stop();
-    CancelAll(&tcp);
-  }
-}
-
-void ParallelTcpConnector::Connect() {
-  if (IsConnected()) {
-    return;
-  }
-
-  CancelAll();
-  m_reconnectTimer->Start(m_reconnectRate);
-
-  WPI_DEBUG3(m_logger, "{}", "starting new connection attempts");
-
-  // kick off parallel lookups
-  for (auto&& server : m_servers) {
-    auto req = std::make_shared<uv::GetAddrInfoReq>();
-    m_resolvers.emplace_back(req);
-
-    req->resolved.connect(
-        [this, req = req.get()](const addrinfo& addrinfo) {
-          if (IsConnected()) {
-            return;
-          }
-
-          // kick off parallel connection attempts
-          for (auto ai = &addrinfo; ai; ai = ai->ai_next) {
-            auto tcp = uv::Tcp::Create(m_loop);
-            m_attempts.emplace_back(tcp);
-
-            auto connreq = std::make_shared<uv::TcpConnectReq>();
-            connreq->connected.connect(
-                [this, tcp = tcp.get()] {
-                  if (m_logger.min_level() <= wpi::WPI_LOG_DEBUG4) {
-                    std::string ip;
-                    unsigned int port = 0;
-                    uv::AddrToName(tcp->GetPeer(), &ip, &port);
-                    WPI_DEBUG4(m_logger,
-                               "successful connection ({}) to {} port {}",
-                               static_cast<void*>(tcp), ip, port);
-                  }
-                  if (IsConnected()) {
-                    tcp->Shutdown([tcp] { tcp->Close(); });
-                    return;
-                  }
-                  if (m_connected) {
-                    m_connected(*tcp);
-                  }
-                },
-                shared_from_this());
-
-            connreq->error = [selfWeak = weak_from_this(),
-                              tcp = tcp.get()](uv::Error err) {
-              if (auto self = selfWeak.lock()) {
-                WPI_DEBUG1(self->m_logger, "connect failure ({}): {}",
-                           static_cast<void*>(tcp), err.str());
-              }
-            };
-
-            if (m_logger.min_level() <= wpi::WPI_LOG_DEBUG4) {
-              std::string ip;
-              unsigned int port = 0;
-              uv::AddrToName(*reinterpret_cast<sockaddr_storage*>(ai->ai_addr),
-                             &ip, &port);
-              WPI_DEBUG4(
-                  m_logger,
-                  "Info({}) starting connection attempt ({}) to {} port {}",
-                  static_cast<void*>(req), static_cast<void*>(tcp.get()), ip,
-                  port);
-            }
-            tcp->Connect(*ai->ai_addr, connreq);
-          }
-        },
-        shared_from_this());
-
-    req->error = [req = req.get(), selfWeak = weak_from_this()](uv::Error err) {
-      if (auto self = selfWeak.lock()) {
-        WPI_DEBUG1(self->m_logger, "GetAddrInfo({}) failure: {}",
-                   static_cast<void*>(req), err.str());
-      }
-    };
-
-    WPI_DEBUG4(m_logger, "starting GetAddrInfo({}) for {} port {}",
-               static_cast<void*>(req.get()), server.first, server.second);
-    addrinfo hints;
-    std::memset(&hints, 0, sizeof(hints));
-    hints.ai_family = AF_UNSPEC;
-    hints.ai_socktype = SOCK_STREAM;
-    hints.ai_protocol = IPPROTO_TCP;
-    hints.ai_flags = AI_NUMERICSERV | AI_ADDRCONFIG;
-    uv::GetAddrInfo(m_loop, req, server.first, fmt::format("{}", server.second),
-                    &hints);
-  }
-}
-
-void ParallelTcpConnector::CancelAll(wpi::uv::Tcp* except) {
-  WPI_DEBUG4(m_logger, "{}", "canceling previous attempts");
-  for (auto&& resolverWeak : m_resolvers) {
-    if (auto resolver = resolverWeak.lock()) {
-      WPI_DEBUG4(m_logger, "canceling GetAddrInfo({})",
-                 static_cast<void*>(resolver.get()));
-      resolver->Cancel();
-    }
-  }
-  m_resolvers.clear();
-
-  for (auto&& tcpWeak : m_attempts) {
-    if (auto tcp = tcpWeak.lock()) {
-      if (tcp.get() != except) {
-        WPI_DEBUG4(m_logger, "canceling connection attempt ({})",
-                   static_cast<void*>(tcp.get()));
-        tcp->Close();
-      }
-    }
-  }
-  m_attempts.clear();
-}
diff --git a/wpiutil/src/main/native/cpp/PortForwarder.cpp b/wpiutil/src/main/native/cpp/PortForwarder.cpp
deleted file mode 100644
index a423d48..0000000
--- a/wpiutil/src/main/native/cpp/PortForwarder.cpp
+++ /dev/null
@@ -1,157 +0,0 @@
-// Copyright (c) FIRST and other WPILib contributors.
-// Open Source Software; you can modify and/or share it under the terms of
-// the WPILib BSD license file in the root directory of this project.
-
-#include "wpi/PortForwarder.h"
-
-#include "fmt/format.h"
-#include "wpi/DenseMap.h"
-#include "wpi/EventLoopRunner.h"
-#include "wpi/uv/GetAddrInfo.h"
-#include "wpi/uv/Tcp.h"
-#include "wpi/uv/Timer.h"
-
-using namespace wpi;
-
-struct PortForwarder::Impl {
- public:
-  EventLoopRunner runner;
-  DenseMap<unsigned int, std::weak_ptr<uv::Tcp>> servers;
-};
-
-PortForwarder::PortForwarder() : m_impl{new Impl} {}
-
-PortForwarder& PortForwarder::GetInstance() {
-  static PortForwarder instance;
-  return instance;
-}
-
-static void CopyStream(uv::Stream& in, std::weak_ptr<uv::Stream> outWeak) {
-  in.data.connect([&in, outWeak](uv::Buffer& buf, size_t len) {
-    uv::Buffer buf2 = buf.Dup();
-    buf2.len = len;
-    auto out = outWeak.lock();
-    if (!out) {
-      buf2.Deallocate();
-      in.Close();
-      return;
-    }
-    out->Write({buf2}, [](auto bufs, uv::Error) {
-      for (auto buf : bufs) {
-        buf.Deallocate();
-      }
-    });
-  });
-}
-
-void PortForwarder::Add(unsigned int port, std::string_view remoteHost,
-                        unsigned int remotePort) {
-  m_impl->runner.ExecSync([&](uv::Loop& loop) {
-    auto server = uv::Tcp::Create(loop);
-
-    // bind to local port
-    server->Bind("", port);
-
-    // when we get a connection, accept it
-    server->connection.connect([serverPtr = server.get(),
-                                host = std::string{remoteHost}, remotePort] {
-      auto& loop = serverPtr->GetLoopRef();
-      auto client = serverPtr->Accept();
-      if (!client) {
-        return;
-      }
-
-      // close on error
-      client->error.connect(
-          [clientPtr = client.get()](uv::Error err) { clientPtr->Close(); });
-
-      // connected flag
-      auto connected = std::make_shared<bool>(false);
-      client->SetData(connected);
-
-      auto remote = uv::Tcp::Create(loop);
-      remote->error.connect(
-          [remotePtr = remote.get(),
-           clientWeak = std::weak_ptr<uv::Tcp>(client)](uv::Error err) {
-            remotePtr->Close();
-            if (auto client = clientWeak.lock()) {
-              client->Close();
-            }
-          });
-
-      // resolve address
-      uv::GetAddrInfo(
-          loop,
-          [clientWeak = std::weak_ptr<uv::Tcp>(client),
-           remoteWeak = std::weak_ptr<uv::Tcp>(remote)](const addrinfo& addr) {
-            auto remote = remoteWeak.lock();
-            if (!remote) {
-              return;
-            }
-
-            // connect to remote address/port
-            remote->Connect(*addr.ai_addr, [remotePtr = remote.get(),
-                                            remoteWeak, clientWeak] {
-              auto client = clientWeak.lock();
-              if (!client) {
-                remotePtr->Close();
-                return;
-              }
-              *(client->GetData<bool>()) = true;
-
-              // close both when either side closes
-              client->end.connect([clientPtr = client.get(), remoteWeak] {
-                clientPtr->Close();
-                if (auto remote = remoteWeak.lock()) {
-                  remote->Close();
-                }
-              });
-              remotePtr->end.connect([remotePtr, clientWeak] {
-                remotePtr->Close();
-                if (auto client = clientWeak.lock()) {
-                  client->Close();
-                }
-              });
-
-              // copy bidirectionally
-              client->StartRead();
-              remotePtr->StartRead();
-              CopyStream(*client, remoteWeak);
-              CopyStream(*remotePtr, clientWeak);
-            });
-          },
-          host, fmt::to_string(remotePort));
-
-      // time out for connection
-      uv::Timer::SingleShot(loop, uv::Timer::Time{500},
-                            [connectedWeak = std::weak_ptr<bool>(connected),
-                             clientWeak = std::weak_ptr<uv::Tcp>(client),
-                             remoteWeak = std::weak_ptr<uv::Tcp>(remote)] {
-                              if (auto connected = connectedWeak.lock()) {
-                                if (!*connected) {
-                                  if (auto client = clientWeak.lock()) {
-                                    client->Close();
-                                  }
-                                  if (auto remote = remoteWeak.lock()) {
-                                    remote->Close();
-                                  }
-                                }
-                              }
-                            });
-    });
-
-    // start listening for incoming connections
-    server->Listen();
-
-    m_impl->servers[port] = server;
-  });
-}
-
-void PortForwarder::Remove(unsigned int port) {
-  m_impl->runner.ExecSync([&](uv::Loop& loop) {
-    if (auto server = m_impl->servers.lookup(port).lock()) {
-      server->Close();
-      m_impl->servers.erase(port);
-    }
-  });
-}
diff --git a/wpiutil/src/main/native/cpp/SafeThread.cpp b/wpiutil/src/main/native/cpp/SafeThread.cpp
index bbecc5c..ba1eba3 100644
--- a/wpiutil/src/main/native/cpp/SafeThread.cpp
+++ b/wpiutil/src/main/native/cpp/SafeThread.cpp
@@ -4,10 +4,50 @@
 
 #include "wpi/SafeThread.h"
 
+#include <atomic>
+
 using namespace wpi;
 
+// thread start/stop notifications for bindings that need to set up
+// per-thread state
+
+static void* DefaultOnThreadStart() {
+  return nullptr;
+}
+static void DefaultOnThreadEnd(void*) {}
+
+using OnThreadStartFn = void* (*)();
+using OnThreadEndFn = void (*)(void*);
+static std::atomic<int> gSafeThreadRefcount;
+static std::atomic<OnThreadStartFn> gOnSafeThreadStart{DefaultOnThreadStart};
+static std::atomic<OnThreadEndFn> gOnSafeThreadEnd{DefaultOnThreadEnd};
+
+namespace wpi::impl {
+void SetSafeThreadNotifiers(OnThreadStartFn OnStart, OnThreadEndFn OnEnd) {
+  if (gSafeThreadRefcount != 0) {
+    throw std::runtime_error(
+        "cannot set notifier while safe threads are running");
+  }
+  // Note: there's a race here, but if you're not calling this function on
+  // the main thread before you start anything else, you're using this function
+  // incorrectly
+  gOnSafeThreadStart = OnStart ? OnStart : DefaultOnThreadStart;
+  gOnSafeThreadEnd = OnEnd ? OnEnd : DefaultOnThreadEnd;
+}
+}  // namespace wpi::impl
+
+void SafeThread::Stop() {
+  m_active = false;
+  m_cond.notify_all();
+}
+
+void SafeThreadEvent::Stop() {
+  m_active = false;
+  m_stopEvent.Set();
+}
+
 detail::SafeThreadProxyBase::SafeThreadProxyBase(
-    std::shared_ptr<SafeThread> thr)
+    std::shared_ptr<SafeThreadBase> thr)
     : m_thread(std::move(thr)) {
   if (!m_thread) {
     return;
@@ -28,12 +68,18 @@
   }
 }
 
-void detail::SafeThreadOwnerBase::Start(std::shared_ptr<SafeThread> thr) {
+void detail::SafeThreadOwnerBase::Start(std::shared_ptr<SafeThreadBase> thr) {
   std::scoped_lock lock(m_mutex);
   if (auto thr = m_thread.lock()) {
     return;
   }
-  m_stdThread = std::thread([=] { thr->Main(); });
+  m_stdThread = std::thread([=] {
+    gSafeThreadRefcount++;
+    void* opaque = (gOnSafeThreadStart.load())();
+    thr->Main();
+    (gOnSafeThreadEnd.load())(opaque);
+    gSafeThreadRefcount--;
+  });
   thr->m_threadId = m_stdThread.get_id();
   m_thread = thr;
 }
@@ -41,8 +87,7 @@
 void detail::SafeThreadOwnerBase::Stop() {
   std::scoped_lock lock(m_mutex);
   if (auto thr = m_thread.lock()) {
-    thr->m_active = false;
-    thr->m_cond.notify_all();
+    thr->Stop();
     m_thread.reset();
   }
   if (m_stdThread.joinable()) {
@@ -56,8 +101,7 @@
     auto stdThread = std::move(m_stdThread);
     m_thread.reset();
     lock.unlock();
-    thr->m_active = false;
-    thr->m_cond.notify_all();
+    thr->Stop();
     stdThread.join();
   } else if (m_stdThread.joinable()) {
     m_stdThread.detach();
@@ -85,8 +129,8 @@
   return m_stdThread.native_handle();
 }
 
-std::shared_ptr<SafeThread> detail::SafeThreadOwnerBase::GetThreadSharedPtr()
-    const {
+std::shared_ptr<SafeThreadBase>
+detail::SafeThreadOwnerBase::GetThreadSharedPtr() const {
   std::scoped_lock lock(m_mutex);
   return m_thread.lock();
 }
diff --git a/wpiutil/src/main/native/cpp/SocketError.cpp b/wpiutil/src/main/native/cpp/SocketError.cpp
deleted file mode 100644
index 3f08d1e..0000000
--- a/wpiutil/src/main/native/cpp/SocketError.cpp
+++ /dev/null
@@ -1,37 +0,0 @@
-// Copyright (c) FIRST and other WPILib contributors.
-// Open Source Software; you can modify and/or share it under the terms of
-// the WPILib BSD license file in the root directory of this project.
-
-#include "wpi/SocketError.h"
-
-#ifdef _WIN32
-#include <winsock2.h>
-#else
-#include <cerrno>
-#include <cstring>
-#endif
-
-namespace wpi {
-
-int SocketErrno() {
-#ifdef _WIN32
-  return WSAGetLastError();
-#else
-  return errno;
-#endif
-}
-
-std::string SocketStrerror(int code) {
-#ifdef _WIN32
-  LPSTR errstr = nullptr;
-  FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, 0,
-                code, 0, (LPSTR)&errstr, 0, 0);
-  std::string rv(errstr);
-  LocalFree(errstr);
-  return rv;
-#else
-  return std::strerror(code);
-#endif
-}
-
-}  // namespace wpi
diff --git a/wpiutil/src/main/native/cpp/StringExtras.cpp b/wpiutil/src/main/native/cpp/StringExtras.cpp
deleted file mode 100644
index 968ffc3..0000000
--- a/wpiutil/src/main/native/cpp/StringExtras.cpp
+++ /dev/null
@@ -1,360 +0,0 @@
-// Copyright (c) FIRST and other WPILib contributors.
-// Open Source Software; you can modify and/or share it under the terms of
-// the WPILib BSD license file in the root directory of this project.
-
-//===-- StringExtras.cpp - Implement the StringExtras header --------------===//
-//
-//                     The LLVM Compiler Infrastructure
-//
-// This file is distributed under the University of Illinois Open Source
-// License. See LICENSE.TXT for details.
-//
-//===----------------------------------------------------------------------===//
-//
-// This file implements the StringExtras.h header
-//
-//===----------------------------------------------------------------------===//
-
-#include "wpi/StringExtras.h"
-
-#include <algorithm>
-#include <cstdlib>
-#include <string_view>
-
-#include "wpi/SmallString.h"
-#include "wpi/SmallVector.h"
-
-// strncasecmp() is not available on non-POSIX systems, so define an
-// alternative function here.
-static int ascii_strncasecmp(const char* lhs, const char* rhs,
-                             size_t length) noexcept {
-  for (size_t i = 0; i < length; ++i) {
-    unsigned char lhc = wpi::toLower(lhs[i]);
-    unsigned char rhc = wpi::toLower(rhs[i]);
-    if (lhc != rhc) {
-      return lhc < rhc ? -1 : 1;
-    }
-  }
-  return 0;
-}
-
-int wpi::compare_lower(std::string_view lhs, std::string_view rhs) noexcept {
-  if (int Res = ascii_strncasecmp(lhs.data(), rhs.data(),
-                                  (std::min)(lhs.size(), rhs.size()))) {
-    return Res;
-  }
-  if (lhs.size() == rhs.size()) {
-    return 0;
-  }
-  return lhs.size() < rhs.size() ? -1 : 1;
-}
-
-std::string_view::size_type wpi::find_lower(
-    std::string_view str, char ch, std::string_view::size_type from) noexcept {
-  char lch = toLower(ch);
-  auto s = drop_front(str, from);
-  while (!s.empty()) {
-    if (toLower(s.front()) == lch) {
-      return str.size() - s.size();
-    }
-    s.remove_prefix(1);
-  }
-  return std::string_view::npos;
-}
-
-std::string_view::size_type wpi::find_lower(
-    std::string_view str, std::string_view other,
-    std::string_view::size_type from) noexcept {
-  auto s = substr(str, from);
-  while (s.size() >= other.size()) {
-    if (starts_with_lower(s, other)) {
-      return from;
-    }
-    s.remove_prefix(1);
-    ++from;
-  }
-  return std::string_view::npos;
-}
-
-std::string_view::size_type wpi::rfind_lower(
-    std::string_view str, char ch, std::string_view::size_type from) noexcept {
-  from = (std::min)(from, str.size());
-  auto data = str.data();
-  std::string_view::size_type i = from;
-  while (i != 0) {
-    --i;
-    if (toLower(data[i]) == toLower(ch)) {
-      return i;
-    }
-  }
-  return std::string_view::npos;
-}
-
-std::string_view::size_type wpi::rfind_lower(std::string_view str,
-                                             std::string_view other) noexcept {
-  std::string_view::size_type n = other.size();
-  if (n > str.size()) {
-    return std::string_view::npos;
-  }
-  for (size_t i = str.size() - n + 1, e = 0; i != e;) {
-    --i;
-    if (equals_lower(substr(str, i, n), other)) {
-      return i;
-    }
-  }
-  return std::string_view::npos;
-}
-
-bool wpi::starts_with_lower(std::string_view str,
-                            std::string_view prefix) noexcept {
-  return str.size() >= prefix.size() &&
-         ascii_strncasecmp(str.data(), prefix.data(), prefix.size()) == 0;
-}
-
-bool wpi::ends_with_lower(std::string_view str,
-                          std::string_view suffix) noexcept {
-  return str.size() >= suffix.size() &&
-         ascii_strncasecmp(str.data() + str.size() - suffix.size(),
-                           suffix.data(), suffix.size()) == 0;
-}
-
-void wpi::split(std::string_view str, SmallVectorImpl<std::string_view>& arr,
-                std::string_view separator, int maxSplit,
-                bool keepEmpty) noexcept {
-  std::string_view s = str;
-
-  // Count down from maxSplit. When maxSplit is -1, this will just split
-  // "forever". This doesn't support splitting more than 2^31 times
-  // intentionally; if we ever want that we can make maxSplit a 64-bit integer
-  // but that seems unlikely to be useful.
-  while (maxSplit-- != 0) {
-    auto idx = s.find(separator);
-    if (idx == std::string_view::npos) {
-      break;
-    }
-
-    // Push this split.
-    if (keepEmpty || idx > 0) {
-      arr.push_back(slice(s, 0, idx));
-    }
-
-    // Jump forward.
-    s = slice(s, idx + separator.size(), std::string_view::npos);
-  }
-
-  // Push the tail.
-  if (keepEmpty || !s.empty()) {
-    arr.push_back(s);
-  }
-}
-
-void wpi::split(std::string_view str, SmallVectorImpl<std::string_view>& arr,
-                char separator, int maxSplit, bool keepEmpty) noexcept {
-  std::string_view s = str;
-
-  // Count down from maxSplit. When maxSplit is -1, this will just split
-  // "forever". This doesn't support splitting more than 2^31 times
-  // intentionally; if we ever want that we can make maxSplit a 64-bit integer
-  // but that seems unlikely to be useful.
-  while (maxSplit-- != 0) {
-    size_t idx = s.find(separator);
-    if (idx == std::string_view::npos) {
-      break;
-    }
-
-    // Push this split.
-    if (keepEmpty || idx > 0) {
-      arr.push_back(slice(s, 0, idx));
-    }
-
-    // Jump forward.
-    s = slice(s, idx + 1, std::string_view::npos);
-  }
-
-  // Push the tail.
-  if (keepEmpty || !s.empty()) {
-    arr.push_back(s);
-  }
-}
-
-static unsigned GetAutoSenseRadix(std::string_view& str) noexcept {
-  if (str.empty()) {
-    return 10;
-  }
-
-  if (wpi::starts_with(str, "0x") || wpi::starts_with(str, "0X")) {
-    str.remove_prefix(2);
-    return 16;
-  }
-
-  if (wpi::starts_with(str, "0b") || wpi::starts_with(str, "0B")) {
-    str.remove_prefix(2);
-    return 2;
-  }
-
-  if (wpi::starts_with(str, "0o")) {
-    str.remove_prefix(2);
-    return 8;
-  }
-
-  if (str[0] == '0' && str.size() > 1 && wpi::isDigit(str[1])) {
-    str.remove_prefix(1);
-    return 8;
-  }
-
-  return 10;
-}
-
-bool wpi::detail::ConsumeUnsignedInteger(
-    std::string_view& str, unsigned radix,
-    unsigned long long& result) noexcept {  // NOLINT(runtime/int)
-  // Autosense radix if not specified.
-  if (radix == 0) {
-    radix = GetAutoSenseRadix(str);
-  }
-
-  // Empty strings (after the radix autosense) are invalid.
-  if (str.empty()) {
-    return true;
-  }
-
-  // Parse all the bytes of the string given this radix.  Watch for overflow.
-  std::string_view str2 = str;
-  result = 0;
-  while (!str2.empty()) {
-    unsigned charVal;
-    if (str2[0] >= '0' && str2[0] <= '9') {
-      charVal = str2[0] - '0';
-    } else if (str2[0] >= 'a' && str2[0] <= 'z') {
-      charVal = str2[0] - 'a' + 10;
-    } else if (str2[0] >= 'A' && str2[0] <= 'Z') {
-      charVal = str2[0] - 'A' + 10;
-    } else {
-      break;
-    }
-
-    // If the parsed value is larger than the integer radix, we cannot
-    // consume any more characters.
-    if (charVal >= radix) {
-      break;
-    }
-
-    // Add in this character.
-    unsigned long long prevResult = result;  // NOLINT(runtime/int)
-    result = result * radix + charVal;
-
-    // Check for overflow by shifting back and seeing if bits were lost.
-    if (result / radix < prevResult) {
-      return true;
-    }
-
-    str2.remove_prefix(1);
-  }
-
-  // We consider the operation a failure if no characters were consumed
-  // successfully.
-  if (str.size() == str2.size()) {
-    return true;
-  }
-
-  str = str2;
-  return false;
-}
-
-bool wpi::detail::ConsumeSignedInteger(
-    std::string_view& str, unsigned radix,
-    long long& result) noexcept {  // NOLINT(runtime/int)
-  unsigned long long ullVal;       // NOLINT(runtime/int)
-
-  // Handle positive strings first.
-  if (str.empty() || str.front() != '-') {
-    if (wpi::detail::ConsumeUnsignedInteger(str, radix, ullVal) ||
-        // Check for value so large it overflows a signed value.
-        static_cast<long long>(ullVal) < 0) {  // NOLINT(runtime/int)
-      return true;
-    }
-    result = ullVal;
-    return false;
-  }
-
-  // Get the positive part of the value.
-  std::string_view str2 = wpi::drop_front(str, 1);
-  if (wpi::detail::ConsumeUnsignedInteger(str2, radix, ullVal) ||
-      // Reject values so large they'd overflow as negative signed, but allow
-      // "-0".  This negates the unsigned so that the negative isn't undefined
-      // on signed overflow.
-      static_cast<long long>(-ullVal) > 0) {  // NOLINT(runtime/int)
-    return true;
-  }
-
-  str = str2;
-  result = -ullVal;
-  return false;
-}
-
-bool wpi::detail::GetAsUnsignedInteger(
-    std::string_view str, unsigned radix,
-    unsigned long long& result) noexcept {  // NOLINT(runtime/int)
-  if (wpi::detail::ConsumeUnsignedInteger(str, radix, result)) {
-    return true;
-  }
-
-  // For getAsUnsignedInteger, we require the whole string to be consumed or
-  // else we consider it a failure.
-  return !str.empty();
-}
-
-bool wpi::detail::GetAsSignedInteger(
-    std::string_view str, unsigned radix,
-    long long& result) noexcept {  // NOLINT(runtime/int)
-  if (wpi::detail::ConsumeSignedInteger(str, radix, result)) {
-    return true;
-  }
-
-  // For getAsSignedInteger, we require the whole string to be consumed or else
-  // we consider it a failure.
-  return !str.empty();
-}
-
-template <>
-std::optional<float> wpi::parse_float<float>(std::string_view str) noexcept {
-  if (str.empty()) {
-    return std::nullopt;
-  }
-  wpi::SmallString<32> storage{str};
-  char* end;
-  float val = std::strtof(storage.c_str(), &end);
-  if (*end != '\0') {
-    return std::nullopt;
-  }
-  return val;
-}
-
-template <>
-std::optional<double> wpi::parse_float<double>(std::string_view str) noexcept {
-  if (str.empty()) {
-    return std::nullopt;
-  }
-  wpi::SmallString<32> storage{str};
-  char* end;
-  double val = std::strtod(storage.c_str(), &end);
-  if (*end != '\0') {
-    return std::nullopt;
-  }
-  return val;
-}
-
-template <>
-std::optional<long double> wpi::parse_float<long double>(
-    std::string_view str) noexcept {
-  if (str.empty()) {
-    return std::nullopt;
-  }
-  wpi::SmallString<32> storage{str};
-  char* end;
-  long double val = std::strtold(storage.c_str(), &end);
-  if (*end != '\0') {
-    return std::nullopt;
-  }
-  return val;
-}
diff --git a/wpiutil/src/main/native/cpp/Synchronization.cpp b/wpiutil/src/main/native/cpp/Synchronization.cpp
index da97897..f9ac75b 100644
--- a/wpiutil/src/main/native/cpp/Synchronization.cpp
+++ b/wpiutil/src/main/native/cpp/Synchronization.cpp
@@ -5,6 +5,7 @@
 #include "wpi/Synchronization.h"
 
 #include <algorithm>
+#include <atomic>
 #include <cstring>
 #include <mutex>
 
@@ -16,6 +17,8 @@
 
 using namespace wpi;
 
+static std::atomic_bool gShutdown{false};
+
 namespace {
 
 struct State {
@@ -25,6 +28,8 @@
 };
 
 struct HandleManager {
+  ~HandleManager() { gShutdown = true; }
+
   wpi::mutex mutex;
   wpi::UidVector<int, 8> eventIds;
   wpi::UidVector<int, 8> semaphoreIds;
@@ -40,6 +45,9 @@
 
 WPI_EventHandle wpi::CreateEvent(bool manualReset, bool initialState) {
   auto& manager = GetManager();
+  if (gShutdown) {
+    return {};
+  }
   std::scoped_lock lock{manager.mutex};
 
   auto index = manager.eventIds.emplace_back(0);
@@ -61,6 +69,9 @@
   DestroySignalObject(handle);
 
   auto& manager = GetManager();
+  if (gShutdown) {
+    return;
+  }
   std::scoped_lock lock{manager.mutex};
   manager.eventIds.erase(handle & 0xffffff);
 }
@@ -83,6 +94,9 @@
 
 WPI_SemaphoreHandle wpi::CreateSemaphore(int initialCount, int maximumCount) {
   auto& manager = GetManager();
+  if (gShutdown) {
+    return {};
+  }
   std::scoped_lock lock{manager.mutex};
 
   auto index = manager.semaphoreIds.emplace_back(maximumCount);
@@ -104,6 +118,9 @@
   DestroySignalObject(handle);
 
   auto& manager = GetManager();
+  if (gShutdown) {
+    return;
+  }
   std::scoped_lock lock{manager.mutex};
   manager.eventIds.erase(handle & 0xffffff);
 }
@@ -119,6 +136,9 @@
   int index = handle & 0xffffff;
 
   auto& manager = GetManager();
+  if (gShutdown) {
+    return true;
+  }
   std::scoped_lock lock{manager.mutex};
   auto it = manager.states.find(handle);
   if (it == manager.states.end()) {
@@ -146,22 +166,26 @@
 bool wpi::WaitForObject(WPI_Handle handle, double timeout, bool* timedOut) {
   WPI_Handle signaledValue;
   auto signaled = WaitForObjects(
-      wpi::span(&handle, 1), wpi::span(&signaledValue, 1), timeout, timedOut);
+      std::span(&handle, 1), std::span(&signaledValue, 1), timeout, timedOut);
   if (signaled.empty()) {
     return false;
   }
   return (signaled[0] & 0x80000000ul) == 0;
 }
 
-wpi::span<WPI_Handle> wpi::WaitForObjects(wpi::span<const WPI_Handle> handles,
-                                          wpi::span<WPI_Handle> signaled) {
+std::span<WPI_Handle> wpi::WaitForObjects(std::span<const WPI_Handle> handles,
+                                          std::span<WPI_Handle> signaled) {
   return WaitForObjects(handles, signaled, -1, nullptr);
 }
 
-wpi::span<WPI_Handle> wpi::WaitForObjects(wpi::span<const WPI_Handle> handles,
-                                          wpi::span<WPI_Handle> signaled,
+std::span<WPI_Handle> wpi::WaitForObjects(std::span<const WPI_Handle> handles,
+                                          std::span<WPI_Handle> signaled,
                                           double timeout, bool* timedOut) {
   auto& manager = GetManager();
+  if (gShutdown) {
+    *timedOut = false;
+    return {};
+  }
   std::unique_lock lock{manager.mutex};
   wpi::condition_variable cv;
   bool addedWaiters = false;
@@ -240,6 +264,9 @@
 void wpi::CreateSignalObject(WPI_Handle handle, bool manualReset,
                              bool initialState) {
   auto& manager = GetManager();
+  if (gShutdown) {
+    return;
+  }
   std::scoped_lock lock{manager.mutex};
   auto& state = manager.states[handle];
   state.signaled = initialState ? 1 : 0;
@@ -248,6 +275,9 @@
 
 void wpi::SetSignalObject(WPI_Handle handle) {
   auto& manager = GetManager();
+  if (gShutdown) {
+    return;
+  }
   std::scoped_lock lock{manager.mutex};
   auto it = manager.states.find(handle);
   if (it == manager.states.end()) {
@@ -266,6 +296,9 @@
 
 void wpi::ResetSignalObject(WPI_Handle handle) {
   auto& manager = GetManager();
+  if (gShutdown) {
+    return;
+  }
   std::scoped_lock lock{manager.mutex};
   auto it = manager.states.find(handle);
   if (it != manager.states.end()) {
@@ -275,6 +308,9 @@
 
 void wpi::DestroySignalObject(WPI_Handle handle) {
   auto& manager = GetManager();
+  if (gShutdown) {
+    return;
+  }
   std::scoped_lock lock{manager.mutex};
 
   auto it = manager.states.find(handle);
@@ -332,8 +368,8 @@
 
 int WPI_WaitForObjects(const WPI_Handle* handles, int handles_count,
                        WPI_Handle* signaled) {
-  return wpi::WaitForObjects(wpi::span(handles, handles_count),
-                             wpi::span(signaled, handles_count))
+  return wpi::WaitForObjects(std::span(handles, handles_count),
+                             std::span(signaled, handles_count))
       .size();
 }
 
@@ -341,8 +377,8 @@
                               WPI_Handle* signaled, double timeout,
                               int* timed_out) {
   bool timedOutBool;
-  auto signaledResult = wpi::WaitForObjects(wpi::span(handles, handles_count),
-                                            wpi::span(signaled, handles_count),
+  auto signaledResult = wpi::WaitForObjects(std::span(handles, handles_count),
+                                            std::span(signaled, handles_count),
                                             timeout, &timedOutBool);
   *timed_out = timedOutBool ? 1 : 0;
   return signaledResult.size();
diff --git a/wpiutil/src/main/native/cpp/TCPAcceptor.cpp b/wpiutil/src/main/native/cpp/TCPAcceptor.cpp
deleted file mode 100644
index 8d12ac3..0000000
--- a/wpiutil/src/main/native/cpp/TCPAcceptor.cpp
+++ /dev/null
@@ -1,210 +0,0 @@
-/*
-   TCPAcceptor.cpp
-
-   TCPAcceptor class definition. TCPAcceptor provides methods to passively
-   establish TCP/IP connections with clients.
-
-   ------------------------------------------
-
-   Copyright (c) 2013 [Vic Hargrave - http://vichargrave.com]
-
-   Licensed under the Apache License, Version 2.0 (the "License");
-   you may not use this file except in compliance with the License.
-   You may obtain a copy of the License at
-
-       http://www.apache.org/licenses/LICENSE-2.0
-
-   Unless required by applicable law or agreed to in writing, software
-   distributed under the License is distributed on an "AS IS" BASIS,
-   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-   See the License for the specific language governing permissions and
-   limitations under the License.
-*/
-
-#include "wpi/TCPAcceptor.h"
-
-#include <cstdio>
-#include <cstring>
-
-#ifdef _WIN32
-#define _WINSOCK_DEPRECATED_NO_WARNINGS
-#include <WinSock2.h>
-#include <Ws2tcpip.h>
-#pragma comment(lib, "Ws2_32.lib")
-#else
-#include <arpa/inet.h>
-#include <fcntl.h>
-#include <netinet/in.h>
-#include <unistd.h>
-#endif
-
-#include "wpi/Logger.h"
-#include "wpi/SmallString.h"
-#include "wpi/SocketError.h"
-
-using namespace wpi;
-
-TCPAcceptor::TCPAcceptor(int port, std::string_view address, Logger& logger)
-    : m_lsd(0),
-      m_port(port),
-      m_address(address),
-      m_listening(false),
-      m_logger(logger) {
-  m_shutdown = false;
-#ifdef _WIN32
-  WSAData wsaData;
-  WORD wVersionRequested = MAKEWORD(2, 2);
-  (void)WSAStartup(wVersionRequested, &wsaData);
-#endif
-}
-
-TCPAcceptor::~TCPAcceptor() {
-  if (m_lsd > 0) {
-    shutdown();
-#ifdef _WIN32
-    closesocket(m_lsd);
-#else
-    close(m_lsd);
-#endif
-  }
-#ifdef _WIN32
-  WSACleanup();
-#endif
-}
-
-int TCPAcceptor::start() {
-  if (m_listening) {
-    return 0;
-  }
-
-  m_lsd = socket(PF_INET, SOCK_STREAM, 0);
-  if (m_lsd < 0) {
-    WPI_ERROR(m_logger, "{}", "could not create socket");
-    return -1;
-  }
-  struct sockaddr_in address;
-
-  std::memset(&address, 0, sizeof(address));
-  address.sin_family = PF_INET;
-  if (m_address.size() > 0) {
-#ifdef _WIN32
-    SmallString<128> addr_copy(m_address);
-    addr_copy.push_back('\0');
-    int res = InetPton(PF_INET, addr_copy.data(), &(address.sin_addr));
-#else
-    int res = inet_pton(PF_INET, m_address.c_str(), &(address.sin_addr));
-#endif
-    if (res != 1) {
-      WPI_ERROR(m_logger, "could not resolve {} address", m_address);
-      return -1;
-    }
-  } else {
-    address.sin_addr.s_addr = INADDR_ANY;
-  }
-  address.sin_port = htons(m_port);
-
-#ifdef _WIN32
-  int optval = 1;
-  setsockopt(m_lsd, SOL_SOCKET, SO_EXCLUSIVEADDRUSE,
-             reinterpret_cast<char*>(&optval), sizeof optval);
-#else
-  int optval = 1;
-  setsockopt(m_lsd, SOL_SOCKET, SO_REUSEADDR, reinterpret_cast<char*>(&optval),
-             sizeof optval);
-#endif
-
-  int result = bind(m_lsd, reinterpret_cast<struct sockaddr*>(&address),
-                    sizeof(address));
-  if (result != 0) {
-    WPI_ERROR(m_logger, "bind() to port {} failed: {}", m_port,
-              SocketStrerror());
-    return result;
-  }
-
-  result = listen(m_lsd, 5);
-  if (result != 0) {
-    WPI_ERROR(m_logger, "listen() on port {} failed: {}", m_port,
-              SocketStrerror());
-    return result;
-  }
-  m_listening = true;
-  return result;
-}
-
-void TCPAcceptor::shutdown() {
-  m_shutdown = true;
-#ifdef _WIN32
-  ::shutdown(m_lsd, SD_BOTH);
-
-  // this is ugly, but the easiest way to do this
-  // force wakeup of accept() with a non-blocking connect to ourselves
-  struct sockaddr_in address;
-
-  std::memset(&address, 0, sizeof(address));
-  address.sin_family = PF_INET;
-  SmallString<128> addr_copy;
-  if (m_address.size() > 0)
-    addr_copy = m_address;
-  else
-    addr_copy = "127.0.0.1";
-  addr_copy.push_back('\0');
-  int size = sizeof(address);
-  if (WSAStringToAddress(addr_copy.data(), PF_INET, nullptr,
-                         (struct sockaddr*)&address, &size) != 0)
-    return;
-  address.sin_port = htons(m_port);
-
-  int result = -1, sd = socket(AF_INET, SOCK_STREAM, 0);
-  if (sd < 0)
-    return;
-
-  // Set socket to non-blocking
-  u_long mode = 1;
-  ioctlsocket(sd, FIONBIO, &mode);
-
-  // Try to connect
-  ::connect(sd, (struct sockaddr*)&address, sizeof(address));
-
-  // Close
-  ::closesocket(sd);
-
-#else
-  ::shutdown(m_lsd, SHUT_RDWR);
-  int nullfd = ::open("/dev/null", O_RDONLY);
-  if (nullfd >= 0) {
-    ::dup2(nullfd, m_lsd);
-    ::close(nullfd);
-  }
-#endif
-}
-
-std::unique_ptr<NetworkStream> TCPAcceptor::accept() {
-  if (!m_listening || m_shutdown) {
-    return nullptr;
-  }
-
-  struct sockaddr_in address;
-#ifdef _WIN32
-  int len = sizeof(address);
-#else
-  socklen_t len = sizeof(address);
-#endif
-  std::memset(&address, 0, sizeof(address));
-  int sd = ::accept(m_lsd, reinterpret_cast<struct sockaddr*>(&address), &len);
-  if (sd < 0) {
-    if (!m_shutdown) {
-      WPI_ERROR(m_logger, "accept() on port {} failed: {}", m_port,
-                SocketStrerror());
-    }
-    return nullptr;
-  }
-  if (m_shutdown) {
-#ifdef _WIN32
-    closesocket(sd);
-#else
-    close(sd);
-#endif
-    return nullptr;
-  }
-  return std::unique_ptr<NetworkStream>(new TCPStream(sd, &address));
-}
diff --git a/wpiutil/src/main/native/cpp/TCPConnector.cpp b/wpiutil/src/main/native/cpp/TCPConnector.cpp
deleted file mode 100644
index ed97962..0000000
--- a/wpiutil/src/main/native/cpp/TCPConnector.cpp
+++ /dev/null
@@ -1,218 +0,0 @@
-/*
-   TCPConnector.h
-
-   TCPConnector class definition. TCPConnector provides methods to actively
-   establish TCP/IP connections with a server.
-
-   ------------------------------------------
-
-   Copyright (c) 2013 [Vic Hargrave - http://vichargrave.com]
-
-   Licensed under the Apache License, Version 2.0 (the "License");
-   you may not use this file except in compliance with the License.
-   You may obtain a copy of the License at
-
-       http://www.apache.org/licenses/LICENSE-2.0
-
-   Unless required by applicable law or agreed to in writing, software
-   distributed under the License is distributed on an "AS IS" BASIS,
-   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-   See the License for the specific language governing permissions and
-   limitations under the License
-*/
-
-#include "wpi/TCPConnector.h"
-
-#include <fcntl.h>
-
-#include <cerrno>
-#include <cstdio>
-#include <cstring>
-
-#ifdef _WIN32
-#include <WS2tcpip.h>
-#include <WinSock2.h>
-#else
-#include <arpa/inet.h>
-#include <netdb.h>
-#include <netinet/in.h>
-#include <sys/select.h>
-#include <unistd.h>
-#endif
-
-#include "wpi/Logger.h"
-#include "wpi/SmallString.h"
-#include "wpi/SocketError.h"
-#include "wpi/TCPStream.h"
-
-using namespace wpi;
-
-static int ResolveHostName(const char* hostname, struct in_addr* addr) {
-  struct addrinfo hints;
-  struct addrinfo* res;
-
-  hints.ai_flags = 0;
-  hints.ai_family = AF_INET;
-  hints.ai_socktype = SOCK_STREAM;
-  hints.ai_protocol = 0;
-  hints.ai_addrlen = 0;
-  hints.ai_addr = nullptr;
-  hints.ai_canonname = nullptr;
-  hints.ai_next = nullptr;
-  int result = getaddrinfo(hostname, nullptr, &hints, &res);
-  if (result == 0) {
-    std::memcpy(
-        addr, &(reinterpret_cast<struct sockaddr_in*>(res->ai_addr)->sin_addr),
-        sizeof(struct in_addr));
-    freeaddrinfo(res);
-  }
-  return result;
-}
-
-std::unique_ptr<NetworkStream> TCPConnector::connect(const char* server,
-                                                     int port, Logger& logger,
-                                                     int timeout) {
-#ifdef _WIN32
-  struct WSAHelper {
-    WSAHelper() {
-      WSAData wsaData;
-      WORD wVersionRequested = MAKEWORD(2, 2);
-      WSAStartup(wVersionRequested, &wsaData);
-    }
-    ~WSAHelper() { WSACleanup(); }
-  };
-  static WSAHelper helper;
-#endif
-  struct sockaddr_in address;
-
-  std::memset(&address, 0, sizeof(address));
-  address.sin_family = AF_INET;
-  if (ResolveHostName(server, &(address.sin_addr)) != 0) {
-#ifdef _WIN32
-    SmallString<128> addr_copy(server);
-    addr_copy.push_back('\0');
-    int res = InetPton(PF_INET, addr_copy.data(), &(address.sin_addr));
-#else
-    int res = inet_pton(PF_INET, server, &(address.sin_addr));
-#endif
-    if (res != 1) {
-      WPI_ERROR(logger, "could not resolve {} address", server);
-      return nullptr;
-    }
-  }
-  address.sin_port = htons(port);
-
-  if (timeout == 0) {
-    int sd = socket(AF_INET, SOCK_STREAM, 0);
-    if (sd < 0) {
-      WPI_ERROR(logger, "{}", "could not create socket");
-      return nullptr;
-    }
-    if (::connect(sd, reinterpret_cast<struct sockaddr*>(&address),
-                  sizeof(address)) != 0) {
-      WPI_ERROR(logger, "connect() to {} port {} failed: {}", server, port,
-                SocketStrerror());
-#ifdef _WIN32
-      closesocket(sd);
-#else
-      ::close(sd);
-#endif
-      return nullptr;
-    }
-    return std::unique_ptr<NetworkStream>(new TCPStream(sd, &address));
-  }
-
-  fd_set sdset;
-  struct timeval tv;
-  socklen_t len;
-  int result = -1, valopt, sd = socket(AF_INET, SOCK_STREAM, 0);
-  if (sd < 0) {
-    WPI_ERROR(logger, "{}", "could not create socket");
-    return nullptr;
-  }
-
-// Set socket to non-blocking
-#ifdef _WIN32
-  u_long mode = 1;
-  if (ioctlsocket(sd, FIONBIO, &mode) == SOCKET_ERROR)
-    WPI_WARNING(logger, "could not set socket to non-blocking: {}",
-                SocketStrerror());
-#else
-  int arg;
-  arg = fcntl(sd, F_GETFL, nullptr);
-  if (arg < 0) {
-    WPI_WARNING(logger, "could not set socket to non-blocking: {}",
-                SocketStrerror());
-  } else {
-    arg |= O_NONBLOCK;
-    if (fcntl(sd, F_SETFL, arg) < 0) {
-      WPI_WARNING(logger, "could not set socket to non-blocking: {}",
-                  SocketStrerror());
-    }
-  }
-#endif
-
-  // Connect with time limit
-  if ((result = ::connect(sd, reinterpret_cast<struct sockaddr*>(&address),
-                          sizeof(address))) < 0) {
-    int my_errno = SocketErrno();
-#ifdef _WIN32
-    if (my_errno == WSAEWOULDBLOCK || my_errno == WSAEINPROGRESS) {
-#else
-    if (my_errno == EWOULDBLOCK || my_errno == EINPROGRESS) {
-#endif
-      tv.tv_sec = timeout;
-      tv.tv_usec = 0;
-      FD_ZERO(&sdset);
-      FD_SET(sd, &sdset);
-      if (select(sd + 1, nullptr, &sdset, nullptr, &tv) > 0) {
-        len = sizeof(int);
-        getsockopt(sd, SOL_SOCKET, SO_ERROR, reinterpret_cast<char*>(&valopt),
-                   &len);
-        if (valopt) {
-          WPI_ERROR(logger, "select() to {} port {} error {} - {}", server,
-                    port, valopt, SocketStrerror(valopt));
-        } else {
-          // connection established
-          result = 0;
-        }
-      } else {
-        WPI_INFO(logger, "connect() to {} port {} timed out", server, port);
-      }
-    } else {
-      WPI_ERROR(logger, "connect() to {} port {} error {} - {}", server, port,
-                SocketErrno(), SocketStrerror());
-    }
-  }
-
-// Return socket to blocking mode
-#ifdef _WIN32
-  mode = 0;
-  if (ioctlsocket(sd, FIONBIO, &mode) == SOCKET_ERROR)
-    WPI_WARNING(logger, "could not set socket to blocking: {}",
-                SocketStrerror());
-#else
-  arg = fcntl(sd, F_GETFL, nullptr);
-  if (arg < 0) {
-    WPI_WARNING(logger, "could not set socket to blocking: {}",
-                SocketStrerror());
-  } else {
-    arg &= (~O_NONBLOCK);
-    if (fcntl(sd, F_SETFL, arg) < 0) {
-      WPI_WARNING(logger, "could not set socket to blocking: {}",
-                  SocketStrerror());
-    }
-  }
-#endif
-
-  // Create stream object if connected, close if not.
-  if (result == -1) {
-#ifdef _WIN32
-    closesocket(sd);
-#else
-    ::close(sd);
-#endif
-    return nullptr;
-  }
-  return std::unique_ptr<NetworkStream>(new TCPStream(sd, &address));
-}
diff --git a/wpiutil/src/main/native/cpp/TCPConnector_parallel.cpp b/wpiutil/src/main/native/cpp/TCPConnector_parallel.cpp
deleted file mode 100644
index 26258cf..0000000
--- a/wpiutil/src/main/native/cpp/TCPConnector_parallel.cpp
+++ /dev/null
@@ -1,131 +0,0 @@
-// Copyright (c) FIRST and other WPILib contributors.
-// Open Source Software; you can modify and/or share it under the terms of
-// the WPILib BSD license file in the root directory of this project.
-
-#include "wpi/TCPConnector.h"  // NOLINT(build/include_order)
-
-#include <atomic>
-#include <chrono>
-#include <thread>
-#include <tuple>
-
-#include "wpi/SmallSet.h"
-#include "wpi/condition_variable.h"
-#include "wpi/mutex.h"
-
-using namespace wpi;
-
-// MSVC < 1900 doesn't have support for thread_local
-#if !defined(_MSC_VER) || _MSC_VER >= 1900
-// clang check for availability of thread_local
-#if !defined(__has_feature) || __has_feature(cxx_thread_local)
-#define HAVE_THREAD_LOCAL
-#endif
-#endif
-
-std::unique_ptr<NetworkStream> TCPConnector::connect_parallel(
-    span<const std::pair<const char*, int>> servers, Logger& logger,
-    int timeout) {
-  if (servers.empty()) {
-    return nullptr;
-  }
-
-  // structure to make sure we don't start duplicate workers
-  struct GlobalState {
-    wpi::mutex mtx;
-#ifdef HAVE_THREAD_LOCAL
-    SmallSet<std::pair<std::string, int>, 16> active;
-#else
-    SmallSet<std::tuple<std::thread::id, std::string, int>, 16> active;
-#endif
-  };
-#ifdef HAVE_THREAD_LOCAL
-  thread_local auto global = std::make_shared<GlobalState>();
-#else
-  static auto global = std::make_shared<GlobalState>();
-  auto this_id = std::this_thread::get_id();
-#endif
-  auto local = global;  // copy to an automatic variable for lambda capture
-
-  // structure shared between threads and this function
-  struct Result {
-    wpi::mutex mtx;
-    wpi::condition_variable cv;
-    std::unique_ptr<NetworkStream> stream;
-    std::atomic<unsigned int> count{0};
-    std::atomic<bool> done{false};
-  };
-  auto result = std::make_shared<Result>();
-
-  // start worker threads; this is I/O bound so we don't limit to # of procs
-  Logger* plogger = &logger;
-  unsigned int num_workers = 0;
-  for (const auto& server : servers) {
-    std::pair<std::string, int> server_copy{std::string{server.first},
-                                            server.second};
-#ifdef HAVE_THREAD_LOCAL
-    const auto& active_tracker = server_copy;
-#else
-    std::tuple<std::thread::id, std::string, int> active_tracker{
-        this_id, server_copy.first, server_copy.second};
-#endif
-
-    // don't start a new worker if we had a previously still-active connection
-    // attempt to the same server
-    {
-      std::scoped_lock lock(local->mtx);
-      if (local->active.count(active_tracker) > 0) {
-        continue;  // already in set
-      }
-    }
-
-    ++num_workers;
-
-    // start the worker
-    std::thread([=] {
-      if (!result->done) {
-        // add to global state
-        {
-          std::scoped_lock lock(local->mtx);
-          local->active.insert(active_tracker);
-        }
-
-        // try to connect
-        auto stream = connect(server_copy.first.c_str(), server_copy.second,
-                              *plogger, timeout);
-
-        // remove from global state
-        {
-          std::scoped_lock lock(local->mtx);
-          local->active.erase(active_tracker);
-        }
-
-        // successful connection
-        if (stream) {
-          std::scoped_lock lock(result->mtx);
-          if (!result->done.exchange(true)) {
-            result->stream = std::move(stream);
-          }
-        }
-      }
-      ++result->count;
-      result->cv.notify_all();
-    }).detach();
-  }
-
-  // wait for a result, timeout, or all finished
-  std::unique_lock lock(result->mtx);
-  if (timeout == 0) {
-    result->cv.wait(
-        lock, [&] { return result->stream || result->count >= num_workers; });
-  } else {
-    auto timeout_time =
-        std::chrono::steady_clock::now() + std::chrono::seconds(timeout);
-    result->cv.wait_until(lock, timeout_time, [&] {
-      return result->stream || result->count >= num_workers;
-    });
-  }
-
-  // no need to wait for remaining worker threads; shared_ptr will clean up
-  return std::move(result->stream);
-}
diff --git a/wpiutil/src/main/native/cpp/TCPStream.cpp b/wpiutil/src/main/native/cpp/TCPStream.cpp
deleted file mode 100644
index 4567161..0000000
--- a/wpiutil/src/main/native/cpp/TCPStream.cpp
+++ /dev/null
@@ -1,230 +0,0 @@
-/*
-   TCPStream.h
-
-   TCPStream class definition. TCPStream provides methods to transfer
-   data between peers over a TCP/IP connection.
-
-   ------------------------------------------
-
-   Copyright (c) 2013 [Vic Hargrave - http://vichargrave.com]
-
-   Licensed under the Apache License, Version 2.0 (the "License");
-   you may not use this file except in compliance with the License.
-   You may obtain a copy of the License at
-
-       http://www.apache.org/licenses/LICENSE-2.0
-
-   Unless required by applicable law or agreed to in writing, software
-   distributed under the License is distributed on an "AS IS" BASIS,
-   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-   See the License for the specific language governing permissions and
-   limitations under the License.
-*/
-
-#include "wpi/TCPStream.h"
-
-#include <fcntl.h>
-
-#ifdef _WIN32
-#include <winsock2.h>
-#include <ws2tcpip.h>
-#else
-#include <arpa/inet.h>
-#include <netinet/tcp.h>
-#include <unistd.h>
-#endif
-
-#include <cerrno>
-
-using namespace wpi;
-
-TCPStream::TCPStream(int sd, sockaddr_in* address)
-    : m_sd(sd), m_blocking(true) {
-  char ip[50];
-#ifdef _WIN32
-  InetNtop(PF_INET, &(address->sin_addr.s_addr), ip, sizeof(ip) - 1);
-#else
-  inet_ntop(PF_INET, reinterpret_cast<in_addr*>(&(address->sin_addr.s_addr)),
-            ip, sizeof(ip) - 1);
-#ifdef SO_NOSIGPIPE
-  // disable SIGPIPE on Mac OS X
-  int set = 1;
-  setsockopt(m_sd, SOL_SOCKET, SO_NOSIGPIPE, reinterpret_cast<char*>(&set),
-             sizeof set);
-#endif
-#endif
-  m_peerIP = ip;
-  m_peerPort = ntohs(address->sin_port);
-}
-
-TCPStream::~TCPStream() {
-  close();
-}
-
-size_t TCPStream::send(const char* buffer, size_t len, Error* err) {
-  if (m_sd < 0) {
-    *err = kConnectionClosed;
-    return 0;
-  }
-#ifdef _WIN32
-  WSABUF wsaBuf;
-  wsaBuf.buf = const_cast<char*>(buffer);
-  wsaBuf.len = (ULONG)len;
-  DWORD rv;
-  bool result = true;
-  while (WSASend(m_sd, &wsaBuf, 1, &rv, 0, nullptr, nullptr) == SOCKET_ERROR) {
-    if (WSAGetLastError() != WSAEWOULDBLOCK) {
-      result = false;
-      break;
-    }
-    if (!m_blocking) {
-      *err = kWouldBlock;
-      return 0;
-    }
-    Sleep(1);
-  }
-  if (!result) {
-    char Buffer[128];
-#ifdef _MSC_VER
-    sprintf_s(Buffer, "Send() failed: WSA error=%d\n", WSAGetLastError());
-#else
-    std::snprintf(Buffer, sizeof(Buffer), "Send() failed: WSA error=%d\n",
-                  WSAGetLastError());
-#endif
-    OutputDebugStringA(Buffer);
-    *err = kConnectionReset;
-    return 0;
-  }
-#else
-#ifdef MSG_NOSIGNAL
-  // disable SIGPIPE on Linux
-  ssize_t rv = ::send(m_sd, buffer, len, MSG_NOSIGNAL);
-#else
-  ssize_t rv = ::send(m_sd, buffer, len, 0);
-#endif
-  if (rv < 0) {
-    if (!m_blocking && (errno == EAGAIN || errno == EWOULDBLOCK)) {
-      *err = kWouldBlock;
-    } else {
-      *err = kConnectionReset;
-    }
-    return 0;
-  }
-#endif
-  return static_cast<size_t>(rv);
-}
-
-size_t TCPStream::receive(char* buffer, size_t len, Error* err, int timeout) {
-  if (m_sd < 0) {
-    *err = kConnectionClosed;
-    return 0;
-  }
-#ifdef _WIN32
-  int rv;
-#else
-  ssize_t rv;
-#endif
-  if (timeout <= 0) {
-#ifdef _WIN32
-    rv = recv(m_sd, buffer, len, 0);
-#else
-    rv = read(m_sd, buffer, len);
-#endif
-  } else if (WaitForReadEvent(timeout)) {
-#ifdef _WIN32
-    rv = recv(m_sd, buffer, len, 0);
-#else
-    rv = read(m_sd, buffer, len);
-#endif
-  } else {
-    *err = kConnectionTimedOut;
-    return 0;
-  }
-  if (rv < 0) {
-#ifdef _WIN32
-    if (!m_blocking && WSAGetLastError() == WSAEWOULDBLOCK) {
-#else
-    if (!m_blocking && (errno == EAGAIN || errno == EWOULDBLOCK)) {
-#endif
-      *err = kWouldBlock;
-    } else {
-      *err = kConnectionReset;
-    }
-    return 0;
-  }
-  return static_cast<size_t>(rv);
-}
-
-void TCPStream::close() {
-  if (m_sd >= 0) {
-#ifdef _WIN32
-    ::shutdown(m_sd, SD_BOTH);
-    closesocket(m_sd);
-#else
-    ::shutdown(m_sd, SHUT_RDWR);
-    ::close(m_sd);
-#endif
-  }
-  m_sd = -1;
-}
-
-std::string_view TCPStream::getPeerIP() const {
-  return m_peerIP;
-}
-
-int TCPStream::getPeerPort() const {
-  return m_peerPort;
-}
-
-void TCPStream::setNoDelay() {
-  if (m_sd < 0) {
-    return;
-  }
-  int optval = 1;
-  setsockopt(m_sd, IPPROTO_TCP, TCP_NODELAY, reinterpret_cast<char*>(&optval),
-             sizeof optval);
-}
-
-bool TCPStream::setBlocking(bool enabled) {
-  if (m_sd < 0) {
-    return true;  // silently accept
-  }
-#ifdef _WIN32
-  u_long mode = enabled ? 0 : 1;
-  if (ioctlsocket(m_sd, FIONBIO, &mode) == SOCKET_ERROR) {
-    return false;
-  }
-#else
-  int flags = fcntl(m_sd, F_GETFL, nullptr);
-  if (flags < 0) {
-    return false;
-  }
-  if (enabled) {
-    flags &= ~O_NONBLOCK;
-  } else {
-    flags |= O_NONBLOCK;
-  }
-  if (fcntl(m_sd, F_SETFL, flags) < 0) {
-    return false;
-  }
-#endif
-  return true;
-}
-
-int TCPStream::getNativeHandle() const {
-  return m_sd;
-}
-
-bool TCPStream::WaitForReadEvent(int timeout) {
-  fd_set sdset;
-  struct timeval tv;
-
-  tv.tv_sec = timeout;
-  tv.tv_usec = 0;
-  FD_ZERO(&sdset);
-  FD_SET(m_sd, &sdset);
-  if (select(m_sd + 1, &sdset, nullptr, nullptr, &tv) > 0) {
-    return true;
-  }
-  return false;
-}
diff --git a/wpiutil/src/main/native/cpp/UDPClient.cpp b/wpiutil/src/main/native/cpp/UDPClient.cpp
deleted file mode 100644
index 108ef54..0000000
--- a/wpiutil/src/main/native/cpp/UDPClient.cpp
+++ /dev/null
@@ -1,248 +0,0 @@
-// Copyright (c) FIRST and other WPILib contributors.
-// Open Source Software; you can modify and/or share it under the terms of
-// the WPILib BSD license file in the root directory of this project.
-
-#include "wpi/UDPClient.h"
-
-#ifdef _WIN32
-#include <WinSock2.h>
-#include <Ws2tcpip.h>
-#pragma comment(lib, "Ws2_32.lib")
-#else
-#include <arpa/inet.h>
-#include <fcntl.h>
-#include <netinet/in.h>
-#include <unistd.h>
-#endif
-
-#include "wpi/Logger.h"
-#include "wpi/SmallString.h"
-#include "wpi/SocketError.h"
-
-using namespace wpi;
-
-UDPClient::UDPClient(Logger& logger) : UDPClient("", logger) {}
-
-UDPClient::UDPClient(std::string_view address, Logger& logger)
-    : m_lsd(0), m_port(0), m_address(address), m_logger(logger) {}
-
-UDPClient::UDPClient(UDPClient&& other)
-    : m_lsd(other.m_lsd),
-      m_port(other.m_port),
-      m_address(std::move(other.m_address)),
-      m_logger(other.m_logger) {
-  other.m_lsd = 0;
-  other.m_port = 0;
-}
-
-UDPClient::~UDPClient() {
-  if (m_lsd > 0) {
-    shutdown();
-  }
-}
-
-UDPClient& UDPClient::operator=(UDPClient&& other) {
-  if (this == &other) {
-    return *this;
-  }
-  shutdown();
-  m_logger = other.m_logger;
-  m_lsd = other.m_lsd;
-  m_address = std::move(other.m_address);
-  m_port = other.m_port;
-  other.m_lsd = 0;
-  other.m_port = 0;
-  return *this;
-}
-
-int UDPClient::start() {
-  return start(0);
-}
-
-int UDPClient::start(int port) {
-  if (m_lsd > 0) {
-    return 0;
-  }
-
-#ifdef _WIN32
-  WSAData wsaData;
-  WORD wVersionRequested = MAKEWORD(2, 2);
-  WSAStartup(wVersionRequested, &wsaData);
-#endif
-
-  m_lsd = socket(AF_INET, SOCK_DGRAM, 0);
-
-  if (m_lsd < 0) {
-    WPI_ERROR(m_logger, "{}", "could not create socket");
-    return -1;
-  }
-
-  struct sockaddr_in addr;
-  std::memset(&addr, 0, sizeof(addr));
-  addr.sin_family = AF_INET;
-  if (m_address.size() > 0) {
-#ifdef _WIN32
-    SmallString<128> addr_copy(m_address);
-    addr_copy.push_back('\0');
-    int res = InetPton(PF_INET, addr_copy.data(), &(addr.sin_addr));
-#else
-    int res = inet_pton(PF_INET, m_address.c_str(), &(addr.sin_addr));
-#endif
-    if (res != 1) {
-      WPI_ERROR(m_logger, "could not resolve {} address", m_address);
-      return -1;
-    }
-  } else {
-    addr.sin_addr.s_addr = INADDR_ANY;
-  }
-  addr.sin_port = htons(port);
-
-  if (port != 0) {
-#ifdef _WIN32
-    int optval = 1;
-    setsockopt(m_lsd, SOL_SOCKET, SO_EXCLUSIVEADDRUSE,
-               reinterpret_cast<char*>(&optval), sizeof optval);
-#else
-    int optval = 1;
-    setsockopt(m_lsd, SOL_SOCKET, SO_REUSEADDR,
-               reinterpret_cast<char*>(&optval), sizeof optval);
-#endif
-  }
-
-  int result = bind(m_lsd, reinterpret_cast<sockaddr*>(&addr), sizeof(addr));
-  if (result != 0) {
-    WPI_ERROR(m_logger, "bind() failed: {}", SocketStrerror());
-    return result;
-  }
-  m_port = port;
-  return 0;
-}
-
-void UDPClient::shutdown() {
-  if (m_lsd > 0) {
-#ifdef _WIN32
-    ::shutdown(m_lsd, SD_BOTH);
-    closesocket(m_lsd);
-    WSACleanup();
-#else
-    ::shutdown(m_lsd, SHUT_RDWR);
-    close(m_lsd);
-#endif
-    m_lsd = 0;
-    m_port = 0;
-  }
-}
-
-int UDPClient::send(span<const uint8_t> data, std::string_view server,
-                    int port) {
-  // server must be a resolvable IP address
-  struct sockaddr_in addr;
-  std::memset(&addr, 0, sizeof(addr));
-  addr.sin_family = AF_INET;
-  SmallString<128> remoteAddr{server};
-  if (remoteAddr.empty()) {
-    WPI_ERROR(m_logger, "{}", "server must be passed");
-    return -1;
-  }
-
-#ifdef _WIN32
-  int res = InetPton(AF_INET, remoteAddr.c_str(), &(addr.sin_addr));
-#else
-  int res = inet_pton(AF_INET, remoteAddr.c_str(), &(addr.sin_addr));
-#endif
-  if (res != 1) {
-    WPI_ERROR(m_logger, "could not resolve {} address", server);
-    return -1;
-  }
-  addr.sin_port = htons(port);
-
-  // sendto should not block
-  int result =
-      sendto(m_lsd, reinterpret_cast<const char*>(data.data()), data.size(), 0,
-             reinterpret_cast<sockaddr*>(&addr), sizeof(addr));
-  return result;
-}
-
-int UDPClient::send(std::string_view data, std::string_view server, int port) {
-  // server must be a resolvable IP address
-  struct sockaddr_in addr;
-  std::memset(&addr, 0, sizeof(addr));
-  addr.sin_family = AF_INET;
-  SmallString<128> remoteAddr{server};
-  if (remoteAddr.empty()) {
-    WPI_ERROR(m_logger, "{}", "server must be passed");
-    return -1;
-  }
-
-#ifdef _WIN32
-  int res = InetPton(AF_INET, remoteAddr.c_str(), &(addr.sin_addr));
-#else
-  int res = inet_pton(AF_INET, remoteAddr.c_str(), &(addr.sin_addr));
-#endif
-  if (res != 1) {
-    WPI_ERROR(m_logger, "could not resolve {} address", server);
-    return -1;
-  }
-  addr.sin_port = htons(port);
-
-  // sendto should not block
-  int result = sendto(m_lsd, data.data(), data.size(), 0,
-                      reinterpret_cast<sockaddr*>(&addr), sizeof(addr));
-  return result;
-}
-
-int UDPClient::receive(uint8_t* data_received, int receive_len) {
-  if (m_port == 0) {
-    return -1;  // return if not receiving
-  }
-  return recv(m_lsd, reinterpret_cast<char*>(data_received), receive_len, 0);
-}
-
-int UDPClient::receive(uint8_t* data_received, int receive_len,
-                       SmallVectorImpl<char>* addr_received,
-                       int* port_received) {
-  if (m_port == 0) {
-    return -1;  // return if not receiving
-  }
-
-  struct sockaddr_in remote;
-  socklen_t remote_len = sizeof(remote);
-  std::memset(&remote, 0, sizeof(remote));
-
-  int result =
-      recvfrom(m_lsd, reinterpret_cast<char*>(data_received), receive_len, 0,
-               reinterpret_cast<sockaddr*>(&remote), &remote_len);
-
-  char ip[50];
-#ifdef _WIN32
-  InetNtop(PF_INET, &(remote.sin_addr.s_addr), ip, sizeof(ip) - 1);
-#else
-  inet_ntop(PF_INET, reinterpret_cast<in_addr*>(&(remote.sin_addr.s_addr)), ip,
-            sizeof(ip) - 1);
-#endif
-
-  ip[49] = '\0';
-  int addr_len = std::strlen(ip);
-  addr_received->clear();
-  addr_received->append(&ip[0], &ip[addr_len]);
-
-  *port_received = ntohs(remote.sin_port);
-
-  return result;
-}
-
-int UDPClient::set_timeout(double timeout) {
-  if (timeout < 0) {
-    return -1;
-  }
-  struct timeval tv;
-  tv.tv_sec = timeout;             // truncating will give seconds
-  timeout -= tv.tv_sec;            // remove seconds portion
-  tv.tv_usec = timeout * 1000000;  // fractions of a second to us
-  int ret = setsockopt(m_lsd, SOL_SOCKET, SO_RCVTIMEO,
-                       reinterpret_cast<char*>(&tv), sizeof(tv));
-  if (ret < 0) {
-    WPI_ERROR(m_logger, "{}", "set timeout failed");
-  }
-  return ret;
-}
diff --git a/wpiutil/src/main/native/cpp/WebSocket.cpp b/wpiutil/src/main/native/cpp/WebSocket.cpp
deleted file mode 100644
index 4bb49d3..0000000
--- a/wpiutil/src/main/native/cpp/WebSocket.cpp
+++ /dev/null
@@ -1,656 +0,0 @@
-// Copyright (c) FIRST and other WPILib contributors.
-// Open Source Software; you can modify and/or share it under the terms of
-// the WPILib BSD license file in the root directory of this project.
-
-#include "wpi/WebSocket.h"
-
-#include <random>
-
-#include "fmt/format.h"
-#include "wpi/Base64.h"
-#include "wpi/HttpParser.h"
-#include "wpi/SmallString.h"
-#include "wpi/SmallVector.h"
-#include "wpi/StringExtras.h"
-#include "wpi/raw_uv_ostream.h"
-#include "wpi/sha1.h"
-#include "wpi/uv/Stream.h"
-
-using namespace wpi;
-
-namespace {
-class WebSocketWriteReq : public uv::WriteReq {
- public:
-  explicit WebSocketWriteReq(
-      std::function<void(span<uv::Buffer>, uv::Error)> callback)
-      : m_callback{std::move(callback)} {
-    finish.connect([this](uv::Error err) {
-      span<uv::Buffer> bufs{m_bufs};
-      for (auto&& buf : bufs.subspan(0, m_startUser)) {
-        buf.Deallocate();
-      }
-      m_callback(bufs.subspan(m_startUser), err);
-    });
-  }
-
-  std::function<void(span<uv::Buffer>, uv::Error)> m_callback;
-  SmallVector<uv::Buffer, 4> m_bufs;
-  size_t m_startUser;
-};
-}  // namespace
-
-class WebSocket::ClientHandshakeData {
- public:
-  ClientHandshakeData() {
-    // key is a random nonce
-    static std::random_device rd;
-    static std::default_random_engine gen{rd()};
-    std::uniform_int_distribution<unsigned int> dist(0, 255);
-    char nonce[16];  // the nonce sent to the server
-    for (char& v : nonce) {
-      v = static_cast<char>(dist(gen));
-    }
-    raw_svector_ostream os(key);
-    Base64Encode(os, {nonce, 16});
-  }
-  ~ClientHandshakeData() {
-    if (auto t = timer.lock()) {
-      t->Stop();
-      t->Close();
-    }
-  }
-
-  SmallString<64> key;                       // the key sent to the server
-  SmallVector<std::string, 2> protocols;     // valid protocols
-  HttpParser parser{HttpParser::kResponse};  // server response parser
-  bool hasUpgrade = false;
-  bool hasConnection = false;
-  bool hasAccept = false;
-  bool hasProtocol = false;
-
-  std::weak_ptr<uv::Timer> timer;
-};
-
-static std::string_view AcceptHash(std::string_view key,
-                                   SmallVectorImpl<char>& buf) {
-  SHA1 hash;
-  hash.Update(key);
-  hash.Update("258EAFA5-E914-47DA-95CA-C5AB0DC85B11");
-  SmallString<64> hashBuf;
-  return Base64Encode(hash.RawFinal(hashBuf), buf);
-}
-
-WebSocket::WebSocket(uv::Stream& stream, bool server, const private_init&)
-    : m_stream{stream}, m_server{server} {
-  // Connect closed and error signals to ourselves
-  m_stream.closed.connect([this]() { SetClosed(1006, "handle closed"); });
-  m_stream.error.connect([this](uv::Error err) {
-    Terminate(1006, fmt::format("stream error: {}", err.name()));
-  });
-
-  // Start reading
-  m_stream.StopRead();  // we may have been reading
-  m_stream.StartRead();
-  m_stream.data.connect(
-      [this](uv::Buffer& buf, size_t size) { HandleIncoming(buf, size); });
-  m_stream.end.connect(
-      [this]() { Terminate(1006, "remote end closed connection"); });
-}
-
-WebSocket::~WebSocket() = default;
-
-std::shared_ptr<WebSocket> WebSocket::CreateClient(
-    uv::Stream& stream, std::string_view uri, std::string_view host,
-    span<const std::string_view> protocols, const ClientOptions& options) {
-  auto ws = std::make_shared<WebSocket>(stream, false, private_init{});
-  stream.SetData(ws);
-  ws->StartClient(uri, host, protocols, options);
-  return ws;
-}
-
-std::shared_ptr<WebSocket> WebSocket::CreateServer(uv::Stream& stream,
-                                                   std::string_view key,
-                                                   std::string_view version,
-                                                   std::string_view protocol) {
-  auto ws = std::make_shared<WebSocket>(stream, true, private_init{});
-  stream.SetData(ws);
-  ws->StartServer(key, version, protocol);
-  return ws;
-}
-
-void WebSocket::Close(uint16_t code, std::string_view reason) {
-  SendClose(code, reason);
-  if (m_state != FAILED && m_state != CLOSED) {
-    m_state = CLOSING;
-  }
-}
-
-void WebSocket::Fail(uint16_t code, std::string_view reason) {
-  if (m_state == FAILED || m_state == CLOSED) {
-    return;
-  }
-  SendClose(code, reason);
-  SetClosed(code, reason, true);
-  Shutdown();
-}
-
-void WebSocket::Terminate(uint16_t code, std::string_view reason) {
-  if (m_state == FAILED || m_state == CLOSED) {
-    return;
-  }
-  SetClosed(code, reason);
-  Shutdown();
-}
-
-void WebSocket::StartClient(std::string_view uri, std::string_view host,
-                            span<const std::string_view> protocols,
-                            const ClientOptions& options) {
-  // Create client handshake data
-  m_clientHandshake = std::make_unique<ClientHandshakeData>();
-
-  // Build client request
-  SmallVector<uv::Buffer, 4> bufs;
-  raw_uv_ostream os{bufs, 4096};
-
-  os << "GET " << uri << " HTTP/1.1\r\n";
-  os << "Host: " << host << "\r\n";
-  os << "Upgrade: websocket\r\n";
-  os << "Connection: Upgrade\r\n";
-  os << "Sec-WebSocket-Key: " << m_clientHandshake->key << "\r\n";
-  os << "Sec-WebSocket-Version: 13\r\n";
-
-  // protocols (if provided)
-  if (!protocols.empty()) {
-    os << "Sec-WebSocket-Protocol: ";
-    bool first = true;
-    for (auto protocol : protocols) {
-      if (!first) {
-        os << ", ";
-      } else {
-        first = false;
-      }
-      os << protocol;
-      // also save for later checking against server response
-      m_clientHandshake->protocols.emplace_back(protocol);
-    }
-    os << "\r\n";
-  }
-
-  // other headers
-  for (auto&& header : options.extraHeaders) {
-    os << header.first << ": " << header.second << "\r\n";
-  }
-
-  // finish headers
-  os << "\r\n";
-
-  // Send client request
-  m_stream.Write(bufs, [](auto bufs, uv::Error) {
-    for (auto& buf : bufs) {
-      buf.Deallocate();
-    }
-  });
-
-  // Set up client response handling
-  m_clientHandshake->parser.status.connect([this](std::string_view status) {
-    unsigned int code = m_clientHandshake->parser.GetStatusCode();
-    if (code != 101) {
-      Terminate(code, status);
-    }
-  });
-  m_clientHandshake->parser.header.connect(
-      [this](std::string_view name, std::string_view value) {
-        value = trim(value);
-        if (equals_lower(name, "upgrade")) {
-          if (!equals_lower(value, "websocket")) {
-            return Terminate(1002, "invalid upgrade response value");
-          }
-          m_clientHandshake->hasUpgrade = true;
-        } else if (equals_lower(name, "connection")) {
-          if (!equals_lower(value, "upgrade")) {
-            return Terminate(1002, "invalid connection response value");
-          }
-          m_clientHandshake->hasConnection = true;
-        } else if (equals_lower(name, "sec-websocket-accept")) {
-          // Check against expected response
-          SmallString<64> acceptBuf;
-          if (!equals(value, AcceptHash(m_clientHandshake->key, acceptBuf))) {
-            return Terminate(1002, "invalid accept key");
-          }
-          m_clientHandshake->hasAccept = true;
-        } else if (equals_lower(name, "sec-websocket-extensions")) {
-          // No extensions are supported
-          if (!value.empty()) {
-            return Terminate(1010, "unsupported extension");
-          }
-        } else if (equals_lower(name, "sec-websocket-protocol")) {
-          // Make sure it was one of the provided protocols
-          bool match = false;
-          for (auto&& protocol : m_clientHandshake->protocols) {
-            if (equals_lower(value, protocol)) {
-              match = true;
-              break;
-            }
-          }
-          if (!match) {
-            return Terminate(1003, "unsupported protocol");
-          }
-          m_clientHandshake->hasProtocol = true;
-          m_protocol = value;
-        }
-      });
-  m_clientHandshake->parser.headersComplete.connect([this](bool) {
-    if (!m_clientHandshake->hasUpgrade || !m_clientHandshake->hasConnection ||
-        !m_clientHandshake->hasAccept ||
-        (!m_clientHandshake->hasProtocol &&
-         !m_clientHandshake->protocols.empty())) {
-      return Terminate(1002, "invalid response");
-    }
-    if (m_state == CONNECTING) {
-      m_state = OPEN;
-      open(m_protocol);
-    }
-  });
-
-  // Start handshake timer if a timeout was specified
-  if (options.handshakeTimeout != (uv::Timer::Time::max)()) {
-    auto timer = uv::Timer::Create(m_stream.GetLoopRef());
-    timer->timeout.connect(
-        [this]() { Terminate(1006, "connection timed out"); });
-    timer->Start(options.handshakeTimeout);
-    m_clientHandshake->timer = timer;
-  }
-}
-
-void WebSocket::StartServer(std::string_view key, std::string_view version,
-                            std::string_view protocol) {
-  m_protocol = protocol;
-
-  // Build server response
-  SmallVector<uv::Buffer, 4> bufs;
-  raw_uv_ostream os{bufs, 4096};
-
-  // Handle unsupported version
-  if (version != "13") {
-    os << "HTTP/1.1 426 Upgrade Required\r\n";
-    os << "Upgrade: WebSocket\r\n";
-    os << "Sec-WebSocket-Version: 13\r\n\r\n";
-    m_stream.Write(bufs, [this](auto bufs, uv::Error) {
-      for (auto& buf : bufs) {
-        buf.Deallocate();
-      }
-      // XXX: Should we support sending a new handshake on the same connection?
-      // XXX: "this->" is required by GCC 5.5 (bug)
-      this->Terminate(1003, "unsupported protocol version");
-    });
-    return;
-  }
-
-  os << "HTTP/1.1 101 Switching Protocols\r\n";
-  os << "Upgrade: websocket\r\n";
-  os << "Connection: Upgrade\r\n";
-
-  // accept hash
-  SmallString<64> acceptBuf;
-  os << "Sec-WebSocket-Accept: " << AcceptHash(key, acceptBuf) << "\r\n";
-
-  if (!protocol.empty()) {
-    os << "Sec-WebSocket-Protocol: " << protocol << "\r\n";
-  }
-
-  // end headers
-  os << "\r\n";
-
-  // Send server response
-  m_stream.Write(bufs, [this](auto bufs, uv::Error) {
-    for (auto& buf : bufs) {
-      buf.Deallocate();
-    }
-    if (m_state == CONNECTING) {
-      m_state = OPEN;
-      open(m_protocol);
-    }
-  });
-}
-
-void WebSocket::SendClose(uint16_t code, std::string_view reason) {
-  SmallVector<uv::Buffer, 4> bufs;
-  if (code != 1005) {
-    raw_uv_ostream os{bufs, 4096};
-    const uint8_t codeMsb[] = {static_cast<uint8_t>((code >> 8) & 0xff),
-                               static_cast<uint8_t>(code & 0xff)};
-    os << span{codeMsb};
-    os << reason;
-  }
-  Send(kFlagFin | kOpClose, bufs, [](auto bufs, uv::Error) {
-    for (auto&& buf : bufs) {
-      buf.Deallocate();
-    }
-  });
-}
-
-void WebSocket::SetClosed(uint16_t code, std::string_view reason, bool failed) {
-  if (m_state == FAILED || m_state == CLOSED) {
-    return;
-  }
-  m_state = failed ? FAILED : CLOSED;
-  closed(code, reason);
-}
-
-void WebSocket::Shutdown() {
-  m_stream.Shutdown([this] { m_stream.Close(); });
-}
-
-void WebSocket::HandleIncoming(uv::Buffer& buf, size_t size) {
-  // ignore incoming data if we're failed or closed
-  if (m_state == FAILED || m_state == CLOSED) {
-    return;
-  }
-
-  std::string_view data{buf.base, size};
-
-  // Handle connecting state (mainly on client)
-  if (m_state == CONNECTING) {
-    if (m_clientHandshake) {
-      data = m_clientHandshake->parser.Execute(data);
-      // check for parser failure
-      if (m_clientHandshake->parser.HasError()) {
-        return Terminate(1003, "invalid response");
-      }
-      if (m_state != OPEN) {
-        return;  // not done with handshake yet
-      }
-
-      // we're done with the handshake, so release its memory
-      m_clientHandshake.reset();
-
-      // fall through to process additional data after handshake
-    } else {
-      return Terminate(1003, "got data on server before response");
-    }
-  }
-
-  // Message processing
-  while (!data.empty()) {
-    if (m_frameSize == UINT64_MAX) {
-      // Need at least two bytes to determine header length
-      if (m_header.size() < 2u) {
-        size_t toCopy = (std::min)(2u - m_header.size(), data.size());
-        m_header.append(data.data(), data.data() + toCopy);
-        data.remove_prefix(toCopy);
-        if (m_header.size() < 2u) {
-          return;  // need more data
-        }
-
-        // Validate RSV bits are zero
-        if ((m_header[0] & 0x70) != 0) {
-          return Fail(1002, "nonzero RSV");
-        }
-      }
-
-      // Once we have first two bytes, we can calculate the header size
-      if (m_headerSize == 0) {
-        m_headerSize = 2;
-        uint8_t len = m_header[1] & kLenMask;
-        if (len == 126) {
-          m_headerSize += 2;
-        } else if (len == 127) {
-          m_headerSize += 8;
-        }
-        bool masking = (m_header[1] & kFlagMasking) != 0;
-        if (masking) {
-          m_headerSize += 4;  // masking key
-        }
-        // On server side, incoming messages MUST be masked
-        // On client side, incoming messages MUST NOT be masked
-        if (m_server && !masking) {
-          return Fail(1002, "client data not masked");
-        }
-        if (!m_server && masking) {
-          return Fail(1002, "server data masked");
-        }
-      }
-
-      // Need to complete header to calculate message size
-      if (m_header.size() < m_headerSize) {
-        size_t toCopy = (std::min)(m_headerSize - m_header.size(), data.size());
-        m_header.append(data.data(), data.data() + toCopy);
-        data.remove_prefix(toCopy);
-        if (m_header.size() < m_headerSize) {
-          return;  // need more data
-        }
-      }
-
-      if (m_header.size() >= m_headerSize) {
-        // get payload length
-        uint8_t len = m_header[1] & kLenMask;
-        if (len == 126) {
-          m_frameSize = (static_cast<uint16_t>(m_header[2]) << 8) |
-                        static_cast<uint16_t>(m_header[3]);
-        } else if (len == 127) {
-          m_frameSize = (static_cast<uint64_t>(m_header[2]) << 56) |
-                        (static_cast<uint64_t>(m_header[3]) << 48) |
-                        (static_cast<uint64_t>(m_header[4]) << 40) |
-                        (static_cast<uint64_t>(m_header[5]) << 32) |
-                        (static_cast<uint64_t>(m_header[6]) << 24) |
-                        (static_cast<uint64_t>(m_header[7]) << 16) |
-                        (static_cast<uint64_t>(m_header[8]) << 8) |
-                        static_cast<uint64_t>(m_header[9]);
-        } else {
-          m_frameSize = len;
-        }
-
-        // limit maximum size
-        if ((m_payload.size() + m_frameSize) > m_maxMessageSize) {
-          return Fail(1009, "message too large");
-        }
-      }
-    }
-
-    if (m_frameSize != UINT64_MAX) {
-      size_t need = m_frameStart + m_frameSize - m_payload.size();
-      size_t toCopy = (std::min)(need, data.size());
-      m_payload.append(data.data(), data.data() + toCopy);
-      data.remove_prefix(toCopy);
-      need -= toCopy;
-      if (need == 0) {
-        // We have a complete frame
-        // If the message had masking, unmask it
-        if ((m_header[1] & kFlagMasking) != 0) {
-          uint8_t key[4] = {
-              m_header[m_headerSize - 4], m_header[m_headerSize - 3],
-              m_header[m_headerSize - 2], m_header[m_headerSize - 1]};
-          int n = 0;
-          for (uint8_t& ch : span{m_payload}.subspan(m_frameStart)) {
-            ch ^= key[n++];
-            if (n >= 4) {
-              n = 0;
-            }
-          }
-        }
-
-        // Handle message
-        bool fin = (m_header[0] & kFlagFin) != 0;
-        uint8_t opcode = m_header[0] & kOpMask;
-        switch (opcode) {
-          case kOpCont:
-            switch (m_fragmentOpcode) {
-              case kOpText:
-                if (!m_combineFragments || fin) {
-                  text(std::string_view{reinterpret_cast<char*>(
-                                            m_payload.data()),
-                                        m_payload.size()},
-                       fin);
-                }
-                break;
-              case kOpBinary:
-                if (!m_combineFragments || fin) {
-                  binary(m_payload, fin);
-                }
-                break;
-              default:
-                // no preceding message?
-                return Fail(1002, "invalid continuation message");
-            }
-            if (fin) {
-              m_fragmentOpcode = 0;
-            }
-            break;
-          case kOpText:
-            if (m_fragmentOpcode != 0) {
-              return Fail(1002, "incomplete fragment");
-            }
-            if (!m_combineFragments || fin) {
-              text(std::string_view{reinterpret_cast<char*>(m_payload.data()),
-                                    m_payload.size()},
-                   fin);
-            }
-            if (!fin) {
-              m_fragmentOpcode = opcode;
-            }
-            break;
-          case kOpBinary:
-            if (m_fragmentOpcode != 0) {
-              return Fail(1002, "incomplete fragment");
-            }
-            if (!m_combineFragments || fin) {
-              binary(m_payload, fin);
-            }
-            if (!fin) {
-              m_fragmentOpcode = opcode;
-            }
-            break;
-          case kOpClose: {
-            uint16_t code;
-            std::string_view reason;
-            if (!fin) {
-              code = 1002;
-              reason = "cannot fragment control frames";
-            } else if (m_payload.size() < 2) {
-              code = 1005;
-            } else {
-              code = (static_cast<uint16_t>(m_payload[0]) << 8) |
-                     static_cast<uint16_t>(m_payload[1]);
-              reason = drop_front(
-                  {reinterpret_cast<char*>(m_payload.data()), m_payload.size()},
-                  2);
-            }
-            // Echo the close if we didn't previously send it
-            if (m_state != CLOSING) {
-              SendClose(code, reason);
-            }
-            SetClosed(code, reason);
-            // If we're the server, shutdown the connection.
-            if (m_server) {
-              Shutdown();
-            }
-            break;
-          }
-          case kOpPing:
-            if (!fin) {
-              return Fail(1002, "cannot fragment control frames");
-            }
-            ping(m_payload);
-            break;
-          case kOpPong:
-            if (!fin) {
-              return Fail(1002, "cannot fragment control frames");
-            }
-            pong(m_payload);
-            break;
-          default:
-            return Fail(1002, "invalid message opcode");
-        }
-
-        // Prepare for next message
-        m_header.clear();
-        m_headerSize = 0;
-        if (!m_combineFragments || fin) {
-          m_payload.clear();
-        }
-        m_frameStart = m_payload.size();
-        m_frameSize = UINT64_MAX;
-      }
-    }
-  }
-}
-
-void WebSocket::Send(
-    uint8_t opcode, span<const uv::Buffer> data,
-    std::function<void(span<uv::Buffer>, uv::Error)> callback) {
-  // If we're not open, emit an error and don't send the data
-  if (m_state != OPEN) {
-    int err;
-    if (m_state == CONNECTING) {
-      err = UV_EAGAIN;
-    } else {
-      err = UV_ESHUTDOWN;
-    }
-    SmallVector<uv::Buffer, 4> bufs{data.begin(), data.end()};
-    callback(bufs, uv::Error{err});
-    return;
-  }
-
-  auto req = std::make_shared<WebSocketWriteReq>(std::move(callback));
-  raw_uv_ostream os{req->m_bufs, 4096};
-
-  // opcode (includes FIN bit)
-  os << static_cast<unsigned char>(opcode);
-
-  // payload length
-  uint64_t size = 0;
-  for (auto&& buf : data) {
-    size += buf.len;
-  }
-  if (size < 126) {
-    os << static_cast<unsigned char>((m_server ? 0x00 : kFlagMasking) | size);
-  } else if (size <= 0xffff) {
-    os << static_cast<unsigned char>((m_server ? 0x00 : kFlagMasking) | 126);
-    const uint8_t sizeMsb[] = {static_cast<uint8_t>((size >> 8) & 0xff),
-                               static_cast<uint8_t>(size & 0xff)};
-    os << span{sizeMsb};
-  } else {
-    os << static_cast<unsigned char>((m_server ? 0x00 : kFlagMasking) | 127);
-    const uint8_t sizeMsb[] = {static_cast<uint8_t>((size >> 56) & 0xff),
-                               static_cast<uint8_t>((size >> 48) & 0xff),
-                               static_cast<uint8_t>((size >> 40) & 0xff),
-                               static_cast<uint8_t>((size >> 32) & 0xff),
-                               static_cast<uint8_t>((size >> 24) & 0xff),
-                               static_cast<uint8_t>((size >> 16) & 0xff),
-                               static_cast<uint8_t>((size >> 8) & 0xff),
-                               static_cast<uint8_t>(size & 0xff)};
-    os << span{sizeMsb};
-  }
-
-  // clients need to mask the input data
-  if (!m_server) {
-    // generate masking key
-    static std::random_device rd;
-    static std::default_random_engine gen{rd()};
-    std::uniform_int_distribution<unsigned int> dist(0, 255);
-    uint8_t key[4];
-    for (uint8_t& v : key) {
-      v = dist(gen);
-    }
-    os << span<const uint8_t>{key, 4};
-    // copy and mask data
-    int n = 0;
-    for (auto&& buf : data) {
-      for (auto&& ch : buf.data()) {
-        os << static_cast<unsigned char>(static_cast<uint8_t>(ch) ^ key[n++]);
-        if (n >= 4) {
-          n = 0;
-        }
-      }
-    }
-    req->m_startUser = req->m_bufs.size();
-    req->m_bufs.append(data.begin(), data.end());
-    // don't send the user bufs as we copied their data
-    m_stream.Write(span{req->m_bufs}.subspan(0, req->m_startUser), req);
-  } else {
-    // servers can just send the buffers directly without masking
-    req->m_startUser = req->m_bufs.size();
-    req->m_bufs.append(data.begin(), data.end());
-    m_stream.Write(req->m_bufs, req);
-  }
-}
diff --git a/wpiutil/src/main/native/cpp/WebSocketServer.cpp b/wpiutil/src/main/native/cpp/WebSocketServer.cpp
deleted file mode 100644
index 1562f3b..0000000
--- a/wpiutil/src/main/native/cpp/WebSocketServer.cpp
+++ /dev/null
@@ -1,171 +0,0 @@
-// Copyright (c) FIRST and other WPILib contributors.
-// Open Source Software; you can modify and/or share it under the terms of
-// the WPILib BSD license file in the root directory of this project.
-
-#include "wpi/WebSocketServer.h"
-
-#include <utility>
-
-#include "wpi/StringExtras.h"
-#include "wpi/fmt/raw_ostream.h"
-#include "wpi/raw_uv_ostream.h"
-#include "wpi/uv/Buffer.h"
-#include "wpi/uv/Stream.h"
-
-using namespace wpi;
-
-WebSocketServerHelper::WebSocketServerHelper(HttpParser& req) {
-  req.header.connect([this](std::string_view name, std::string_view value) {
-    if (equals_lower(name, "host")) {
-      m_gotHost = true;
-    } else if (equals_lower(name, "upgrade")) {
-      if (equals_lower(value, "websocket")) {
-        m_websocket = true;
-      }
-    } else if (equals_lower(name, "sec-websocket-key")) {
-      m_key = value;
-    } else if (equals_lower(name, "sec-websocket-version")) {
-      m_version = value;
-    } else if (equals_lower(name, "sec-websocket-protocol")) {
-      // Protocols are comma delimited, repeated headers add to list
-      SmallVector<std::string_view, 2> protocols;
-      split(value, protocols, ",", -1, false);
-      for (auto protocol : protocols) {
-        protocol = trim(protocol);
-        if (!protocol.empty()) {
-          m_protocols.emplace_back(protocol);
-        }
-      }
-    }
-  });
-  req.headersComplete.connect([&req, this](bool) {
-    if (req.IsUpgrade() && IsUpgrade()) {
-      upgrade();
-    }
-  });
-}
-
-std::pair<bool, std::string_view> WebSocketServerHelper::MatchProtocol(
-    span<const std::string_view> protocols) {
-  if (protocols.empty() && m_protocols.empty()) {
-    return {true, {}};
-  }
-  for (auto protocol : protocols) {
-    for (auto&& clientProto : m_protocols) {
-      if (protocol == clientProto) {
-        return {true, protocol};
-      }
-    }
-  }
-  return {false, {}};
-}
-
-WebSocketServer::WebSocketServer(uv::Stream& stream,
-                                 span<const std::string_view> protocols,
-                                 ServerOptions options, const private_init&)
-    : m_stream{stream},
-      m_helper{m_req},
-      m_protocols{protocols.begin(), protocols.end()},
-      m_options{std::move(options)} {
-  // Header handling
-  m_req.header.connect([this](std::string_view name, std::string_view value) {
-    if (equals_lower(name, "host")) {
-      if (m_options.checkHost) {
-        if (!m_options.checkHost(value)) {
-          Abort(401, "Unrecognized Host");
-        }
-      }
-    }
-  });
-  m_req.url.connect([this](std::string_view name) {
-    if (m_options.checkUrl) {
-      if (!m_options.checkUrl(name)) {
-        Abort(404, "Not Found");
-      }
-    }
-  });
-  m_req.headersComplete.connect([this](bool) {
-    // We only accept websocket connections
-    if (!m_helper.IsUpgrade() || !m_req.IsUpgrade()) {
-      Abort(426, "Upgrade Required");
-    }
-  });
-
-  // Handle upgrade event
-  m_helper.upgrade.connect([this] {
-    if (m_aborted) {
-      return;
-    }
-
-    // Negotiate sub-protocol
-    SmallVector<std::string_view, 2> protocols{m_protocols.begin(),
-                                               m_protocols.end()};
-    std::string_view protocol = m_helper.MatchProtocol(protocols).second;
-
-    // Disconnect our header reader
-    m_dataConn.disconnect();
-
-    // Accepting the stream may destroy this (as it replaces the stream user
-    // data), so grab a shared pointer first.
-    auto self = shared_from_this();
-
-    // Accept the upgrade
-    auto ws = m_helper.Accept(m_stream, protocol);
-
-    // Connect the websocket open event to our connected event.
-    ws->open.connect_extended(
-        [self, s = ws.get()](auto conn, std::string_view) {
-          self->connected(self->m_req.GetUrl(), *s);
-          conn.disconnect();  // one-shot
-        });
-  });
-
-  // Set up stream
-  stream.StartRead();
-  m_dataConn =
-      stream.data.connect_connection([this](uv::Buffer& buf, size_t size) {
-        if (m_aborted) {
-          return;
-        }
-        m_req.Execute(std::string_view{buf.base, size});
-        if (m_req.HasError()) {
-          Abort(400, "Bad Request");
-        }
-      });
-  m_errorConn =
-      stream.error.connect_connection([this](uv::Error) { m_stream.Close(); });
-  m_endConn = stream.end.connect_connection([this] { m_stream.Close(); });
-}
-
-std::shared_ptr<WebSocketServer> WebSocketServer::Create(
-    uv::Stream& stream, span<const std::string_view> protocols,
-    const ServerOptions& options) {
-  auto server = std::make_shared<WebSocketServer>(stream, protocols, options,
-                                                  private_init{});
-  stream.SetData(server);
-  return server;
-}
-
-void WebSocketServer::Abort(uint16_t code, std::string_view reason) {
-  if (m_aborted) {
-    return;
-  }
-  m_aborted = true;
-
-  // Build response
-  SmallVector<uv::Buffer, 4> bufs;
-  raw_uv_ostream os{bufs, 1024};
-
-  // Handle unsupported version
-  fmt::print(os, "HTTP/1.1 {} {}\r\n", code, reason);
-  if (code == 426) {
-    os << "Upgrade: WebSocket\r\n";
-  }
-  os << "\r\n";
-  m_stream.Write(bufs, [this](auto bufs, uv::Error) {
-    for (auto& buf : bufs) {
-      buf.Deallocate();
-    }
-    m_stream.Shutdown([this] { m_stream.Close(); });
-  });
-}
diff --git a/wpiutil/src/main/native/cpp/fs.cpp b/wpiutil/src/main/native/cpp/fs.cpp
index 34d6cca..fad6a66 100644
--- a/wpiutil/src/main/native/cpp/fs.cpp
+++ b/wpiutil/src/main/native/cpp/fs.cpp
@@ -82,17 +82,6 @@
 const file_t kInvalidFile = INVALID_HANDLE_VALUE;
 
 static DWORD nativeDisposition(CreationDisposition Disp, OpenFlags Flags) {
-  // This is a compatibility hack.  Really we should respect the creation
-  // disposition, but a lot of old code relied on the implicit assumption that
-  // OF_Append implied it would open an existing file.  Since the disposition is
-  // now explicit and defaults to CD_CreateAlways, this assumption would cause
-  // any usage of OF_Append to append to a new file, even if the file already
-  // existed.  A better solution might have two new creation dispositions:
-  // CD_AppendAlways and CD_AppendNew.  This would also address the problem of
-  // OF_Append being used on a read-only descriptor, which doesn't make sense.
-  if (Flags & OF_Append)
-    return OPEN_ALWAYS;
-
   switch (Disp) {
     case CD_CreateAlways:
       return CREATE_ALWAYS;
@@ -251,12 +240,6 @@
     Result |= O_RDWR;
   }
 
-  // This is for compatibility with old code that assumed F_Append implied
-  // would open an existing file.  See Windows/Path.inc for a longer comment.
-  if (Flags & F_Append) {
-    Disp = CD_OpenAlways;
-  }
-
   if (Disp == CD_CreateNew) {
     Result |= O_CREAT;  // Create if it doesn't exist.
     Result |= O_EXCL;   // Fail if it does.
diff --git a/wpiutil/src/main/native/cpp/hostname.cpp b/wpiutil/src/main/native/cpp/hostname.cpp
deleted file mode 100644
index d907023..0000000
--- a/wpiutil/src/main/native/cpp/hostname.cpp
+++ /dev/null
@@ -1,57 +0,0 @@
-// Copyright (c) FIRST and other WPILib contributors.
-// Open Source Software; you can modify and/or share it under the terms of
-// the WPILib BSD license file in the root directory of this project.
-
-#include "wpi/hostname.h"
-
-#include <cstdlib>
-#include <string>
-#include <string_view>
-
-#include "uv.h"
-#include "wpi/SmallVector.h"
-
-namespace wpi {
-
-std::string GetHostname() {
-  std::string rv;
-  char name[256];
-  size_t size = sizeof(name);
-
-  int err = uv_os_gethostname(name, &size);
-  if (err == 0) {
-    rv.assign(name, size);
-  } else if (err == UV_ENOBUFS) {
-    char* name2 = static_cast<char*>(std::malloc(size));
-    err = uv_os_gethostname(name2, &size);
-    if (err == 0) {
-      rv.assign(name2, size);
-    }
-    std::free(name2);
-  }
-
-  return rv;
-}
-
-std::string_view GetHostname(SmallVectorImpl<char>& name) {
-  // Use a tmp array to not require the SmallVector to be too large.
-  char tmpName[256];
-  size_t size = sizeof(tmpName);
-
-  name.clear();
-
-  int err = uv_os_gethostname(tmpName, &size);
-  if (err == 0) {
-    name.append(tmpName, tmpName + size);
-  } else if (err == UV_ENOBUFS) {
-    name.resize(size);
-    err = uv_os_gethostname(name.data(), &size);
-    if (err != 0) {
-      size = 0;
-    }
-  }
-
-  return {name.data(), size};
-}
-
-}  // namespace wpi
diff --git a/wpiutil/src/main/native/cpp/http_parser.cpp b/wpiutil/src/main/native/cpp/http_parser.cpp
deleted file mode 100644
index bc442b2..0000000
--- a/wpiutil/src/main/native/cpp/http_parser.cpp
+++ /dev/null
@@ -1,2475 +0,0 @@
-/* Copyright Joyent, Inc. and other Node contributors.
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to
- * deal in the Software without restriction, including without limitation the
- * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
- * sell copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in
- * all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
- * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
- * IN THE SOFTWARE.
- */
-#include "wpi/http_parser.h"
-#include <assert.h>
-#include <stddef.h>
-#include <ctype.h>
-#include <string.h>
-#include <limits.h>
-
-#ifdef _WIN32
-#pragma warning(disable : 4018 26451)
-#endif
-
-#ifndef ULLONG_MAX
-# define ULLONG_MAX ((uint64_t) -1) /* 2^64-1 */
-#endif
-
-#ifndef MIN
-# define MIN(a,b) ((a) < (b) ? (a) : (b))
-#endif
-
-#ifndef ARRAY_SIZE
-# define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]))
-#endif
-
-#ifndef BIT_AT
-# define BIT_AT(a, i)                                                \
-  (!!((unsigned int) (a)[(unsigned int) (i) >> 3] &                  \
-   (1 << ((unsigned int) (i) & 7))))
-#endif
-
-#ifndef ELEM_AT
-# define ELEM_AT(a, i, v) ((unsigned int) (i) < ARRAY_SIZE(a) ? (a)[(i)] : (v))
-#endif
-
-#define SET_ERRNO(e)                                                 \
-do {                                                                 \
-  parser->nread = nread;                                             \
-  parser->http_errno = (e);                                          \
-} while(0)
-
-#define CURRENT_STATE() p_state
-#define UPDATE_STATE(V) p_state = (enum state) (V);
-#define RETURN(V)                                                    \
-do {                                                                 \
-  parser->nread = nread;                                             \
-  parser->state = CURRENT_STATE();                                   \
-  return (V);                                                        \
-} while (0);
-#define REEXECUTE()                                                  \
-  goto reexecute;                                                    \
-
-
-#ifdef __GNUC__
-# define LIKELY(X) __builtin_expect(!!(X), 1)
-# define UNLIKELY(X) __builtin_expect(!!(X), 0)
-#else
-# define LIKELY(X) (X)
-# define UNLIKELY(X) (X)
-#endif
-
-
-/* Run the notify callback FOR, returning ER if it fails */
-#define CALLBACK_NOTIFY_(FOR, ER)                                    \
-do {                                                                 \
-  assert(HTTP_PARSER_ERRNO(parser) == HPE_OK);                       \
-                                                                     \
-  if (LIKELY(settings->on_##FOR)) {                                  \
-    parser->state = CURRENT_STATE();                                 \
-    if (UNLIKELY(0 != settings->on_##FOR(parser))) {                 \
-      SET_ERRNO(HPE_CB_##FOR);                                       \
-    }                                                                \
-    UPDATE_STATE(parser->state);                                     \
-                                                                     \
-    /* We either errored above or got paused; get out */             \
-    if (UNLIKELY(HTTP_PARSER_ERRNO(parser) != HPE_OK)) {             \
-      return (ER);                                                   \
-    }                                                                \
-  }                                                                  \
-} while (0)
-
-/* Run the notify callback FOR and consume the current byte */
-#define CALLBACK_NOTIFY(FOR)            CALLBACK_NOTIFY_(FOR, p - data + 1)
-
-/* Run the notify callback FOR and don't consume the current byte */
-#define CALLBACK_NOTIFY_NOADVANCE(FOR)  CALLBACK_NOTIFY_(FOR, p - data)
-
-/* Run data callback FOR with LEN bytes, returning ER if it fails */
-#define CALLBACK_DATA_(FOR, LEN, ER)                                 \
-do {                                                                 \
-  assert(HTTP_PARSER_ERRNO(parser) == HPE_OK);                       \
-                                                                     \
-  if (FOR##_mark) {                                                  \
-    if (LIKELY(settings->on_##FOR)) {                                \
-      parser->state = CURRENT_STATE();                               \
-      if (UNLIKELY(0 !=                                              \
-                   settings->on_##FOR(parser, FOR##_mark, (LEN)))) { \
-        SET_ERRNO(HPE_CB_##FOR);                                     \
-      }                                                              \
-      UPDATE_STATE(parser->state);                                   \
-                                                                     \
-      /* We either errored above or got paused; get out */           \
-      if (UNLIKELY(HTTP_PARSER_ERRNO(parser) != HPE_OK)) {           \
-        return (ER);                                                 \
-      }                                                              \
-    }                                                                \
-    FOR##_mark = NULL;                                               \
-  }                                                                  \
-} while (0)
-
-/* Run the data callback FOR and consume the current byte */
-#define CALLBACK_DATA(FOR)                                           \
-    CALLBACK_DATA_(FOR, p - FOR##_mark, p - data + 1)
-
-/* Run the data callback FOR and don't consume the current byte */
-#define CALLBACK_DATA_NOADVANCE(FOR)                                 \
-    CALLBACK_DATA_(FOR, p - FOR##_mark, p - data)
-
-/* Set the mark FOR; non-destructive if mark is already set */
-#define MARK(FOR)                                                    \
-do {                                                                 \
-  if (!FOR##_mark) {                                                 \
-    FOR##_mark = p;                                                  \
-  }                                                                  \
-} while (0)
-
-/* Don't allow the total size of the HTTP headers (including the status
- * line) to exceed HTTP_MAX_HEADER_SIZE.  This check is here to protect
- * embedders against denial-of-service attacks where the attacker feeds
- * us a never-ending header that the embedder keeps buffering.
- *
- * This check is arguably the responsibility of embedders but we're doing
- * it on the embedder's behalf because most won't bother and this way we
- * make the web a little safer.  HTTP_MAX_HEADER_SIZE is still far bigger
- * than any reasonable request or response so this should never affect
- * day-to-day operation.
- */
-#define COUNT_HEADER_SIZE(V)                                         \
-do {                                                                 \
-  nread += (V);                                                      \
-  if (UNLIKELY(nread > (HTTP_MAX_HEADER_SIZE))) {                    \
-    SET_ERRNO(HPE_HEADER_OVERFLOW);                                  \
-    goto error;                                                      \
-  }                                                                  \
-} while (0)
-
-
-#define PROXY_CONNECTION "proxy-connection"
-#define CONNECTION "connection"
-#define CONTENT_LENGTH "content-length"
-#define TRANSFER_ENCODING "transfer-encoding"
-#define UPGRADE "upgrade"
-#define CHUNKED "chunked"
-#define KEEP_ALIVE "keep-alive"
-#define CLOSE "close"
-
-namespace wpi {
-
-
-static const char *method_strings[] =
-  {
-#define XX(num, name, string) #string,
-  HTTP_METHOD_MAP(XX)
-#undef XX
-  };
-
-
-/* Tokens as defined by rfc 2616. Also lowercases them.
- *        token       = 1*<any CHAR except CTLs or separators>
- *     separators     = "(" | ")" | "<" | ">" | "@"
- *                    | "," | ";" | ":" | "\" | <">
- *                    | "/" | "[" | "]" | "?" | "="
- *                    | "{" | "}" | SP | HT
- */
-static const char tokens[256] = {
-/*   0 nul    1 soh    2 stx    3 etx    4 eot    5 enq    6 ack    7 bel  */
-        0,       0,       0,       0,       0,       0,       0,       0,
-/*   8 bs     9 ht    10 nl    11 vt    12 np    13 cr    14 so    15 si   */
-        0,       0,       0,       0,       0,       0,       0,       0,
-/*  16 dle   17 dc1   18 dc2   19 dc3   20 dc4   21 nak   22 syn   23 etb */
-        0,       0,       0,       0,       0,       0,       0,       0,
-/*  24 can   25 em    26 sub   27 esc   28 fs    29 gs    30 rs    31 us  */
-        0,       0,       0,       0,       0,       0,       0,       0,
-/*  32 sp    33  !    34  "    35  #    36  $    37  %    38  &    39  '  */
-       ' ',     '!',      0,      '#',     '$',     '%',     '&',    '\'',
-/*  40  (    41  )    42  *    43  +    44  ,    45  -    46  .    47  /  */
-        0,       0,      '*',     '+',      0,      '-',     '.',      0,
-/*  48  0    49  1    50  2    51  3    52  4    53  5    54  6    55  7  */
-       '0',     '1',     '2',     '3',     '4',     '5',     '6',     '7',
-/*  56  8    57  9    58  :    59  ;    60  <    61  =    62  >    63  ?  */
-       '8',     '9',      0,       0,       0,       0,       0,       0,
-/*  64  @    65  A    66  B    67  C    68  D    69  E    70  F    71  G  */
-        0,      'a',     'b',     'c',     'd',     'e',     'f',     'g',
-/*  72  H    73  I    74  J    75  K    76  L    77  M    78  N    79  O  */
-       'h',     'i',     'j',     'k',     'l',     'm',     'n',     'o',
-/*  80  P    81  Q    82  R    83  S    84  T    85  U    86  V    87  W  */
-       'p',     'q',     'r',     's',     't',     'u',     'v',     'w',
-/*  88  X    89  Y    90  Z    91  [    92  \    93  ]    94  ^    95  _  */
-       'x',     'y',     'z',      0,       0,       0,      '^',     '_',
-/*  96  `    97  a    98  b    99  c   100  d   101  e   102  f   103  g  */
-       '`',     'a',     'b',     'c',     'd',     'e',     'f',     'g',
-/* 104  h   105  i   106  j   107  k   108  l   109  m   110  n   111  o  */
-       'h',     'i',     'j',     'k',     'l',     'm',     'n',     'o',
-/* 112  p   113  q   114  r   115  s   116  t   117  u   118  v   119  w  */
-       'p',     'q',     'r',     's',     't',     'u',     'v',     'w',
-/* 120  x   121  y   122  z   123  {   124  |   125  }   126  ~   127 del */
-       'x',     'y',     'z',      0,      '|',      0,      '~',       0 };
-
-
-static const int8_t unhex[256] =
-  {-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
-  ,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
-  ,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
-  , 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,-1,-1,-1,-1,-1,-1
-  ,-1,10,11,12,13,14,15,-1,-1,-1,-1,-1,-1,-1,-1,-1
-  ,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
-  ,-1,10,11,12,13,14,15,-1,-1,-1,-1,-1,-1,-1,-1,-1
-  ,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
-  };
-
-
-#if HTTP_PARSER_STRICT
-# define T(v) 0
-#else
-# define T(v) v
-#endif
-
-
-static const uint8_t normal_url_char[32] = {
-/*   0 nul    1 soh    2 stx    3 etx    4 eot    5 enq    6 ack    7 bel  */
-        0    |   0    |   0    |   0    |   0    |   0    |   0    |   0,
-/*   8 bs     9 ht    10 nl    11 vt    12 np    13 cr    14 so    15 si   */
-        0    | T(2)   |   0    |   0    | T(16)  |   0    |   0    |   0,
-/*  16 dle   17 dc1   18 dc2   19 dc3   20 dc4   21 nak   22 syn   23 etb */
-        0    |   0    |   0    |   0    |   0    |   0    |   0    |   0,
-/*  24 can   25 em    26 sub   27 esc   28 fs    29 gs    30 rs    31 us  */
-        0    |   0    |   0    |   0    |   0    |   0    |   0    |   0,
-/*  32 sp    33  !    34  "    35  #    36  $    37  %    38  &    39  '  */
-        0    |   2    |   4    |   0    |   16   |   32   |   64   |  128,
-/*  40  (    41  )    42  *    43  +    44  ,    45  -    46  .    47  /  */
-        1    |   2    |   4    |   8    |   16   |   32   |   64   |  128,
-/*  48  0    49  1    50  2    51  3    52  4    53  5    54  6    55  7  */
-        1    |   2    |   4    |   8    |   16   |   32   |   64   |  128,
-/*  56  8    57  9    58  :    59  ;    60  <    61  =    62  >    63  ?  */
-        1    |   2    |   4    |   8    |   16   |   32   |   64   |   0,
-/*  64  @    65  A    66  B    67  C    68  D    69  E    70  F    71  G  */
-        1    |   2    |   4    |   8    |   16   |   32   |   64   |  128,
-/*  72  H    73  I    74  J    75  K    76  L    77  M    78  N    79  O  */
-        1    |   2    |   4    |   8    |   16   |   32   |   64   |  128,
-/*  80  P    81  Q    82  R    83  S    84  T    85  U    86  V    87  W  */
-        1    |   2    |   4    |   8    |   16   |   32   |   64   |  128,
-/*  88  X    89  Y    90  Z    91  [    92  \    93  ]    94  ^    95  _  */
-        1    |   2    |   4    |   8    |   16   |   32   |   64   |  128,
-/*  96  `    97  a    98  b    99  c   100  d   101  e   102  f   103  g  */
-        1    |   2    |   4    |   8    |   16   |   32   |   64   |  128,
-/* 104  h   105  i   106  j   107  k   108  l   109  m   110  n   111  o  */
-        1    |   2    |   4    |   8    |   16   |   32   |   64   |  128,
-/* 112  p   113  q   114  r   115  s   116  t   117  u   118  v   119  w  */
-        1    |   2    |   4    |   8    |   16   |   32   |   64   |  128,
-/* 120  x   121  y   122  z   123  {   124  |   125  }   126  ~   127 del */
-        1    |   2    |   4    |   8    |   16   |   32   |   64   |   0, };
-
-#undef T
-
-enum state
-  { s_dead = 1 /* important that this is > 0 */
-
-  , s_start_req_or_res
-  , s_res_or_resp_H
-  , s_start_res
-  , s_res_H
-  , s_res_HT
-  , s_res_HTT
-  , s_res_HTTP
-  , s_res_http_major
-  , s_res_http_dot
-  , s_res_http_minor
-  , s_res_http_end
-  , s_res_first_status_code
-  , s_res_status_code
-  , s_res_status_start
-  , s_res_status
-  , s_res_line_almost_done
-
-  , s_start_req
-
-  , s_req_method
-  , s_req_spaces_before_url
-  , s_req_schema
-  , s_req_schema_slash
-  , s_req_schema_slash_slash
-  , s_req_server_start
-  , s_req_server
-  , s_req_server_with_at
-  , s_req_path
-  , s_req_query_string_start
-  , s_req_query_string
-  , s_req_fragment_start
-  , s_req_fragment
-  , s_req_http_start
-  , s_req_http_H
-  , s_req_http_HT
-  , s_req_http_HTT
-  , s_req_http_HTTP
-  , s_req_http_major
-  , s_req_http_dot
-  , s_req_http_minor
-  , s_req_http_end
-  , s_req_line_almost_done
-
-  , s_header_field_start
-  , s_header_field
-  , s_header_value_discard_ws
-  , s_header_value_discard_ws_almost_done
-  , s_header_value_discard_lws
-  , s_header_value_start
-  , s_header_value
-  , s_header_value_lws
-
-  , s_header_almost_done
-
-  , s_chunk_size_start
-  , s_chunk_size
-  , s_chunk_parameters
-  , s_chunk_size_almost_done
-
-  , s_headers_almost_done
-  , s_headers_done
-
-  /* Important: 's_headers_done' must be the last 'header' state. All
-   * states beyond this must be 'body' states. It is used for overflow
-   * checking. See the PARSING_HEADER() macro.
-   */
-
-  , s_chunk_data
-  , s_chunk_data_almost_done
-  , s_chunk_data_done
-
-  , s_body_identity
-  , s_body_identity_eof
-
-  , s_message_done
-  };
-
-
-#define PARSING_HEADER(state) (state <= s_headers_done)
-
-
-enum header_states
-  { h_general = 0
-  , h_C
-  , h_CO
-  , h_CON
-
-  , h_matching_connection
-  , h_matching_proxy_connection
-  , h_matching_content_length
-  , h_matching_transfer_encoding
-  , h_matching_upgrade
-
-  , h_connection
-  , h_content_length
-  , h_content_length_num
-  , h_content_length_ws
-  , h_transfer_encoding
-  , h_upgrade
-
-  , h_matching_transfer_encoding_chunked
-  , h_matching_connection_token_start
-  , h_matching_connection_keep_alive
-  , h_matching_connection_close
-  , h_matching_connection_upgrade
-  , h_matching_connection_token
-
-  , h_transfer_encoding_chunked
-  , h_connection_keep_alive
-  , h_connection_close
-  , h_connection_upgrade
-  };
-
-enum http_host_state
-  {
-    s_http_host_dead = 1
-  , s_http_userinfo_start
-  , s_http_userinfo
-  , s_http_host_start
-  , s_http_host_v6_start
-  , s_http_host
-  , s_http_host_v6
-  , s_http_host_v6_end
-  , s_http_host_v6_zone_start
-  , s_http_host_v6_zone
-  , s_http_host_port_start
-  , s_http_host_port
-};
-
-/* Macros for character classes; depends on strict-mode  */
-#define CR                  '\r'
-#define LF                  '\n'
-#define LOWER(c)            (unsigned char)(c | 0x20)
-#define IS_ALPHA(c)         (LOWER(c) >= 'a' && LOWER(c) <= 'z')
-#define IS_NUM(c)           ((c) >= '0' && (c) <= '9')
-#define IS_ALPHANUM(c)      (IS_ALPHA(c) || IS_NUM(c))
-#define IS_HEX(c)           (IS_NUM(c) || (LOWER(c) >= 'a' && LOWER(c) <= 'f'))
-#define IS_MARK(c)          ((c) == '-' || (c) == '_' || (c) == '.' || \
-  (c) == '!' || (c) == '~' || (c) == '*' || (c) == '\'' || (c) == '(' || \
-  (c) == ')')
-#define IS_USERINFO_CHAR(c) (IS_ALPHANUM(c) || IS_MARK(c) || (c) == '%' || \
-  (c) == ';' || (c) == ':' || (c) == '&' || (c) == '=' || (c) == '+' || \
-  (c) == '$' || (c) == ',')
-
-#define STRICT_TOKEN(c)     ((c == ' ') ? 0 : tokens[(unsigned char)c])
-
-#if HTTP_PARSER_STRICT
-#define TOKEN(c)            STRICT_TOKEN(c)
-#define IS_URL_CHAR(c)      (BIT_AT(normal_url_char, (unsigned char)c))
-#define IS_HOST_CHAR(c)     (IS_ALPHANUM(c) || (c) == '.' || (c) == '-')
-#else
-#define TOKEN(c)            tokens[(unsigned char)c]
-#define IS_URL_CHAR(c)                                                         \
-  (BIT_AT(normal_url_char, (unsigned char)c) || ((c) & 0x80))
-#define IS_HOST_CHAR(c)                                                        \
-  (IS_ALPHANUM(c) || (c) == '.' || (c) == '-' || (c) == '_')
-#endif
-
-/**
- * Verify that a char is a valid visible (printable) US-ASCII
- * character or %x80-FF
- **/
-#define IS_HEADER_CHAR(ch)                                                     \
-  (ch == CR || ch == LF || ch == 9 || ((unsigned char)ch > 31 && ch != 127))
-
-#define start_state (parser->type == HTTP_REQUEST ? s_start_req : s_start_res)
-
-
-#if HTTP_PARSER_STRICT
-# define STRICT_CHECK(cond)                                          \
-do {                                                                 \
-  if (cond) {                                                        \
-    SET_ERRNO(HPE_STRICT);                                           \
-    goto error;                                                      \
-  }                                                                  \
-} while (0)
-# define NEW_MESSAGE() (http_should_keep_alive(parser) ? start_state : s_dead)
-#else
-# define STRICT_CHECK(cond)
-# define NEW_MESSAGE() start_state
-#endif
-
-
-/* Map errno values to strings for human-readable output */
-#define HTTP_STRERROR_GEN(n, s) { "HPE_" #n, s },
-static struct {
-  const char *name;
-  const char *description;
-} http_strerror_tab[] = {
-  HTTP_ERRNO_MAP(HTTP_STRERROR_GEN)
-};
-#undef HTTP_STRERROR_GEN
-
-int http_message_needs_eof(const http_parser *parser);
-
-/* Our URL parser.
- *
- * This is designed to be shared by http_parser_execute() for URL validation,
- * hence it has a state transition + byte-for-byte interface. In addition, it
- * is meant to be embedded in http_parser_parse_url(), which does the dirty
- * work of turning state transitions URL components for its API.
- *
- * This function should only be invoked with non-space characters. It is
- * assumed that the caller cares about (and can detect) the transition between
- * URL and non-URL states by looking for these.
- */
-static enum state
-parse_url_char(enum state s, const char ch)
-{
-  if (ch == ' ' || ch == '\r' || ch == '\n') {
-    return s_dead;
-  }
-
-#if HTTP_PARSER_STRICT
-  if (ch == '\t' || ch == '\f') {
-    return s_dead;
-  }
-#endif
-
-  switch (s) {
-    case s_req_spaces_before_url:
-      /* Proxied requests are followed by scheme of an absolute URI (alpha).
-       * All methods except CONNECT are followed by '/' or '*'.
-       */
-
-      if (ch == '/' || ch == '*') {
-        return s_req_path;
-      }
-
-      if (IS_ALPHA(ch)) {
-        return s_req_schema;
-      }
-
-      break;
-
-    case s_req_schema:
-      if (IS_ALPHA(ch)) {
-        return s;
-      }
-
-      if (ch == ':') {
-        return s_req_schema_slash;
-      }
-
-      break;
-
-    case s_req_schema_slash:
-      if (ch == '/') {
-        return s_req_schema_slash_slash;
-      }
-
-      break;
-
-    case s_req_schema_slash_slash:
-      if (ch == '/') {
-        return s_req_server_start;
-      }
-
-      break;
-
-    case s_req_server_with_at:
-      if (ch == '@') {
-        return s_dead;
-      }
-
-    /* fall through */
-    case s_req_server_start:
-    case s_req_server:
-      if (ch == '/') {
-        return s_req_path;
-      }
-
-      if (ch == '?') {
-        return s_req_query_string_start;
-      }
-
-      if (ch == '@') {
-        return s_req_server_with_at;
-      }
-
-      if (IS_USERINFO_CHAR(ch) || ch == '[' || ch == ']') {
-        return s_req_server;
-      }
-
-      break;
-
-    case s_req_path:
-      if (IS_URL_CHAR(ch)) {
-        return s;
-      }
-
-      switch (ch) {
-        case '?':
-          return s_req_query_string_start;
-
-        case '#':
-          return s_req_fragment_start;
-      }
-
-      break;
-
-    case s_req_query_string_start:
-    case s_req_query_string:
-      if (IS_URL_CHAR(ch)) {
-        return s_req_query_string;
-      }
-
-      switch (ch) {
-        case '?':
-          /* allow extra '?' in query string */
-          return s_req_query_string;
-
-        case '#':
-          return s_req_fragment_start;
-      }
-
-      break;
-
-    case s_req_fragment_start:
-      if (IS_URL_CHAR(ch)) {
-        return s_req_fragment;
-      }
-
-      switch (ch) {
-        case '?':
-          return s_req_fragment;
-
-        case '#':
-          return s;
-      }
-
-      break;
-
-    case s_req_fragment:
-      if (IS_URL_CHAR(ch)) {
-        return s;
-      }
-
-      switch (ch) {
-        case '?':
-        case '#':
-          return s;
-      }
-
-      break;
-
-    default:
-      break;
-  }
-
-  /* We should never fall out of the switch above unless there's an error */
-  return s_dead;
-}
-
-size_t http_parser_execute (http_parser *parser,
-                            const http_parser_settings *settings,
-                            const char *data,
-                            size_t len)
-{
-  char c, ch;
-  int8_t unhex_val;
-  const char *p = data;
-  const char *header_field_mark = 0;
-  const char *header_value_mark = 0;
-  const char *url_mark = 0;
-  const char *body_mark = 0;
-  const char *status_mark = 0;
-  enum state p_state = (enum state) parser->state;
-  const unsigned int lenient = parser->lenient_http_headers;
-  uint32_t nread = parser->nread;
-
-  /* We're in an error state. Don't bother doing anything. */
-  if (HTTP_PARSER_ERRNO(parser) != HPE_OK) {
-    return 0;
-  }
-
-  if (len == 0) {
-    switch (CURRENT_STATE()) {
-      case s_body_identity_eof:
-        /* Use of CALLBACK_NOTIFY() here would erroneously return 1 byte read if
-         * we got paused.
-         */
-        CALLBACK_NOTIFY_NOADVANCE(message_complete);
-        return 0;
-
-      case s_dead:
-      case s_start_req_or_res:
-      case s_start_res:
-      case s_start_req:
-        return 0;
-
-      default:
-        SET_ERRNO(HPE_INVALID_EOF_STATE);
-        return 1;
-    }
-  }
-
-
-  if (CURRENT_STATE() == s_header_field)
-    header_field_mark = data;
-  if (CURRENT_STATE() == s_header_value)
-    header_value_mark = data;
-  switch (CURRENT_STATE()) {
-  case s_req_path:
-  case s_req_schema:
-  case s_req_schema_slash:
-  case s_req_schema_slash_slash:
-  case s_req_server_start:
-  case s_req_server:
-  case s_req_server_with_at:
-  case s_req_query_string_start:
-  case s_req_query_string:
-  case s_req_fragment_start:
-  case s_req_fragment:
-    url_mark = data;
-    break;
-  case s_res_status:
-    status_mark = data;
-    break;
-  default:
-    break;
-  }
-
-  for (p=data; p != data + len; p++) {
-    ch = *p;
-
-    if (PARSING_HEADER(CURRENT_STATE()))
-      COUNT_HEADER_SIZE(1);
-
-reexecute:
-    switch (CURRENT_STATE()) {
-
-      case s_dead:
-        /* this state is used after a 'Connection: close' message
-         * the parser will error out if it reads another message
-         */
-        if (LIKELY(ch == CR || ch == LF))
-          break;
-
-        SET_ERRNO(HPE_CLOSED_CONNECTION);
-        goto error;
-
-      case s_start_req_or_res:
-      {
-        if (ch == CR || ch == LF)
-          break;
-        parser->flags = 0;
-        parser->content_length = ULLONG_MAX;
-
-        if (ch == 'H') {
-          UPDATE_STATE(s_res_or_resp_H);
-
-          CALLBACK_NOTIFY(message_begin);
-        } else {
-          parser->type = HTTP_REQUEST;
-          UPDATE_STATE(s_start_req);
-          REEXECUTE();
-        }
-
-        break;
-      }
-
-      case s_res_or_resp_H:
-        if (ch == 'T') {
-          parser->type = HTTP_RESPONSE;
-          UPDATE_STATE(s_res_HT);
-        } else {
-          if (UNLIKELY(ch != 'E')) {
-            SET_ERRNO(HPE_INVALID_CONSTANT);
-            goto error;
-          }
-
-          parser->type = HTTP_REQUEST;
-          parser->method = HTTP_HEAD;
-          parser->index = 2;
-          UPDATE_STATE(s_req_method);
-        }
-        break;
-
-      case s_start_res:
-      {
-        parser->flags = 0;
-        parser->content_length = ULLONG_MAX;
-
-        switch (ch) {
-          case 'H':
-            UPDATE_STATE(s_res_H);
-            break;
-
-          case CR:
-          case LF:
-            break;
-
-          default:
-            SET_ERRNO(HPE_INVALID_CONSTANT);
-            goto error;
-        }
-
-        CALLBACK_NOTIFY(message_begin);
-        break;
-      }
-
-      case s_res_H:
-        STRICT_CHECK(ch != 'T');
-        UPDATE_STATE(s_res_HT);
-        break;
-
-      case s_res_HT:
-        STRICT_CHECK(ch != 'T');
-        UPDATE_STATE(s_res_HTT);
-        break;
-
-      case s_res_HTT:
-        STRICT_CHECK(ch != 'P');
-        UPDATE_STATE(s_res_HTTP);
-        break;
-
-      case s_res_HTTP:
-        STRICT_CHECK(ch != '/');
-        UPDATE_STATE(s_res_http_major);
-        break;
-
-      case s_res_http_major:
-        if (UNLIKELY(!IS_NUM(ch))) {
-          SET_ERRNO(HPE_INVALID_VERSION);
-          goto error;
-        }
-
-        parser->http_major = ch - '0';
-        UPDATE_STATE(s_res_http_dot);
-        break;
-
-      case s_res_http_dot:
-      {
-        if (UNLIKELY(ch != '.')) {
-          SET_ERRNO(HPE_INVALID_VERSION);
-          goto error;
-        }
-
-        UPDATE_STATE(s_res_http_minor);
-        break;
-      }
-
-      case s_res_http_minor:
-        if (UNLIKELY(!IS_NUM(ch))) {
-          SET_ERRNO(HPE_INVALID_VERSION);
-          goto error;
-        }
-
-        parser->http_minor = ch - '0';
-        UPDATE_STATE(s_res_http_end);
-        break;
-
-      case s_res_http_end:
-      {
-        if (UNLIKELY(ch != ' ')) {
-          SET_ERRNO(HPE_INVALID_VERSION);
-          goto error;
-        }
-
-        UPDATE_STATE(s_res_first_status_code);
-        break;
-      }
-
-      case s_res_first_status_code:
-      {
-        if (!IS_NUM(ch)) {
-          if (ch == ' ') {
-            break;
-          }
-
-          SET_ERRNO(HPE_INVALID_STATUS);
-          goto error;
-        }
-        parser->status_code = ch - '0';
-        UPDATE_STATE(s_res_status_code);
-        break;
-      }
-
-      case s_res_status_code:
-      {
-        if (!IS_NUM(ch)) {
-          switch (ch) {
-            case ' ':
-              UPDATE_STATE(s_res_status_start);
-              break;
-            case CR:
-            case LF:
-              UPDATE_STATE(s_res_status_start);
-              REEXECUTE();
-              break;
-            default:
-              SET_ERRNO(HPE_INVALID_STATUS);
-              goto error;
-          }
-          break;
-        }
-
-        parser->status_code *= 10;
-        parser->status_code += ch - '0';
-
-        if (UNLIKELY(parser->status_code > 999)) {
-          SET_ERRNO(HPE_INVALID_STATUS);
-          goto error;
-        }
-
-        break;
-      }
-
-      case s_res_status_start:
-      {
-        MARK(status);
-        UPDATE_STATE(s_res_status);
-        parser->index = 0;
-
-        if (ch == CR || ch == LF)
-          REEXECUTE();
-
-        break;
-      }
-
-      case s_res_status:
-        if (ch == CR) {
-          UPDATE_STATE(s_res_line_almost_done);
-          CALLBACK_DATA(status);
-          break;
-        }
-
-        if (ch == LF) {
-          UPDATE_STATE(s_header_field_start);
-          CALLBACK_DATA(status);
-          break;
-        }
-
-        break;
-
-      case s_res_line_almost_done:
-        STRICT_CHECK(ch != LF);
-        UPDATE_STATE(s_header_field_start);
-        break;
-
-      case s_start_req:
-      {
-        if (ch == CR || ch == LF)
-          break;
-        parser->flags = 0;
-        parser->content_length = ULLONG_MAX;
-
-        if (UNLIKELY(!IS_ALPHA(ch))) {
-          SET_ERRNO(HPE_INVALID_METHOD);
-          goto error;
-        }
-
-        parser->method = (enum http_method) 0;
-        parser->index = 1;
-        switch (ch) {
-          case 'A': parser->method = HTTP_ACL; break;
-          case 'B': parser->method = HTTP_BIND; break;
-          case 'C': parser->method = HTTP_CONNECT; /* or COPY, CHECKOUT */ break;
-          case 'D': parser->method = HTTP_DELETE; break;
-          case 'G': parser->method = HTTP_GET; break;
-          case 'H': parser->method = HTTP_HEAD; break;
-          case 'L': parser->method = HTTP_LOCK; /* or LINK */ break;
-          case 'M': parser->method = HTTP_MKCOL; /* or MOVE, MKACTIVITY, MERGE, M-SEARCH, MKCALENDAR */ break;
-          case 'N': parser->method = HTTP_NOTIFY; break;
-          case 'O': parser->method = HTTP_OPTIONS; break;
-          case 'P': parser->method = HTTP_POST;
-            /* or PROPFIND|PROPPATCH|PUT|PATCH|PURGE */
-            break;
-          case 'R': parser->method = HTTP_REPORT; /* or REBIND */ break;
-          case 'S': parser->method = HTTP_SUBSCRIBE; /* or SEARCH, SOURCE */ break;
-          case 'T': parser->method = HTTP_TRACE; break;
-          case 'U': parser->method = HTTP_UNLOCK; /* or UNSUBSCRIBE, UNBIND, UNLINK */ break;
-          default:
-            SET_ERRNO(HPE_INVALID_METHOD);
-            goto error;
-        }
-        UPDATE_STATE(s_req_method);
-
-        CALLBACK_NOTIFY(message_begin);
-
-        break;
-      }
-
-      case s_req_method:
-      {
-        const char *matcher;
-        if (UNLIKELY(ch == '\0')) {
-          SET_ERRNO(HPE_INVALID_METHOD);
-          goto error;
-        }
-
-        matcher = method_strings[parser->method];
-        if (ch == ' ' && matcher[parser->index] == '\0') {
-          UPDATE_STATE(s_req_spaces_before_url);
-        } else if (ch == matcher[parser->index]) {
-          ; /* nada */
-        } else if ((ch >= 'A' && ch <= 'Z') || ch == '-') {
-
-          switch (parser->method << 16 | parser->index << 8 | ch) {
-#define XX(meth, pos, ch, new_meth) \
-            case (HTTP_##meth << 16 | pos << 8 | ch): \
-              parser->method = HTTP_##new_meth; break;
-
-            XX(POST,      1, 'U', PUT)
-            XX(POST,      1, 'A', PATCH)
-            XX(POST,      1, 'R', PROPFIND)
-            XX(PUT,       2, 'R', PURGE)
-            XX(CONNECT,   1, 'H', CHECKOUT)
-            XX(CONNECT,   2, 'P', COPY)
-            XX(MKCOL,     1, 'O', MOVE)
-            XX(MKCOL,     1, 'E', MERGE)
-            XX(MKCOL,     1, '-', MSEARCH)
-            XX(MKCOL,     2, 'A', MKACTIVITY)
-            XX(MKCOL,     3, 'A', MKCALENDAR)
-            XX(SUBSCRIBE, 1, 'E', SEARCH)
-            XX(SUBSCRIBE, 1, 'O', SOURCE)
-            XX(REPORT,    2, 'B', REBIND)
-            XX(PROPFIND,  4, 'P', PROPPATCH)
-            XX(LOCK,      1, 'I', LINK)
-            XX(UNLOCK,    2, 'S', UNSUBSCRIBE)
-            XX(UNLOCK,    2, 'B', UNBIND)
-            XX(UNLOCK,    3, 'I', UNLINK)
-#undef XX
-            default:
-              SET_ERRNO(HPE_INVALID_METHOD);
-              goto error;
-          }
-        } else {
-          SET_ERRNO(HPE_INVALID_METHOD);
-          goto error;
-        }
-
-        ++parser->index;
-        break;
-      }
-
-      case s_req_spaces_before_url:
-      {
-        if (ch == ' ') break;
-
-        MARK(url);
-        if (parser->method == HTTP_CONNECT) {
-          UPDATE_STATE(s_req_server_start);
-        }
-
-        UPDATE_STATE(parse_url_char(CURRENT_STATE(), ch));
-        if (UNLIKELY(CURRENT_STATE() == s_dead)) {
-          SET_ERRNO(HPE_INVALID_URL);
-          goto error;
-        }
-
-        break;
-      }
-
-      case s_req_schema:
-      case s_req_schema_slash:
-      case s_req_schema_slash_slash:
-      case s_req_server_start:
-      {
-        switch (ch) {
-          /* No whitespace allowed here */
-          case ' ':
-          case CR:
-          case LF:
-            SET_ERRNO(HPE_INVALID_URL);
-            goto error;
-          default:
-            UPDATE_STATE(parse_url_char(CURRENT_STATE(), ch));
-            if (UNLIKELY(CURRENT_STATE() == s_dead)) {
-              SET_ERRNO(HPE_INVALID_URL);
-              goto error;
-            }
-        }
-
-        break;
-      }
-
-      case s_req_server:
-      case s_req_server_with_at:
-      case s_req_path:
-      case s_req_query_string_start:
-      case s_req_query_string:
-      case s_req_fragment_start:
-      case s_req_fragment:
-      {
-        switch (ch) {
-          case ' ':
-            UPDATE_STATE(s_req_http_start);
-            CALLBACK_DATA(url);
-            break;
-          case CR:
-          case LF:
-            parser->http_major = 0;
-            parser->http_minor = 9;
-            UPDATE_STATE((ch == CR) ?
-              s_req_line_almost_done :
-              s_header_field_start);
-            CALLBACK_DATA(url);
-            break;
-          default:
-            UPDATE_STATE(parse_url_char(CURRENT_STATE(), ch));
-            if (UNLIKELY(CURRENT_STATE() == s_dead)) {
-              SET_ERRNO(HPE_INVALID_URL);
-              goto error;
-            }
-        }
-        break;
-      }
-
-      case s_req_http_start:
-        switch (ch) {
-          case 'H':
-            UPDATE_STATE(s_req_http_H);
-            break;
-          case ' ':
-            break;
-          default:
-            SET_ERRNO(HPE_INVALID_CONSTANT);
-            goto error;
-        }
-        break;
-
-      case s_req_http_H:
-        STRICT_CHECK(ch != 'T');
-        UPDATE_STATE(s_req_http_HT);
-        break;
-
-      case s_req_http_HT:
-        STRICT_CHECK(ch != 'T');
-        UPDATE_STATE(s_req_http_HTT);
-        break;
-
-      case s_req_http_HTT:
-        STRICT_CHECK(ch != 'P');
-        UPDATE_STATE(s_req_http_HTTP);
-        break;
-
-      case s_req_http_HTTP:
-        STRICT_CHECK(ch != '/');
-        UPDATE_STATE(s_req_http_major);
-        break;
-
-      case s_req_http_major:
-        if (UNLIKELY(!IS_NUM(ch))) {
-          SET_ERRNO(HPE_INVALID_VERSION);
-          goto error;
-        }
-
-        parser->http_major = ch - '0';
-        UPDATE_STATE(s_req_http_dot);
-        break;
-
-      case s_req_http_dot:
-      {
-        if (UNLIKELY(ch != '.')) {
-          SET_ERRNO(HPE_INVALID_VERSION);
-          goto error;
-        }
-
-        UPDATE_STATE(s_req_http_minor);
-        break;
-      }
-
-      case s_req_http_minor:
-        if (UNLIKELY(!IS_NUM(ch))) {
-          SET_ERRNO(HPE_INVALID_VERSION);
-          goto error;
-        }
-
-        parser->http_minor = ch - '0';
-        UPDATE_STATE(s_req_http_end);
-        break;
-
-      case s_req_http_end:
-      {
-        if (ch == CR) {
-          UPDATE_STATE(s_req_line_almost_done);
-          break;
-        }
-
-        if (ch == LF) {
-          UPDATE_STATE(s_header_field_start);
-          break;
-        }
-
-        SET_ERRNO(HPE_INVALID_VERSION);
-        goto error;
-        break;
-      }
-
-      /* end of request line */
-      case s_req_line_almost_done:
-      {
-        if (UNLIKELY(ch != LF)) {
-          SET_ERRNO(HPE_LF_EXPECTED);
-          goto error;
-        }
-
-        UPDATE_STATE(s_header_field_start);
-        break;
-      }
-
-      case s_header_field_start:
-      {
-        if (ch == CR) {
-          UPDATE_STATE(s_headers_almost_done);
-          break;
-        }
-
-        if (ch == LF) {
-          /* they might be just sending \n instead of \r\n so this would be
-           * the second \n to denote the end of headers*/
-          UPDATE_STATE(s_headers_almost_done);
-          REEXECUTE();
-        }
-
-        c = TOKEN(ch);
-
-        if (UNLIKELY(!c)) {
-          SET_ERRNO(HPE_INVALID_HEADER_TOKEN);
-          goto error;
-        }
-
-        MARK(header_field);
-
-        parser->index = 0;
-        UPDATE_STATE(s_header_field);
-
-        switch (c) {
-          case 'c':
-            parser->header_state = h_C;
-            break;
-
-          case 'p':
-            parser->header_state = h_matching_proxy_connection;
-            break;
-
-          case 't':
-            parser->header_state = h_matching_transfer_encoding;
-            break;
-
-          case 'u':
-            parser->header_state = h_matching_upgrade;
-            break;
-
-          default:
-            parser->header_state = h_general;
-            break;
-        }
-        break;
-      }
-
-      case s_header_field:
-      {
-        const char* start = p;
-        for (; p != data + len; p++) {
-          ch = *p;
-          c = TOKEN(ch);
-
-          if (!c)
-            break;
-
-          switch (parser->header_state) {
-            case h_general: {
-              size_t limit = data + len - p;
-              limit = MIN(limit, HTTP_MAX_HEADER_SIZE);
-              while (p+1 < data + limit && TOKEN(p[1])) {
-                p++;
-              }
-              break;
-            }
-
-            case h_C:
-              parser->index++;
-              parser->header_state = (c == 'o' ? h_CO : h_general);
-              break;
-
-            case h_CO:
-              parser->index++;
-              parser->header_state = (c == 'n' ? h_CON : h_general);
-              break;
-
-            case h_CON:
-              parser->index++;
-              switch (c) {
-                case 'n':
-                  parser->header_state = h_matching_connection;
-                  break;
-                case 't':
-                  parser->header_state = h_matching_content_length;
-                  break;
-                default:
-                  parser->header_state = h_general;
-                  break;
-              }
-              break;
-
-            /* connection */
-
-            case h_matching_connection:
-              parser->index++;
-              if (parser->index > sizeof(CONNECTION)-1
-                  || c != CONNECTION[parser->index]) {
-                parser->header_state = h_general;
-              } else if (parser->index == sizeof(CONNECTION)-2) {
-                parser->header_state = h_connection;
-              }
-              break;
-
-            /* proxy-connection */
-
-            case h_matching_proxy_connection:
-              parser->index++;
-              if (parser->index > sizeof(PROXY_CONNECTION)-1
-                  || c != PROXY_CONNECTION[parser->index]) {
-                parser->header_state = h_general;
-              } else if (parser->index == sizeof(PROXY_CONNECTION)-2) {
-                parser->header_state = h_connection;
-              }
-              break;
-
-            /* content-length */
-
-            case h_matching_content_length:
-              parser->index++;
-              if (parser->index > sizeof(CONTENT_LENGTH)-1
-                  || c != CONTENT_LENGTH[parser->index]) {
-                parser->header_state = h_general;
-              } else if (parser->index == sizeof(CONTENT_LENGTH)-2) {
-                parser->header_state = h_content_length;
-              }
-              break;
-
-            /* transfer-encoding */
-
-            case h_matching_transfer_encoding:
-              parser->index++;
-              if (parser->index > sizeof(TRANSFER_ENCODING)-1
-                  || c != TRANSFER_ENCODING[parser->index]) {
-                parser->header_state = h_general;
-              } else if (parser->index == sizeof(TRANSFER_ENCODING)-2) {
-                parser->header_state = h_transfer_encoding;
-              }
-              break;
-
-            /* upgrade */
-
-            case h_matching_upgrade:
-              parser->index++;
-              if (parser->index > sizeof(UPGRADE)-1
-                  || c != UPGRADE[parser->index]) {
-                parser->header_state = h_general;
-              } else if (parser->index == sizeof(UPGRADE)-2) {
-                parser->header_state = h_upgrade;
-              }
-              break;
-
-            case h_connection:
-            case h_content_length:
-            case h_transfer_encoding:
-            case h_upgrade:
-              if (ch != ' ') parser->header_state = h_general;
-              break;
-
-            default:
-              assert(0 && "Unknown header_state");
-              break;
-          }
-        }
-
-        if (p == data + len) {
-          --p;
-          COUNT_HEADER_SIZE(p - start);
-          break;
-        }
-
-        COUNT_HEADER_SIZE(p - start);
-
-        if (ch == ':') {
-          UPDATE_STATE(s_header_value_discard_ws);
-          CALLBACK_DATA(header_field);
-          break;
-        }
-
-        SET_ERRNO(HPE_INVALID_HEADER_TOKEN);
-        goto error;
-      }
-
-      case s_header_value_discard_ws:
-        if (ch == ' ' || ch == '\t') break;
-
-        if (ch == CR) {
-          UPDATE_STATE(s_header_value_discard_ws_almost_done);
-          break;
-        }
-
-        if (ch == LF) {
-          UPDATE_STATE(s_header_value_discard_lws);
-          break;
-        }
-
-        /* fall through */
-
-      case s_header_value_start:
-      {
-        MARK(header_value);
-
-        UPDATE_STATE(s_header_value);
-        parser->index = 0;
-
-        c = LOWER(ch);
-
-        switch (parser->header_state) {
-          case h_upgrade:
-            parser->flags |= F_UPGRADE;
-            parser->header_state = h_general;
-            break;
-
-          case h_transfer_encoding:
-            /* looking for 'Transfer-Encoding: chunked' */
-            if ('c' == c) {
-              parser->header_state = h_matching_transfer_encoding_chunked;
-            } else {
-              parser->header_state = h_general;
-            }
-            break;
-
-          case h_content_length:
-            if (UNLIKELY(!IS_NUM(ch))) {
-              SET_ERRNO(HPE_INVALID_CONTENT_LENGTH);
-              goto error;
-            }
-
-            if (parser->flags & F_CONTENTLENGTH) {
-              SET_ERRNO(HPE_UNEXPECTED_CONTENT_LENGTH);
-              goto error;
-            }
-
-            parser->flags |= F_CONTENTLENGTH;
-            parser->content_length = ch - '0';
-            parser->header_state = h_content_length_num;
-            break;
-
-          case h_connection:
-            /* looking for 'Connection: keep-alive' */
-            if (c == 'k') {
-              parser->header_state = h_matching_connection_keep_alive;
-            /* looking for 'Connection: close' */
-            } else if (c == 'c') {
-              parser->header_state = h_matching_connection_close;
-            } else if (c == 'u') {
-              parser->header_state = h_matching_connection_upgrade;
-            } else {
-              parser->header_state = h_matching_connection_token;
-            }
-            break;
-
-          /* Multi-value `Connection` header */
-          case h_matching_connection_token_start:
-            break;
-
-          default:
-            parser->header_state = h_general;
-            break;
-        }
-        break;
-      }
-
-      case s_header_value:
-      {
-        const char* start = p;
-        enum header_states h_state = (enum header_states) parser->header_state;
-        for (; p != data + len; p++) {
-          ch = *p;
-          if (ch == CR) {
-            UPDATE_STATE(s_header_almost_done);
-            parser->header_state = h_state;
-            CALLBACK_DATA(header_value);
-            break;
-          }
-
-          if (ch == LF) {
-            UPDATE_STATE(s_header_almost_done);
-            COUNT_HEADER_SIZE(p - start);
-            parser->header_state = h_state;
-            CALLBACK_DATA_NOADVANCE(header_value);
-            REEXECUTE();
-          }
-
-          if (!lenient && !IS_HEADER_CHAR(ch)) {
-            SET_ERRNO(HPE_INVALID_HEADER_TOKEN);
-            goto error;
-          }
-
-          c = LOWER(ch);
-
-          switch (h_state) {
-            case h_general:
-            {
-              const char* p_cr;
-              const char* p_lf;
-              size_t limit = data + len - p;
-
-              limit = MIN(limit, HTTP_MAX_HEADER_SIZE);
-
-              p_cr = (const char*) memchr(p, CR, limit);
-              p_lf = (const char*) memchr(p, LF, limit);
-              if (p_cr != NULL) {
-                if (p_lf != NULL && p_cr >= p_lf)
-                  p = p_lf;
-                else
-                  p = p_cr;
-              } else if (UNLIKELY(p_lf != NULL)) {
-                p = p_lf;
-              } else {
-                p = data + len;
-              }
-              --p;
-              break;
-            }
-
-            case h_connection:
-            case h_transfer_encoding:
-              assert(0 && "Shouldn't get here.");
-              break;
-
-            case h_content_length:
-              if (ch == ' ') break;
-              h_state = h_content_length_num;
-              /* fall through */
-
-            case h_content_length_num:
-            {
-              uint64_t t;
-
-              if (ch == ' ') {
-                h_state = h_content_length_ws;
-                break;
-              }
-
-              if (UNLIKELY(!IS_NUM(ch))) {
-                SET_ERRNO(HPE_INVALID_CONTENT_LENGTH);
-                parser->header_state = h_state;
-                goto error;
-              }
-
-              t = parser->content_length;
-              t *= 10;
-              t += ch - '0';
-
-              /* Overflow? Test against a conservative limit for simplicity. */
-              if (UNLIKELY((ULLONG_MAX - 10) / 10 < parser->content_length)) {
-                SET_ERRNO(HPE_INVALID_CONTENT_LENGTH);
-                parser->header_state = h_state;
-                goto error;
-              }
-
-              parser->content_length = t;
-              break;
-            }
-
-            case h_content_length_ws:
-              if (ch == ' ') break;
-              SET_ERRNO(HPE_INVALID_CONTENT_LENGTH);
-              parser->header_state = h_state;
-              goto error;
-
-            /* Transfer-Encoding: chunked */
-            case h_matching_transfer_encoding_chunked:
-              parser->index++;
-              if (parser->index > sizeof(CHUNKED)-1
-                  || c != CHUNKED[parser->index]) {
-                h_state = h_general;
-              } else if (parser->index == sizeof(CHUNKED)-2) {
-                h_state = h_transfer_encoding_chunked;
-              }
-              break;
-
-            case h_matching_connection_token_start:
-              /* looking for 'Connection: keep-alive' */
-              if (c == 'k') {
-                h_state = h_matching_connection_keep_alive;
-              /* looking for 'Connection: close' */
-              } else if (c == 'c') {
-                h_state = h_matching_connection_close;
-              } else if (c == 'u') {
-                h_state = h_matching_connection_upgrade;
-              } else if (STRICT_TOKEN(c)) {
-                h_state = h_matching_connection_token;
-              } else if (c == ' ' || c == '\t') {
-                /* Skip lws */
-              } else {
-                h_state = h_general;
-              }
-              break;
-
-            /* looking for 'Connection: keep-alive' */
-            case h_matching_connection_keep_alive:
-              parser->index++;
-              if (parser->index > sizeof(KEEP_ALIVE)-1
-                  || c != KEEP_ALIVE[parser->index]) {
-                h_state = h_matching_connection_token;
-              } else if (parser->index == sizeof(KEEP_ALIVE)-2) {
-                h_state = h_connection_keep_alive;
-              }
-              break;
-
-            /* looking for 'Connection: close' */
-            case h_matching_connection_close:
-              parser->index++;
-              if (parser->index > sizeof(CLOSE)-1 || c != CLOSE[parser->index]) {
-                h_state = h_matching_connection_token;
-              } else if (parser->index == sizeof(CLOSE)-2) {
-                h_state = h_connection_close;
-              }
-              break;
-
-            /* looking for 'Connection: upgrade' */
-            case h_matching_connection_upgrade:
-              parser->index++;
-              if (parser->index > sizeof(UPGRADE) - 1 ||
-                  c != UPGRADE[parser->index]) {
-                h_state = h_matching_connection_token;
-              } else if (parser->index == sizeof(UPGRADE)-2) {
-                h_state = h_connection_upgrade;
-              }
-              break;
-
-            case h_matching_connection_token:
-              if (ch == ',') {
-                h_state = h_matching_connection_token_start;
-                parser->index = 0;
-              }
-              break;
-
-            case h_transfer_encoding_chunked:
-              if (ch != ' ') h_state = h_general;
-              break;
-
-            case h_connection_keep_alive:
-            case h_connection_close:
-            case h_connection_upgrade:
-              if (ch == ',') {
-                if (h_state == h_connection_keep_alive) {
-                  parser->flags |= F_CONNECTION_KEEP_ALIVE;
-                } else if (h_state == h_connection_close) {
-                  parser->flags |= F_CONNECTION_CLOSE;
-                } else if (h_state == h_connection_upgrade) {
-                  parser->flags |= F_CONNECTION_UPGRADE;
-                }
-                h_state = h_matching_connection_token_start;
-                parser->index = 0;
-              } else if (ch != ' ') {
-                h_state = h_matching_connection_token;
-              }
-              break;
-
-            default:
-              UPDATE_STATE(s_header_value);
-              h_state = h_general;
-              break;
-          }
-        }
-        parser->header_state = h_state;
-
-        if (p == data + len)
-          --p;
-
-        COUNT_HEADER_SIZE(p - start);
-        break;
-      }
-
-      case s_header_almost_done:
-      {
-        if (UNLIKELY(ch != LF)) {
-          SET_ERRNO(HPE_LF_EXPECTED);
-          goto error;
-        }
-
-        UPDATE_STATE(s_header_value_lws);
-        break;
-      }
-
-      case s_header_value_lws:
-      {
-        if (ch == ' ' || ch == '\t') {
-          UPDATE_STATE(s_header_value_start);
-          REEXECUTE();
-        }
-
-        /* finished the header */
-        switch (parser->header_state) {
-          case h_connection_keep_alive:
-            parser->flags |= F_CONNECTION_KEEP_ALIVE;
-            break;
-          case h_connection_close:
-            parser->flags |= F_CONNECTION_CLOSE;
-            break;
-          case h_transfer_encoding_chunked:
-            parser->flags |= F_CHUNKED;
-            break;
-          case h_connection_upgrade:
-            parser->flags |= F_CONNECTION_UPGRADE;
-            break;
-          default:
-            break;
-        }
-
-        UPDATE_STATE(s_header_field_start);
-        REEXECUTE();
-      }
-
-      case s_header_value_discard_ws_almost_done:
-      {
-        STRICT_CHECK(ch != LF);
-        UPDATE_STATE(s_header_value_discard_lws);
-        break;
-      }
-
-      case s_header_value_discard_lws:
-      {
-        if (ch == ' ' || ch == '\t') {
-          UPDATE_STATE(s_header_value_discard_ws);
-          break;
-        } else {
-          switch (parser->header_state) {
-            case h_connection_keep_alive:
-              parser->flags |= F_CONNECTION_KEEP_ALIVE;
-              break;
-            case h_connection_close:
-              parser->flags |= F_CONNECTION_CLOSE;
-              break;
-            case h_connection_upgrade:
-              parser->flags |= F_CONNECTION_UPGRADE;
-              break;
-            case h_transfer_encoding_chunked:
-              parser->flags |= F_CHUNKED;
-              break;
-            default:
-              break;
-          }
-
-          /* header value was empty */
-          MARK(header_value);
-          UPDATE_STATE(s_header_field_start);
-          CALLBACK_DATA_NOADVANCE(header_value);
-          REEXECUTE();
-        }
-      }
-
-      case s_headers_almost_done:
-      {
-        STRICT_CHECK(ch != LF);
-
-        if (parser->flags & F_TRAILING) {
-          /* End of a chunked request */
-          UPDATE_STATE(s_message_done);
-          CALLBACK_NOTIFY_NOADVANCE(chunk_complete);
-          REEXECUTE();
-        }
-
-        /* Cannot use chunked encoding and a content-length header together
-           per the HTTP specification. */
-        if ((parser->flags & F_CHUNKED) &&
-            (parser->flags & F_CONTENTLENGTH)) {
-          SET_ERRNO(HPE_UNEXPECTED_CONTENT_LENGTH);
-          goto error;
-        }
-
-        UPDATE_STATE(s_headers_done);
-
-        /* Set this here so that on_headers_complete() callbacks can see it */
-        if ((parser->flags & F_UPGRADE) &&
-            (parser->flags & F_CONNECTION_UPGRADE)) {
-          /* For responses, "Upgrade: foo" and "Connection: upgrade" are
-           * mandatory only when it is a 101 Switching Protocols response,
-           * otherwise it is purely informational, to announce support.
-           */
-          parser->upgrade =
-              (parser->type == HTTP_REQUEST || parser->status_code == 101);
-        } else {
-          parser->upgrade = (parser->method == HTTP_CONNECT);
-        }
-
-        /* Here we call the headers_complete callback. This is somewhat
-         * different than other callbacks because if the user returns 1, we
-         * will interpret that as saying that this message has no body. This
-         * is needed for the annoying case of receiving a response to a HEAD
-         * request.
-         *
-         * We'd like to use CALLBACK_NOTIFY_NOADVANCE() here but we cannot, so
-         * we have to simulate it by handling a change in errno below.
-         */
-        if (settings->on_headers_complete) {
-          switch (settings->on_headers_complete(parser)) {
-            case 0:
-              break;
-
-            case 2:
-              parser->upgrade = 1;
-
-              /* fall through */
-            case 1:
-              parser->flags |= F_SKIPBODY;
-              break;
-
-            default:
-              SET_ERRNO(HPE_CB_headers_complete);
-              RETURN(p - data); /* Error */
-          }
-        }
-
-        if (HTTP_PARSER_ERRNO(parser) != HPE_OK) {
-          RETURN(p - data);
-        }
-
-        REEXECUTE();
-      }
-
-      case s_headers_done:
-      {
-        int hasBody;
-        STRICT_CHECK(ch != LF);
-
-        parser->nread = 0;
-        nread = 0;
-
-        hasBody = parser->flags & F_CHUNKED ||
-          (parser->content_length > 0 && parser->content_length != ULLONG_MAX);
-        if (parser->upgrade && (parser->method == HTTP_CONNECT ||
-                                (parser->flags & F_SKIPBODY) || !hasBody)) {
-          /* Exit, the rest of the message is in a different protocol. */
-          UPDATE_STATE(NEW_MESSAGE());
-          CALLBACK_NOTIFY(message_complete);
-          RETURN((p - data) + 1);
-        }
-
-        if (parser->flags & F_SKIPBODY) {
-          UPDATE_STATE(NEW_MESSAGE());
-          CALLBACK_NOTIFY(message_complete);
-        } else if (parser->flags & F_CHUNKED) {
-          /* chunked encoding - ignore Content-Length header */
-          UPDATE_STATE(s_chunk_size_start);
-        } else {
-          if (parser->content_length == 0) {
-            /* Content-Length header given but zero: Content-Length: 0\r\n */
-            UPDATE_STATE(NEW_MESSAGE());
-            CALLBACK_NOTIFY(message_complete);
-          } else if (parser->content_length != ULLONG_MAX) {
-            /* Content-Length header given and non-zero */
-            UPDATE_STATE(s_body_identity);
-          } else {
-            if (!http_message_needs_eof(parser)) {
-              /* Assume content-length 0 - read the next */
-              UPDATE_STATE(NEW_MESSAGE());
-              CALLBACK_NOTIFY(message_complete);
-            } else {
-              /* Read body until EOF */
-              UPDATE_STATE(s_body_identity_eof);
-            }
-          }
-        }
-
-        break;
-      }
-
-      case s_body_identity:
-      {
-        uint64_t to_read = MIN(parser->content_length,
-                               (uint64_t) ((data + len) - p));
-
-        assert(parser->content_length != 0
-            && parser->content_length != ULLONG_MAX);
-
-        /* The difference between advancing content_length and p is because
-         * the latter will automaticaly advance on the next loop iteration.
-         * Further, if content_length ends up at 0, we want to see the last
-         * byte again for our message complete callback.
-         */
-        MARK(body);
-        parser->content_length -= to_read;
-        p += to_read - 1;
-
-        if (parser->content_length == 0) {
-          UPDATE_STATE(s_message_done);
-
-          /* Mimic CALLBACK_DATA_NOADVANCE() but with one extra byte.
-           *
-           * The alternative to doing this is to wait for the next byte to
-           * trigger the data callback, just as in every other case. The
-           * problem with this is that this makes it difficult for the test
-           * harness to distinguish between complete-on-EOF and
-           * complete-on-length. It's not clear that this distinction is
-           * important for applications, but let's keep it for now.
-           */
-          CALLBACK_DATA_(body, p - body_mark + 1, p - data);
-          REEXECUTE();
-        }
-
-        break;
-      }
-
-      /* read until EOF */
-      case s_body_identity_eof:
-        MARK(body);
-        p = data + len - 1;
-
-        break;
-
-      case s_message_done:
-        UPDATE_STATE(NEW_MESSAGE());
-        CALLBACK_NOTIFY(message_complete);
-        if (parser->upgrade) {
-          /* Exit, the rest of the message is in a different protocol. */
-          RETURN((p - data) + 1);
-        }
-        break;
-
-      case s_chunk_size_start:
-      {
-        assert(nread == 1);
-        assert(parser->flags & F_CHUNKED);
-
-        unhex_val = unhex[(unsigned char)ch];
-        if (UNLIKELY(unhex_val == -1)) {
-          SET_ERRNO(HPE_INVALID_CHUNK_SIZE);
-          goto error;
-        }
-
-        parser->content_length = unhex_val;
-        UPDATE_STATE(s_chunk_size);
-        break;
-      }
-
-      case s_chunk_size:
-      {
-        uint64_t t;
-
-        assert(parser->flags & F_CHUNKED);
-
-        if (ch == CR) {
-          UPDATE_STATE(s_chunk_size_almost_done);
-          break;
-        }
-
-        unhex_val = unhex[(unsigned char)ch];
-
-        if (unhex_val == -1) {
-          if (ch == ';' || ch == ' ') {
-            UPDATE_STATE(s_chunk_parameters);
-            break;
-          }
-
-          SET_ERRNO(HPE_INVALID_CHUNK_SIZE);
-          goto error;
-        }
-
-        t = parser->content_length;
-        t *= 16;
-        t += unhex_val;
-
-        /* Overflow? Test against a conservative limit for simplicity. */
-        if (UNLIKELY((ULLONG_MAX - 16) / 16 < parser->content_length)) {
-          SET_ERRNO(HPE_INVALID_CONTENT_LENGTH);
-          goto error;
-        }
-
-        parser->content_length = t;
-        break;
-      }
-
-      case s_chunk_parameters:
-      {
-        assert(parser->flags & F_CHUNKED);
-        /* just ignore this shit. TODO check for overflow */
-        if (ch == CR) {
-          UPDATE_STATE(s_chunk_size_almost_done);
-          break;
-        }
-        break;
-      }
-
-      case s_chunk_size_almost_done:
-      {
-        assert(parser->flags & F_CHUNKED);
-        STRICT_CHECK(ch != LF);
-
-        parser->nread = 0;
-        nread = 0;
-
-        if (parser->content_length == 0) {
-          parser->flags |= F_TRAILING;
-          UPDATE_STATE(s_header_field_start);
-        } else {
-          UPDATE_STATE(s_chunk_data);
-        }
-        CALLBACK_NOTIFY(chunk_header);
-        break;
-      }
-
-      case s_chunk_data:
-      {
-        uint64_t to_read = MIN(parser->content_length,
-                               (uint64_t) ((data + len) - p));
-
-        assert(parser->flags & F_CHUNKED);
-        assert(parser->content_length != 0
-            && parser->content_length != ULLONG_MAX);
-
-        /* See the explanation in s_body_identity for why the content
-         * length and data pointers are managed this way.
-         */
-        MARK(body);
-        parser->content_length -= to_read;
-        p += to_read - 1;
-
-        if (parser->content_length == 0) {
-          UPDATE_STATE(s_chunk_data_almost_done);
-        }
-
-        break;
-      }
-
-      case s_chunk_data_almost_done:
-        assert(parser->flags & F_CHUNKED);
-        assert(parser->content_length == 0);
-        STRICT_CHECK(ch != CR);
-        UPDATE_STATE(s_chunk_data_done);
-        CALLBACK_DATA(body);
-        break;
-
-      case s_chunk_data_done:
-        assert(parser->flags & F_CHUNKED);
-        STRICT_CHECK(ch != LF);
-        parser->nread = 0;
-        nread = 0;
-        UPDATE_STATE(s_chunk_size_start);
-        CALLBACK_NOTIFY(chunk_complete);
-        break;
-
-      default:
-        assert(0 && "unhandled state");
-        SET_ERRNO(HPE_INVALID_INTERNAL_STATE);
-        goto error;
-    }
-  }
-
-  /* Run callbacks for any marks that we have leftover after we ran our of
-   * bytes. There should be at most one of these set, so it's OK to invoke
-   * them in series (unset marks will not result in callbacks).
-   *
-   * We use the NOADVANCE() variety of callbacks here because 'p' has already
-   * overflowed 'data' and this allows us to correct for the off-by-one that
-   * we'd otherwise have (since CALLBACK_DATA() is meant to be run with a 'p'
-   * value that's in-bounds).
-   */
-
-  assert(((header_field_mark ? 1 : 0) +
-          (header_value_mark ? 1 : 0) +
-          (url_mark ? 1 : 0)  +
-          (body_mark ? 1 : 0) +
-          (status_mark ? 1 : 0)) <= 1);
-
-  CALLBACK_DATA_NOADVANCE(header_field);
-  CALLBACK_DATA_NOADVANCE(header_value);
-  CALLBACK_DATA_NOADVANCE(url);
-  CALLBACK_DATA_NOADVANCE(body);
-  CALLBACK_DATA_NOADVANCE(status);
-
-  RETURN(len);
-
-error:
-  if (HTTP_PARSER_ERRNO(parser) == HPE_OK) {
-    SET_ERRNO(HPE_UNKNOWN);
-  }
-
-  RETURN(p - data);
-}
-
-
-/* Does the parser need to see an EOF to find the end of the message? */
-int
-http_message_needs_eof (const http_parser *parser)
-{
-  if (parser->type == HTTP_REQUEST) {
-    return 0;
-  }
-
-  /* See RFC 2616 section 4.4 */
-  if (parser->status_code / 100 == 1 || /* 1xx e.g. Continue */
-      parser->status_code == 204 ||     /* No Content */
-      parser->status_code == 304 ||     /* Not Modified */
-      parser->flags & F_SKIPBODY) {     /* response to a HEAD request */
-    return 0;
-  }
-
-  if ((parser->flags & F_CHUNKED) || parser->content_length != ULLONG_MAX) {
-    return 0;
-  }
-
-  return 1;
-}
-
-
-int
-http_should_keep_alive (const http_parser *parser)
-{
-  if (parser->http_major > 0 && parser->http_minor > 0) {
-    /* HTTP/1.1 */
-    if (parser->flags & F_CONNECTION_CLOSE) {
-      return 0;
-    }
-  } else {
-    /* HTTP/1.0 or earlier */
-    if (!(parser->flags & F_CONNECTION_KEEP_ALIVE)) {
-      return 0;
-    }
-  }
-
-  return !http_message_needs_eof(parser);
-}
-
-
-const char *
-http_method_str (enum http_method m)
-{
-  return ELEM_AT(method_strings, m, "<unknown>");
-}
-
-const char *
-http_status_str (enum http_status s)
-{
-  switch (s) {
-#define XX(num, name, string) case HTTP_STATUS_##name: return #string;
-    HTTP_STATUS_MAP(XX)
-#undef XX
-    default: return "<unknown>";
-  }
-}
-
-void
-http_parser_init (http_parser *parser, enum http_parser_type t)
-{
-  void *data = parser->data; /* preserve application data */
-  memset(parser, 0, sizeof(*parser));
-  parser->data = data;
-  parser->type = t;
-  parser->state = (t == HTTP_REQUEST ? s_start_req : (t == HTTP_RESPONSE ? s_start_res : s_start_req_or_res));
-  parser->http_errno = HPE_OK;
-}
-
-void
-http_parser_settings_init(http_parser_settings *settings)
-{
-  memset(settings, 0, sizeof(*settings));
-}
-
-const char *
-http_errno_name(enum http_errno err) {
-  assert(((size_t) err) < ARRAY_SIZE(http_strerror_tab));
-  return http_strerror_tab[err].name;
-}
-
-const char *
-http_errno_description(enum http_errno err) {
-  assert(((size_t) err) < ARRAY_SIZE(http_strerror_tab));
-  return http_strerror_tab[err].description;
-}
-
-static enum http_host_state
-http_parse_host_char(enum http_host_state s, const char ch) {
-  switch(s) {
-    case s_http_userinfo:
-    case s_http_userinfo_start:
-      if (ch == '@') {
-        return s_http_host_start;
-      }
-
-      if (IS_USERINFO_CHAR(ch)) {
-        return s_http_userinfo;
-      }
-      break;
-
-    case s_http_host_start:
-      if (ch == '[') {
-        return s_http_host_v6_start;
-      }
-
-      if (IS_HOST_CHAR(ch)) {
-        return s_http_host;
-      }
-
-      break;
-
-    case s_http_host:
-      if (IS_HOST_CHAR(ch)) {
-        return s_http_host;
-      }
-
-    /* fall through */
-    case s_http_host_v6_end:
-      if (ch == ':') {
-        return s_http_host_port_start;
-      }
-
-      break;
-
-    case s_http_host_v6:
-      if (ch == ']') {
-        return s_http_host_v6_end;
-      }
-
-    /* fall through */
-    case s_http_host_v6_start:
-      if (IS_HEX(ch) || ch == ':' || ch == '.') {
-        return s_http_host_v6;
-      }
-
-      if (s == s_http_host_v6 && ch == '%') {
-        return s_http_host_v6_zone_start;
-      }
-      break;
-
-    case s_http_host_v6_zone:
-      if (ch == ']') {
-        return s_http_host_v6_end;
-      }
-
-    /* fall through */
-    case s_http_host_v6_zone_start:
-      /* RFC 6874 Zone ID consists of 1*( unreserved / pct-encoded) */
-      if (IS_ALPHANUM(ch) || ch == '%' || ch == '.' || ch == '-' || ch == '_' ||
-          ch == '~') {
-        return s_http_host_v6_zone;
-      }
-      break;
-
-    case s_http_host_port:
-    case s_http_host_port_start:
-      if (IS_NUM(ch)) {
-        return s_http_host_port;
-      }
-
-      break;
-
-    default:
-      break;
-  }
-  return s_http_host_dead;
-}
-
-static int
-http_parse_host(const char * buf, struct http_parser_url *u, int found_at) {
-  enum http_host_state s;
-
-  const char *p;
-  size_t buflen = u->field_data[UF_HOST].off + u->field_data[UF_HOST].len;
-
-  assert(u->field_set & (1 << UF_HOST));
-
-  u->field_data[UF_HOST].len = 0;
-
-  s = found_at ? s_http_userinfo_start : s_http_host_start;
-
-  for (p = buf + u->field_data[UF_HOST].off; p < buf + buflen; p++) {
-    enum http_host_state new_s = http_parse_host_char(s, *p);
-
-    if (new_s == s_http_host_dead) {
-      return 1;
-    }
-
-    switch(new_s) {
-      case s_http_host:
-        if (s != s_http_host) {
-          u->field_data[UF_HOST].off = p - buf;
-        }
-        u->field_data[UF_HOST].len++;
-        break;
-
-      case s_http_host_v6:
-        if (s != s_http_host_v6) {
-          u->field_data[UF_HOST].off = p - buf;
-        }
-        u->field_data[UF_HOST].len++;
-        break;
-
-      case s_http_host_v6_zone_start:
-      case s_http_host_v6_zone:
-        u->field_data[UF_HOST].len++;
-        break;
-
-      case s_http_host_port:
-        if (s != s_http_host_port) {
-          u->field_data[UF_PORT].off = p - buf;
-          u->field_data[UF_PORT].len = 0;
-          u->field_set |= (1 << UF_PORT);
-        }
-        u->field_data[UF_PORT].len++;
-        break;
-
-      case s_http_userinfo:
-        if (s != s_http_userinfo) {
-          u->field_data[UF_USERINFO].off = p - buf ;
-          u->field_data[UF_USERINFO].len = 0;
-          u->field_set |= (1 << UF_USERINFO);
-        }
-        u->field_data[UF_USERINFO].len++;
-        break;
-
-      default:
-        break;
-    }
-    s = new_s;
-  }
-
-  /* Make sure we don't end somewhere unexpected */
-  switch (s) {
-    case s_http_host_start:
-    case s_http_host_v6_start:
-    case s_http_host_v6:
-    case s_http_host_v6_zone_start:
-    case s_http_host_v6_zone:
-    case s_http_host_port_start:
-    case s_http_userinfo:
-    case s_http_userinfo_start:
-      return 1;
-    default:
-      break;
-  }
-
-  return 0;
-}
-
-void
-http_parser_url_init(struct http_parser_url *u) {
-  memset(u, 0, sizeof(*u));
-}
-
-int
-http_parser_parse_url(const char *buf, size_t buflen, int is_connect,
-                      struct http_parser_url *u)
-{
-  enum state s;
-  const char *p;
-  enum http_parser_url_fields uf, old_uf;
-  int found_at = 0;
-
-  if (buflen == 0) {
-    return 1;
-  }
-
-  u->port = u->field_set = 0;
-  s = is_connect ? s_req_server_start : s_req_spaces_before_url;
-  old_uf = UF_MAX;
-
-  for (p = buf; p < buf + buflen; p++) {
-    s = parse_url_char(s, *p);
-
-    /* Figure out the next field that we're operating on */
-    switch (s) {
-      case s_dead:
-        return 1;
-
-      /* Skip delimeters */
-      case s_req_schema_slash:
-      case s_req_schema_slash_slash:
-      case s_req_server_start:
-      case s_req_query_string_start:
-      case s_req_fragment_start:
-        continue;
-
-      case s_req_schema:
-        uf = UF_SCHEMA;
-        break;
-
-      case s_req_server_with_at:
-        found_at = 1;
-
-      /* fall through */
-      case s_req_server:
-        uf = UF_HOST;
-        break;
-
-      case s_req_path:
-        uf = UF_PATH;
-        break;
-
-      case s_req_query_string:
-        uf = UF_QUERY;
-        break;
-
-      case s_req_fragment:
-        uf = UF_FRAGMENT;
-        break;
-
-      default:
-        assert(!"Unexpected state");
-        return 1;
-    }
-
-    /* Nothing's changed; soldier on */
-    if (uf == old_uf) {
-      u->field_data[uf].len++;
-      continue;
-    }
-
-    u->field_data[uf].off = p - buf;
-    u->field_data[uf].len = 1;
-
-    u->field_set |= (1 << uf);
-    old_uf = uf;
-  }
-
-  /* host must be present if there is a schema */
-  /* parsing http:///toto will fail */
-  if ((u->field_set & (1 << UF_SCHEMA)) &&
-      (u->field_set & (1 << UF_HOST)) == 0) {
-    return 1;
-  }
-
-  if (u->field_set & (1 << UF_HOST)) {
-    if (http_parse_host(buf, u, found_at) != 0) {
-      return 1;
-    }
-  }
-
-  /* CONNECT requests can only contain "hostname:port" */
-  if (is_connect && u->field_set != ((1 << UF_HOST)|(1 << UF_PORT))) {
-    return 1;
-  }
-
-  if (u->field_set & (1 << UF_PORT)) {
-    uint16_t off;
-    uint16_t len;
-    const char* p;
-    const char* end;
-    unsigned long v;
-
-    off = u->field_data[UF_PORT].off;
-    len = u->field_data[UF_PORT].len;
-    end = buf + off + len;
-
-    /* NOTE: The characters are already validated and are in the [0-9] range */
-    assert(off + len <= buflen && "Port number overflow");
-    v = 0;
-    for (p = buf + off; p < end; p++) {
-      v *= 10;
-      v += *p - '0';
-
-      /* Ports have a max value of 2^16 */
-      if (v > 0xffff) {
-        return 1;
-      }
-    }
-
-    u->port = (uint16_t) v;
-  }
-
-  return 0;
-}
-
-void
-http_parser_pause(http_parser *parser, int paused) {
-  /* Users should only be pausing/unpausing a parser that is not in an error
-   * state. In non-debug builds, there's not much that we can do about this
-   * other than ignore it.
-   */
-  if (HTTP_PARSER_ERRNO(parser) == HPE_OK ||
-      HTTP_PARSER_ERRNO(parser) == HPE_PAUSED) {
-    uint32_t nread = parser->nread; /* used by the SET_ERRNO macro */
-    SET_ERRNO((paused) ? HPE_PAUSED : HPE_OK);
-  } else {
-    assert(0 && "Attempting to pause parser in error state");
-  }
-}
-
-int
-http_body_is_final(const struct http_parser *parser) {
-    return parser->state == s_message_done;
-}
-
-unsigned long
-http_parser_version(void) {
-  return HTTP_PARSER_VERSION_MAJOR * 0x10000 |
-         HTTP_PARSER_VERSION_MINOR * 0x00100 |
-         HTTP_PARSER_VERSION_PATCH * 0x00001;
-}
-
-}  // namespace wpi
diff --git a/wpiutil/src/main/native/cpp/jni/DataLogJNI.cpp b/wpiutil/src/main/native/cpp/jni/DataLogJNI.cpp
new file mode 100644
index 0000000..997b90b
--- /dev/null
+++ b/wpiutil/src/main/native/cpp/jni/DataLogJNI.cpp
@@ -0,0 +1,357 @@
+// Copyright (c) FIRST and other WPILib contributors.
+// Open Source Software; you can modify and/or share it under the terms of
+// the WPILib BSD license file in the root directory of this project.
+
+#include <jni.h>
+
+#include "edu_wpi_first_util_datalog_DataLogJNI.h"
+#include "wpi/DataLog.h"
+#include "wpi/jni_util.h"
+
+using namespace wpi::java;
+using namespace wpi::log;
+
+extern "C" {
+
+/*
+ * Class:     edu_wpi_first_util_datalog_DataLogJNI
+ * Method:    create
+ * Signature: (Ljava/lang/String;Ljava/lang/String;DLjava/lang/String;)J
+ */
+JNIEXPORT jlong JNICALL
+Java_edu_wpi_first_util_datalog_DataLogJNI_create
+  (JNIEnv* env, jclass, jstring dir, jstring filename, jdouble period,
+   jstring extraHeader)
+{
+  return reinterpret_cast<jlong>(new DataLog{JStringRef{env, dir},
+                                             JStringRef{env, filename}, period,
+                                             JStringRef{env, extraHeader}});
+}
+
+/*
+ * Class:     edu_wpi_first_util_datalog_DataLogJNI
+ * Method:    setFilename
+ * Signature: (JLjava/lang/String;)V
+ */
+JNIEXPORT void JNICALL
+Java_edu_wpi_first_util_datalog_DataLogJNI_setFilename
+  (JNIEnv* env, jclass, jlong impl, jstring filename)
+{
+  if (impl == 0) {
+    return;
+  }
+  reinterpret_cast<DataLog*>(impl)->SetFilename(JStringRef{env, filename});
+}
+
+/*
+ * Class:     edu_wpi_first_util_datalog_DataLogJNI
+ * Method:    flush
+ * Signature: (J)V
+ */
+JNIEXPORT void JNICALL
+Java_edu_wpi_first_util_datalog_DataLogJNI_flush
+  (JNIEnv*, jclass, jlong impl)
+{
+  if (impl == 0) {
+    return;
+  }
+  reinterpret_cast<DataLog*>(impl)->Flush();
+}
+
+/*
+ * Class:     edu_wpi_first_util_datalog_DataLogJNI
+ * Method:    pause
+ * Signature: (J)V
+ */
+JNIEXPORT void JNICALL
+Java_edu_wpi_first_util_datalog_DataLogJNI_pause
+  (JNIEnv*, jclass, jlong impl)
+{
+  if (impl == 0) {
+    return;
+  }
+  reinterpret_cast<DataLog*>(impl)->Pause();
+}
+
+/*
+ * Class:     edu_wpi_first_util_datalog_DataLogJNI
+ * Method:    resume
+ * Signature: (J)V
+ */
+JNIEXPORT void JNICALL
+Java_edu_wpi_first_util_datalog_DataLogJNI_resume
+  (JNIEnv*, jclass, jlong impl)
+{
+  if (impl == 0) {
+    return;
+  }
+  reinterpret_cast<DataLog*>(impl)->Resume();
+}
+
+/*
+ * Class:     edu_wpi_first_util_datalog_DataLogJNI
+ * Method:    start
+ * Signature: (JLjava/lang/String;Ljava/lang/String;Ljava/lang/String;J)I
+ */
+JNIEXPORT jint JNICALL
+Java_edu_wpi_first_util_datalog_DataLogJNI_start
+  (JNIEnv* env, jclass, jlong impl, jstring name, jstring type,
+   jstring metadata, jlong timestamp)
+{
+  if (impl == 0) {
+    return 0;
+  }
+  return reinterpret_cast<DataLog*>(impl)->Start(
+      JStringRef{env, name}, JStringRef{env, type}, JStringRef{env, metadata},
+      timestamp);
+}
+
+/*
+ * Class:     edu_wpi_first_util_datalog_DataLogJNI
+ * Method:    finish
+ * Signature: (JIJ)V
+ */
+JNIEXPORT void JNICALL
+Java_edu_wpi_first_util_datalog_DataLogJNI_finish
+  (JNIEnv*, jclass, jlong impl, jint entry, jlong timestamp)
+{
+  if (impl == 0) {
+    return;
+  }
+  reinterpret_cast<DataLog*>(impl)->Finish(entry, timestamp);
+}
+
+/*
+ * Class:     edu_wpi_first_util_datalog_DataLogJNI
+ * Method:    setMetadata
+ * Signature: (JILjava/lang/String;J)V
+ */
+JNIEXPORT void JNICALL
+Java_edu_wpi_first_util_datalog_DataLogJNI_setMetadata
+  (JNIEnv* env, jclass, jlong impl, jint entry, jstring metadata,
+   jlong timestamp)
+{
+  if (impl == 0) {
+    return;
+  }
+  reinterpret_cast<DataLog*>(impl)->SetMetadata(
+      entry, JStringRef{env, metadata}, timestamp);
+}
+
+/*
+ * Class:     edu_wpi_first_util_datalog_DataLogJNI
+ * Method:    close
+ * Signature: (J)V
+ */
+JNIEXPORT void JNICALL
+Java_edu_wpi_first_util_datalog_DataLogJNI_close
+  (JNIEnv*, jclass, jlong impl)
+{
+  delete reinterpret_cast<DataLog*>(impl);
+}
+
+/*
+ * Class:     edu_wpi_first_util_datalog_DataLogJNI
+ * Method:    appendRaw
+ * Signature: (JI[BJ)V
+ */
+JNIEXPORT void JNICALL
+Java_edu_wpi_first_util_datalog_DataLogJNI_appendRaw
+  (JNIEnv* env, jclass, jlong impl, jint entry, jbyteArray value,
+   jlong timestamp)
+{
+  if (impl == 0) {
+    return;
+  }
+  JByteArrayRef cvalue{env, value};
+  reinterpret_cast<DataLog*>(impl)->AppendRaw(
+      entry,
+      {reinterpret_cast<const uint8_t*>(cvalue.array().data()), cvalue.size()},
+      timestamp);
+}
+
+/*
+ * Class:     edu_wpi_first_util_datalog_DataLogJNI
+ * Method:    appendBoolean
+ * Signature: (JIZJ)V
+ */
+JNIEXPORT void JNICALL
+Java_edu_wpi_first_util_datalog_DataLogJNI_appendBoolean
+  (JNIEnv*, jclass, jlong impl, jint entry, jboolean value, jlong timestamp)
+{
+  if (impl == 0) {
+    return;
+  }
+  reinterpret_cast<DataLog*>(impl)->AppendBoolean(entry, value, timestamp);
+}
+
+/*
+ * Class:     edu_wpi_first_util_datalog_DataLogJNI
+ * Method:    appendInteger
+ * Signature: (JIJJ)V
+ */
+JNIEXPORT void JNICALL
+Java_edu_wpi_first_util_datalog_DataLogJNI_appendInteger
+  (JNIEnv*, jclass, jlong impl, jint entry, jlong value, jlong timestamp)
+{
+  if (impl == 0) {
+    return;
+  }
+  reinterpret_cast<DataLog*>(impl)->AppendInteger(entry, value, timestamp);
+}
+
+/*
+ * Class:     edu_wpi_first_util_datalog_DataLogJNI
+ * Method:    appendFloat
+ * Signature: (JIFJ)V
+ */
+JNIEXPORT void JNICALL
+Java_edu_wpi_first_util_datalog_DataLogJNI_appendFloat
+  (JNIEnv*, jclass, jlong impl, jint entry, jfloat value, jlong timestamp)
+{
+  if (impl == 0) {
+    return;
+  }
+  reinterpret_cast<DataLog*>(impl)->AppendFloat(entry, value, timestamp);
+}
+
+/*
+ * Class:     edu_wpi_first_util_datalog_DataLogJNI
+ * Method:    appendDouble
+ * Signature: (JIDJ)V
+ */
+JNIEXPORT void JNICALL
+Java_edu_wpi_first_util_datalog_DataLogJNI_appendDouble
+  (JNIEnv*, jclass, jlong impl, jint entry, jdouble value, jlong timestamp)
+{
+  if (impl == 0) {
+    return;
+  }
+  reinterpret_cast<DataLog*>(impl)->AppendDouble(entry, value, timestamp);
+}
+
+/*
+ * Class:     edu_wpi_first_util_datalog_DataLogJNI
+ * Method:    appendString
+ * Signature: (JILjava/lang/String;J)V
+ */
+JNIEXPORT void JNICALL
+Java_edu_wpi_first_util_datalog_DataLogJNI_appendString
+  (JNIEnv* env, jclass, jlong impl, jint entry, jstring value, jlong timestamp)
+{
+  if (impl == 0) {
+    return;
+  }
+  reinterpret_cast<DataLog*>(impl)->AppendString(entry, JStringRef{env, value},
+                                                 timestamp);
+}
+
+/*
+ * Class:     edu_wpi_first_util_datalog_DataLogJNI
+ * Method:    appendBooleanArray
+ * Signature: (JI[ZJ)V
+ */
+JNIEXPORT void JNICALL
+Java_edu_wpi_first_util_datalog_DataLogJNI_appendBooleanArray
+  (JNIEnv* env, jclass, jlong impl, jint entry, jbooleanArray value,
+   jlong timestamp)
+{
+  if (impl == 0) {
+    return;
+  }
+  reinterpret_cast<DataLog*>(impl)->AppendBooleanArray(
+      entry, JBooleanArrayRef{env, value}, timestamp);
+}
+
+/*
+ * Class:     edu_wpi_first_util_datalog_DataLogJNI
+ * Method:    appendIntegerArray
+ * Signature: (JI[JJ)V
+ */
+JNIEXPORT void JNICALL
+Java_edu_wpi_first_util_datalog_DataLogJNI_appendIntegerArray
+  (JNIEnv* env, jclass, jlong impl, jint entry, jlongArray value,
+   jlong timestamp)
+{
+  if (impl == 0) {
+    return;
+  }
+  JLongArrayRef jarr{env, value};
+  if constexpr (sizeof(jlong) == sizeof(int64_t)) {
+    reinterpret_cast<DataLog*>(impl)->AppendIntegerArray(
+        entry,
+        {reinterpret_cast<const int64_t*>(jarr.array().data()),
+         jarr.array().size()},
+        timestamp);
+  } else {
+    wpi::SmallVector<int64_t, 16> arr;
+    arr.reserve(jarr.size());
+    for (auto v : jarr.array()) {
+      arr.push_back(v);
+    }
+    reinterpret_cast<DataLog*>(impl)->AppendIntegerArray(entry, arr, timestamp);
+  }
+}
+
+/*
+ * Class:     edu_wpi_first_util_datalog_DataLogJNI
+ * Method:    appendFloatArray
+ * Signature: (JI[FJ)V
+ */
+JNIEXPORT void JNICALL
+Java_edu_wpi_first_util_datalog_DataLogJNI_appendFloatArray
+  (JNIEnv* env, jclass, jlong impl, jint entry, jfloatArray value,
+   jlong timestamp)
+{
+  if (impl == 0) {
+    return;
+  }
+  reinterpret_cast<DataLog*>(impl)->AppendFloatArray(
+      entry, JFloatArrayRef{env, value}, timestamp);
+}
+
+/*
+ * Class:     edu_wpi_first_util_datalog_DataLogJNI
+ * Method:    appendDoubleArray
+ * Signature: (JI[DJ)V
+ */
+JNIEXPORT void JNICALL
+Java_edu_wpi_first_util_datalog_DataLogJNI_appendDoubleArray
+  (JNIEnv* env, jclass, jlong impl, jint entry, jdoubleArray value,
+   jlong timestamp)
+{
+  if (impl == 0) {
+    return;
+  }
+  reinterpret_cast<DataLog*>(impl)->AppendDoubleArray(
+      entry, JDoubleArrayRef{env, value}, timestamp);
+}
+
+/*
+ * Class:     edu_wpi_first_util_datalog_DataLogJNI
+ * Method:    appendStringArray
+ * Signature: (JI[Ljava/lang/Object;J)V
+ */
+JNIEXPORT void JNICALL
+Java_edu_wpi_first_util_datalog_DataLogJNI_appendStringArray
+  (JNIEnv* env, jclass, jlong impl, jint entry, jobjectArray value,
+   jlong timestamp)
+{
+  if (impl == 0) {
+    return;
+  }
+  size_t len = env->GetArrayLength(value);
+  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(value, i))};
+    if (!elem) {
+      return;
+    }
+    arr.emplace_back(JStringRef{env, elem}.str());
+  }
+  reinterpret_cast<DataLog*>(impl)->AppendStringArray(entry, arr, timestamp);
+}
+
+}  // extern "C"
diff --git a/wpiutil/src/main/native/cpp/jni/WPIUtilJNI.cpp b/wpiutil/src/main/native/cpp/jni/WPIUtilJNI.cpp
index e876d9c..5fa4ddf 100644
--- a/wpiutil/src/main/native/cpp/jni/WPIUtilJNI.cpp
+++ b/wpiutil/src/main/native/cpp/jni/WPIUtilJNI.cpp
@@ -4,14 +4,9 @@
 
 #include <jni.h>
 
-#include "../MulticastHandleManager.h"
 #include "edu_wpi_first_util_WPIUtilJNI.h"
-#include "wpi/DenseMap.h"
-#include "wpi/MulticastServiceAnnouncer.h"
-#include "wpi/MulticastServiceResolver.h"
-#include "wpi/PortForwarder.h"
+#include "fmt/format.h"
 #include "wpi/Synchronization.h"
-#include "wpi/UidVector.h"
 #include "wpi/jni_util.h"
 #include "wpi/timestamp.h"
 
@@ -21,8 +16,6 @@
 static uint64_t mockNow = 0;
 
 static JException interruptedEx;
-static JClass serviceDataCls;
-static JGlobal<jobjectArray> serviceDataEmptyArray;
 
 extern "C" {
 
@@ -37,17 +30,6 @@
     return JNI_ERR;
   }
 
-  serviceDataCls = JClass{env, "edu/wpi/first/util/ServiceData"};
-  if (!serviceDataCls) {
-    return JNI_ERR;
-  }
-
-  serviceDataEmptyArray = JGlobal<jobjectArray>{
-      env, env->NewObjectArray(0, serviceDataCls, nullptr)};
-  if (serviceDataEmptyArray == nullptr) {
-    return JNI_ERR;
-  }
-
   return JNI_VERSION_1_6;
 }
 
@@ -57,13 +39,23 @@
     return;
   }
 
-  serviceDataEmptyArray.free(env);
-  serviceDataCls.free(env);
   interruptedEx.free(env);
 }
 
 /*
  * Class:     edu_wpi_first_util_WPIUtilJNI
+ * Method:    writeStderr
+ * Signature: (Ljava/lang/String;)V
+ */
+JNIEXPORT void JNICALL
+Java_edu_wpi_first_util_WPIUtilJNI_writeStderr
+  (JNIEnv* env, jclass, jstring str)
+{
+  fmt::print(stderr, "{}", JStringRef{env, str});
+}
+
+/*
+ * Class:     edu_wpi_first_util_WPIUtilJNI
  * Method:    enableMockTime
  * Signature: ()V
  */
@@ -130,32 +122,6 @@
 
 /*
  * Class:     edu_wpi_first_util_WPIUtilJNI
- * Method:    addPortForwarder
- * Signature: (ILjava/lang/String;I)V
- */
-JNIEXPORT void JNICALL
-Java_edu_wpi_first_util_WPIUtilJNI_addPortForwarder
-  (JNIEnv* env, jclass, jint port, jstring remoteHost, jint remotePort)
-{
-  wpi::PortForwarder::GetInstance().Add(static_cast<unsigned int>(port),
-                                        JStringRef{env, remoteHost}.str(),
-                                        static_cast<unsigned int>(remotePort));
-}
-
-/*
- * Class:     edu_wpi_first_util_WPIUtilJNI
- * Method:    removePortForwarder
- * Signature: (I)V
- */
-JNIEXPORT void JNICALL
-Java_edu_wpi_first_util_WPIUtilJNI_removePortForwarder
-  (JNIEnv* env, jclass, jint port)
-{
-  wpi::PortForwarder::GetInstance().Remove(port);
-}
-
-/*
- * Class:     edu_wpi_first_util_WPIUtilJNI
  * Method:    createEvent
  * Signature: (ZZ)I
  */
@@ -281,7 +247,7 @@
   JIntArrayRef handlesArr{env, handles};
   wpi::SmallVector<WPI_Handle, 8> signaledBuf;
   signaledBuf.resize(handlesArr.size());
-  wpi::span<const WPI_Handle> handlesArr2{
+  std::span<const WPI_Handle> handlesArr2{
       reinterpret_cast<const WPI_Handle*>(handlesArr.array().data()),
       handlesArr.size()};
 
@@ -305,7 +271,7 @@
   JIntArrayRef handlesArr{env, handles};
   wpi::SmallVector<WPI_Handle, 8> signaledBuf;
   signaledBuf.resize(handlesArr.size());
-  wpi::span<const WPI_Handle> handlesArr2{
+  std::span<const WPI_Handle> handlesArr2{
       reinterpret_cast<const WPI_Handle*>(handlesArr.array().data()),
       handlesArr.size()};
 
@@ -319,257 +285,4 @@
   return MakeJIntArray(env, signaled);
 }
 
-/*
- * Class:     edu_wpi_first_util_WPIUtilJNI
- * Method:    createMulticastServiceAnnouncer
- * Signature: (Ljava/lang/String;Ljava/lang/String;I[Ljava/lang/Object;[Ljava/lang/Object;)I
- */
-JNIEXPORT jint JNICALL
-Java_edu_wpi_first_util_WPIUtilJNI_createMulticastServiceAnnouncer
-  (JNIEnv* env, jclass, jstring serviceName, jstring serviceType, jint port,
-   jobjectArray keys, jobjectArray values)
-{
-  auto& manager = wpi::GetMulticastManager();
-  std::scoped_lock lock{manager.mutex};
-
-  JStringRef serviceNameRef{env, serviceName};
-  JStringRef serviceTypeRef{env, serviceType};
-
-  size_t keysLen = env->GetArrayLength(keys);
-  wpi::SmallVector<std::pair<std::string, std::string>, 8> txtVec;
-  txtVec.reserve(keysLen);
-  for (size_t i = 0; i < keysLen; i++) {
-    JLocal<jstring> key{
-        env, static_cast<jstring>(env->GetObjectArrayElement(keys, i))};
-    JLocal<jstring> value{
-        env, static_cast<jstring>(env->GetObjectArrayElement(values, i))};
-
-    txtVec.emplace_back(std::pair<std::string, std::string>{
-        JStringRef{env, key}.str(), JStringRef{env, value}.str()});
-  }
-
-  auto announcer = std::make_unique<wpi::MulticastServiceAnnouncer>(
-      serviceNameRef.str(), serviceTypeRef.str(), port, txtVec);
-
-  size_t index = manager.handleIds.emplace_back(1);
-
-  manager.announcers[index] = std::move(announcer);
-
-  return static_cast<jint>(index);
-}
-
-/*
- * Class:     edu_wpi_first_util_WPIUtilJNI
- * Method:    freeMulticastServiceAnnouncer
- * Signature: (I)V
- */
-JNIEXPORT void JNICALL
-Java_edu_wpi_first_util_WPIUtilJNI_freeMulticastServiceAnnouncer
-  (JNIEnv* env, jclass, jint handle)
-{
-  auto& manager = wpi::GetMulticastManager();
-  std::scoped_lock lock{manager.mutex};
-  manager.announcers[handle] = nullptr;
-  manager.handleIds.erase(handle);
-}
-
-/*
- * Class:     edu_wpi_first_util_WPIUtilJNI
- * Method:    startMulticastServiceAnnouncer
- * Signature: (I)V
- */
-JNIEXPORT void JNICALL
-Java_edu_wpi_first_util_WPIUtilJNI_startMulticastServiceAnnouncer
-  (JNIEnv* env, jclass, jint handle)
-{
-  auto& manager = wpi::GetMulticastManager();
-  std::scoped_lock lock{manager.mutex};
-  auto& announcer = manager.announcers[handle];
-  announcer->Start();
-}
-
-/*
- * Class:     edu_wpi_first_util_WPIUtilJNI
- * Method:    stopMulticastServiceAnnouncer
- * Signature: (I)V
- */
-JNIEXPORT void JNICALL
-Java_edu_wpi_first_util_WPIUtilJNI_stopMulticastServiceAnnouncer
-  (JNIEnv* env, jclass, jint handle)
-{
-  auto& manager = wpi::GetMulticastManager();
-  std::scoped_lock lock{manager.mutex};
-  auto& announcer = manager.announcers[handle];
-  announcer->Stop();
-}
-
-/*
- * Class:     edu_wpi_first_util_WPIUtilJNI
- * Method:    getMulticastServiceAnnouncerHasImplementation
- * Signature: (I)Z
- */
-JNIEXPORT jboolean JNICALL
-Java_edu_wpi_first_util_WPIUtilJNI_getMulticastServiceAnnouncerHasImplementation
-  (JNIEnv* env, jclass, jint handle)
-{
-  auto& manager = wpi::GetMulticastManager();
-  std::scoped_lock lock{manager.mutex};
-  auto& announcer = manager.announcers[handle];
-  return announcer->HasImplementation();
-}
-
-/*
- * Class:     edu_wpi_first_util_WPIUtilJNI
- * Method:    createMulticastServiceResolver
- * Signature: (Ljava/lang/String;)I
- */
-JNIEXPORT jint JNICALL
-Java_edu_wpi_first_util_WPIUtilJNI_createMulticastServiceResolver
-  (JNIEnv* env, jclass, jstring serviceType)
-{
-  auto& manager = wpi::GetMulticastManager();
-  std::scoped_lock lock{manager.mutex};
-  JStringRef serviceTypeRef{env, serviceType};
-
-  auto resolver =
-      std::make_unique<wpi::MulticastServiceResolver>(serviceTypeRef.str());
-
-  size_t index = manager.handleIds.emplace_back(2);
-
-  manager.resolvers[index] = std::move(resolver);
-
-  return static_cast<jint>(index);
-}
-
-/*
- * Class:     edu_wpi_first_util_WPIUtilJNI
- * Method:    freeMulticastServiceResolver
- * Signature: (I)V
- */
-JNIEXPORT void JNICALL
-Java_edu_wpi_first_util_WPIUtilJNI_freeMulticastServiceResolver
-  (JNIEnv* env, jclass, jint handle)
-{
-  auto& manager = wpi::GetMulticastManager();
-  std::scoped_lock lock{manager.mutex};
-  manager.resolvers[handle] = nullptr;
-  manager.handleIds.erase(handle);
-}
-
-/*
- * Class:     edu_wpi_first_util_WPIUtilJNI
- * Method:    startMulticastServiceResolver
- * Signature: (I)V
- */
-JNIEXPORT void JNICALL
-Java_edu_wpi_first_util_WPIUtilJNI_startMulticastServiceResolver
-  (JNIEnv* env, jclass, jint handle)
-{
-  auto& manager = wpi::GetMulticastManager();
-  std::scoped_lock lock{manager.mutex};
-  auto& resolver = manager.resolvers[handle];
-  resolver->Start();
-}
-
-/*
- * Class:     edu_wpi_first_util_WPIUtilJNI
- * Method:    stopMulticastServiceResolver
- * Signature: (I)V
- */
-JNIEXPORT void JNICALL
-Java_edu_wpi_first_util_WPIUtilJNI_stopMulticastServiceResolver
-  (JNIEnv* env, jclass, jint handle)
-{
-  auto& manager = wpi::GetMulticastManager();
-  std::scoped_lock lock{manager.mutex};
-  auto& resolver = manager.resolvers[handle];
-  resolver->Stop();
-}
-
-/*
- * Class:     edu_wpi_first_util_WPIUtilJNI
- * Method:    getMulticastServiceResolverHasImplementation
- * Signature: (I)Z
- */
-JNIEXPORT jboolean JNICALL
-Java_edu_wpi_first_util_WPIUtilJNI_getMulticastServiceResolverHasImplementation
-  (JNIEnv* env, jclass, jint handle)
-{
-  auto& manager = wpi::GetMulticastManager();
-  std::scoped_lock lock{manager.mutex};
-  auto& resolver = manager.resolvers[handle];
-  return resolver->HasImplementation();
-}
-
-/*
- * Class:     edu_wpi_first_util_WPIUtilJNI
- * Method:    getMulticastServiceResolverEventHandle
- * Signature: (I)I
- */
-JNIEXPORT jint JNICALL
-Java_edu_wpi_first_util_WPIUtilJNI_getMulticastServiceResolverEventHandle
-  (JNIEnv* env, jclass, jint handle)
-{
-  auto& manager = wpi::GetMulticastManager();
-  std::scoped_lock lock{manager.mutex};
-  auto& resolver = manager.resolvers[handle];
-  return resolver->GetEventHandle();
-}
-
-/*
- * Class:     edu_wpi_first_util_WPIUtilJNI
- * Method:    getMulticastServiceResolverData
- * Signature: (I)[Ljava/lang/Object;
- */
-JNIEXPORT jobjectArray JNICALL
-Java_edu_wpi_first_util_WPIUtilJNI_getMulticastServiceResolverData
-  (JNIEnv* env, jclass, jint handle)
-{
-  static jmethodID constructor =
-      env->GetMethodID(serviceDataCls, "<init>",
-                       "(JILjava/lang/String;Ljava/lang/String;[Ljava/lang/"
-                       "String;[Ljava/lang/String;)V");
-  auto& manager = wpi::GetMulticastManager();
-  std::vector<wpi::MulticastServiceResolver::ServiceData> allData;
-  {
-    std::scoped_lock lock{manager.mutex};
-    auto& resolver = manager.resolvers[handle];
-    allData = resolver->GetData();
-  }
-  if (allData.empty()) {
-    return serviceDataEmptyArray;
-  }
-
-  JLocal<jobjectArray> returnData{
-      env, env->NewObjectArray(allData.size(), serviceDataCls, nullptr)};
-
-  for (auto&& data : allData) {
-    JLocal<jstring> serviceName{env, MakeJString(env, data.serviceName)};
-    JLocal<jstring> hostName{env, MakeJString(env, data.hostName)};
-
-    wpi::SmallVector<std::string_view, 8> keysRef;
-    wpi::SmallVector<std::string_view, 8> valuesRef;
-
-    size_t index = 0;
-    for (auto&& txt : data.txt) {
-      keysRef.emplace_back(txt.first);
-      valuesRef.emplace_back(txt.second);
-    }
-
-    JLocal<jobjectArray> keys{env, MakeJStringArray(env, keysRef)};
-    JLocal<jobjectArray> values{env, MakeJStringArray(env, valuesRef)};
-
-    JLocal<jobject> dataItem{
-        env, env->NewObject(serviceDataCls, constructor,
-                            static_cast<jlong>(data.ipv4Address),
-                            static_cast<jint>(data.port), serviceName.obj(),
-                            hostName.obj(), keys.obj(), values.obj())};
-
-    env->SetObjectArrayElement(returnData, index, dataItem);
-    index++;
-  }
-
-  return returnData;
-}
-
 }  // extern "C"
diff --git a/wpiutil/src/main/native/cpp/json.cpp b/wpiutil/src/main/native/cpp/json.cpp
deleted file mode 100644
index f1cfb14..0000000
--- a/wpiutil/src/main/native/cpp/json.cpp
+++ /dev/null
@@ -1,1505 +0,0 @@
-/*----------------------------------------------------------------------------*/
-/* Modifications 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.                                                               */
-/*----------------------------------------------------------------------------*/
-/*
-    __ _____ _____ _____
- __|  |   __|     |   | |  JSON for Modern C++
-|  |  |__   |  |  | | | |  version 3.1.2
-|_____|_____|_____|_|___|  https://github.com/nlohmann/json
-
-Licensed under the MIT License <http://opensource.org/licenses/MIT>.
-Copyright (c) 2013-2018 Niels Lohmann <http://nlohmann.me>.
-
-Permission is hereby  granted, free of charge, to any  person obtaining a copy
-of this software and associated  documentation files (the "Software"), to deal
-in the Software  without restriction, including without  limitation the rights
-to  use, copy,  modify, merge,  publish, distribute,  sublicense, and/or  sell
-copies  of  the Software,  and  to  permit persons  to  whom  the Software  is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-THE SOFTWARE  IS PROVIDED "AS  IS", WITHOUT WARRANTY  OF ANY KIND,  EXPRESS OR
-IMPLIED,  INCLUDING BUT  NOT  LIMITED TO  THE  WARRANTIES OF  MERCHANTABILITY,
-FITNESS FOR  A PARTICULAR PURPOSE AND  NONINFRINGEMENT. IN NO EVENT  SHALL THE
-AUTHORS  OR COPYRIGHT  HOLDERS  BE  LIABLE FOR  ANY  CLAIM,  DAMAGES OR  OTHER
-LIABILITY, WHETHER IN AN ACTION OF  CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE  OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
-*/
-#define WPI_JSON_IMPLEMENTATION
-#include "wpi/json.h"
-
-#include "fmt/format.h"
-#include "wpi/raw_ostream.h"
-
-namespace wpi {
-namespace detail {
-
-exception::exception(int id_, std::string_view what_arg)
-    : id(id_), m(std::string{what_arg}) {}
-
-parse_error parse_error::create(int id_, std::size_t byte_, std::string_view what_arg)
-{
-    if (byte_ != 0)
-        return parse_error(id_, byte_, fmt::format("[json.exception.parse_error.{}] parse error at {}: {}", id_, byte_, what_arg));
-    else
-        return parse_error(id_, byte_, fmt::format("[json.exception.parse_error.{}] parse error: {}", id_, what_arg));
-}
-
-invalid_iterator invalid_iterator::create(int id_, std::string_view what_arg)
-{
-    return invalid_iterator(id_, fmt::format("[json.exception.invalid_iterator.{}] {}", id_, what_arg));
-}
-
-invalid_iterator invalid_iterator::create(int id_, std::string_view what_arg, std::string_view type_info)
-{
-    return invalid_iterator(id_, fmt::format("[json.exception.invalid_iterator.{}] {} {}", id_, what_arg, type_info));
-}
-
-type_error type_error::create(int id_, std::string_view what_arg)
-{
-    return type_error(id_, fmt::format("[json.exception.type_error.{}] {}", id_, what_arg));
-}
-
-type_error type_error::create(int id_, std::string_view what_arg, std::string_view type_info)
-{
-    return type_error(id_, fmt::format("[json.exception.type_error.{}] {} {}", id_, what_arg, type_info));
-}
-
-out_of_range out_of_range::create(int id_, std::string_view what_arg)
-{
-    return out_of_range(id_, fmt::format("[json.exception.out_of_range.{}] {}", id_, what_arg));
-}
-
-other_error other_error::create(int id_, std::string_view what_arg)
-{
-    return other_error(id_, fmt::format("[json.exception.other_error.{}] {}", id_, what_arg));
-}
-
-}  // namespace detail
-
-json::json_value::json_value(value_t t)
-{
-    switch (t)
-    {
-        case value_t::object:
-        {
-            object = create<object_t>();
-            break;
-        }
-
-        case value_t::array:
-        {
-            array = create<array_t>();
-            break;
-        }
-
-        case value_t::string:
-        {
-            string = create<std::string>("");
-            break;
-        }
-
-        case value_t::boolean:
-        {
-            boolean = false;
-            break;
-        }
-
-        case value_t::number_integer:
-        {
-            number_integer = 0;
-            break;
-        }
-
-        case value_t::number_unsigned:
-        {
-            number_unsigned = 0u;
-            break;
-        }
-
-        case value_t::number_float:
-        {
-            number_float = 0.0;
-            break;
-        }
-
-        case value_t::null:
-        {
-            object = nullptr;  // silence warning, see #821
-            break;
-        }
-
-        default:
-        {
-            object = nullptr;  // silence warning, see #821
-            if (JSON_UNLIKELY(t == value_t::null))
-            {
-                JSON_THROW(other_error::create(500, "961c151d2e87f2686a955a9be24d316f1362bf21 3.1.2")); // LCOV_EXCL_LINE
-            }
-            break;
-        }
-    }
-}
-
-void json::json_value::destroy(value_t t) noexcept
-{
-    switch (t)
-    {
-        case value_t::object:
-        {
-            std::allocator<object_t> alloc;
-            std::allocator_traits<decltype(alloc)>::destroy(alloc, object);
-            std::allocator_traits<decltype(alloc)>::deallocate(alloc, object, 1);
-            break;
-        }
-
-        case value_t::array:
-        {
-            std::allocator<array_t> alloc;
-            std::allocator_traits<decltype(alloc)>::destroy(alloc, array);
-            std::allocator_traits<decltype(alloc)>::deallocate(alloc, array, 1);
-            break;
-        }
-
-        case value_t::string:
-        {
-            std::allocator<std::string> alloc;
-            std::allocator_traits<decltype(alloc)>::destroy(alloc, string);
-            std::allocator_traits<decltype(alloc)>::deallocate(alloc, string, 1);
-            break;
-        }
-
-        default:
-        {
-            break;
-        }
-    }
-}
-
-json::json(initializer_list_t init,
-           bool type_deduction,
-           value_t manual_type)
-{
-    // check if each element is an array with two elements whose first
-    // element is a string
-    bool is_an_object = std::all_of(init.begin(), init.end(),
-                                    [](const detail::json_ref<json>& element_ref)
-    {
-        return (element_ref->is_array() and element_ref->size() == 2 and (*element_ref)[0].is_string());
-    });
-
-    // adjust type if type deduction is not wanted
-    if (not type_deduction)
-    {
-        // if array is wanted, do not create an object though possible
-        if (manual_type == value_t::array)
-        {
-            is_an_object = false;
-        }
-
-        // if object is wanted but impossible, throw an exception
-        if (JSON_UNLIKELY(manual_type == value_t::object and not is_an_object))
-        {
-            JSON_THROW(type_error::create(301, "cannot create object from initializer list"));
-        }
-    }
-
-    if (is_an_object)
-    {
-        // the initializer list is a list of pairs -> create object
-        m_type = value_t::object;
-        m_value = value_t::object;
-
-        std::for_each(init.begin(), init.end(), [this](const detail::json_ref<json>& element_ref)
-        {
-            auto element = element_ref.moved_or_copied();
-            m_value.object->try_emplace(
-                *((*element.m_value.array)[0].m_value.string),
-                std::move((*element.m_value.array)[1]));
-        });
-    }
-    else
-    {
-        // the initializer list describes an array -> create array
-        m_type = value_t::array;
-        m_value.array = create<array_t>(init.begin(), init.end());
-    }
-
-    assert_invariant();
-}
-
-json::json(size_type cnt, const json& val)
-    : m_type(value_t::array)
-{
-    m_value.array = create<array_t>(cnt, val);
-    assert_invariant();
-}
-
-json::json(const json& other)
-    : m_type(other.m_type)
-{
-    // check of passed value is valid
-    other.assert_invariant();
-
-    switch (m_type)
-    {
-        case value_t::object:
-        {
-            m_value = *other.m_value.object;
-            break;
-        }
-
-        case value_t::array:
-        {
-            m_value = *other.m_value.array;
-            break;
-        }
-
-        case value_t::string:
-        {
-            m_value = *other.m_value.string;
-            break;
-        }
-
-        case value_t::boolean:
-        {
-            m_value = other.m_value.boolean;
-            break;
-        }
-
-        case value_t::number_integer:
-        {
-            m_value = other.m_value.number_integer;
-            break;
-        }
-
-        case value_t::number_unsigned:
-        {
-            m_value = other.m_value.number_unsigned;
-            break;
-        }
-
-        case value_t::number_float:
-        {
-            m_value = other.m_value.number_float;
-            break;
-        }
-
-        default:
-            break;
-    }
-
-    assert_invariant();
-}
-
-json json::meta()
-{
-    json result;
-
-    result["copyright"] = "(C) 2013-2017 Niels Lohmann, (C) 2017-2018 FIRST";
-    result["name"] = "WPI version of JSON for Modern C++";
-    result["url"] = "https://github.com/wpilibsuite/allwpilib";
-    result["version"]["string"] =
-        std::to_string(NLOHMANN_JSON_VERSION_MAJOR) + "." +
-        std::to_string(NLOHMANN_JSON_VERSION_MINOR) + "." +
-        std::to_string(NLOHMANN_JSON_VERSION_PATCH);
-    result["version"]["major"] = NLOHMANN_JSON_VERSION_MAJOR;
-    result["version"]["minor"] = NLOHMANN_JSON_VERSION_MINOR;
-    result["version"]["patch"] = NLOHMANN_JSON_VERSION_PATCH;
-
-#ifdef _WIN32
-    result["platform"] = "win32";
-#elif defined __linux__
-    result["platform"] = "linux";
-#elif defined __APPLE__
-    result["platform"] = "apple";
-#elif defined __unix__
-    result["platform"] = "unix";
-#else
-    result["platform"] = "unknown";
-#endif
-
-#if defined(__ICC) || defined(__INTEL_COMPILER)
-    result["compiler"] = {{"family", "icc"}, {"version", __INTEL_COMPILER}};
-#elif defined(__clang__)
-    result["compiler"] = {{"family", "clang"}, {"version", __clang_version__}};
-#elif defined(__GNUC__) || defined(__GNUG__)
-    result["compiler"] = {{"family", "gcc"}, {"version", std::to_string(__GNUC__) + "." + std::to_string(__GNUC_MINOR__) + "." + std::to_string(__GNUC_PATCHLEVEL__)}};
-#elif defined(__HP_cc) || defined(__HP_aCC)
-    result["compiler"] = "hp"
-#elif defined(__IBMCPP__)
-    result["compiler"] = {{"family", "ilecpp"}, {"version", __IBMCPP__}};
-#elif defined(_MSC_VER)
-    result["compiler"] = {{"family", "msvc"}, {"version", _MSC_VER}};
-#elif defined(__PGI)
-    result["compiler"] = {{"family", "pgcpp"}, {"version", __PGI}};
-#elif defined(__SUNPRO_CC)
-    result["compiler"] = {{"family", "sunpro"}, {"version", __SUNPRO_CC}};
-#else
-    result["compiler"] = {{"family", "unknown"}, {"version", "unknown"}};
-#endif
-
-#ifdef __cplusplus
-    result["compiler"]["c++"] = std::to_string(__cplusplus);
-#else
-    result["compiler"]["c++"] = "unknown";
-#endif
-    return result;
-}
-
-json::reference json::at(size_type idx)
-{
-    // at only works for arrays
-    if (JSON_LIKELY(is_array()))
-    {
-        JSON_TRY
-        {
-            return m_value.array->at(idx);
-        }
-        JSON_CATCH (std::out_of_range&)
-        {
-            // create better exception explanation
-            JSON_THROW(out_of_range::create(401, fmt::format("array index {} is out of range", idx)));
-        }
-    }
-    else
-    {
-        JSON_THROW(type_error::create(304, "cannot use at() with", type_name()));
-    }
-}
-
-json::const_reference json::at(size_type idx) const
-{
-    // at only works for arrays
-    if (JSON_LIKELY(is_array()))
-    {
-        JSON_TRY
-        {
-            return m_value.array->at(idx);
-        }
-        JSON_CATCH (std::out_of_range&)
-        {
-            // create better exception explanation
-            JSON_THROW(out_of_range::create(401, fmt::format("array index {} is out of range", idx)));
-        }
-    }
-    else
-    {
-        JSON_THROW(type_error::create(304, "cannot use at() with", type_name()));
-    }
-}
-
-json::reference json::at(std::string_view key)
-{
-    // at only works for objects
-    if (JSON_LIKELY(is_object()))
-    {
-        auto it = m_value.object->find(key);
-        if (it == m_value.object->end())
-        {
-            // create better exception explanation
-            JSON_THROW(out_of_range::create(403, fmt::format("key '{}' not found", key)));
-        }
-        return it->second;
-    }
-    else
-    {
-        JSON_THROW(type_error::create(304, "cannot use at() with", type_name()));
-    }
-}
-
-json::const_reference json::at(std::string_view key) const
-{
-    // at only works for objects
-    if (JSON_LIKELY(is_object()))
-    {
-        auto it = m_value.object->find(key);
-        if (it == m_value.object->end())
-        {
-            // create better exception explanation
-            JSON_THROW(out_of_range::create(403, fmt::format("key '{}' not found", key)));
-        }
-        return it->second;
-    }
-    else
-    {
-        JSON_THROW(type_error::create(304, "cannot use at() with", type_name()));
-    }
-}
-
-json::reference json::operator[](size_type idx)
-{
-    // implicitly convert null value to an empty array
-    if (is_null())
-    {
-        m_type = value_t::array;
-        m_value.array = create<array_t>();
-        assert_invariant();
-    }
-
-    // operator[] only works for arrays
-    if (JSON_LIKELY(is_array()))
-    {
-        // fill up array with null values if given idx is outside range
-        if (idx >= m_value.array->size())
-        {
-            m_value.array->insert(m_value.array->end(),
-                                  idx - m_value.array->size() + 1,
-                                  json());
-        }
-
-        return m_value.array->operator[](idx);
-    }
-
-    JSON_THROW(type_error::create(305, "cannot use operator[] with", type_name()));
-}
-
-json::const_reference json::operator[](size_type idx) const
-{
-    // const operator[] only works for arrays
-    if (JSON_LIKELY(is_array()))
-    {
-        return m_value.array->operator[](idx);
-    }
-
-    JSON_THROW(type_error::create(305, "cannot use operator[] with", type_name()));
-}
-
-json::reference json::operator[](std::string_view key)
-{
-    // implicitly convert null value to an empty object
-    if (is_null())
-    {
-        m_type = value_t::object;
-        m_value.object = create<object_t>();
-        assert_invariant();
-    }
-
-    // operator[] only works for objects
-    if (JSON_LIKELY(is_object()))
-    {
-        return m_value.object->operator[](key);
-    }
-
-    JSON_THROW(type_error::create(305, "cannot use operator[] with", type_name()));
-}
-
-json::const_reference json::operator[](std::string_view key) const
-{
-    // const operator[] only works for objects
-    if (JSON_LIKELY(is_object()))
-    {
-        assert(m_value.object->find(key) != m_value.object->end());
-        return m_value.object->find(key)->second;
-    }
-
-    JSON_THROW(type_error::create(305, "cannot use operator[] with", type_name()));
-}
-
-json::size_type json::erase(std::string_view key)
-{
-    // this erase only works for objects
-    if (JSON_LIKELY(is_object()))
-    {
-        return m_value.object->erase(key);
-    }
-
-    JSON_THROW(type_error::create(307, "cannot use erase() with", type_name()));
-}
-
-void json::erase(const size_type idx)
-{
-    // this erase only works for arrays
-    if (JSON_LIKELY(is_array()))
-    {
-        if (JSON_UNLIKELY(idx >= size()))
-        {
-            JSON_THROW(out_of_range::create(401, fmt::format("array index {} is out of range", idx)));
-        }
-
-        m_value.array->erase(m_value.array->begin() + static_cast<difference_type>(idx));
-    }
-    else
-    {
-        JSON_THROW(type_error::create(307, "cannot use erase() with", type_name()));
-    }
-}
-
-json::iterator json::find(std::string_view key)
-{
-    auto result = end();
-
-    if (is_object())
-    {
-        result.m_it.object_iterator = m_value.object->find(key);
-    }
-
-    return result;
-}
-
-json::const_iterator json::find(std::string_view key) const
-{
-    auto result = cend();
-
-    if (is_object())
-    {
-        result.m_it.object_iterator = m_value.object->find(key);
-    }
-
-    return result;
-}
-
-json::size_type json::count(std::string_view key) const
-{
-    // return 0 for all nonobject types
-    return is_object() ? m_value.object->count(key) : 0;
-}
-
-bool json::empty() const noexcept
-{
-    switch (m_type)
-    {
-        case value_t::null:
-        {
-            // null values are empty
-            return true;
-        }
-
-        case value_t::array:
-        {
-            // delegate call to array_t::empty()
-            return m_value.array->empty();
-        }
-
-        case value_t::object:
-        {
-            // delegate call to object_t::empty()
-            return m_value.object->empty();
-        }
-
-        default:
-        {
-            // all other types are nonempty
-            return false;
-        }
-    }
-}
-
-json::size_type json::size() const noexcept
-{
-    switch (m_type)
-    {
-        case value_t::null:
-        {
-            // null values are empty
-            return 0;
-        }
-
-        case value_t::array:
-        {
-            // delegate call to array_t::size()
-            return m_value.array->size();
-        }
-
-        case value_t::object:
-        {
-            // delegate call to object_t::size()
-            return m_value.object->size();
-        }
-
-        default:
-        {
-            // all other types have size 1
-            return 1;
-        }
-    }
-}
-
-json::size_type json::max_size() const noexcept
-{
-    switch (m_type)
-    {
-        case value_t::array:
-        {
-            // delegate call to array_t::max_size()
-            return m_value.array->max_size();
-        }
-
-        case value_t::object:
-        {
-            // delegate call to std::allocator<object_t>::max_size()
-            return std::allocator_traits<object_t>::max_size(*m_value.object);
-        }
-
-        default:
-        {
-            // all other types have max_size() == size()
-            return size();
-        }
-    }
-}
-
-void json::clear() noexcept
-{
-    switch (m_type)
-    {
-        case value_t::number_integer:
-        {
-            m_value.number_integer = 0;
-            break;
-        }
-
-        case value_t::number_unsigned:
-        {
-            m_value.number_unsigned = 0;
-            break;
-        }
-
-        case value_t::number_float:
-        {
-            m_value.number_float = 0.0;
-            break;
-        }
-
-        case value_t::boolean:
-        {
-            m_value.boolean = false;
-            break;
-        }
-
-        case value_t::string:
-        {
-            m_value.string->clear();
-            break;
-        }
-
-        case value_t::array:
-        {
-            m_value.array->clear();
-            break;
-        }
-
-        case value_t::object:
-        {
-            m_value.object->clear();
-            break;
-        }
-
-        default:
-            break;
-    }
-}
-
-void json::push_back(json&& val)
-{
-    // push_back only works for null objects or arrays
-    if (JSON_UNLIKELY(not(is_null() or is_array())))
-    {
-        JSON_THROW(type_error::create(308, "cannot use push_back() with", type_name()));
-    }
-
-    // transform null object into an array
-    if (is_null())
-    {
-        m_type = value_t::array;
-        m_value = value_t::array;
-        assert_invariant();
-    }
-
-    // add element to array (move semantics)
-    m_value.array->push_back(std::move(val));
-    // invalidate object
-    val.m_type = value_t::null;
-}
-
-void json::push_back(const json& val)
-{
-    // push_back only works for null objects or arrays
-    if (JSON_UNLIKELY(not(is_null() or is_array())))
-    {
-        JSON_THROW(type_error::create(308, "cannot use push_back() with", type_name()));
-    }
-
-    // transform null object into an array
-    if (is_null())
-    {
-        m_type = value_t::array;
-        m_value = value_t::array;
-        assert_invariant();
-    }
-
-    // add element to array
-    m_value.array->push_back(val);
-}
-
-void json::push_back(initializer_list_t init)
-{
-    if (is_object() and init.size() == 2 and (*init.begin())->is_string())
-    {
-        std::string key = init.begin()->moved_or_copied();
-        push_back(std::pair<std::string_view, json>(key, (init.begin() + 1)->moved_or_copied()));
-    }
-    else
-    {
-        push_back(json(init));
-    }
-}
-
-json::iterator json::insert(const_iterator pos, const json& val)
-{
-    // insert only works for arrays
-    if (JSON_LIKELY(is_array()))
-    {
-        // check if iterator pos fits to this JSON value
-        if (JSON_UNLIKELY(pos.m_object != this))
-        {
-            JSON_THROW(invalid_iterator::create(202, "iterator does not fit current value"));
-        }
-
-        // insert to array and return iterator
-        iterator result(this);
-        result.m_it.array_iterator = m_value.array->insert(pos.m_it.array_iterator, val);
-        return result;
-    }
-
-    JSON_THROW(type_error::create(309, "cannot use insert() with", type_name()));
-}
-
-json::iterator json::insert(const_iterator pos, size_type cnt, const json& val)
-{
-    // insert only works for arrays
-    if (JSON_LIKELY(is_array()))
-    {
-        // check if iterator pos fits to this JSON value
-        if (JSON_UNLIKELY(pos.m_object != this))
-        {
-            JSON_THROW(invalid_iterator::create(202, "iterator does not fit current value"));
-        }
-
-        // insert to array and return iterator
-        iterator result(this);
-        result.m_it.array_iterator = m_value.array->insert(pos.m_it.array_iterator, cnt, val);
-        return result;
-    }
-
-    JSON_THROW(type_error::create(309, "cannot use insert() with", type_name()));
-}
-
-json::iterator json::insert(const_iterator pos, const_iterator first, const_iterator last)
-{
-    // insert only works for arrays
-    if (JSON_UNLIKELY(not is_array()))
-    {
-        JSON_THROW(type_error::create(309, "cannot use insert() with", type_name()));
-    }
-
-    // check if iterator pos fits to this JSON value
-    if (JSON_UNLIKELY(pos.m_object != this))
-    {
-        JSON_THROW(invalid_iterator::create(202, "iterator does not fit current value"));
-    }
-
-    // check if range iterators belong to the same JSON object
-    if (JSON_UNLIKELY(first.m_object != last.m_object))
-    {
-        JSON_THROW(invalid_iterator::create(210, "iterators do not fit"));
-    }
-
-    if (JSON_UNLIKELY(first.m_object == this))
-    {
-        JSON_THROW(invalid_iterator::create(211, "passed iterators may not belong to container"));
-    }
-
-    // insert to array and return iterator
-    iterator result(this);
-    result.m_it.array_iterator = m_value.array->insert(
-                                     pos.m_it.array_iterator,
-                                     first.m_it.array_iterator,
-                                     last.m_it.array_iterator);
-    return result;
-}
-
-json::iterator json::insert(const_iterator pos, initializer_list_t ilist)
-{
-    // insert only works for arrays
-    if (JSON_UNLIKELY(not is_array()))
-    {
-        JSON_THROW(type_error::create(309, "cannot use insert() with", type_name()));
-    }
-
-    // check if iterator pos fits to this JSON value
-    if (JSON_UNLIKELY(pos.m_object != this))
-    {
-        JSON_THROW(invalid_iterator::create(202, "iterator does not fit current value"));
-    }
-
-    // insert to array and return iterator
-    iterator result(this);
-    result.m_it.array_iterator = m_value.array->insert(pos.m_it.array_iterator, ilist.begin(), ilist.end());
-    return result;
-}
-
-void json::insert(const_iterator first, const_iterator last)
-{
-    // insert only works for objects
-    if (JSON_UNLIKELY(not is_object()))
-    {
-        JSON_THROW(type_error::create(309, "cannot use insert() with", type_name()));
-    }
-
-    // check if range iterators belong to the same JSON object
-    if (JSON_UNLIKELY(first.m_object != last.m_object))
-    {
-        JSON_THROW(invalid_iterator::create(210, "iterators do not fit"));
-    }
-
-    // passed iterators must belong to objects
-    if (JSON_UNLIKELY(not first.m_object->is_object()))
-    {
-        JSON_THROW(invalid_iterator::create(202, "iterators first and last must point to objects"));
-    }
-
-    for (auto it = first.m_it.object_iterator, end = last.m_it.object_iterator; it != end; ++it)
-    {
-        m_value.object->insert(std::make_pair(it->first(), it->second));
-    }
-}
-
-void json::update(const_reference j)
-{
-    // implicitly convert null value to an empty object
-    if (is_null())
-    {
-        m_type = value_t::object;
-        m_value.object = create<object_t>();
-        assert_invariant();
-    }
-
-    if (JSON_UNLIKELY(not is_object()))
-    {
-        JSON_THROW(type_error::create(312, "cannot use update() with", type_name()));
-    }
-    if (JSON_UNLIKELY(not j.is_object()))
-    {
-        JSON_THROW(type_error::create(312, "cannot use update() with", j.type_name()));
-    }
-
-    for (auto it = j.cbegin(); it != j.cend(); ++it)
-    {
-        m_value.object->operator[](it.key()) = it.value();
-    }
-}
-
-void json::update(const_iterator first, const_iterator last)
-{
-    // implicitly convert null value to an empty object
-    if (is_null())
-    {
-        m_type = value_t::object;
-        m_value.object = create<object_t>();
-        assert_invariant();
-    }
-
-    if (JSON_UNLIKELY(not is_object()))
-    {
-        JSON_THROW(type_error::create(312, "cannot use update() with", type_name()));
-    }
-
-    // check if range iterators belong to the same JSON object
-    if (JSON_UNLIKELY(first.m_object != last.m_object))
-    {
-        JSON_THROW(invalid_iterator::create(210, "iterators do not fit"));
-    }
-
-    // passed iterators must belong to objects
-    if (JSON_UNLIKELY(not first.m_object->is_object()
-                      or not last.m_object->is_object()))
-    {
-        JSON_THROW(invalid_iterator::create(202, "iterators first and last must point to objects"));
-    }
-
-    for (auto it = first; it != last; ++it)
-    {
-        m_value.object->operator[](it.key()) = it.value();
-    }
-}
-
-bool operator==(json::const_reference lhs, json::const_reference rhs) noexcept
-{
-    const auto lhs_type = lhs.type();
-    const auto rhs_type = rhs.type();
-
-    if (lhs_type == rhs_type)
-    {
-        switch (lhs_type)
-        {
-            case json::value_t::array:
-                return (*lhs.m_value.array == *rhs.m_value.array);
-
-            case json::value_t::object:
-                return (*lhs.m_value.object == *rhs.m_value.object);
-
-            case json::value_t::null:
-                return true;
-
-            case json::value_t::string:
-                return (*lhs.m_value.string == *rhs.m_value.string);
-
-            case json::value_t::boolean:
-                return (lhs.m_value.boolean == rhs.m_value.boolean);
-
-            case json::value_t::number_integer:
-                return (lhs.m_value.number_integer == rhs.m_value.number_integer);
-
-            case json::value_t::number_unsigned:
-                return (lhs.m_value.number_unsigned == rhs.m_value.number_unsigned);
-
-            case json::value_t::number_float:
-                return (lhs.m_value.number_float == rhs.m_value.number_float);
-
-            default:
-                return false;
-        }
-    }
-    else if (lhs_type == json::value_t::number_integer and rhs_type == json::value_t::number_float)
-    {
-        return (static_cast<double>(lhs.m_value.number_integer) == rhs.m_value.number_float);
-    }
-    else if (lhs_type == json::value_t::number_float and rhs_type == json::value_t::number_integer)
-    {
-        return (lhs.m_value.number_float == static_cast<double>(rhs.m_value.number_integer));
-    }
-    else if (lhs_type == json::value_t::number_unsigned and rhs_type == json::value_t::number_float)
-    {
-        return (static_cast<double>(lhs.m_value.number_unsigned) == rhs.m_value.number_float);
-    }
-    else if (lhs_type == json::value_t::number_float and rhs_type == json::value_t::number_unsigned)
-    {
-        return (lhs.m_value.number_float == static_cast<double>(rhs.m_value.number_unsigned));
-    }
-    else if (lhs_type == json::value_t::number_unsigned and rhs_type == json::value_t::number_integer)
-    {
-        return (static_cast<int64_t>(lhs.m_value.number_unsigned) == rhs.m_value.number_integer);
-    }
-    else if (lhs_type == json::value_t::number_integer and rhs_type == json::value_t::number_unsigned)
-    {
-        return (lhs.m_value.number_integer == static_cast<int64_t>(rhs.m_value.number_unsigned));
-    }
-
-    return false;
-}
-
-bool operator<(json::const_reference lhs, json::const_reference rhs) noexcept
-{
-    const auto lhs_type = lhs.type();
-    const auto rhs_type = rhs.type();
-
-    if (lhs_type == rhs_type)
-    {
-        switch (lhs_type)
-        {
-            case json::value_t::array:
-                return (*lhs.m_value.array) < (*rhs.m_value.array);
-
-            case json::value_t::object:
-                return *lhs.m_value.object < *rhs.m_value.object;
-
-            case json::value_t::null:
-                return false;
-
-            case json::value_t::string:
-                return *lhs.m_value.string < *rhs.m_value.string;
-
-            case json::value_t::boolean:
-                return lhs.m_value.boolean < rhs.m_value.boolean;
-
-            case json::value_t::number_integer:
-                return lhs.m_value.number_integer < rhs.m_value.number_integer;
-
-            case json::value_t::number_unsigned:
-                return lhs.m_value.number_unsigned < rhs.m_value.number_unsigned;
-
-            case json::value_t::number_float:
-                return lhs.m_value.number_float < rhs.m_value.number_float;
-
-            default:
-                return false;
-        }
-    }
-    else if (lhs_type == json::value_t::number_integer and rhs_type == json::value_t::number_float)
-    {
-        return static_cast<double>(lhs.m_value.number_integer) < rhs.m_value.number_float;
-    }
-    else if (lhs_type == json::value_t::number_float and rhs_type == json::value_t::number_integer)
-    {
-        return lhs.m_value.number_float < static_cast<double>(rhs.m_value.number_integer);
-    }
-    else if (lhs_type == json::value_t::number_unsigned and rhs_type == json::value_t::number_float)
-    {
-        return static_cast<double>(lhs.m_value.number_unsigned) < rhs.m_value.number_float;
-    }
-    else if (lhs_type == json::value_t::number_float and rhs_type == json::value_t::number_unsigned)
-    {
-        return lhs.m_value.number_float < static_cast<double>(rhs.m_value.number_unsigned);
-    }
-    else if (lhs_type == json::value_t::number_integer and rhs_type == json::value_t::number_unsigned)
-    {
-        return lhs.m_value.number_integer < static_cast<int64_t>(rhs.m_value.number_unsigned);
-    }
-    else if (lhs_type == json::value_t::number_unsigned and rhs_type == json::value_t::number_integer)
-    {
-        return static_cast<int64_t>(lhs.m_value.number_unsigned) < rhs.m_value.number_integer;
-    }
-
-    // We only reach this line if we cannot compare values. In that case,
-    // we compare types. Note we have to call the operator explicitly,
-    // because MSVC has problems otherwise.
-    return operator<(lhs_type, rhs_type);
-}
-
-const char* json::type_name() const noexcept
-{
-    {
-        switch (m_type)
-        {
-            case value_t::null:
-                return "null";
-            case value_t::object:
-                return "object";
-            case value_t::array:
-                return "array";
-            case value_t::string:
-                return "string";
-            case value_t::boolean:
-                return "boolean";
-            case value_t::discarded:
-                return "discarded";
-            default:
-                return "number";
-        }
-    }
-}
-
-json json::patch(const json& json_patch) const
-{
-    // make a working copy to apply the patch to
-    json result = *this;
-
-    // the valid JSON Patch operations
-    enum class patch_operations {add, remove, replace, move, copy, test, invalid};
-
-    const auto get_op = [](const std::string & op)
-    {
-        if (op == "add")
-        {
-            return patch_operations::add;
-        }
-        if (op == "remove")
-        {
-            return patch_operations::remove;
-        }
-        if (op == "replace")
-        {
-            return patch_operations::replace;
-        }
-        if (op == "move")
-        {
-            return patch_operations::move;
-        }
-        if (op == "copy")
-        {
-            return patch_operations::copy;
-        }
-        if (op == "test")
-        {
-            return patch_operations::test;
-        }
-
-        return patch_operations::invalid;
-    };
-
-    // wrapper for "add" operation; add value at ptr
-    const auto operation_add = [&result](json_pointer & ptr, json val)
-    {
-        // adding to the root of the target document means replacing it
-        if (ptr.is_root())
-        {
-            result = val;
-        }
-        else
-        {
-            // make sure the top element of the pointer exists
-            json_pointer top_pointer = ptr.top();
-            if (top_pointer != ptr)
-            {
-                result.at(top_pointer);
-            }
-
-            // get reference to parent of JSON pointer ptr
-            const auto last_path = ptr.pop_back();
-            json& parent = result[ptr];
-
-            switch (parent.m_type)
-            {
-                case value_t::null:
-                case value_t::object:
-                {
-                    // use operator[] to add value
-                    parent[last_path] = val;
-                    break;
-                }
-
-                case value_t::array:
-                {
-                    if (last_path == "-")
-                    {
-                        // special case: append to back
-                        parent.push_back(val);
-                    }
-                    else
-                    {
-                        const auto idx = json_pointer::array_index(last_path);
-                        if (JSON_UNLIKELY(static_cast<size_type>(idx) > parent.size()))
-                        {
-                            // avoid undefined behavior
-                            JSON_THROW(out_of_range::create(401, fmt::format("array index {} is out of range", idx)));
-                        }
-                        else
-                        {
-                            // default case: insert add offset
-                            parent.insert(parent.begin() + static_cast<difference_type>(idx), val);
-                        }
-                    }
-                    break;
-                }
-
-                default:
-                {
-                    // if there exists a parent it cannot be primitive
-                    assert(false);  // LCOV_EXCL_LINE
-                }
-            }
-        }
-    };
-
-    // wrapper for "remove" operation; remove value at ptr
-    const auto operation_remove = [&result](json_pointer & ptr)
-    {
-        // get reference to parent of JSON pointer ptr
-        const auto last_path = ptr.pop_back();
-        json& parent = result.at(ptr);
-
-        // remove child
-        if (parent.is_object())
-        {
-            // perform range check
-            auto it = parent.find(last_path);
-            if (JSON_LIKELY(it != parent.end()))
-            {
-                parent.erase(it);
-            }
-            else
-            {
-                JSON_THROW(out_of_range::create(403, fmt::format("key '{}' not found", last_path)));
-            }
-        }
-        else if (parent.is_array())
-        {
-            // note erase performs range check
-            parent.erase(static_cast<size_type>(json_pointer::array_index(last_path)));
-        }
-    };
-
-    // type check: top level value must be an array
-    if (JSON_UNLIKELY(not json_patch.is_array()))
-    {
-        JSON_THROW(parse_error::create(104, 0, "JSON patch must be an array of objects"));
-    }
-
-    // iterate and apply the operations
-    for (const auto& val : json_patch)
-    {
-        // wrapper to get a value for an operation
-        const auto get_value = [&val](const std::string & op,
-                                      const std::string & member,
-                                      bool string_type) -> json &
-        {
-            // find value
-            auto it = val.m_value.object->find(member);
-
-            // context-sensitive error message
-            const auto error_msg = (op == "op") ? "operation" : "operation '" + op + "'";
-
-            // check if desired value is present
-            if (JSON_UNLIKELY(it == val.m_value.object->end()))
-            {
-                JSON_THROW(parse_error::create(105, 0, fmt::format("{} must have member '{}'", error_msg, member)));
-            }
-
-            // check if result is of type string
-            if (JSON_UNLIKELY(string_type and not it->second.is_string()))
-            {
-                JSON_THROW(parse_error::create(105, 0, fmt::format("{} must have string member '{}'", error_msg, member)));
-            }
-
-            // no error: return value
-            return it->second;
-        };
-
-        // type check: every element of the array must be an object
-        if (JSON_UNLIKELY(not val.is_object()))
-        {
-            JSON_THROW(parse_error::create(104, 0, "JSON patch must be an array of objects"));
-        }
-
-        // collect mandatory members
-        const std::string op = get_value("op", "op", true);
-        const std::string path = get_value(op, "path", true);
-        json_pointer ptr(path);
-
-        switch (get_op(op))
-        {
-            case patch_operations::add:
-            {
-                operation_add(ptr, get_value("add", "value", false));
-                break;
-            }
-
-            case patch_operations::remove:
-            {
-                operation_remove(ptr);
-                break;
-            }
-
-            case patch_operations::replace:
-            {
-                // the "path" location must exist - use at()
-                result.at(ptr) = get_value("replace", "value", false);
-                break;
-            }
-
-            case patch_operations::move:
-            {
-                const std::string from_path = get_value("move", "from", true);
-                json_pointer from_ptr(from_path);
-
-                // the "from" location must exist - use at()
-                json v = result.at(from_ptr);
-
-                // The move operation is functionally identical to a
-                // "remove" operation on the "from" location, followed
-                // immediately by an "add" operation at the target
-                // location with the value that was just removed.
-                operation_remove(from_ptr);
-                operation_add(ptr, v);
-                break;
-            }
-
-            case patch_operations::copy:
-            {
-                const std::string from_path = get_value("copy", "from", true);
-                const json_pointer from_ptr(from_path);
-
-                // the "from" location must exist - use at()
-                json v = result.at(from_ptr);
-
-                // The copy is functionally identical to an "add"
-                // operation at the target location using the value
-                // specified in the "from" member.
-                operation_add(ptr, v);
-                break;
-            }
-
-            case patch_operations::test:
-            {
-                bool success = false;
-                JSON_TRY
-                {
-                    // check if "value" matches the one at "path"
-                    // the "path" location must exist - use at()
-                    success = (result.at(ptr) == get_value("test", "value", false));
-                }
-                JSON_CATCH (out_of_range&)
-                {
-                    // ignore out of range errors: success remains false
-                }
-
-                // throw an exception if test fails
-                if (JSON_UNLIKELY(not success))
-                {
-                    JSON_THROW(other_error::create(501, fmt::format("unsuccessful: {}", val.dump())));
-                }
-
-                break;
-            }
-
-            case patch_operations::invalid:
-            {
-                // op must be "add", "remove", "replace", "move", "copy", or
-                // "test"
-                JSON_THROW(parse_error::create(105, 0, fmt::format("operation value '{}' is invalid", op)));
-            }
-        }
-    }
-
-    return result;
-}
-
-json json::diff(const json& source, const json& target,
-                       const std::string& path)
-{
-    // the patch
-    json result(value_t::array);
-
-    // if the values are the same, return empty patch
-    if (source == target)
-    {
-        return result;
-    }
-
-    if (source.type() != target.type())
-    {
-        // different types: replace value
-        result.push_back(
-        {
-            {"op", "replace"}, {"path", path}, {"value", target}
-        });
-    }
-    else
-    {
-        switch (source.type())
-        {
-            case value_t::array:
-            {
-                // first pass: traverse common elements
-                std::size_t i = 0;
-                while (i < source.size() and i < target.size())
-                {
-                    // recursive call to compare array values at index i
-                    auto temp_diff = diff(source[i], target[i], path + "/" + std::to_string(i));
-                    result.insert(result.end(), temp_diff.begin(), temp_diff.end());
-                    ++i;
-                }
-
-                // i now reached the end of at least one array
-                // in a second pass, traverse the remaining elements
-
-                // remove my remaining elements
-                const auto end_index = static_cast<difference_type>(result.size());
-                while (i < source.size())
-                {
-                    // add operations in reverse order to avoid invalid
-                    // indices
-                    result.insert(result.begin() + end_index, object(
-                    {
-                        {"op", "remove"},
-                        {"path", path + "/" + std::to_string(i)}
-                    }));
-                    ++i;
-                }
-
-                // add other remaining elements
-                while (i < target.size())
-                {
-                    result.push_back(
-                    {
-                        {"op", "add"},
-                        {"path", path + "/" + std::to_string(i)},
-                        {"value", target[i]}
-                    });
-                    ++i;
-                }
-
-                break;
-            }
-
-            case value_t::object:
-            {
-                // first pass: traverse this object's elements
-                for (auto it = source.cbegin(); it != source.cend(); ++it)
-                {
-                    // escape the key name to be used in a JSON patch
-                    const auto key = json_pointer::escape(std::string{it.key()});
-
-                    if (target.find(it.key()) != target.end())
-                    {
-                        // recursive call to compare object values at key it
-                        auto temp_diff = diff(it.value(), target[it.key()], path + "/" + key);
-                        result.insert(result.end(), temp_diff.begin(), temp_diff.end());
-                    }
-                    else
-                    {
-                        // found a key that is not in o -> remove it
-                        result.push_back(object(
-                        {
-                            {"op", "remove"}, {"path", path + "/" + key}
-                        }));
-                    }
-                }
-
-                // second pass: traverse other object's elements
-                for (auto it = target.cbegin(); it != target.cend(); ++it)
-                {
-                    if (source.find(it.key()) == source.end())
-                    {
-                        // found a key that is not in this -> add it
-                        const auto key = json_pointer::escape(std::string{it.key()});
-                        result.push_back(
-                        {
-                            {"op", "add"}, {"path", path + "/" + key},
-                            {"value", it.value()}
-                        });
-                    }
-                }
-
-                break;
-            }
-
-            default:
-            {
-                // both primitive type: replace value
-                result.push_back(
-                {
-                    {"op", "replace"}, {"path", path}, {"value", target}
-                });
-                break;
-            }
-        }
-    }
-
-    return result;
-}
-
-void json::merge_patch(const json& patch)
-{
-    if (patch.is_object())
-    {
-        if (not is_object())
-        {
-            *this = object();
-        }
-        for (auto it = patch.begin(); it != patch.end(); ++it)
-        {
-            if (it.value().is_null())
-            {
-                erase(it.key());
-            }
-            else
-            {
-                operator[](it.key()).merge_patch(it.value());
-            }
-        }
-    }
-    else
-    {
-        *this = patch;
-    }
-}
-
-}  // namespace wpi
diff --git a/wpiutil/src/main/native/cpp/json_binary_reader.cpp b/wpiutil/src/main/native/cpp/json_binary_reader.cpp
deleted file mode 100644
index cee5e39..0000000
--- a/wpiutil/src/main/native/cpp/json_binary_reader.cpp
+++ /dev/null
@@ -1,1415 +0,0 @@
-/*----------------------------------------------------------------------------*/
-/* Modifications 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.                                                               */
-/*----------------------------------------------------------------------------*/
-/*
-    __ _____ _____ _____
- __|  |   __|     |   | |  JSON for Modern C++
-|  |  |__   |  |  | | | |  version 3.1.2
-|_____|_____|_____|_|___|  https://github.com/nlohmann/json
-
-Licensed under the MIT License <http://opensource.org/licenses/MIT>.
-Copyright (c) 2013-2018 Niels Lohmann <http://nlohmann.me>.
-
-Permission is hereby  granted, free of charge, to any  person obtaining a copy
-of this software and associated  documentation files (the "Software"), to deal
-in the Software  without restriction, including without  limitation the rights
-to  use, copy,  modify, merge,  publish, distribute,  sublicense, and/or  sell
-copies  of  the Software,  and  to  permit persons  to  whom  the Software  is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-THE SOFTWARE  IS PROVIDED "AS  IS", WITHOUT WARRANTY  OF ANY KIND,  EXPRESS OR
-IMPLIED,  INCLUDING BUT  NOT  LIMITED TO  THE  WARRANTIES OF  MERCHANTABILITY,
-FITNESS FOR  A PARTICULAR PURPOSE AND  NONINFRINGEMENT. IN NO EVENT  SHALL THE
-AUTHORS  OR COPYRIGHT  HOLDERS  BE  LIABLE FOR  ANY  CLAIM,  DAMAGES OR  OTHER
-LIABILITY, WHETHER IN AN ACTION OF  CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE  OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
-*/
-#define WPI_JSON_IMPLEMENTATION
-#include "wpi/json.h"
-
-#include <cmath>  // ldexp
-
-#include "fmt/format.h"
-#include "wpi/raw_istream.h"
-
-namespace wpi {
-
-/*!
-@brief deserialization of CBOR and MessagePack values
-*/
-class json::binary_reader
-{
-  public:
-    /*!
-    @brief create a binary reader
-
-    @param[in] adapter  input adapter to read from
-    */
-    explicit binary_reader(raw_istream& s) : is(s)
-    {
-    }
-
-    /*!
-    @brief create a JSON value from CBOR input
-
-    @param[in] strict  whether to expect the input to be consumed completed
-    @return JSON value created from CBOR input
-
-    @throw parse_error.110 if input ended unexpectedly or the end of file was
-                           not reached when @a strict was set to true
-    @throw parse_error.112 if unsupported byte was read
-    */
-    json parse_cbor(const bool strict)
-    {
-        const auto res = parse_cbor_internal();
-        if (strict)
-        {
-            get();
-            expect_eof();
-        }
-        return res;
-    }
-
-    /*!
-    @brief create a JSON value from MessagePack input
-
-    @param[in] strict  whether to expect the input to be consumed completed
-    @return JSON value created from MessagePack input
-
-    @throw parse_error.110 if input ended unexpectedly or the end of file was
-                           not reached when @a strict was set to true
-    @throw parse_error.112 if unsupported byte was read
-    */
-    json parse_msgpack(const bool strict)
-    {
-        const auto res = parse_msgpack_internal();
-        if (strict)
-        {
-            get();
-            expect_eof();
-        }
-        return res;
-    }
-
-    /*!
-    @brief create a JSON value from UBJSON input
-
-    @param[in] strict  whether to expect the input to be consumed completed
-    @return JSON value created from UBJSON input
-
-    @throw parse_error.110 if input ended unexpectedly or the end of file was
-                           not reached when @a strict was set to true
-    @throw parse_error.112 if unsupported byte was read
-    */
-    json parse_ubjson(const bool strict)
-    {
-        const auto res = parse_ubjson_internal();
-        if (strict)
-        {
-            get_ignore_noop();
-            expect_eof();
-        }
-        return res;
-    }
-
-    /*!
-    @brief determine system byte order
-
-    @return true if and only if system's byte order is little endian
-
-    @note from http://stackoverflow.com/a/1001328/266378
-    */
-    static bool little_endianess(int num = 1) noexcept
-    {
-        return (*reinterpret_cast<char*>(&num) == 1);
-    }
-
-  private:
-    /*!
-    @param[in] get_char  whether a new character should be retrieved from the
-                         input (true, default) or whether the last read
-                         character should be considered instead
-    */
-    json parse_cbor_internal(const bool get_char = true);
-
-    json parse_msgpack_internal();
-
-    /*!
-    @param[in] get_char  whether a new character should be retrieved from the
-                         input (true, default) or whether the last read
-                         character should be considered instead
-    */
-    json parse_ubjson_internal(const bool get_char = true)
-    {
-        return get_ubjson_value(get_char ? get_ignore_noop() : current);
-    }
-
-    /*!
-    @brief get next character from the input
-
-    This function provides the interface to the used input adapter. It does
-    not throw in case the input reached EOF, but returns a -'ve valued
-    `std::char_traits<char>::eof()` in that case.
-
-    @return character read from the input
-    */
-    int get()
-    {
-        ++chars_read;
-        unsigned char c;
-        is.read(c);
-        if (is.has_error())
-        {
-            current = std::char_traits<char>::eof();
-        }
-        else
-        {
-            current = c;
-        }
-        return current;
-    }
-
-    /*!
-    @return character read from the input after ignoring all 'N' entries
-    */
-    int get_ignore_noop()
-    {
-        do
-        {
-            get();
-        }
-        while (current == 'N');
-
-        return current;
-    }
-
-    /*
-    @brief read a number from the input
-
-    @tparam NumberType the type of the number
-
-    @return number of type @a NumberType
-
-    @note This function needs to respect the system's endianess, because
-          bytes in CBOR and MessagePack are stored in network order (big
-          endian) and therefore need reordering on little endian systems.
-
-    @throw parse_error.110 if input has less than `sizeof(NumberType)` bytes
-    */
-    template<typename NumberType> NumberType get_number()
-    {
-        // step 1: read input into array with system's byte order
-        std::array<uint8_t, sizeof(NumberType)> vec;
-        for (std::size_t i = 0; i < sizeof(NumberType); ++i)
-        {
-            get();
-            unexpect_eof();
-
-            // reverse byte order prior to conversion if necessary
-            if (is_little_endian)
-            {
-                vec[sizeof(NumberType) - i - 1] = static_cast<uint8_t>(current);
-            }
-            else
-            {
-                vec[i] = static_cast<uint8_t>(current); // LCOV_EXCL_LINE
-            }
-        }
-
-        // step 2: convert array into number of type T and return
-        NumberType result;
-        std::memcpy(&result, vec.data(), sizeof(NumberType));
-        return result;
-    }
-
-    /*!
-    @brief create a string by reading characters from the input
-
-    @param[in] len number of bytes to read
-
-    @note We can not reserve @a len bytes for the result, because @a len
-          may be too large. Usually, @ref unexpect_eof() detects the end of
-          the input before we run out of string memory.
-
-    @return string created by reading @a len bytes
-
-    @throw parse_error.110 if input has less than @a len bytes
-    */
-    template<typename NumberType>
-    std::string get_string(const NumberType len)
-    {
-        std::string result;
-        std::generate_n(std::back_inserter(result), len, [this]()
-        {
-            get();
-            unexpect_eof();
-            return static_cast<char>(current);
-        });
-        return result;
-    }
-
-    /*!
-    @brief reads a CBOR string
-
-    This function first reads starting bytes to determine the expected
-    string length and then copies this number of bytes into a string.
-    Additionally, CBOR's strings with indefinite lengths are supported.
-
-    @return string
-
-    @throw parse_error.110 if input ended
-    @throw parse_error.113 if an unexpected byte is read
-    */
-    std::string get_cbor_string();
-
-    template<typename NumberType>
-    json get_cbor_array(const NumberType len)
-    {
-        json result = value_t::array;
-        std::generate_n(std::back_inserter(*result.m_value.array), len, [this]()
-        {
-            return parse_cbor_internal();
-        });
-        return result;
-    }
-
-    template<typename NumberType>
-    json get_cbor_object(const NumberType len)
-    {
-        json result = value_t::object;
-        for (NumberType i = 0; i < len; ++i)
-        {
-            get();
-            auto key = get_cbor_string();
-            (*result.m_value.object)[key] = parse_cbor_internal();
-        }
-        return result;
-    }
-
-    /*!
-    @brief reads a MessagePack string
-
-    This function first reads starting bytes to determine the expected
-    string length and then copies this number of bytes into a string.
-
-    @return string
-
-    @throw parse_error.110 if input ended
-    @throw parse_error.113 if an unexpected byte is read
-    */
-    std::string get_msgpack_string();
-
-    template<typename NumberType>
-    json get_msgpack_array(const NumberType len)
-    {
-        json result = value_t::array;
-        std::generate_n(std::back_inserter(*result.m_value.array), len, [this]()
-        {
-            return parse_msgpack_internal();
-        });
-        return result;
-    }
-
-    template<typename NumberType>
-    json get_msgpack_object(const NumberType len)
-    {
-        json result = value_t::object;
-        for (NumberType i = 0; i < len; ++i)
-        {
-            get();
-            auto key = get_msgpack_string();
-            (*result.m_value.object)[key] = parse_msgpack_internal();
-        }
-        return result;
-    }
-
-    /*!
-    @brief reads a UBJSON string
-
-    This function is either called after reading the 'S' byte explicitly
-    indicating a string, or in case of an object key where the 'S' byte can be
-    left out.
-
-    @param[in] get_char  whether a new character should be retrieved from the
-                         input (true, default) or whether the last read
-                         character should be considered instead
-
-    @return string
-
-    @throw parse_error.110 if input ended
-    @throw parse_error.113 if an unexpected byte is read
-    */
-    std::string get_ubjson_string(const bool get_char = true);
-
-    /*!
-    @brief determine the type and size for a container
-
-    In the optimized UBJSON format, a type and a size can be provided to allow
-    for a more compact representation.
-
-    @return pair of the size and the type
-    */
-    std::pair<std::size_t, int> get_ubjson_size_type();
-
-    json get_ubjson_value(const int prefix);
-
-    json get_ubjson_array();
-
-    json get_ubjson_object();
-
-    /*!
-    @brief throw if end of input is not reached
-    @throw parse_error.110 if input not ended
-    */
-    void expect_eof() const
-    {
-        if (JSON_UNLIKELY(current != std::char_traits<char>::eof()))
-        {
-            JSON_THROW(parse_error::create(110, chars_read, "expected end of input"));
-        }
-    }
-
-    /*!
-    @briefthrow if end of input is reached
-    @throw parse_error.110 if input ended
-    */
-    void unexpect_eof() const
-    {
-        if (JSON_UNLIKELY(current == std::char_traits<char>::eof()))
-        {
-            JSON_THROW(parse_error::create(110, chars_read, "unexpected end of input"));
-        }
-    }
-
-  private:
-    /// input adapter
-    raw_istream& is;
-
-    /// the current character
-    int current = std::char_traits<char>::eof();
-
-    /// the number of characters read
-    std::size_t chars_read = 0;
-
-    /// whether we can assume little endianess
-    const bool is_little_endian = little_endianess();
-};
-
-json json::binary_reader::parse_cbor_internal(const bool get_char)
-{
-    switch (get_char ? get() : current)
-    {
-        // EOF
-        case std::char_traits<char>::eof():
-            JSON_THROW(parse_error::create(110, chars_read, "unexpected end of input"));
-
-        // Integer 0x00..0x17 (0..23)
-        case 0x00:
-        case 0x01:
-        case 0x02:
-        case 0x03:
-        case 0x04:
-        case 0x05:
-        case 0x06:
-        case 0x07:
-        case 0x08:
-        case 0x09:
-        case 0x0A:
-        case 0x0B:
-        case 0x0C:
-        case 0x0D:
-        case 0x0E:
-        case 0x0F:
-        case 0x10:
-        case 0x11:
-        case 0x12:
-        case 0x13:
-        case 0x14:
-        case 0x15:
-        case 0x16:
-        case 0x17:
-            return static_cast<uint64_t>(current);
-
-        case 0x18: // Unsigned integer (one-byte uint8_t follows)
-            return get_number<uint8_t>();
-
-        case 0x19: // Unsigned integer (two-byte uint16_t follows)
-            return get_number<uint16_t>();
-
-        case 0x1A: // Unsigned integer (four-byte uint32_t follows)
-            return get_number<uint32_t>();
-
-        case 0x1B: // Unsigned integer (eight-byte uint64_t follows)
-            return get_number<uint64_t>();
-
-        // Negative integer -1-0x00..-1-0x17 (-1..-24)
-        case 0x20:
-        case 0x21:
-        case 0x22:
-        case 0x23:
-        case 0x24:
-        case 0x25:
-        case 0x26:
-        case 0x27:
-        case 0x28:
-        case 0x29:
-        case 0x2A:
-        case 0x2B:
-        case 0x2C:
-        case 0x2D:
-        case 0x2E:
-        case 0x2F:
-        case 0x30:
-        case 0x31:
-        case 0x32:
-        case 0x33:
-        case 0x34:
-        case 0x35:
-        case 0x36:
-        case 0x37:
-            return static_cast<int8_t>(0x20 - 1 - current);
-
-        case 0x38: // Negative integer (one-byte uint8_t follows)
-        {
-            return static_cast<int64_t>(-1) - get_number<uint8_t>();
-        }
-
-        case 0x39: // Negative integer -1-n (two-byte uint16_t follows)
-        {
-            return static_cast<int64_t>(-1) - get_number<uint16_t>();
-        }
-
-        case 0x3A: // Negative integer -1-n (four-byte uint32_t follows)
-        {
-            return static_cast<int64_t>(-1) - get_number<uint32_t>();
-        }
-
-        case 0x3B: // Negative integer -1-n (eight-byte uint64_t follows)
-        {
-            return static_cast<int64_t>(-1) -
-                   static_cast<int64_t>(get_number<uint64_t>());
-        }
-
-        // UTF-8 string (0x00..0x17 bytes follow)
-        case 0x60:
-        case 0x61:
-        case 0x62:
-        case 0x63:
-        case 0x64:
-        case 0x65:
-        case 0x66:
-        case 0x67:
-        case 0x68:
-        case 0x69:
-        case 0x6A:
-        case 0x6B:
-        case 0x6C:
-        case 0x6D:
-        case 0x6E:
-        case 0x6F:
-        case 0x70:
-        case 0x71:
-        case 0x72:
-        case 0x73:
-        case 0x74:
-        case 0x75:
-        case 0x76:
-        case 0x77:
-        case 0x78: // UTF-8 string (one-byte uint8_t for n follows)
-        case 0x79: // UTF-8 string (two-byte uint16_t for n follow)
-        case 0x7A: // UTF-8 string (four-byte uint32_t for n follow)
-        case 0x7B: // UTF-8 string (eight-byte uint64_t for n follow)
-        case 0x7F: // UTF-8 string (indefinite length)
-        {
-            return get_cbor_string();
-        }
-
-        // array (0x00..0x17 data items follow)
-        case 0x80:
-        case 0x81:
-        case 0x82:
-        case 0x83:
-        case 0x84:
-        case 0x85:
-        case 0x86:
-        case 0x87:
-        case 0x88:
-        case 0x89:
-        case 0x8A:
-        case 0x8B:
-        case 0x8C:
-        case 0x8D:
-        case 0x8E:
-        case 0x8F:
-        case 0x90:
-        case 0x91:
-        case 0x92:
-        case 0x93:
-        case 0x94:
-        case 0x95:
-        case 0x96:
-        case 0x97:
-        {
-            return get_cbor_array(current & 0x1F);
-        }
-
-        case 0x98: // array (one-byte uint8_t for n follows)
-        {
-            return get_cbor_array(get_number<uint8_t>());
-        }
-
-        case 0x99: // array (two-byte uint16_t for n follow)
-        {
-            return get_cbor_array(get_number<uint16_t>());
-        }
-
-        case 0x9A: // array (four-byte uint32_t for n follow)
-        {
-            return get_cbor_array(get_number<uint32_t>());
-        }
-
-        case 0x9B: // array (eight-byte uint64_t for n follow)
-        {
-            return get_cbor_array(get_number<uint64_t>());
-        }
-
-        case 0x9F: // array (indefinite length)
-        {
-            json result = value_t::array;
-            while (get() != 0xFF)
-            {
-                result.push_back(parse_cbor_internal(false));
-            }
-            return result;
-        }
-
-        // map (0x00..0x17 pairs of data items follow)
-        case 0xA0:
-        case 0xA1:
-        case 0xA2:
-        case 0xA3:
-        case 0xA4:
-        case 0xA5:
-        case 0xA6:
-        case 0xA7:
-        case 0xA8:
-        case 0xA9:
-        case 0xAA:
-        case 0xAB:
-        case 0xAC:
-        case 0xAD:
-        case 0xAE:
-        case 0xAF:
-        case 0xB0:
-        case 0xB1:
-        case 0xB2:
-        case 0xB3:
-        case 0xB4:
-        case 0xB5:
-        case 0xB6:
-        case 0xB7:
-        {
-            return get_cbor_object(current & 0x1F);
-        }
-
-        case 0xB8: // map (one-byte uint8_t for n follows)
-        {
-            return get_cbor_object(get_number<uint8_t>());
-        }
-
-        case 0xB9: // map (two-byte uint16_t for n follow)
-        {
-            return get_cbor_object(get_number<uint16_t>());
-        }
-
-        case 0xBA: // map (four-byte uint32_t for n follow)
-        {
-            return get_cbor_object(get_number<uint32_t>());
-        }
-
-        case 0xBB: // map (eight-byte uint64_t for n follow)
-        {
-            return get_cbor_object(get_number<uint64_t>());
-        }
-
-        case 0xBF: // map (indefinite length)
-        {
-            json result = value_t::object;
-            while (get() != 0xFF)
-            {
-                auto key = get_cbor_string();
-                result[key] = parse_cbor_internal();
-            }
-            return result;
-        }
-
-        case 0xF4: // false
-        {
-            return false;
-        }
-
-        case 0xF5: // true
-        {
-            return true;
-        }
-
-        case 0xF6: // null
-        {
-            return value_t::null;
-        }
-
-        case 0xF9: // Half-Precision Float (two-byte IEEE 754)
-        {
-            const int byte1 = get();
-            unexpect_eof();
-            const int byte2 = get();
-            unexpect_eof();
-
-            // code from RFC 7049, Appendix D, Figure 3:
-            // As half-precision floating-point numbers were only added
-            // to IEEE 754 in 2008, today's programming platforms often
-            // still only have limited support for them. It is very
-            // easy to include at least decoding support for them even
-            // without such support. An example of a small decoder for
-            // half-precision floating-point numbers in the C language
-            // is shown in Fig. 3.
-            const int half = (byte1 << 8) + byte2;
-            const int exp = (half >> 10) & 0x1F;
-            const int mant = half & 0x3FF;
-            double val;
-            if (exp == 0)
-            {
-                val = std::ldexp(mant, -24);
-            }
-            else if (exp != 31)
-            {
-                val = std::ldexp(mant + 1024, exp - 25);
-            }
-            else
-            {
-                val = (mant == 0) ? std::numeric_limits<double>::infinity()
-                      : std::numeric_limits<double>::quiet_NaN();
-            }
-            return (half & 0x8000) != 0 ? -val : val;
-        }
-
-        case 0xFA: // Single-Precision Float (four-byte IEEE 754)
-        {
-            return get_number<float>();
-        }
-
-        case 0xFB: // Double-Precision Float (eight-byte IEEE 754)
-        {
-            return get_number<double>();
-        }
-
-        default: // anything else (0xFF is handled inside the other types)
-        {
-            JSON_THROW(parse_error::create(112, chars_read, fmt::format("error reading CBOR; last byte: {:#02x}", current)));
-        }
-    }
-}
-
-json json::binary_reader::parse_msgpack_internal()
-{
-    switch (get())
-    {
-        // EOF
-        case std::char_traits<char>::eof():
-            JSON_THROW(parse_error::create(110, chars_read, "unexpected end of input"));
-
-        // positive fixint
-        case 0x00:
-        case 0x01:
-        case 0x02:
-        case 0x03:
-        case 0x04:
-        case 0x05:
-        case 0x06:
-        case 0x07:
-        case 0x08:
-        case 0x09:
-        case 0x0A:
-        case 0x0B:
-        case 0x0C:
-        case 0x0D:
-        case 0x0E:
-        case 0x0F:
-        case 0x10:
-        case 0x11:
-        case 0x12:
-        case 0x13:
-        case 0x14:
-        case 0x15:
-        case 0x16:
-        case 0x17:
-        case 0x18:
-        case 0x19:
-        case 0x1A:
-        case 0x1B:
-        case 0x1C:
-        case 0x1D:
-        case 0x1E:
-        case 0x1F:
-        case 0x20:
-        case 0x21:
-        case 0x22:
-        case 0x23:
-        case 0x24:
-        case 0x25:
-        case 0x26:
-        case 0x27:
-        case 0x28:
-        case 0x29:
-        case 0x2A:
-        case 0x2B:
-        case 0x2C:
-        case 0x2D:
-        case 0x2E:
-        case 0x2F:
-        case 0x30:
-        case 0x31:
-        case 0x32:
-        case 0x33:
-        case 0x34:
-        case 0x35:
-        case 0x36:
-        case 0x37:
-        case 0x38:
-        case 0x39:
-        case 0x3A:
-        case 0x3B:
-        case 0x3C:
-        case 0x3D:
-        case 0x3E:
-        case 0x3F:
-        case 0x40:
-        case 0x41:
-        case 0x42:
-        case 0x43:
-        case 0x44:
-        case 0x45:
-        case 0x46:
-        case 0x47:
-        case 0x48:
-        case 0x49:
-        case 0x4A:
-        case 0x4B:
-        case 0x4C:
-        case 0x4D:
-        case 0x4E:
-        case 0x4F:
-        case 0x50:
-        case 0x51:
-        case 0x52:
-        case 0x53:
-        case 0x54:
-        case 0x55:
-        case 0x56:
-        case 0x57:
-        case 0x58:
-        case 0x59:
-        case 0x5A:
-        case 0x5B:
-        case 0x5C:
-        case 0x5D:
-        case 0x5E:
-        case 0x5F:
-        case 0x60:
-        case 0x61:
-        case 0x62:
-        case 0x63:
-        case 0x64:
-        case 0x65:
-        case 0x66:
-        case 0x67:
-        case 0x68:
-        case 0x69:
-        case 0x6A:
-        case 0x6B:
-        case 0x6C:
-        case 0x6D:
-        case 0x6E:
-        case 0x6F:
-        case 0x70:
-        case 0x71:
-        case 0x72:
-        case 0x73:
-        case 0x74:
-        case 0x75:
-        case 0x76:
-        case 0x77:
-        case 0x78:
-        case 0x79:
-        case 0x7A:
-        case 0x7B:
-        case 0x7C:
-        case 0x7D:
-        case 0x7E:
-        case 0x7F:
-            return static_cast<uint64_t>(current);
-
-        // fixmap
-        case 0x80:
-        case 0x81:
-        case 0x82:
-        case 0x83:
-        case 0x84:
-        case 0x85:
-        case 0x86:
-        case 0x87:
-        case 0x88:
-        case 0x89:
-        case 0x8A:
-        case 0x8B:
-        case 0x8C:
-        case 0x8D:
-        case 0x8E:
-        case 0x8F:
-        {
-            return get_msgpack_object(current & 0x0F);
-        }
-
-        // fixarray
-        case 0x90:
-        case 0x91:
-        case 0x92:
-        case 0x93:
-        case 0x94:
-        case 0x95:
-        case 0x96:
-        case 0x97:
-        case 0x98:
-        case 0x99:
-        case 0x9A:
-        case 0x9B:
-        case 0x9C:
-        case 0x9D:
-        case 0x9E:
-        case 0x9F:
-        {
-            return get_msgpack_array(current & 0x0F);
-        }
-
-        // fixstr
-        case 0xA0:
-        case 0xA1:
-        case 0xA2:
-        case 0xA3:
-        case 0xA4:
-        case 0xA5:
-        case 0xA6:
-        case 0xA7:
-        case 0xA8:
-        case 0xA9:
-        case 0xAA:
-        case 0xAB:
-        case 0xAC:
-        case 0xAD:
-        case 0xAE:
-        case 0xAF:
-        case 0xB0:
-        case 0xB1:
-        case 0xB2:
-        case 0xB3:
-        case 0xB4:
-        case 0xB5:
-        case 0xB6:
-        case 0xB7:
-        case 0xB8:
-        case 0xB9:
-        case 0xBA:
-        case 0xBB:
-        case 0xBC:
-        case 0xBD:
-        case 0xBE:
-        case 0xBF:
-            return get_msgpack_string();
-
-        case 0xC0: // nil
-            return value_t::null;
-
-        case 0xC2: // false
-            return false;
-
-        case 0xC3: // true
-            return true;
-
-        case 0xCA: // float 32
-            return get_number<float>();
-
-        case 0xCB: // float 64
-            return get_number<double>();
-
-        case 0xCC: // uint 8
-            return get_number<uint8_t>();
-
-        case 0xCD: // uint 16
-            return get_number<uint16_t>();
-
-        case 0xCE: // uint 32
-            return get_number<uint32_t>();
-
-        case 0xCF: // uint 64
-            return get_number<uint64_t>();
-
-        case 0xD0: // int 8
-            return get_number<int8_t>();
-
-        case 0xD1: // int 16
-            return get_number<int16_t>();
-
-        case 0xD2: // int 32
-            return get_number<int32_t>();
-
-        case 0xD3: // int 64
-            return get_number<int64_t>();
-
-        case 0xD9: // str 8
-        case 0xDA: // str 16
-        case 0xDB: // str 32
-            return get_msgpack_string();
-
-        case 0xDC: // array 16
-        {
-            return get_msgpack_array(get_number<uint16_t>());
-        }
-
-        case 0xDD: // array 32
-        {
-            return get_msgpack_array(get_number<uint32_t>());
-        }
-
-        case 0xDE: // map 16
-        {
-            return get_msgpack_object(get_number<uint16_t>());
-        }
-
-        case 0xDF: // map 32
-        {
-            return get_msgpack_object(get_number<uint32_t>());
-        }
-
-        // positive fixint
-        case 0xE0:
-        case 0xE1:
-        case 0xE2:
-        case 0xE3:
-        case 0xE4:
-        case 0xE5:
-        case 0xE6:
-        case 0xE7:
-        case 0xE8:
-        case 0xE9:
-        case 0xEA:
-        case 0xEB:
-        case 0xEC:
-        case 0xED:
-        case 0xEE:
-        case 0xEF:
-        case 0xF0:
-        case 0xF1:
-        case 0xF2:
-        case 0xF3:
-        case 0xF4:
-        case 0xF5:
-        case 0xF6:
-        case 0xF7:
-        case 0xF8:
-        case 0xF9:
-        case 0xFA:
-        case 0xFB:
-        case 0xFC:
-        case 0xFD:
-        case 0xFE:
-        case 0xFF:
-            return static_cast<int8_t>(current);
-
-        default: // anything else
-        {
-            JSON_THROW(parse_error::create(112, chars_read,
-                fmt::format("error reading MessagePack; last byte: {:#02x}", current)));
-        }
-    }
-}
-
-std::string json::binary_reader::get_cbor_string()
-{
-    unexpect_eof();
-
-    switch (current)
-    {
-        // UTF-8 string (0x00..0x17 bytes follow)
-        case 0x60:
-        case 0x61:
-        case 0x62:
-        case 0x63:
-        case 0x64:
-        case 0x65:
-        case 0x66:
-        case 0x67:
-        case 0x68:
-        case 0x69:
-        case 0x6A:
-        case 0x6B:
-        case 0x6C:
-        case 0x6D:
-        case 0x6E:
-        case 0x6F:
-        case 0x70:
-        case 0x71:
-        case 0x72:
-        case 0x73:
-        case 0x74:
-        case 0x75:
-        case 0x76:
-        case 0x77:
-        {
-            return get_string(current & 0x1F);
-        }
-
-        case 0x78: // UTF-8 string (one-byte uint8_t for n follows)
-        {
-            return get_string(get_number<uint8_t>());
-        }
-
-        case 0x79: // UTF-8 string (two-byte uint16_t for n follow)
-        {
-            return get_string(get_number<uint16_t>());
-        }
-
-        case 0x7A: // UTF-8 string (four-byte uint32_t for n follow)
-        {
-            return get_string(get_number<uint32_t>());
-        }
-
-        case 0x7B: // UTF-8 string (eight-byte uint64_t for n follow)
-        {
-            return get_string(get_number<uint64_t>());
-        }
-
-        case 0x7F: // UTF-8 string (indefinite length)
-        {
-            std::string result;
-            while (get() != 0xFF)
-            {
-                result.append(get_cbor_string());
-            }
-            return result;
-        }
-
-        default:
-        {
-            JSON_THROW(parse_error::create(113, chars_read,
-                fmt::format("expected a CBOR string; last byte: {:#02x}", current)));
-        }
-    }
-}
-
-std::string json::binary_reader::get_msgpack_string()
-{
-    unexpect_eof();
-
-    switch (current)
-    {
-        // fixstr
-        case 0xA0:
-        case 0xA1:
-        case 0xA2:
-        case 0xA3:
-        case 0xA4:
-        case 0xA5:
-        case 0xA6:
-        case 0xA7:
-        case 0xA8:
-        case 0xA9:
-        case 0xAA:
-        case 0xAB:
-        case 0xAC:
-        case 0xAD:
-        case 0xAE:
-        case 0xAF:
-        case 0xB0:
-        case 0xB1:
-        case 0xB2:
-        case 0xB3:
-        case 0xB4:
-        case 0xB5:
-        case 0xB6:
-        case 0xB7:
-        case 0xB8:
-        case 0xB9:
-        case 0xBA:
-        case 0xBB:
-        case 0xBC:
-        case 0xBD:
-        case 0xBE:
-        case 0xBF:
-        {
-            return get_string(current & 0x1F);
-        }
-
-        case 0xD9: // str 8
-        {
-            return get_string(get_number<uint8_t>());
-        }
-
-        case 0xDA: // str 16
-        {
-            return get_string(get_number<uint16_t>());
-        }
-
-        case 0xDB: // str 32
-        {
-            return get_string(get_number<uint32_t>());
-        }
-
-        default:
-        {
-            JSON_THROW(parse_error::create(113, chars_read,
-                fmt::format("expected a MessagePack string; last byte: {:#02x}", current)));
-        }
-    }
-}
-
-std::string json::binary_reader::get_ubjson_string(const bool get_char)
-{
-    if (get_char)
-    {
-        get();  // TODO: may we ignore N here?
-    }
-
-    unexpect_eof();
-
-    switch (current)
-    {
-        case 'U':
-            return get_string(get_number<uint8_t>());
-        case 'i':
-            return get_string(get_number<int8_t>());
-        case 'I':
-            return get_string(get_number<int16_t>());
-        case 'l':
-            return get_string(get_number<int32_t>());
-        case 'L':
-            return get_string(get_number<int64_t>());
-        default:
-            JSON_THROW(parse_error::create(113, chars_read,
-                fmt::format("expected a UBJSON string; last byte: {:#02x}", current)));
-    }
-}
-
-std::pair<std::size_t, int> json::binary_reader::get_ubjson_size_type()
-{
-    std::size_t sz = std::string::npos;
-    int tc = 0;
-
-    get_ignore_noop();
-
-    if (current == '$')
-    {
-        tc = get();  // must not ignore 'N', because 'N' maybe the type
-        unexpect_eof();
-
-        get_ignore_noop();
-        if (current != '#')
-        {
-            JSON_THROW(parse_error::create(112, chars_read,
-                fmt::format("expected '#' after UBJSON type information; last byte: {:#02x}", current)));
-        }
-        sz = parse_ubjson_internal();
-    }
-    else if (current == '#')
-    {
-        sz = parse_ubjson_internal();
-    }
-
-    return std::make_pair(sz, tc);
-}
-
-json json::binary_reader::get_ubjson_value(const int prefix)
-{
-    switch (prefix)
-    {
-        case std::char_traits<char>::eof():  // EOF
-            JSON_THROW(parse_error::create(110, chars_read, "unexpected end of input"));
-
-        case 'T':  // true
-            return true;
-        case 'F':  // false
-            return false;
-
-        case 'Z':  // null
-            return nullptr;
-
-        case 'U':
-            return get_number<uint8_t>();
-        case 'i':
-            return get_number<int8_t>();
-        case 'I':
-            return get_number<int16_t>();
-        case 'l':
-            return get_number<int32_t>();
-        case 'L':
-            return get_number<int64_t>();
-        case 'd':
-            return get_number<float>();
-        case 'D':
-            return get_number<double>();
-
-        case 'C':  // char
-        {
-            get();
-            unexpect_eof();
-            if (JSON_UNLIKELY(current > 127))
-            {
-                JSON_THROW(parse_error::create(113, chars_read,
-                    fmt::format("byte after 'C' must be in range 0x00..0x7F; last byte: {:#02x}", current)));
-            }
-            return std::string(1, static_cast<char>(current));
-        }
-
-        case 'S':  // string
-            return get_ubjson_string();
-
-        case '[':  // array
-            return get_ubjson_array();
-
-        case '{':  // object
-            return get_ubjson_object();
-
-        default: // anything else
-            JSON_THROW(parse_error::create(112, chars_read,
-                fmt::format("error reading UBJSON; last byte: {:#02x}", current)));
-    }
-}
-
-json json::binary_reader::get_ubjson_array()
-{
-    json result = value_t::array;
-    const auto size_and_type = get_ubjson_size_type();
-
-    if (size_and_type.first != std::string::npos)
-    {
-        if (JSON_UNLIKELY(size_and_type.first > result.max_size()))
-        {
-            JSON_THROW(out_of_range::create(408,
-                fmt::format("excessive array size: {}", size_and_type.first)));
-        }
-
-        if (size_and_type.second != 0)
-        {
-            if (size_and_type.second != 'N')
-            {
-                std::generate_n(std::back_inserter(*result.m_value.array),
-                                size_and_type.first, [this, size_and_type]()
-                {
-                    return get_ubjson_value(size_and_type.second);
-                });
-            }
-        }
-        else
-        {
-            std::generate_n(std::back_inserter(*result.m_value.array),
-                            size_and_type.first, [this]()
-            {
-                return parse_ubjson_internal();
-            });
-        }
-    }
-    else
-    {
-        while (current != ']')
-        {
-            result.push_back(parse_ubjson_internal(false));
-            get_ignore_noop();
-        }
-    }
-
-    return result;
-}
-
-json json::binary_reader::get_ubjson_object()
-{
-    json result = value_t::object;
-    const auto size_and_type = get_ubjson_size_type();
-
-    if (size_and_type.first != std::string::npos)
-    {
-        if (JSON_UNLIKELY(size_and_type.first > result.max_size()))
-        {
-            JSON_THROW(out_of_range::create(408,
-                fmt::format("excessive object size: {}", size_and_type.first)));
-        }
-
-        if (size_and_type.second != 0)
-        {
-            for (size_t i = 0; i < size_and_type.first; ++i)
-            {
-                auto key = get_ubjson_string();
-                (*result.m_value.object)[key] = get_ubjson_value(size_and_type.second);
-            }
-        }
-        else
-        {
-            for (size_t i = 0; i < size_and_type.first; ++i)
-            {
-                auto key = get_ubjson_string();
-                (*result.m_value.object)[key] = parse_ubjson_internal();
-            }
-        }
-    }
-    else
-    {
-        while (current != '}')
-        {
-            auto key = get_ubjson_string(false);
-            result[std::move(key)] = parse_ubjson_internal();
-            get_ignore_noop();
-        }
-    }
-
-    return result;
-}
-
-json json::from_cbor(raw_istream& is, const bool strict)
-{
-    return binary_reader(is).parse_cbor(strict);
-}
-
-json json::from_cbor(span<const uint8_t> arr, const bool strict)
-{
-    raw_mem_istream is(arr);
-    return from_cbor(is, strict);
-}
-
-json json::from_msgpack(raw_istream& is, const bool strict)
-{
-    return binary_reader(is).parse_msgpack(strict);
-}
-
-json json::from_msgpack(span<const uint8_t> arr, const bool strict)
-{
-    raw_mem_istream is(arr);
-    return from_msgpack(is, strict);
-}
-
-json json::from_ubjson(raw_istream& is, const bool strict)
-{
-    return binary_reader(is).parse_ubjson(strict);
-}
-
-json json::from_ubjson(span<const uint8_t> arr, const bool strict)
-{
-    raw_mem_istream is(arr);
-    return from_ubjson(is, strict);
-}
-
-}  // namespace wpi
diff --git a/wpiutil/src/main/native/cpp/json_binary_writer.cpp b/wpiutil/src/main/native/cpp/json_binary_writer.cpp
deleted file mode 100644
index db23f8d..0000000
--- a/wpiutil/src/main/native/cpp/json_binary_writer.cpp
+++ /dev/null
@@ -1,1091 +0,0 @@
-/*----------------------------------------------------------------------------*/
-/* Modifications 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.                                                               */
-/*----------------------------------------------------------------------------*/
-/*
-    __ _____ _____ _____
- __|  |   __|     |   | |  JSON for Modern C++
-|  |  |__   |  |  | | | |  version 3.1.2
-|_____|_____|_____|_|___|  https://github.com/nlohmann/json
-
-Licensed under the MIT License <http://opensource.org/licenses/MIT>.
-Copyright (c) 2013-2018 Niels Lohmann <http://nlohmann.me>.
-
-Permission is hereby  granted, free of charge, to any  person obtaining a copy
-of this software and associated  documentation files (the "Software"), to deal
-in the Software  without restriction, including without  limitation the rights
-to  use, copy,  modify, merge,  publish, distribute,  sublicense, and/or  sell
-copies  of  the Software,  and  to  permit persons  to  whom  the Software  is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-THE SOFTWARE  IS PROVIDED "AS  IS", WITHOUT WARRANTY  OF ANY KIND,  EXPRESS OR
-IMPLIED,  INCLUDING BUT  NOT  LIMITED TO  THE  WARRANTIES OF  MERCHANTABILITY,
-FITNESS FOR  A PARTICULAR PURPOSE AND  NONINFRINGEMENT. IN NO EVENT  SHALL THE
-AUTHORS  OR COPYRIGHT  HOLDERS  BE  LIABLE FOR  ANY  CLAIM,  DAMAGES OR  OTHER
-LIABILITY, WHETHER IN AN ACTION OF  CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE  OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
-*/
-#define WPI_JSON_IMPLEMENTATION
-#include "wpi/json.h"
-
-#include "fmt/format.h"
-#include "wpi/raw_ostream.h"
-
-namespace wpi {
-
-/*!
-@brief serialization to CBOR and MessagePack values
-*/
-class json::binary_writer
-{
-    using CharType = unsigned char;
-
-  public:
-    /*!
-    @brief create a binary writer
-
-    @param[in] adapter  output adapter to write to
-    */
-    explicit binary_writer(raw_ostream& s) : o(s)
-    {
-    }
-
-    /*!
-    @brief[in] j  JSON value to serialize
-    */
-    void write_cbor(const json& j);
-
-    /*!
-    @brief[in] j  JSON value to serialize
-    */
-    void write_msgpack(const json& j);
-
-    /*!
-    @param[in] j  JSON value to serialize
-    @param[in] use_count   whether to use '#' prefixes (optimized format)
-    @param[in] use_type    whether to use '$' prefixes (optimized format)
-    @param[in] add_prefix  whether prefixes need to be used for this value
-    */
-    void write_ubjson(const json& j, const bool use_count,
-                      const bool use_type, const bool add_prefix = true);
-
-  private:
-    void write_cbor_string(std::string_view str);
-
-    void write_msgpack_string(std::string_view str);
-
-    /*
-    @brief write a number to output input
-
-    @param[in] n number of type @a NumberType
-    @tparam NumberType the type of the number
-
-    @note This function needs to respect the system's endianess, because bytes
-          in CBOR, MessagePack, and UBJSON are stored in network order (big
-          endian) and therefore need reordering on little endian systems.
-    */
-    template<typename NumberType>
-    void write_number(const NumberType n);
-
-    // UBJSON: write number (floating point)
-    template<typename NumberType, typename std::enable_if<
-                 std::is_floating_point<NumberType>::value, int>::type = 0>
-    void write_number_with_ubjson_prefix(const NumberType n,
-                                         const bool add_prefix)
-    {
-        if (add_prefix)
-        {
-            o << get_ubjson_float_prefix(n);
-        }
-        write_number(n);
-    }
-
-    // UBJSON: write number (unsigned integer)
-    template<typename NumberType, typename std::enable_if<
-                 std::is_unsigned<NumberType>::value, int>::type = 0>
-    void write_number_with_ubjson_prefix(const NumberType n,
-                                         const bool add_prefix);
-
-    // UBJSON: write number (signed integer)
-    template<typename NumberType, typename std::enable_if<
-                 std::is_signed<NumberType>::value and
-                 not std::is_floating_point<NumberType>::value, int>::type = 0>
-    void write_number_with_ubjson_prefix(const NumberType n,
-                                         const bool add_prefix);
-
-    /*!
-    @brief determine the type prefix of container values
-
-    @note This function does not need to be 100% accurate when it comes to
-          integer limits. In case a number exceeds the limits of int64_t,
-          this will be detected by a later call to function
-          write_number_with_ubjson_prefix. Therefore, we return 'L' for any
-          value that does not fit the previous limits.
-    */
-    CharType ubjson_prefix(const json& j) const noexcept;
-
-    static constexpr CharType get_cbor_float_prefix(float)
-    {
-        return static_cast<CharType>(0xFA);  // Single-Precision Float
-    }
-
-    static constexpr CharType get_cbor_float_prefix(double)
-    {
-        return static_cast<CharType>(0xFB);  // Double-Precision Float
-    }
-
-    static constexpr CharType get_msgpack_float_prefix(float)
-    {
-        return static_cast<CharType>(0xCA);  // float 32
-    }
-
-    static constexpr CharType get_msgpack_float_prefix(double)
-    {
-        return static_cast<CharType>(0xCB);  // float 64
-    }
-
-    static constexpr CharType get_ubjson_float_prefix(float)
-    {
-        return 'd';  // float 32
-    }
-
-    static constexpr CharType get_ubjson_float_prefix(double)
-    {
-        return 'D';  // float 64
-    }
-
-  private:
-    static bool little_endianess(int num = 1) noexcept
-    {
-        return (*reinterpret_cast<char*>(&num) == 1);
-    }
-
-    /// whether we can assume little endianess
-    const bool is_little_endian = little_endianess();
-
-    /// the output
-    raw_ostream& o;
-};
-
-void json::binary_writer::write_cbor(const json& j)
-{
-    switch (j.type())
-    {
-        case value_t::null:
-        {
-            o << static_cast<CharType>(0xF6);
-            break;
-        }
-
-        case value_t::boolean:
-        {
-            o << static_cast<CharType>(j.m_value.boolean ? 0xF5 : 0xF4);
-            break;
-        }
-
-        case value_t::number_integer:
-        {
-            if (j.m_value.number_integer >= 0)
-            {
-                // CBOR does not differentiate between positive signed
-                // integers and unsigned integers. Therefore, we used the
-                // code from the value_t::number_unsigned case here.
-                if (j.m_value.number_integer <= 0x17)
-                {
-                    write_number(static_cast<uint8_t>(j.m_value.number_integer));
-                }
-                else if (j.m_value.number_integer <= (std::numeric_limits<uint8_t>::max)())
-                {
-                    o << static_cast<CharType>(0x18);
-                    write_number(static_cast<uint8_t>(j.m_value.number_integer));
-                }
-                else if (j.m_value.number_integer <= (std::numeric_limits<uint16_t>::max)())
-                {
-                    o << static_cast<CharType>(0x19);
-                    write_number(static_cast<uint16_t>(j.m_value.number_integer));
-                }
-                else if (j.m_value.number_integer <= (std::numeric_limits<uint32_t>::max)())
-                {
-                    o << static_cast<CharType>(0x1A);
-                    write_number(static_cast<uint32_t>(j.m_value.number_integer));
-                }
-                else
-                {
-                    o << static_cast<CharType>(0x1B);
-                    write_number(static_cast<uint64_t>(j.m_value.number_integer));
-                }
-            }
-            else
-            {
-                // The conversions below encode the sign in the first
-                // byte, and the value is converted to a positive number.
-                const auto positive_number = -1 - j.m_value.number_integer;
-                if (j.m_value.number_integer >= -24)
-                {
-                    write_number(static_cast<uint8_t>(0x20 + positive_number));
-                }
-                else if (positive_number <= (std::numeric_limits<uint8_t>::max)())
-                {
-                    o << static_cast<CharType>(0x38);
-                    write_number(static_cast<uint8_t>(positive_number));
-                }
-                else if (positive_number <= (std::numeric_limits<uint16_t>::max)())
-                {
-                    o << static_cast<CharType>(0x39);
-                    write_number(static_cast<uint16_t>(positive_number));
-                }
-                else if (positive_number <= (std::numeric_limits<uint32_t>::max)())
-                {
-                    o << static_cast<CharType>(0x3A);
-                    write_number(static_cast<uint32_t>(positive_number));
-                }
-                else
-                {
-                    o << static_cast<CharType>(0x3B);
-                    write_number(static_cast<uint64_t>(positive_number));
-                }
-            }
-            break;
-        }
-
-        case value_t::number_unsigned:
-        {
-            if (j.m_value.number_unsigned <= 0x17)
-            {
-                write_number(static_cast<uint8_t>(j.m_value.number_unsigned));
-            }
-            else if (j.m_value.number_unsigned <= (std::numeric_limits<uint8_t>::max)())
-            {
-                o << static_cast<CharType>(0x18);
-                write_number(static_cast<uint8_t>(j.m_value.number_unsigned));
-            }
-            else if (j.m_value.number_unsigned <= (std::numeric_limits<uint16_t>::max)())
-            {
-                o << static_cast<CharType>(0x19);
-                write_number(static_cast<uint16_t>(j.m_value.number_unsigned));
-            }
-            else if (j.m_value.number_unsigned <= (std::numeric_limits<uint32_t>::max)())
-            {
-                o << static_cast<CharType>(0x1A);
-                write_number(static_cast<uint32_t>(j.m_value.number_unsigned));
-            }
-            else
-            {
-                o << static_cast<CharType>(0x1B);
-                write_number(static_cast<uint64_t>(j.m_value.number_unsigned));
-            }
-            break;
-        }
-
-        case value_t::number_float:
-        {
-            o << get_cbor_float_prefix(j.m_value.number_float);
-            write_number(j.m_value.number_float);
-            break;
-        }
-
-        case value_t::string:
-        {
-            write_cbor_string(*j.m_value.string);
-            break;
-        }
-
-        case value_t::array:
-        {
-            // step 1: write control byte and the array size
-            const auto N = j.m_value.array->size();
-            if (N <= 0x17)
-            {
-                write_number(static_cast<uint8_t>(0x80 + N));
-            }
-            else if (N <= (std::numeric_limits<uint8_t>::max)())
-            {
-                o << static_cast<CharType>(0x98);
-                write_number(static_cast<uint8_t>(N));
-            }
-            else if (N <= (std::numeric_limits<uint16_t>::max)())
-            {
-                o << static_cast<CharType>(0x99);
-                write_number(static_cast<uint16_t>(N));
-            }
-            else if (N <= (std::numeric_limits<uint32_t>::max)())
-            {
-                o << static_cast<CharType>(0x9A);
-                write_number(static_cast<uint32_t>(N));
-            }
-            // LCOV_EXCL_START
-            else if (N <= (std::numeric_limits<uint64_t>::max)())
-            {
-                o << static_cast<CharType>(0x9B);
-                write_number(static_cast<uint64_t>(N));
-            }
-            // LCOV_EXCL_STOP
-
-            // step 2: write each element
-            for (const auto& el : *j.m_value.array)
-            {
-                write_cbor(el);
-            }
-            break;
-        }
-
-        case value_t::object:
-        {
-            // step 1: write control byte and the object size
-            const auto N = j.m_value.object->size();
-            if (N <= 0x17)
-            {
-                write_number(static_cast<uint8_t>(0xA0 + N));
-            }
-            else if (N <= (std::numeric_limits<uint8_t>::max)())
-            {
-                o << static_cast<CharType>(0xB8);
-                write_number(static_cast<uint8_t>(N));
-            }
-            else if (N <= (std::numeric_limits<uint16_t>::max)())
-            {
-                o << static_cast<CharType>(0xB9);
-                write_number(static_cast<uint16_t>(N));
-            }
-            else if (N <= (std::numeric_limits<uint32_t>::max)())
-            {
-                o << static_cast<CharType>(0xBA);
-                write_number(static_cast<uint32_t>(N));
-            }
-            // LCOV_EXCL_START
-            else /*if (N <= (std::numeric_limits<uint64_t>::max)())*/
-            {
-                o << static_cast<CharType>(0xBB);
-                write_number(static_cast<uint64_t>(N));
-            }
-            // LCOV_EXCL_STOP
-
-            // step 2: write each element
-            for (const auto& el : *j.m_value.object)
-            {
-                write_cbor_string(el.first());
-                write_cbor(el.second);
-            }
-            break;
-        }
-
-        default:
-            break;
-    }
-}
-
-void json::binary_writer::write_msgpack(const json& j)
-{
-    switch (j.type())
-    {
-        case value_t::null: // nil
-        {
-            o << static_cast<CharType>(0xC0);
-            break;
-        }
-
-        case value_t::boolean: // true and false
-        {
-            o << static_cast<CharType>(j.m_value.boolean ? 0xC3 : 0xC2);
-            break;
-        }
-
-        case value_t::number_integer:
-        {
-            if (j.m_value.number_integer >= 0)
-            {
-                // MessagePack does not differentiate between positive
-                // signed integers and unsigned integers. Therefore, we used
-                // the code from the value_t::number_unsigned case here.
-                if (j.m_value.number_unsigned < 128)
-                {
-                    // positive fixnum
-                    write_number(static_cast<uint8_t>(j.m_value.number_integer));
-                }
-                else if (j.m_value.number_unsigned <= (std::numeric_limits<uint8_t>::max)())
-                {
-                    // uint 8
-                    o << static_cast<CharType>(0xCC);
-                    write_number(static_cast<uint8_t>(j.m_value.number_integer));
-                }
-                else if (j.m_value.number_unsigned <= (std::numeric_limits<uint16_t>::max)())
-                {
-                    // uint 16
-                    o << static_cast<CharType>(0xCD);
-                    write_number(static_cast<uint16_t>(j.m_value.number_integer));
-                }
-                else if (j.m_value.number_unsigned <= (std::numeric_limits<uint32_t>::max)())
-                {
-                    // uint 32
-                    o << static_cast<CharType>(0xCE);
-                    write_number(static_cast<uint32_t>(j.m_value.number_integer));
-                }
-                else if (j.m_value.number_unsigned <= (std::numeric_limits<uint64_t>::max)())
-                {
-                    // uint 64
-                    o << static_cast<CharType>(0xCF);
-                    write_number(static_cast<uint64_t>(j.m_value.number_integer));
-                }
-            }
-            else
-            {
-                if (j.m_value.number_integer >= -32)
-                {
-                    // negative fixnum
-                    write_number(static_cast<int8_t>(j.m_value.number_integer));
-                }
-                else if (j.m_value.number_integer >= (std::numeric_limits<int8_t>::min)() and
-                         j.m_value.number_integer <= (std::numeric_limits<int8_t>::max)())
-                {
-                    // int 8
-                    o << static_cast<CharType>(0xD0);
-                    write_number(static_cast<int8_t>(j.m_value.number_integer));
-                }
-                else if (j.m_value.number_integer >= (std::numeric_limits<int16_t>::min)() and
-                         j.m_value.number_integer <= (std::numeric_limits<int16_t>::max)())
-                {
-                    // int 16
-                    o << static_cast<CharType>(0xD1);
-                    write_number(static_cast<int16_t>(j.m_value.number_integer));
-                }
-                else if (j.m_value.number_integer >= (std::numeric_limits<int32_t>::min)() and
-                         j.m_value.number_integer <= (std::numeric_limits<int32_t>::max)())
-                {
-                    // int 32
-                    o << static_cast<CharType>(0xD2);
-                    write_number(static_cast<int32_t>(j.m_value.number_integer));
-                }
-                else if (j.m_value.number_integer >= (std::numeric_limits<int64_t>::min)() and
-                         j.m_value.number_integer <= (std::numeric_limits<int64_t>::max)())
-                {
-                    // int 64
-                    o << static_cast<CharType>(0xD3);
-                    write_number(static_cast<int64_t>(j.m_value.number_integer));
-                }
-            }
-            break;
-        }
-
-        case value_t::number_unsigned:
-        {
-            if (j.m_value.number_unsigned < 128)
-            {
-                // positive fixnum
-                write_number(static_cast<uint8_t>(j.m_value.number_integer));
-            }
-            else if (j.m_value.number_unsigned <= (std::numeric_limits<uint8_t>::max)())
-            {
-                // uint 8
-                o << static_cast<CharType>(0xCC);
-                write_number(static_cast<uint8_t>(j.m_value.number_integer));
-            }
-            else if (j.m_value.number_unsigned <= (std::numeric_limits<uint16_t>::max)())
-            {
-                // uint 16
-                o << static_cast<CharType>(0xCD);
-                write_number(static_cast<uint16_t>(j.m_value.number_integer));
-            }
-            else if (j.m_value.number_unsigned <= (std::numeric_limits<uint32_t>::max)())
-            {
-                // uint 32
-                o << static_cast<CharType>(0xCE);
-                write_number(static_cast<uint32_t>(j.m_value.number_integer));
-            }
-            else if (j.m_value.number_unsigned <= (std::numeric_limits<uint64_t>::max)())
-            {
-                // uint 64
-                o << static_cast<CharType>(0xCF);
-                write_number(static_cast<uint64_t>(j.m_value.number_integer));
-            }
-            break;
-        }
-
-        case value_t::number_float:
-        {
-            o << get_msgpack_float_prefix(j.m_value.number_float);
-            write_number(j.m_value.number_float);
-            break;
-        }
-
-        case value_t::string:
-        {
-            write_msgpack_string(*j.m_value.string);
-            break;
-        }
-
-        case value_t::array:
-        {
-            // step 1: write control byte and the array size
-            const auto N = j.m_value.array->size();
-            if (N <= 15)
-            {
-                // fixarray
-                write_number(static_cast<uint8_t>(0x90 | N));
-            }
-            else if (N <= (std::numeric_limits<uint16_t>::max)())
-            {
-                // array 16
-                o << static_cast<CharType>(0xDC);
-                write_number(static_cast<uint16_t>(N));
-            }
-            else if (N <= (std::numeric_limits<uint32_t>::max)())
-            {
-                // array 32
-                o << static_cast<CharType>(0xDD);
-                write_number(static_cast<uint32_t>(N));
-            }
-
-            // step 2: write each element
-            for (const auto& el : *j.m_value.array)
-            {
-                write_msgpack(el);
-            }
-            break;
-        }
-
-        case value_t::object:
-        {
-            // step 1: write control byte and the object size
-            const auto N = j.m_value.object->size();
-            if (N <= 15)
-            {
-                // fixmap
-                write_number(static_cast<uint8_t>(0x80 | (N & 0xF)));
-            }
-            else if (N <= (std::numeric_limits<uint16_t>::max)())
-            {
-                // map 16
-                o << static_cast<CharType>(0xDE);
-                write_number(static_cast<uint16_t>(N));
-            }
-            else if (N <= (std::numeric_limits<uint32_t>::max)())
-            {
-                // map 32
-                o << static_cast<CharType>(0xDF);
-                write_number(static_cast<uint32_t>(N));
-            }
-
-            // step 2: write each element
-            for (const auto& el : *j.m_value.object)
-            {
-                write_msgpack_string(el.first());
-                write_msgpack(el.second);
-            }
-            break;
-        }
-
-        default:
-            break;
-    }
-}
-
-void json::binary_writer::write_ubjson(const json& j, const bool use_count,
-                  const bool use_type, const bool add_prefix)
-{
-    switch (j.type())
-    {
-        case value_t::null:
-        {
-            if (add_prefix)
-            {
-                o << static_cast<CharType>('Z');
-            }
-            break;
-        }
-
-        case value_t::boolean:
-        {
-            if (add_prefix)
-                o << static_cast<CharType>(j.m_value.boolean ? 'T' : 'F');
-            break;
-        }
-
-        case value_t::number_integer:
-        {
-            write_number_with_ubjson_prefix(j.m_value.number_integer, add_prefix);
-            break;
-        }
-
-        case value_t::number_unsigned:
-        {
-            write_number_with_ubjson_prefix(j.m_value.number_unsigned, add_prefix);
-            break;
-        }
-
-        case value_t::number_float:
-        {
-            write_number_with_ubjson_prefix(j.m_value.number_float, add_prefix);
-            break;
-        }
-
-        case value_t::string:
-        {
-            if (add_prefix)
-            {
-                o << static_cast<CharType>('S');
-            }
-            write_number_with_ubjson_prefix(j.m_value.string->size(), true);
-            o << *j.m_value.string;
-            break;
-        }
-
-        case value_t::array:
-        {
-            if (add_prefix)
-            {
-                o << static_cast<CharType>('[');
-            }
-
-            bool prefix_required = true;
-            if (use_type and not j.m_value.array->empty())
-            {
-                assert(use_count);
-                const CharType first_prefix = ubjson_prefix(j.front());
-                const bool same_prefix = std::all_of(j.begin() + 1, j.end(),
-                                                     [this, first_prefix](const json & v)
-                {
-                    return ubjson_prefix(v) == first_prefix;
-                });
-
-                if (same_prefix)
-                {
-                    prefix_required = false;
-                    o << static_cast<CharType>('$');
-                    o << first_prefix;
-                }
-            }
-
-            if (use_count)
-            {
-                o << static_cast<CharType>('#');
-                write_number_with_ubjson_prefix(j.m_value.array->size(), true);
-            }
-
-            for (const auto& el : *j.m_value.array)
-            {
-                write_ubjson(el, use_count, use_type, prefix_required);
-            }
-
-            if (not use_count)
-            {
-                o << static_cast<CharType>(']');
-            }
-
-            break;
-        }
-
-        case value_t::object:
-        {
-            if (add_prefix)
-            {
-                o << static_cast<CharType>('{');
-            }
-
-            bool prefix_required = true;
-            if (use_type and not j.m_value.object->empty())
-            {
-                assert(use_count);
-                const CharType first_prefix = ubjson_prefix(j.front());
-                const bool same_prefix = std::all_of(j.begin(), j.end(),
-                                                     [this, first_prefix](const json & v)
-                {
-                    return ubjson_prefix(v) == first_prefix;
-                });
-
-                if (same_prefix)
-                {
-                    prefix_required = false;
-                    o << static_cast<CharType>('$');
-                    o << first_prefix;
-                }
-            }
-
-            if (use_count)
-            {
-                o << static_cast<CharType>('#');
-                write_number_with_ubjson_prefix(j.m_value.object->size(), true);
-            }
-
-            for (const auto& el : *j.m_value.object)
-            {
-                write_number_with_ubjson_prefix(el.first().size(), true);
-                o << el.first();
-                write_ubjson(el.second, use_count, use_type, prefix_required);
-            }
-
-            if (not use_count)
-            {
-                o << static_cast<CharType>('}');
-            }
-
-            break;
-        }
-
-        default:
-            break;
-    }
-}
-
-void json::binary_writer::write_cbor_string(std::string_view str)
-{
-    // step 1: write control byte and the string length
-    const auto N = str.size();
-    if (N <= 0x17)
-    {
-        write_number(static_cast<uint8_t>(0x60 + N));
-    }
-    else if (N <= (std::numeric_limits<uint8_t>::max)())
-    {
-        o << static_cast<CharType>(0x78);
-        write_number(static_cast<uint8_t>(N));
-    }
-    else if (N <= (std::numeric_limits<uint16_t>::max)())
-    {
-        o << static_cast<CharType>(0x79);
-        write_number(static_cast<uint16_t>(N));
-    }
-    else if (N <= (std::numeric_limits<uint32_t>::max)())
-    {
-        o << static_cast<CharType>(0x7A);
-        write_number(static_cast<uint32_t>(N));
-    }
-    // LCOV_EXCL_START
-    else if (N <= (std::numeric_limits<uint64_t>::max)())
-    {
-        o << static_cast<CharType>(0x7B);
-        write_number(static_cast<uint64_t>(N));
-    }
-    // LCOV_EXCL_STOP
-
-    // step 2: write the string
-    o << str;
-}
-
-void json::binary_writer::write_msgpack_string(std::string_view str)
-{
-    // step 1: write control byte and the string length
-    const auto N = str.size();
-    if (N <= 31)
-    {
-        // fixstr
-        write_number(static_cast<uint8_t>(0xA0 | N));
-    }
-    else if (N <= (std::numeric_limits<uint8_t>::max)())
-    {
-        // str 8
-        o << static_cast<CharType>(0xD9);
-        write_number(static_cast<uint8_t>(N));
-    }
-    else if (N <= (std::numeric_limits<uint16_t>::max)())
-    {
-        // str 16
-        o << static_cast<CharType>(0xDA);
-        write_number(static_cast<uint16_t>(N));
-    }
-    else if (N <= (std::numeric_limits<uint32_t>::max)())
-    {
-        // str 32
-        o << static_cast<CharType>(0xDB);
-        write_number(static_cast<uint32_t>(N));
-    }
-
-    // step 2: write the string
-    o << str;
-}
-
-template<typename NumberType>
-void json::binary_writer::write_number(const NumberType n)
-{
-    // step 1: write number to array of length NumberType
-    std::array<uint8_t, sizeof(NumberType)> vec;
-    std::memcpy(vec.data(), &n, sizeof(NumberType));
-
-    // step 2: write array to output (with possible reordering)
-    if (is_little_endian)
-    {
-        // reverse byte order prior to conversion if necessary
-        std::reverse(vec.begin(), vec.end());
-    }
-
-    o << span{vec.data(), sizeof(NumberType)};
-}
-
-template<typename NumberType, typename std::enable_if<
-             std::is_unsigned<NumberType>::value, int>::type>
-void json::binary_writer::write_number_with_ubjson_prefix(const NumberType n,
-                                     const bool add_prefix)
-{
-    if (n <= static_cast<uint64_t>((std::numeric_limits<int8_t>::max)()))
-    {
-        if (add_prefix)
-        {
-            o << static_cast<CharType>('i');  // int8
-        }
-        write_number(static_cast<uint8_t>(n));
-    }
-    else if (n <= (std::numeric_limits<uint8_t>::max)())
-    {
-        if (add_prefix)
-        {
-            o << static_cast<CharType>('U');  // uint8
-        }
-        write_number(static_cast<uint8_t>(n));
-    }
-    else if (n <= static_cast<uint64_t>((std::numeric_limits<int16_t>::max)()))
-    {
-        if (add_prefix)
-        {
-            o << static_cast<CharType>('I');  // int16
-        }
-        write_number(static_cast<int16_t>(n));
-    }
-    else if (n <= static_cast<uint64_t>((std::numeric_limits<int32_t>::max)()))
-    {
-        if (add_prefix)
-        {
-            o << static_cast<CharType>('l');  // int32
-        }
-        write_number(static_cast<int32_t>(n));
-    }
-    else if (n <= static_cast<uint64_t>((std::numeric_limits<int64_t>::max)()))
-    {
-        if (add_prefix)
-        {
-            o << static_cast<CharType>('L');  // int64
-        }
-        write_number(static_cast<int64_t>(n));
-    }
-    else
-    {
-        JSON_THROW(out_of_range::create(407, fmt::format("number overflow serializing {}", n)));
-    }
-}
-
-template<typename NumberType, typename std::enable_if<
-             std::is_signed<NumberType>::value and
-             not std::is_floating_point<NumberType>::value, int>::type>
-void json::binary_writer::write_number_with_ubjson_prefix(const NumberType n,
-                                     const bool add_prefix)
-{
-    if ((std::numeric_limits<int8_t>::min)() <= n and n <= (std::numeric_limits<int8_t>::max)())
-    {
-        if (add_prefix)
-        {
-            o << static_cast<CharType>('i');  // int8
-        }
-        write_number(static_cast<int8_t>(n));
-    }
-    else if (static_cast<int64_t>((std::numeric_limits<uint8_t>::min)()) <= n and n <= static_cast<int64_t>((std::numeric_limits<uint8_t>::max)()))
-    {
-        if (add_prefix)
-        {
-            o << static_cast<CharType>('U');  // uint8
-        }
-        write_number(static_cast<uint8_t>(n));
-    }
-    else if ((std::numeric_limits<int16_t>::min)() <= n and n <= (std::numeric_limits<int16_t>::max)())
-    {
-        if (add_prefix)
-        {
-            o << static_cast<CharType>('I');  // int16
-        }
-        write_number(static_cast<int16_t>(n));
-    }
-    else if ((std::numeric_limits<int32_t>::min)() <= n and n <= (std::numeric_limits<int32_t>::max)())
-    {
-        if (add_prefix)
-        {
-            o << static_cast<CharType>('l');  // int32
-        }
-        write_number(static_cast<int32_t>(n));
-    }
-    else if ((std::numeric_limits<int64_t>::min)() <= n and n <= (std::numeric_limits<int64_t>::max)())
-    {
-        if (add_prefix)
-        {
-            o << static_cast<CharType>('L');  // int64
-        }
-        write_number(static_cast<int64_t>(n));
-    }
-    // LCOV_EXCL_START
-    else
-    {
-        JSON_THROW(out_of_range::create(407, fmt::format("number overflow serializing {}", n)));
-    }
-    // LCOV_EXCL_STOP
-}
-
-json::binary_writer::CharType json::binary_writer::ubjson_prefix(const json& j) const noexcept
-{
-    switch (j.type())
-    {
-        case value_t::null:
-            return 'Z';
-
-        case value_t::boolean:
-            return j.m_value.boolean ? 'T' : 'F';
-
-        case value_t::number_integer:
-        {
-            if ((std::numeric_limits<int8_t>::min)() <= j.m_value.number_integer and j.m_value.number_integer <= (std::numeric_limits<int8_t>::max)())
-            {
-                return 'i';
-            }
-            else if ((std::numeric_limits<uint8_t>::min)() <= j.m_value.number_integer and j.m_value.number_integer <= (std::numeric_limits<uint8_t>::max)())
-            {
-                return 'U';
-            }
-            else if ((std::numeric_limits<int16_t>::min)() <= j.m_value.number_integer and j.m_value.number_integer <= (std::numeric_limits<int16_t>::max)())
-            {
-                return 'I';
-            }
-            else if ((std::numeric_limits<int32_t>::min)() <= j.m_value.number_integer and j.m_value.number_integer <= (std::numeric_limits<int32_t>::max)())
-            {
-                return 'l';
-            }
-            else  // no check and assume int64_t (see note above)
-            {
-                return 'L';
-            }
-        }
-
-        case value_t::number_unsigned:
-        {
-            if (j.m_value.number_unsigned <= static_cast<uint64_t>((std::numeric_limits<int8_t>::max)()))
-            {
-                return 'i';
-            }
-            else if (j.m_value.number_unsigned <= (std::numeric_limits<uint8_t>::max)())
-            {
-                return 'U';
-            }
-            else if (j.m_value.number_unsigned <= static_cast<uint64_t>((std::numeric_limits<int16_t>::max)()))
-            {
-                return 'I';
-            }
-            else if (j.m_value.number_unsigned <= static_cast<uint64_t>((std::numeric_limits<int32_t>::max)()))
-            {
-                return 'l';
-            }
-            else  // no check and assume int64_t (see note above)
-            {
-                return 'L';
-            }
-        }
-
-        case value_t::number_float:
-            return get_ubjson_float_prefix(j.m_value.number_float);
-
-        case value_t::string:
-            return 'S';
-
-        case value_t::array:
-            return '[';
-
-        case value_t::object:
-            return '{';
-
-        default:  // discarded values
-            return 'N';
-    }
-}
-
-std::vector<uint8_t> json::to_cbor(const json& j)
-{
-    std::vector<uint8_t> result;
-    raw_uvector_ostream os(result);
-    to_cbor(os, j);
-    return result;
-}
-
-span<uint8_t> json::to_cbor(const json& j, std::vector<uint8_t>& buf)
-{
-    buf.clear();
-    raw_uvector_ostream os(buf);
-    to_cbor(os, j);
-    return os.array();
-}
-
-span<uint8_t> json::to_cbor(const json& j, SmallVectorImpl<uint8_t>& buf)
-{
-    buf.clear();
-    raw_usvector_ostream os(buf);
-    to_cbor(os, j);
-    return os.array();
-}
-
-void json::to_cbor(raw_ostream& os, const json& j)
-{
-    binary_writer(os).write_cbor(j);
-}
-
-std::vector<uint8_t> json::to_msgpack(const json& j)
-{
-    std::vector<uint8_t> result;
-    raw_uvector_ostream os(result);
-    to_msgpack(os, j);
-    return result;
-}
-
-span<uint8_t> json::to_msgpack(const json& j, std::vector<uint8_t>& buf)
-{
-    buf.clear();
-    raw_uvector_ostream os(buf);
-    to_msgpack(os, j);
-    return os.array();
-}
-
-span<uint8_t> json::to_msgpack(const json& j, SmallVectorImpl<uint8_t>& buf)
-{
-    buf.clear();
-    raw_usvector_ostream os(buf);
-    to_msgpack(os, j);
-    return os.array();
-}
-
-void json::to_msgpack(raw_ostream& os, const json& j)
-{
-    binary_writer(os).write_msgpack(j);
-}
-
-std::vector<uint8_t> json::to_ubjson(const json& j,
-                                     const bool use_size,
-                                     const bool use_type)
-{
-    std::vector<uint8_t> result;
-    raw_uvector_ostream os(result);
-    to_ubjson(os, j, use_size, use_type);
-    return result;
-}
-
-span<uint8_t> json::to_ubjson(const json& j, std::vector<uint8_t>& buf,
-                              const bool use_size, const bool use_type)
-{
-    buf.clear();
-    raw_uvector_ostream os(buf);
-    to_ubjson(os, j, use_size, use_type);
-    return os.array();
-}
-
-span<uint8_t> json::to_ubjson(const json& j, SmallVectorImpl<uint8_t>& buf,
-                              const bool use_size, const bool use_type)
-{
-    buf.clear();
-    raw_usvector_ostream os(buf);
-    to_ubjson(os, j, use_size, use_type);
-    return os.array();
-}
-
-void json::to_ubjson(raw_ostream& os, const json& j,
-                     const bool use_size, const bool use_type)
-{
-    binary_writer(os).write_ubjson(j, use_size, use_type);
-}
-
-}  // namespace wpi
diff --git a/wpiutil/src/main/native/cpp/json_parser.cpp b/wpiutil/src/main/native/cpp/json_parser.cpp
deleted file mode 100644
index db1ed11..0000000
--- a/wpiutil/src/main/native/cpp/json_parser.cpp
+++ /dev/null
@@ -1,1968 +0,0 @@
-/*----------------------------------------------------------------------------*/
-/* Modifications 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.                                                               */
-/*----------------------------------------------------------------------------*/
-/*
-    __ _____ _____ _____
- __|  |   __|     |   | |  JSON for Modern C++
-|  |  |__   |  |  | | | |  version 3.1.2
-|_____|_____|_____|_|___|  https://github.com/nlohmann/json
-
-Licensed under the MIT License <http://opensource.org/licenses/MIT>.
-Copyright (c) 2013-2018 Niels Lohmann <http://nlohmann.me>.
-
-Permission is hereby  granted, free of charge, to any  person obtaining a copy
-of this software and associated  documentation files (the "Software"), to deal
-in the Software  without restriction, including without  limitation the rights
-to  use, copy,  modify, merge,  publish, distribute,  sublicense, and/or  sell
-copies  of  the Software,  and  to  permit persons  to  whom  the Software  is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-THE SOFTWARE  IS PROVIDED "AS  IS", WITHOUT WARRANTY  OF ANY KIND,  EXPRESS OR
-IMPLIED,  INCLUDING BUT  NOT  LIMITED TO  THE  WARRANTIES OF  MERCHANTABILITY,
-FITNESS FOR  A PARTICULAR PURPOSE AND  NONINFRINGEMENT. IN NO EVENT  SHALL THE
-AUTHORS  OR COPYRIGHT  HOLDERS  BE  LIABLE FOR  ANY  CLAIM,  DAMAGES OR  OTHER
-LIABILITY, WHETHER IN AN ACTION OF  CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE  OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
-*/
-#define WPI_JSON_IMPLEMENTATION
-#include "wpi/json.h"
-
-#include <clocale>
-#include <cmath>
-#include <cstdlib>
-
-#include "fmt/format.h"
-#include "wpi/SmallString.h"
-#include "wpi/raw_istream.h"
-#include "wpi/raw_ostream.h"
-
-namespace wpi {
-
-/*!
-@brief lexical analysis
-
-This class organizes the lexical analysis during JSON deserialization.
-*/
-class json::lexer
-{
-  public:
-    /// token types for the parser
-    enum class token_type
-    {
-        uninitialized,    ///< indicating the scanner is uninitialized
-        literal_true,     ///< the `true` literal
-        literal_false,    ///< the `false` literal
-        literal_null,     ///< the `null` literal
-        value_string,     ///< a string -- use get_string() for actual value
-        value_unsigned,   ///< an unsigned integer -- use get_number_unsigned() for actual value
-        value_integer,    ///< a signed integer -- use get_number_integer() for actual value
-        value_float,      ///< an floating point number -- use get_number_float() for actual value
-        begin_array,      ///< the character for array begin `[`
-        begin_object,     ///< the character for object begin `{`
-        end_array,        ///< the character for array end `]`
-        end_object,       ///< the character for object end `}`
-        name_separator,   ///< the name separator `:`
-        value_separator,  ///< the value separator `,`
-        parse_error,      ///< indicating a parse error
-        end_of_input,     ///< indicating the end of the input buffer
-        literal_or_value  ///< a literal or the begin of a value (only for diagnostics)
-    };
-
-    /// return name of values of type token_type (only used for errors)
-    static const char* token_type_name(const token_type t) noexcept;
-
-    explicit lexer(raw_istream& s);
-
-    // delete because of pointer members
-    lexer(const lexer&) = delete;
-    lexer& operator=(lexer&) = delete;
-
-  private:
-    /////////////////////
-    // locales
-    /////////////////////
-
-    /// return the locale-dependent decimal point
-    static char get_decimal_point() noexcept
-    {
-        const auto loc = localeconv();
-        assert(loc != nullptr);
-        return (loc->decimal_point == nullptr) ? '.' : *(loc->decimal_point);
-    }
-
-    /////////////////////
-    // scan functions
-    /////////////////////
-
-    /*!
-    @brief get codepoint from 4 hex characters following `\u`
-
-    For input "\u c1 c2 c3 c4" the codepoint is:
-      (c1 * 0x1000) + (c2 * 0x0100) + (c3 * 0x0010) + c4
-    = (c1 << 12) + (c2 << 8) + (c3 << 4) + (c4 << 0)
-
-    Furthermore, the possible characters '0'..'9', 'A'..'F', and 'a'..'f'
-    must be converted to the integers 0x0..0x9, 0xA..0xF, 0xA..0xF, resp. The
-    conversion is done by subtracting the offset (0x30, 0x37, and 0x57)
-    between the ASCII value of the character and the desired integer value.
-
-    @return codepoint (0x0000..0xFFFF) or -1 in case of an error (e.g. EOF or
-            non-hex character)
-    */
-    int get_codepoint();
-
-    /*!
-    @brief check if the next byte(s) are inside a given range
-
-    Adds the current byte and, for each passed range, reads a new byte and
-    checks if it is inside the range. If a violation was detected, set up an
-    error message and return false. Otherwise, return true.
-
-    @param[in] ranges  list of integers; interpreted as list of pairs of
-                       inclusive lower and upper bound, respectively
-
-    @pre The passed list @a ranges must have 2, 4, or 6 elements; that is,
-         1, 2, or 3 pairs. This precondition is enforced by an assertion.
-
-    @return true if and only if no range violation was detected
-    */
-    bool next_byte_in_range(std::initializer_list<int> ranges)
-    {
-        assert(ranges.size() == 2 or ranges.size() == 4 or ranges.size() == 6);
-        add(current);
-
-        for (auto range = ranges.begin(); range != ranges.end(); ++range)
-        {
-            get();
-            if (JSON_LIKELY(*range <= current and current <= *(++range)))
-            {
-                add(current);
-            }
-            else
-            {
-                error_message = "invalid string: ill-formed UTF-8 byte";
-                return false;
-            }
-        }
-
-        return true;
-    }
-
-    /*!
-    @brief scan a string literal
-
-    This function scans a string according to Sect. 7 of RFC 7159. While
-    scanning, bytes are escaped and copied into buffer token_buffer. Then the
-    function returns successfully, token_buffer is *not* null-terminated (as it
-    may contain \0 bytes), and token_buffer.size() is the number of bytes in the
-    string.
-
-    @return token_type::value_string if string could be successfully scanned,
-            token_type::parse_error otherwise
-
-    @note In case of errors, variable error_message contains a textual
-          description.
-    */
-    token_type scan_string();
-
-    static void strtof(float& f, const char* str, char** endptr) noexcept
-    {
-        f = std::strtof(str, endptr);
-    }
-
-    static void strtof(double& f, const char* str, char** endptr) noexcept
-    {
-        f = std::strtod(str, endptr);
-    }
-
-    static void strtof(long double& f, const char* str, char** endptr) noexcept
-    {
-        f = std::strtold(str, endptr);
-    }
-
-    /*!
-    @brief scan a number literal
-
-    This function scans a string according to Sect. 6 of RFC 7159.
-
-    The function is realized with a deterministic finite state machine derived
-    from the grammar described in RFC 7159. Starting in state "init", the
-    input is read and used to determined the next state. Only state "done"
-    accepts the number. State "error" is a trap state to model errors. In the
-    table below, "anything" means any character but the ones listed before.
-
-    state    | 0        | 1-9      | e E      | +       | -       | .        | anything
-    ---------|----------|----------|----------|---------|---------|----------|-----------
-    init     | zero     | any1     | [error]  | [error] | minus   | [error]  | [error]
-    minus    | zero     | any1     | [error]  | [error] | [error] | [error]  | [error]
-    zero     | done     | done     | exponent | done    | done    | decimal1 | done
-    any1     | any1     | any1     | exponent | done    | done    | decimal1 | done
-    decimal1 | decimal2 | [error]  | [error]  | [error] | [error] | [error]  | [error]
-    decimal2 | decimal2 | decimal2 | exponent | done    | done    | done     | done
-    exponent | any2     | any2     | [error]  | sign    | sign    | [error]  | [error]
-    sign     | any2     | any2     | [error]  | [error] | [error] | [error]  | [error]
-    any2     | any2     | any2     | done     | done    | done    | done     | done
-
-    The state machine is realized with one label per state (prefixed with
-    "scan_number_") and `goto` statements between them. The state machine
-    contains cycles, but any cycle can be left when EOF is read. Therefore,
-    the function is guaranteed to terminate.
-
-    During scanning, the read bytes are stored in token_buffer. This string is
-    then converted to a signed integer, an unsigned integer, or a
-    floating-point number.
-
-    @return token_type::value_unsigned, token_type::value_integer, or
-            token_type::value_float if number could be successfully scanned,
-            token_type::parse_error otherwise
-
-    @note The scanner is independent of the current locale. Internally, the
-          locale's decimal point is used instead of `.` to work with the
-          locale-dependent converters.
-    */
-    token_type scan_number();
-
-    /*!
-    @param[in] literal_text  the literal text to expect
-    @param[in] length        the length of the passed literal text
-    @param[in] return_type   the token type to return on success
-    */
-    token_type scan_literal(const char* literal_text, const std::size_t length,
-                            token_type return_type);
-
-    /////////////////////
-    // input management
-    /////////////////////
-
-    /// reset token_buffer; current character is beginning of token
-    void reset() noexcept
-    {
-        token_buffer.clear();
-        token_string.clear();
-        token_string.push_back(std::char_traits<char>::to_char_type(current));
-    }
-
-    /*
-    @brief get next character from the input
-
-    This function provides the interface to the used input adapter. It does
-    not throw in case the input reached EOF, but returns a
-    `std::char_traits<char>::eof()` in that case.  Stores the scanned characters
-    for use in error messages.
-
-    @return character read from the input
-    */
-    std::char_traits<char>::int_type get()
-    {
-        ++chars_read;
-        if (JSON_UNLIKELY(!unget_chars.empty()))
-        {
-            current = unget_chars.back();
-            unget_chars.pop_back();
-            token_string.push_back(current);
-            return current;
-        }
-        char c;
-        is.read(c);
-        if (JSON_UNLIKELY(is.has_error()))
-        {
-            current = std::char_traits<char>::eof();
-        }
-        else
-        {
-            current = std::char_traits<char>::to_int_type(c);
-            token_string.push_back(c);
-        }
-        return current;
-    }
-
-    /// unget current character (return it again on next get)
-    void unget()
-    {
-        --chars_read;
-        if (JSON_LIKELY(current != std::char_traits<char>::eof()))
-        {
-            unget_chars.emplace_back(current);
-            assert(token_string.size() != 0);
-            token_string.pop_back();
-            if (!token_string.empty())
-            {
-                current = token_string.back();
-            }
-        }
-    }
-
-    /// put back character (returned on next get)
-    void putback(std::char_traits<char>::int_type c)
-    {
-        --chars_read;
-        unget_chars.emplace_back(c);
-    }
-
-    /// add a character to token_buffer
-    void add(int c)
-    {
-        token_buffer.push_back(std::char_traits<char>::to_char_type(c));
-    }
-
-  public:
-    /////////////////////
-    // value getters
-    /////////////////////
-
-    /// return integer value
-    int64_t get_number_integer() const noexcept
-    {
-        return value_integer;
-    }
-
-    /// return unsigned integer value
-    uint64_t get_number_unsigned() const noexcept
-    {
-        return value_unsigned;
-    }
-
-    /// return floating-point value
-    double get_number_float() const noexcept
-    {
-        return value_float;
-    }
-
-    /// return current string value
-    std::string_view get_string()
-    {
-        return token_buffer;
-    }
-
-    /////////////////////
-    // diagnostics
-    /////////////////////
-
-    /// return position of last read token
-    std::size_t get_position() const noexcept
-    {
-        return chars_read;
-    }
-
-    /// return the last read token (for errors only).  Will never contain EOF
-    /// (an arbitrary value that is not a valid char value, often -1), because
-    /// 255 may legitimately occur.  May contain NUL, which should be escaped.
-    std::string get_token_string() const;
-
-    /// return syntax error message
-    const char* get_error_message() const noexcept
-    {
-        return error_message;
-    }
-
-    /////////////////////
-    // actual scanner
-    /////////////////////
-
-    token_type scan();
-
-  private:
-    /// input adapter
-    raw_istream& is;
-
-    /// the current character
-    std::char_traits<char>::int_type current = std::char_traits<char>::eof();
-
-    /// unget characters
-    SmallVector<std::char_traits<char>::int_type, 4> unget_chars;
-
-    /// the number of characters read
-    std::size_t chars_read = 0;
-
-    /// raw input token string (for error messages)
-    SmallString<128> token_string {};
-
-    /// buffer for variable-length tokens (numbers, strings)
-    SmallString<128> token_buffer {};
-
-    /// a description of occurred lexer errors
-    const char* error_message = "";
-
-    // number values
-    int64_t value_integer = 0;
-    uint64_t value_unsigned = 0;
-    double value_float = 0;
-
-    /// the decimal point
-    const char decimal_point_char = '.';
-};
-
-////////////
-// parser //
-////////////
-
-/*!
-@brief syntax analysis
-
-This class implements a recursive decent parser.
-*/
-class json::parser
-{
-    using lexer_t = json::lexer;
-    using token_type = typename lexer_t::token_type;
-
-  public:
-    /// a parser reading from an input adapter
-    explicit parser(raw_istream& s,
-                    const parser_callback_t cb = nullptr,
-                    const bool allow_exceptions_ = true)
-        : callback(cb), m_lexer(s), allow_exceptions(allow_exceptions_)
-    {}
-
-    /*!
-    @brief public parser interface
-
-    @param[in] strict      whether to expect the last token to be EOF
-    @param[in,out] result  parsed JSON value
-
-    @throw parse_error.101 in case of an unexpected token
-    @throw parse_error.102 if to_unicode fails or surrogate error
-    @throw parse_error.103 if to_unicode fails
-    */
-    void parse(const bool strict, json& result);
-
-    /*!
-    @brief public accept interface
-
-    @param[in] strict  whether to expect the last token to be EOF
-    @return whether the input is a proper JSON text
-    */
-    bool accept(const bool strict = true)
-    {
-        // read first token
-        get_token();
-
-        if (not accept_internal())
-        {
-            return false;
-        }
-
-        // strict => last token must be EOF
-        return not strict or (get_token() == token_type::end_of_input);
-    }
-
-  private:
-    /*!
-    @brief the actual parser
-    @throw parse_error.101 in case of an unexpected token
-    @throw parse_error.102 if to_unicode fails or surrogate error
-    @throw parse_error.103 if to_unicode fails
-    */
-    void parse_internal(bool keep, json& result);
-
-    /*!
-    @brief the actual acceptor
-
-    @invariant 1. The last token is not yet processed. Therefore, the caller
-                  of this function must make sure a token has been read.
-               2. When this function returns, the last token is processed.
-                  That is, the last read character was already considered.
-
-    This invariant makes sure that no token needs to be "unput".
-    */
-    bool accept_internal();
-
-    /// get next token from lexer
-    token_type get_token()
-    {
-        return (last_token = m_lexer.scan());
-    }
-
-    /*!
-    @throw parse_error.101 if expected token did not occur
-    */
-    bool expect(token_type t)
-    {
-        if (JSON_UNLIKELY(t != last_token))
-        {
-            errored = true;
-            expected = t;
-            if (allow_exceptions)
-            {
-                throw_exception();
-            }
-            else
-            {
-                return false;
-            }
-        }
-
-        return true;
-    }
-
-    [[noreturn]] void throw_exception() const;
-
-  private:
-    /// current level of recursion
-    int depth = 0;
-    /// callback function
-    const parser_callback_t callback = nullptr;
-    /// the type of the last read token
-    token_type last_token = token_type::uninitialized;
-    /// the lexer
-    lexer_t m_lexer;
-    /// whether a syntax error occurred
-    bool errored = false;
-    /// possible reason for the syntax error
-    token_type expected = token_type::uninitialized;
-    /// whether to throw exceptions in case of errors
-    const bool allow_exceptions = true;
-};
-
-const char* json::lexer::token_type_name(const token_type t) noexcept
-{
-    switch (t)
-    {
-        case token_type::uninitialized:
-            return "<uninitialized>";
-        case token_type::literal_true:
-            return "true literal";
-        case token_type::literal_false:
-            return "false literal";
-        case token_type::literal_null:
-            return "null literal";
-        case token_type::value_string:
-            return "string literal";
-        case lexer::token_type::value_unsigned:
-        case lexer::token_type::value_integer:
-        case lexer::token_type::value_float:
-            return "number literal";
-        case token_type::begin_array:
-            return "'['";
-        case token_type::begin_object:
-            return "'{'";
-        case token_type::end_array:
-            return "']'";
-        case token_type::end_object:
-            return "'}'";
-        case token_type::name_separator:
-            return "':'";
-        case token_type::value_separator:
-            return "','";
-        case token_type::parse_error:
-            return "<parse error>";
-        case token_type::end_of_input:
-            return "end of input";
-        case token_type::literal_or_value:
-            return "'[', '{', or a literal";
-        default: // catch non-enum values
-            return "unknown token"; // LCOV_EXCL_LINE
-    }
-}
-
-json::lexer::lexer(raw_istream& s)
-    : is(s), decimal_point_char(get_decimal_point())
-{
-    // skip byte order mark
-    std::char_traits<char>::int_type c;
-    if ((c = get()) == 0xEF)
-    {
-        if ((c = get()) == 0xBB)
-        {
-            if ((c = get()) == 0xBF)
-            {
-                chars_read = 0;
-                return; // Ignore BOM
-            }
-            else if (c != std::char_traits<char>::eof())
-            {
-                unget();
-            }
-            putback('\xBB');
-        }
-        else if (c != std::char_traits<char>::eof())
-        {
-            unget();
-        }
-        putback('\xEF');
-    }
-    unget(); // no byte order mark; process as usual
-}
-
-int json::lexer::get_codepoint()
-{
-    // this function only makes sense after reading `\u`
-    assert(current == 'u');
-    int codepoint = 0;
-
-    const auto factors = { 12, 8, 4, 0 };
-    for (const auto factor : factors)
-    {
-        get();
-
-        if (current >= '0' and current <= '9')
-        {
-            codepoint += ((current - 0x30) << factor);
-        }
-        else if (current >= 'A' and current <= 'F')
-        {
-            codepoint += ((current - 0x37) << factor);
-        }
-        else if (current >= 'a' and current <= 'f')
-        {
-            codepoint += ((current - 0x57) << factor);
-        }
-        else
-        {
-            return -1;
-        }
-    }
-
-    assert(0x0000 <= codepoint and codepoint <= 0xFFFF);
-    return codepoint;
-}
-
-json::lexer::token_type json::lexer::scan_string()
-{
-    // reset token_buffer (ignore opening quote)
-    reset();
-
-    // we entered the function by reading an open quote
-    assert(current == '\"');
-
-    while (true)
-    {
-        // get next character
-        switch (get())
-        {
-            // end of file while parsing string
-            case std::char_traits<char>::eof():
-            {
-                error_message = "invalid string: missing closing quote";
-                return token_type::parse_error;
-            }
-
-            // closing quote
-            case '\"':
-            {
-                return token_type::value_string;
-            }
-
-            // escapes
-            case '\\':
-            {
-                switch (get())
-                {
-                    // quotation mark
-                    case '\"':
-                        add('\"');
-                        break;
-                    // reverse solidus
-                    case '\\':
-                        add('\\');
-                        break;
-                    // solidus
-                    case '/':
-                        add('/');
-                        break;
-                    // backspace
-                    case 'b':
-                        add('\b');
-                        break;
-                    // form feed
-                    case 'f':
-                        add('\f');
-                        break;
-                    // line feed
-                    case 'n':
-                        add('\n');
-                        break;
-                    // carriage return
-                    case 'r':
-                        add('\r');
-                        break;
-                    // tab
-                    case 't':
-                        add('\t');
-                        break;
-
-                    // unicode escapes
-                    case 'u':
-                    {
-                        const int codepoint1 = get_codepoint();
-                        int codepoint = codepoint1; // start with codepoint1
-
-                        if (JSON_UNLIKELY(codepoint1 == -1))
-                        {
-                            error_message = "invalid string: '\\u' must be followed by 4 hex digits";
-                            return token_type::parse_error;
-                        }
-
-                        // check if code point is a high surrogate
-                        if (0xD800 <= codepoint1 and codepoint1 <= 0xDBFF)
-                        {
-                            // expect next \uxxxx entry
-                            if (JSON_LIKELY(get() == '\\' and get() == 'u'))
-                            {
-                                const int codepoint2 = get_codepoint();
-
-                                if (JSON_UNLIKELY(codepoint2 == -1))
-                                {
-                                    error_message = "invalid string: '\\u' must be followed by 4 hex digits";
-                                    return token_type::parse_error;
-                                }
-
-                                // check if codepoint2 is a low surrogate
-                                if (JSON_LIKELY(0xDC00 <= codepoint2 and codepoint2 <= 0xDFFF))
-                                {
-                                    // overwrite codepoint
-                                    codepoint =
-                                        // high surrogate occupies the most significant 22 bits
-                                        (codepoint1 << 10)
-                                        // low surrogate occupies the least significant 15 bits
-                                        + codepoint2
-                                        // there is still the 0xD800, 0xDC00 and 0x10000 noise
-                                        // in the result so we have to subtract with:
-                                        // (0xD800 << 10) + DC00 - 0x10000 = 0x35FDC00
-                                        - 0x35FDC00;
-                                }
-                                else
-                                {
-                                    error_message = "invalid string: surrogate U+DC00..U+DFFF must be followed by U+DC00..U+DFFF";
-                                    return token_type::parse_error;
-                                }
-                            }
-                            else
-                            {
-                                error_message = "invalid string: surrogate U+DC00..U+DFFF must be followed by U+DC00..U+DFFF";
-                                return token_type::parse_error;
-                            }
-                        }
-                        else
-                        {
-                            if (JSON_UNLIKELY(0xDC00 <= codepoint1 and codepoint1 <= 0xDFFF))
-                            {
-                                error_message = "invalid string: surrogate U+DC00..U+DFFF must follow U+D800..U+DBFF";
-                                return token_type::parse_error;
-                            }
-                        }
-
-                        // result of the above calculation yields a proper codepoint
-                        assert(0x00 <= codepoint and codepoint <= 0x10FFFF);
-
-                        // translate codepoint into bytes
-                        if (codepoint < 0x80)
-                        {
-                            // 1-byte characters: 0xxxxxxx (ASCII)
-                            add(codepoint);
-                        }
-                        else if (codepoint <= 0x7FF)
-                        {
-                            // 2-byte characters: 110xxxxx 10xxxxxx
-                            add(0xC0 | (codepoint >> 6));
-                            add(0x80 | (codepoint & 0x3F));
-                        }
-                        else if (codepoint <= 0xFFFF)
-                        {
-                            // 3-byte characters: 1110xxxx 10xxxxxx 10xxxxxx
-                            add(0xE0 | (codepoint >> 12));
-                            add(0x80 | ((codepoint >> 6) & 0x3F));
-                            add(0x80 | (codepoint & 0x3F));
-                        }
-                        else
-                        {
-                            // 4-byte characters: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
-                            add(0xF0 | (codepoint >> 18));
-                            add(0x80 | ((codepoint >> 12) & 0x3F));
-                            add(0x80 | ((codepoint >> 6) & 0x3F));
-                            add(0x80 | (codepoint & 0x3F));
-                        }
-
-                        break;
-                    }
-
-                    // other characters after escape
-                    default:
-                        error_message = "invalid string: forbidden character after backslash";
-                        return token_type::parse_error;
-                }
-
-                break;
-            }
-
-            // invalid control characters
-            case 0x00:
-            case 0x01:
-            case 0x02:
-            case 0x03:
-            case 0x04:
-            case 0x05:
-            case 0x06:
-            case 0x07:
-            case 0x08:
-            case 0x09:
-            case 0x0A:
-            case 0x0B:
-            case 0x0C:
-            case 0x0D:
-            case 0x0E:
-            case 0x0F:
-            case 0x10:
-            case 0x11:
-            case 0x12:
-            case 0x13:
-            case 0x14:
-            case 0x15:
-            case 0x16:
-            case 0x17:
-            case 0x18:
-            case 0x19:
-            case 0x1A:
-            case 0x1B:
-            case 0x1C:
-            case 0x1D:
-            case 0x1E:
-            case 0x1F:
-            {
-                error_message = "invalid string: control character must be escaped";
-                return token_type::parse_error;
-            }
-
-            // U+0020..U+007F (except U+0022 (quote) and U+005C (backspace))
-            case 0x20:
-            case 0x21:
-            case 0x23:
-            case 0x24:
-            case 0x25:
-            case 0x26:
-            case 0x27:
-            case 0x28:
-            case 0x29:
-            case 0x2A:
-            case 0x2B:
-            case 0x2C:
-            case 0x2D:
-            case 0x2E:
-            case 0x2F:
-            case 0x30:
-            case 0x31:
-            case 0x32:
-            case 0x33:
-            case 0x34:
-            case 0x35:
-            case 0x36:
-            case 0x37:
-            case 0x38:
-            case 0x39:
-            case 0x3A:
-            case 0x3B:
-            case 0x3C:
-            case 0x3D:
-            case 0x3E:
-            case 0x3F:
-            case 0x40:
-            case 0x41:
-            case 0x42:
-            case 0x43:
-            case 0x44:
-            case 0x45:
-            case 0x46:
-            case 0x47:
-            case 0x48:
-            case 0x49:
-            case 0x4A:
-            case 0x4B:
-            case 0x4C:
-            case 0x4D:
-            case 0x4E:
-            case 0x4F:
-            case 0x50:
-            case 0x51:
-            case 0x52:
-            case 0x53:
-            case 0x54:
-            case 0x55:
-            case 0x56:
-            case 0x57:
-            case 0x58:
-            case 0x59:
-            case 0x5A:
-            case 0x5B:
-            case 0x5D:
-            case 0x5E:
-            case 0x5F:
-            case 0x60:
-            case 0x61:
-            case 0x62:
-            case 0x63:
-            case 0x64:
-            case 0x65:
-            case 0x66:
-            case 0x67:
-            case 0x68:
-            case 0x69:
-            case 0x6A:
-            case 0x6B:
-            case 0x6C:
-            case 0x6D:
-            case 0x6E:
-            case 0x6F:
-            case 0x70:
-            case 0x71:
-            case 0x72:
-            case 0x73:
-            case 0x74:
-            case 0x75:
-            case 0x76:
-            case 0x77:
-            case 0x78:
-            case 0x79:
-            case 0x7A:
-            case 0x7B:
-            case 0x7C:
-            case 0x7D:
-            case 0x7E:
-            case 0x7F:
-            {
-                add(current);
-                break;
-            }
-
-            // U+0080..U+07FF: bytes C2..DF 80..BF
-            case 0xC2:
-            case 0xC3:
-            case 0xC4:
-            case 0xC5:
-            case 0xC6:
-            case 0xC7:
-            case 0xC8:
-            case 0xC9:
-            case 0xCA:
-            case 0xCB:
-            case 0xCC:
-            case 0xCD:
-            case 0xCE:
-            case 0xCF:
-            case 0xD0:
-            case 0xD1:
-            case 0xD2:
-            case 0xD3:
-            case 0xD4:
-            case 0xD5:
-            case 0xD6:
-            case 0xD7:
-            case 0xD8:
-            case 0xD9:
-            case 0xDA:
-            case 0xDB:
-            case 0xDC:
-            case 0xDD:
-            case 0xDE:
-            case 0xDF:
-            {
-                if (JSON_UNLIKELY(not next_byte_in_range({0x80, 0xBF})))
-                {
-                    return token_type::parse_error;
-                }
-                break;
-            }
-
-            // U+0800..U+0FFF: bytes E0 A0..BF 80..BF
-            case 0xE0:
-            {
-                if (JSON_UNLIKELY(not (next_byte_in_range({0xA0, 0xBF, 0x80, 0xBF}))))
-                {
-                    return token_type::parse_error;
-                }
-                break;
-            }
-
-            // U+1000..U+CFFF: bytes E1..EC 80..BF 80..BF
-            // U+E000..U+FFFF: bytes EE..EF 80..BF 80..BF
-            case 0xE1:
-            case 0xE2:
-            case 0xE3:
-            case 0xE4:
-            case 0xE5:
-            case 0xE6:
-            case 0xE7:
-            case 0xE8:
-            case 0xE9:
-            case 0xEA:
-            case 0xEB:
-            case 0xEC:
-            case 0xEE:
-            case 0xEF:
-            {
-                if (JSON_UNLIKELY(not (next_byte_in_range({0x80, 0xBF, 0x80, 0xBF}))))
-                {
-                    return token_type::parse_error;
-                }
-                break;
-            }
-
-            // U+D000..U+D7FF: bytes ED 80..9F 80..BF
-            case 0xED:
-            {
-                if (JSON_UNLIKELY(not (next_byte_in_range({0x80, 0x9F, 0x80, 0xBF}))))
-                {
-                    return token_type::parse_error;
-                }
-                break;
-            }
-
-            // U+10000..U+3FFFF F0 90..BF 80..BF 80..BF
-            case 0xF0:
-            {
-                if (JSON_UNLIKELY(not (next_byte_in_range({0x90, 0xBF, 0x80, 0xBF, 0x80, 0xBF}))))
-                {
-                    return token_type::parse_error;
-                }
-                break;
-            }
-
-            // U+40000..U+FFFFF F1..F3 80..BF 80..BF 80..BF
-            case 0xF1:
-            case 0xF2:
-            case 0xF3:
-            {
-                if (JSON_UNLIKELY(not (next_byte_in_range({0x80, 0xBF, 0x80, 0xBF, 0x80, 0xBF}))))
-                {
-                    return token_type::parse_error;
-                }
-                break;
-            }
-
-            // U+100000..U+10FFFF F4 80..8F 80..BF 80..BF
-            case 0xF4:
-            {
-                if (JSON_UNLIKELY(not (next_byte_in_range({0x80, 0x8F, 0x80, 0xBF, 0x80, 0xBF}))))
-                {
-                    return token_type::parse_error;
-                }
-                break;
-            }
-
-            // remaining bytes (80..C1 and F5..FF) are ill-formed
-            default:
-            {
-                error_message = "invalid string: ill-formed UTF-8 byte";
-                return token_type::parse_error;
-            }
-        }
-    }
-}
-
-json::lexer::token_type json::lexer::scan_number()
-{
-    // reset token_buffer to store the number's bytes
-    reset();
-
-    // the type of the parsed number; initially set to unsigned; will be
-    // changed if minus sign, decimal point or exponent is read
-    token_type number_type = token_type::value_unsigned;
-
-    // state (init): we just found out we need to scan a number
-    switch (current)
-    {
-        case '-':
-        {
-            add(current);
-            goto scan_number_minus;
-        }
-
-        case '0':
-        {
-            add(current);
-            goto scan_number_zero;
-        }
-
-        case '1':
-        case '2':
-        case '3':
-        case '4':
-        case '5':
-        case '6':
-        case '7':
-        case '8':
-        case '9':
-        {
-            add(current);
-            goto scan_number_any1;
-        }
-
-        default:
-        {
-            // all other characters are rejected outside scan_number()
-            assert(false); // LCOV_EXCL_LINE
-        }
-    }
-
-scan_number_minus:
-    // state: we just parsed a leading minus sign
-    number_type = token_type::value_integer;
-    switch (get())
-    {
-        case '0':
-        {
-            add(current);
-            goto scan_number_zero;
-        }
-
-        case '1':
-        case '2':
-        case '3':
-        case '4':
-        case '5':
-        case '6':
-        case '7':
-        case '8':
-        case '9':
-        {
-            add(current);
-            goto scan_number_any1;
-        }
-
-        default:
-        {
-            error_message = "invalid number; expected digit after '-'";
-            return token_type::parse_error;
-        }
-    }
-
-scan_number_zero:
-    // state: we just parse a zero (maybe with a leading minus sign)
-    switch (get())
-    {
-        case '.':
-        {
-            add(decimal_point_char);
-            goto scan_number_decimal1;
-        }
-
-        case 'e':
-        case 'E':
-        {
-            add(current);
-            goto scan_number_exponent;
-        }
-
-        default:
-            goto scan_number_done;
-    }
-
-scan_number_any1:
-    // state: we just parsed a number 0-9 (maybe with a leading minus sign)
-    switch (get())
-    {
-        case '0':
-        case '1':
-        case '2':
-        case '3':
-        case '4':
-        case '5':
-        case '6':
-        case '7':
-        case '8':
-        case '9':
-        {
-            add(current);
-            goto scan_number_any1;
-        }
-
-        case '.':
-        {
-            add(decimal_point_char);
-            goto scan_number_decimal1;
-        }
-
-        case 'e':
-        case 'E':
-        {
-            add(current);
-            goto scan_number_exponent;
-        }
-
-        default:
-            goto scan_number_done;
-    }
-
-scan_number_decimal1:
-    // state: we just parsed a decimal point
-    number_type = token_type::value_float;
-    switch (get())
-    {
-        case '0':
-        case '1':
-        case '2':
-        case '3':
-        case '4':
-        case '5':
-        case '6':
-        case '7':
-        case '8':
-        case '9':
-        {
-            add(current);
-            goto scan_number_decimal2;
-        }
-
-        default:
-        {
-            error_message = "invalid number; expected digit after '.'";
-            return token_type::parse_error;
-        }
-    }
-
-scan_number_decimal2:
-    // we just parsed at least one number after a decimal point
-    switch (get())
-    {
-        case '0':
-        case '1':
-        case '2':
-        case '3':
-        case '4':
-        case '5':
-        case '6':
-        case '7':
-        case '8':
-        case '9':
-        {
-            add(current);
-            goto scan_number_decimal2;
-        }
-
-        case 'e':
-        case 'E':
-        {
-            add(current);
-            goto scan_number_exponent;
-        }
-
-        default:
-            goto scan_number_done;
-    }
-
-scan_number_exponent:
-    // we just parsed an exponent
-    number_type = token_type::value_float;
-    switch (get())
-    {
-        case '+':
-        case '-':
-        {
-            add(current);
-            goto scan_number_sign;
-        }
-
-        case '0':
-        case '1':
-        case '2':
-        case '3':
-        case '4':
-        case '5':
-        case '6':
-        case '7':
-        case '8':
-        case '9':
-        {
-            add(current);
-            goto scan_number_any2;
-        }
-
-        default:
-        {
-            error_message =
-                "invalid number; expected '+', '-', or digit after exponent";
-            return token_type::parse_error;
-        }
-    }
-
-scan_number_sign:
-    // we just parsed an exponent sign
-    switch (get())
-    {
-        case '0':
-        case '1':
-        case '2':
-        case '3':
-        case '4':
-        case '5':
-        case '6':
-        case '7':
-        case '8':
-        case '9':
-        {
-            add(current);
-            goto scan_number_any2;
-        }
-
-        default:
-        {
-            error_message = "invalid number; expected digit after exponent sign";
-            return token_type::parse_error;
-        }
-    }
-
-scan_number_any2:
-    // we just parsed a number after the exponent or exponent sign
-    switch (get())
-    {
-        case '0':
-        case '1':
-        case '2':
-        case '3':
-        case '4':
-        case '5':
-        case '6':
-        case '7':
-        case '8':
-        case '9':
-        {
-            add(current);
-            goto scan_number_any2;
-        }
-
-        default:
-            goto scan_number_done;
-    }
-
-scan_number_done:
-    // unget the character after the number (we only read it to know that
-    // we are done scanning a number)
-    unget();
-
-    char* endptr = nullptr;
-    errno = 0;
-
-    // try to parse integers first and fall back to floats
-    if (number_type == token_type::value_unsigned)
-    {
-        const auto x = std::strtoull(token_buffer.c_str(), &endptr, 10);
-
-        // we checked the number format before
-        assert(endptr == token_buffer.data() + token_buffer.size());
-
-        if (errno == 0)
-        {
-            value_unsigned = static_cast<uint64_t>(x);
-            if (value_unsigned == x)
-            {
-                return token_type::value_unsigned;
-            }
-        }
-    }
-    else if (number_type == token_type::value_integer)
-    {
-        const auto x = std::strtoll(token_buffer.c_str(), &endptr, 10);
-
-        // we checked the number format before
-        assert(endptr == token_buffer.data() + token_buffer.size());
-
-        if (errno == 0)
-        {
-            value_integer = static_cast<int64_t>(x);
-            if (value_integer == x)
-            {
-                return token_type::value_integer;
-            }
-        }
-    }
-
-    // this code is reached if we parse a floating-point number or if an
-    // integer conversion above failed
-    strtof(value_float, token_buffer.c_str(), &endptr);
-
-    // we checked the number format before
-    assert(endptr == token_buffer.data() + token_buffer.size());
-
-    return token_type::value_float;
-}
-
-json::lexer::token_type json::lexer::scan_literal(const char* literal_text, const std::size_t length,
-                        token_type return_type)
-{
-    assert(current == literal_text[0]);
-    for (std::size_t i = 1; i < length; ++i)
-    {
-        if (JSON_UNLIKELY(get() != literal_text[i]))
-        {
-            error_message = "invalid literal";
-            return token_type::parse_error;
-        }
-    }
-    return return_type;
-}
-
-std::string json::lexer::get_token_string() const
-{
-    // escape control characters
-    std::string result;
-    raw_string_ostream ss(result);
-    for (const unsigned char c : token_string)
-    {
-        if (c <= '\x1F')
-        {
-            // escape control characters
-            ss << fmt::format("<U+{:04X}>", c);
-        }
-        else
-        {
-            // add character as is
-            ss << c;
-        }
-    }
-
-    ss.flush();
-    return result;
-}
-
-json::lexer::token_type json::lexer::scan()
-{
-    // read next character and ignore whitespace
-    do
-    {
-        get();
-    }
-    while (current == ' ' or current == '\t' or current == '\n' or current == '\r');
-
-    switch (current)
-    {
-        // structural characters
-        case '[':
-            return token_type::begin_array;
-        case ']':
-            return token_type::end_array;
-        case '{':
-            return token_type::begin_object;
-        case '}':
-            return token_type::end_object;
-        case ':':
-            return token_type::name_separator;
-        case ',':
-            return token_type::value_separator;
-
-        // literals
-        case 't':
-            return scan_literal("true", 4, token_type::literal_true);
-        case 'f':
-            return scan_literal("false", 5, token_type::literal_false);
-        case 'n':
-            return scan_literal("null", 4, token_type::literal_null);
-
-        // string
-        case '\"':
-            return scan_string();
-
-        // number
-        case '-':
-        case '0':
-        case '1':
-        case '2':
-        case '3':
-        case '4':
-        case '5':
-        case '6':
-        case '7':
-        case '8':
-        case '9':
-            return scan_number();
-
-        // end of input (the null byte is needed when parsing from
-        // string literals)
-        case '\0':
-        case std::char_traits<char>::eof():
-            return token_type::end_of_input;
-
-        // error
-        default:
-            error_message = "invalid literal";
-            return token_type::parse_error;
-    }
-}
-
-void json::parser::parse(const bool strict, json& result)
-{
-    // read first token
-    get_token();
-
-    parse_internal(true, result);
-    result.assert_invariant();
-
-    // in strict mode, input must be completely read
-    if (strict)
-    {
-        get_token();
-        expect(token_type::end_of_input);
-    }
-
-    // in case of an error, return discarded value
-    if (errored)
-    {
-        result = value_t::discarded;
-        return;
-    }
-
-    // set top-level value to null if it was discarded by the callback
-    // function
-    if (result.is_discarded())
-    {
-        result = nullptr;
-    }
-}
-
-void json::parser::parse_internal(bool keep, json& result)
-{
-    // never parse after a parse error was detected
-    assert(not errored);
-
-    // start with a discarded value
-    if (not result.is_discarded())
-    {
-        result.m_value.destroy(result.m_type);
-        result.m_type = value_t::discarded;
-    }
-
-    switch (last_token)
-    {
-        case token_type::begin_object:
-        {
-            if (keep)
-            {
-                if (callback)
-                {
-                    keep = callback(depth++, parse_event_t::object_start, result);
-                }
-
-                if (not callback or keep)
-                {
-                    // explicitly set result to object to cope with {}
-                    result.m_type = value_t::object;
-                    result.m_value = value_t::object;
-                }
-            }
-
-            // read next token
-            get_token();
-
-            // closing } -> we are done
-            if (last_token == token_type::end_object)
-            {
-                if (keep and callback and not callback(--depth, parse_event_t::object_end, result))
-                {
-                    result.m_value.destroy(result.m_type);
-                    result.m_type = value_t::discarded;
-                }
-                break;
-            }
-
-            // parse values
-            SmallString<128> key;
-            json value;
-            while (true)
-            {
-                // store key
-                if (not expect(token_type::value_string))
-                {
-                    return;
-                }
-                key = m_lexer.get_string();
-
-                bool keep_tag = false;
-                if (keep)
-                {
-                    if (callback)
-                    {
-                        json k(key);
-                        keep_tag = callback(depth, parse_event_t::key, k);
-                    }
-                    else
-                    {
-                        keep_tag = true;
-                    }
-                }
-
-                // parse separator (:)
-                get_token();
-                if (not expect(token_type::name_separator))
-                {
-                    return;
-                }
-
-                // parse and add value
-                get_token();
-                value.m_value.destroy(value.m_type);
-                value.m_type = value_t::discarded;
-                parse_internal(keep, value);
-
-                if (JSON_UNLIKELY(errored))
-                {
-                    return;
-                }
-
-                if (keep and keep_tag and not value.is_discarded())
-                {
-                    result.m_value.object->try_emplace(std::string_view(key.data(), key.size()), std::move(value));
-                }
-
-                // comma -> next value
-                get_token();
-                if (last_token == token_type::value_separator)
-                {
-                    get_token();
-                    continue;
-                }
-
-                // closing }
-                if (not expect(token_type::end_object))
-                {
-                    return;
-                }
-                break;
-            }
-
-            if (keep and callback and not callback(--depth, parse_event_t::object_end, result))
-            {
-                result.m_value.destroy(result.m_type);
-                result.m_type = value_t::discarded;
-            }
-            break;
-        }
-
-        case token_type::begin_array:
-        {
-            if (keep)
-            {
-                if (callback)
-                {
-                    keep = callback(depth++, parse_event_t::array_start, result);
-                }
-
-                if (not callback or keep)
-                {
-                    // explicitly set result to array to cope with []
-                    result.m_type = value_t::array;
-                    result.m_value = value_t::array;
-                }
-            }
-
-            // read next token
-            get_token();
-
-            // closing ] -> we are done
-            if (last_token == token_type::end_array)
-            {
-                if (callback and not callback(--depth, parse_event_t::array_end, result))
-                {
-                    result.m_value.destroy(result.m_type);
-                    result.m_type = value_t::discarded;
-                }
-                break;
-            }
-
-            // parse values
-            json value;
-            while (true)
-            {
-                // parse value
-                value.m_value.destroy(value.m_type);
-                value.m_type = value_t::discarded;
-                parse_internal(keep, value);
-
-                if (JSON_UNLIKELY(errored))
-                {
-                    return;
-                }
-
-                if (keep and not value.is_discarded())
-                {
-                    result.m_value.array->push_back(std::move(value));
-                }
-
-                // comma -> next value
-                get_token();
-                if (last_token == token_type::value_separator)
-                {
-                    get_token();
-                    continue;
-                }
-
-                // closing ]
-                if (not expect(token_type::end_array))
-                {
-                    return;
-                }
-                break;
-            }
-
-            if (keep and callback and not callback(--depth, parse_event_t::array_end, result))
-            {
-                result.m_value.destroy(result.m_type);
-                result.m_type = value_t::discarded;
-            }
-            break;
-        }
-
-        case token_type::literal_null:
-        {
-            result.m_type = value_t::null;
-            break;
-        }
-
-        case token_type::value_string:
-        {
-            result.m_type = value_t::string;
-            result.m_value = m_lexer.get_string();
-            break;
-        }
-
-        case token_type::literal_true:
-        {
-            result.m_type = value_t::boolean;
-            result.m_value = true;
-            break;
-        }
-
-        case token_type::literal_false:
-        {
-            result.m_type = value_t::boolean;
-            result.m_value = false;
-            break;
-        }
-
-        case token_type::value_unsigned:
-        {
-            result.m_type = value_t::number_unsigned;
-            result.m_value = m_lexer.get_number_unsigned();
-            break;
-        }
-
-        case token_type::value_integer:
-        {
-            result.m_type = value_t::number_integer;
-            result.m_value = m_lexer.get_number_integer();
-            break;
-        }
-
-        case token_type::value_float:
-        {
-            result.m_type = value_t::number_float;
-            result.m_value = m_lexer.get_number_float();
-
-            // throw in case of infinity or NAN
-            if (JSON_UNLIKELY(not std::isfinite(result.m_value.number_float)))
-            {
-                if (allow_exceptions)
-                {
-                    JSON_THROW(out_of_range::create(406,
-                        fmt::format("number overflow parsing '{}'", m_lexer.get_token_string())));
-                }
-                expect(token_type::uninitialized);
-            }
-            break;
-        }
-
-        case token_type::parse_error:
-        {
-            // using "uninitialized" to avoid "expected" message
-            if (not expect(token_type::uninitialized))
-            {
-                return;
-            }
-            break; // LCOV_EXCL_LINE
-        }
-
-        default:
-        {
-            // the last token was unexpected; we expected a value
-            if (not expect(token_type::literal_or_value))
-            {
-                return;
-            }
-            break; // LCOV_EXCL_LINE
-        }
-    }
-
-    if (keep and callback and not callback(depth, parse_event_t::value, result))
-    {
-        result.m_value.destroy(result.m_type);
-        result.m_type = value_t::discarded;
-    }
-}
-
-bool json::parser::accept_internal()
-{
-    switch (last_token)
-    {
-        case token_type::begin_object:
-        {
-            // read next token
-            get_token();
-
-            // closing } -> we are done
-            if (last_token == token_type::end_object)
-            {
-                return true;
-            }
-
-            // parse values
-            while (true)
-            {
-                // parse key
-                if (last_token != token_type::value_string)
-                {
-                    return false;
-                }
-
-                // parse separator (:)
-                get_token();
-                if (last_token != token_type::name_separator)
-                {
-                    return false;
-                }
-
-                // parse value
-                get_token();
-                if (not accept_internal())
-                {
-                    return false;
-                }
-
-                // comma -> next value
-                get_token();
-                if (last_token == token_type::value_separator)
-                {
-                    get_token();
-                    continue;
-                }
-
-                // closing }
-                return (last_token == token_type::end_object);
-            }
-        }
-
-        case token_type::begin_array:
-        {
-            // read next token
-            get_token();
-
-            // closing ] -> we are done
-            if (last_token == token_type::end_array)
-            {
-                return true;
-            }
-
-            // parse values
-            while (true)
-            {
-                // parse value
-                if (not accept_internal())
-                {
-                    return false;
-                }
-
-                // comma -> next value
-                get_token();
-                if (last_token == token_type::value_separator)
-                {
-                    get_token();
-                    continue;
-                }
-
-                // closing ]
-                return (last_token == token_type::end_array);
-            }
-        }
-
-        case token_type::value_float:
-        {
-            // reject infinity or NAN
-            return std::isfinite(m_lexer.get_number_float());
-        }
-
-        case token_type::literal_false:
-        case token_type::literal_null:
-        case token_type::literal_true:
-        case token_type::value_integer:
-        case token_type::value_string:
-        case token_type::value_unsigned:
-            return true;
-
-        default: // the last token was unexpected
-            return false;
-    }
-}
-
-void json::parser::throw_exception() const
-{
-    std::string error_msg = "syntax error - ";
-    if (last_token == token_type::parse_error)
-    {
-        error_msg += std::string(m_lexer.get_error_message()) + "; last read: '" +
-                     m_lexer.get_token_string() + "'";
-    }
-    else
-    {
-        error_msg += "unexpected " + std::string(lexer_t::token_type_name(last_token));
-    }
-
-    if (expected != token_type::uninitialized)
-    {
-        error_msg += "; expected " + std::string(lexer_t::token_type_name(expected));
-    }
-
-    JSON_THROW(parse_error::create(101, m_lexer.get_position(), error_msg));
-}
-
-json json::parse(std::string_view s,
-                        const parser_callback_t cb,
-                        const bool allow_exceptions)
-{
-    raw_mem_istream is(span<const char>(s.data(), s.size()));
-    return parse(is, cb, allow_exceptions);
-}
-
-json json::parse(span<const uint8_t> arr,
-                        const parser_callback_t cb,
-                        const bool allow_exceptions)
-{
-    raw_mem_istream is(arr);
-    return parse(is, cb, allow_exceptions);
-}
-
-json json::parse(raw_istream& i,
-                        const parser_callback_t cb,
-                        const bool allow_exceptions)
-{
-    json result;
-    parser(i, cb, allow_exceptions).parse(true, result);
-    return result;
-}
-
-bool json::accept(std::string_view s)
-{
-    raw_mem_istream is(span<const char>(s.data(), s.size()));
-    return parser(is).accept(true);
-}
-
-bool json::accept(span<const uint8_t> arr)
-{
-    raw_mem_istream is(arr);
-    return parser(is).accept(true);
-}
-
-bool json::accept(raw_istream& i)
-{
-    return parser(i).accept(true);
-}
-
-raw_istream& operator>>(raw_istream& i, json& j)
-{
-    json::parser(i).parse(false, j);
-    return i;
-}
-
-}  // namespace wpi
diff --git a/wpiutil/src/main/native/cpp/json_pointer.cpp b/wpiutil/src/main/native/cpp/json_pointer.cpp
deleted file mode 100644
index 51548b3..0000000
--- a/wpiutil/src/main/native/cpp/json_pointer.cpp
+++ /dev/null
@@ -1,540 +0,0 @@
-/*----------------------------------------------------------------------------*/
-/* Modifications 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.                                                               */
-/*----------------------------------------------------------------------------*/
-/*
-    __ _____ _____ _____
- __|  |   __|     |   | |  JSON for Modern C++
-|  |  |__   |  |  | | | |  version 3.1.2
-|_____|_____|_____|_|___|  https://github.com/nlohmann/json
-
-Licensed under the MIT License <http://opensource.org/licenses/MIT>.
-Copyright (c) 2013-2018 Niels Lohmann <http://nlohmann.me>.
-
-Permission is hereby  granted, free of charge, to any  person obtaining a copy
-of this software and associated  documentation files (the "Software"), to deal
-in the Software  without restriction, including without  limitation the rights
-to  use, copy,  modify, merge,  publish, distribute,  sublicense, and/or  sell
-copies  of  the Software,  and  to  permit persons  to  whom  the Software  is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-THE SOFTWARE  IS PROVIDED "AS  IS", WITHOUT WARRANTY  OF ANY KIND,  EXPRESS OR
-IMPLIED,  INCLUDING BUT  NOT  LIMITED TO  THE  WARRANTIES OF  MERCHANTABILITY,
-FITNESS FOR  A PARTICULAR PURPOSE AND  NONINFRINGEMENT. IN NO EVENT  SHALL THE
-AUTHORS  OR COPYRIGHT  HOLDERS  BE  LIABLE FOR  ANY  CLAIM,  DAMAGES OR  OTHER
-LIABILITY, WHETHER IN AN ACTION OF  CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE  OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
-*/
-#define WPI_JSON_IMPLEMENTATION
-#include "wpi/json.h"
-
-#include <numeric> // accumulate
-
-#include "fmt/format.h"
-#include "wpi/SmallString.h"
-#include "wpi/StringExtras.h"
-
-namespace wpi {
-
-std::string json_pointer::to_string() const noexcept
-{
-    return std::accumulate(reference_tokens.begin(), reference_tokens.end(),
-                           std::string{},
-                           [](const std::string & a, const std::string & b)
-    {
-        return a + "/" + escape(b);
-    });
-}
-
-int json_pointer::array_index(std::string_view s)
-{
-    SmallString<128> str{s};
-    std::size_t processed_chars = 0;
-    const int res = std::stoi(str.c_str(), &processed_chars);
-
-    // check if the string was completely read
-    if (JSON_UNLIKELY(processed_chars != str.size()))
-    {
-        JSON_THROW(detail::out_of_range::create(404, fmt::format("unresolved reference token '{}'", s)));
-    }
-
-    return res;
-}
-
-json& json_pointer::get_and_create(json& j) const
-{
-    using size_type = typename json::size_type;
-    auto result = &j;
-
-    // in case no reference tokens exist, return a reference to the JSON value
-    // j which will be overwritten by a primitive value
-    for (const auto& reference_token : reference_tokens)
-    {
-        switch (result->m_type)
-        {
-            case detail::value_t::null:
-            {
-                if (reference_token == "0")
-                {
-                    // start a new array if reference token is 0
-                    result = &result->operator[](0);
-                }
-                else
-                {
-                    // start a new object otherwise
-                    result = &result->operator[](reference_token);
-                }
-                break;
-            }
-
-            case detail::value_t::object:
-            {
-                // create an entry in the object
-                result = &result->operator[](reference_token);
-                break;
-            }
-
-            case detail::value_t::array:
-            {
-                // create an entry in the array
-                JSON_TRY
-                {
-                    result = &result->operator[](static_cast<size_type>(array_index(reference_token)));
-                }
-                JSON_CATCH(std::invalid_argument&)
-                {
-                    JSON_THROW(detail::parse_error::create(109, 0, fmt::format("array index '{}' is not a number", reference_token)));
-                }
-                break;
-            }
-
-            /*
-            The following code is only reached if there exists a reference
-            token _and_ the current value is primitive. In this case, we have
-            an error situation, because primitive values may only occur as
-            single value; that is, with an empty list of reference tokens.
-            */
-            default:
-                JSON_THROW(detail::type_error::create(313, "invalid value to unflatten"));
-        }
-    }
-
-    return *result;
-}
-
-json& json_pointer::get_unchecked(json* ptr) const
-{
-    using size_type = typename json::size_type;
-    for (const auto& reference_token : reference_tokens)
-    {
-        // convert null values to arrays or objects before continuing
-        if (ptr->m_type == detail::value_t::null)
-        {
-            // check if reference token is a number
-            const bool nums =
-                std::all_of(reference_token.begin(), reference_token.end(),
-                            [](const char x)
-            {
-                return (x >= '0' and x <= '9');
-            });
-
-            // change value to array for numbers or "-" or to object otherwise
-            *ptr = (nums or reference_token == "-")
-                   ? detail::value_t::array
-                   : detail::value_t::object;
-        }
-
-        switch (ptr->m_type)
-        {
-            case detail::value_t::object:
-            {
-                // use unchecked object access
-                ptr = &ptr->operator[](reference_token);
-                break;
-            }
-
-            case detail::value_t::array:
-            {
-                // error condition (cf. RFC 6901, Sect. 4)
-                if (JSON_UNLIKELY(reference_token.size() > 1 and reference_token[0] == '0'))
-                {
-                    JSON_THROW(detail::parse_error::create(106, 0,
-                        fmt::format("array index '{}' must not begin with '0'", reference_token)));
-                }
-
-                if (reference_token == "-")
-                {
-                    // explicitly treat "-" as index beyond the end
-                    ptr = &ptr->operator[](ptr->m_value.array->size());
-                }
-                else
-                {
-                    // convert array index to number; unchecked access
-                    JSON_TRY
-                    {
-                        ptr = &ptr->operator[](
-                            static_cast<size_type>(array_index(reference_token)));
-                    }
-                    JSON_CATCH(std::invalid_argument&)
-                    {
-                        JSON_THROW(detail::parse_error::create(109, 0,
-                            fmt::format("array index '{}' is not a number", reference_token)));
-                    }
-                }
-                break;
-            }
-
-            default:
-                JSON_THROW(detail::out_of_range::create(404,
-                    fmt::format("unresolved reference token '{}'", reference_token)));
-        }
-    }
-
-    return *ptr;
-}
-
-json& json_pointer::get_checked(json* ptr) const
-{
-    using size_type = typename json::size_type;
-    for (const auto& reference_token : reference_tokens)
-    {
-        switch (ptr->m_type)
-        {
-            case detail::value_t::object:
-            {
-                // note: at performs range check
-                ptr = &ptr->at(reference_token);
-                break;
-            }
-
-            case detail::value_t::array:
-            {
-                if (JSON_UNLIKELY(reference_token == "-"))
-                {
-                    // "-" always fails the range check
-                    JSON_THROW(detail::out_of_range::create(402,
-                        fmt::format("array index '-' ({}) is out of range", ptr->m_value.array->size())));
-                }
-
-                // error condition (cf. RFC 6901, Sect. 4)
-                if (JSON_UNLIKELY(reference_token.size() > 1 and reference_token[0] == '0'))
-                {
-                    JSON_THROW(detail::parse_error::create(106, 0,
-                        fmt::format("array index '{}' must not begin with '0'", reference_token)));
-                }
-
-                // note: at performs range check
-                JSON_TRY
-                {
-                    ptr = &ptr->at(static_cast<size_type>(array_index(reference_token)));
-                }
-                JSON_CATCH(std::invalid_argument&)
-                {
-                    JSON_THROW(detail::parse_error::create(109, 0,
-                        fmt::format("array index '{}' is not a number", reference_token)));
-                }
-                break;
-            }
-
-            default:
-                JSON_THROW(detail::out_of_range::create(404,
-                    fmt::format("unresolved reference token '{}'", reference_token)));
-        }
-    }
-
-    return *ptr;
-}
-
-const json& json_pointer::get_unchecked(const json* ptr) const
-{
-    using size_type = typename json::size_type;
-    for (const auto& reference_token : reference_tokens)
-    {
-        switch (ptr->m_type)
-        {
-            case detail::value_t::object:
-            {
-                // use unchecked object access
-                ptr = &ptr->operator[](reference_token);
-                break;
-            }
-
-            case detail::value_t::array:
-            {
-                if (JSON_UNLIKELY(reference_token == "-"))
-                {
-                    // "-" cannot be used for const access
-                    JSON_THROW(detail::out_of_range::create(402,
-                        fmt::format("array index '-' ({}) is out of range", ptr->m_value.array->size())));
-                }
-
-                // error condition (cf. RFC 6901, Sect. 4)
-                if (JSON_UNLIKELY(reference_token.size() > 1 and reference_token[0] == '0'))
-                {
-                    JSON_THROW(detail::parse_error::create(106, 0,
-                        fmt::format("array index '{}' must not begin with '0'", reference_token)));
-                }
-
-                // use unchecked array access
-                JSON_TRY
-                {
-                    ptr = &ptr->operator[](
-                        static_cast<size_type>(array_index(reference_token)));
-                }
-                JSON_CATCH(std::invalid_argument&)
-                {
-                    JSON_THROW(detail::parse_error::create(109, 0,
-                        fmt::format("array index '{}' is not a number", reference_token)));
-                }
-                break;
-            }
-
-            default:
-                JSON_THROW(detail::out_of_range::create(404,
-                    fmt::format("unresolved reference token '{}'", reference_token)));
-        }
-    }
-
-    return *ptr;
-}
-
-const json& json_pointer::get_checked(const json* ptr) const
-{
-    using size_type = typename json::size_type;
-    for (const auto& reference_token : reference_tokens)
-    {
-        switch (ptr->m_type)
-        {
-            case detail::value_t::object:
-            {
-                // note: at performs range check
-                ptr = &ptr->at(reference_token);
-                break;
-            }
-
-            case detail::value_t::array:
-            {
-                if (JSON_UNLIKELY(reference_token == "-"))
-                {
-                    // "-" always fails the range check
-                    JSON_THROW(detail::out_of_range::create(402,
-                        fmt::format("array index '-' ({}) is out of range", ptr->m_value.array->size())));
-                }
-
-                // error condition (cf. RFC 6901, Sect. 4)
-                if (JSON_UNLIKELY(reference_token.size() > 1 and reference_token[0] == '0'))
-                {
-                    JSON_THROW(detail::parse_error::create(106, 0,
-                        fmt::format("array index '{}' must not begin with '0'", reference_token)));
-                }
-
-                // note: at performs range check
-                JSON_TRY
-                {
-                    ptr = &ptr->at(static_cast<size_type>(array_index(reference_token)));
-                }
-                JSON_CATCH(std::invalid_argument&)
-                {
-                    JSON_THROW(detail::parse_error::create(109, 0,
-                        fmt::format("array index '{}' is not a number", reference_token)));
-                }
-                break;
-            }
-
-            default:
-                JSON_THROW(detail::out_of_range::create(404,
-                    fmt::format("unresolved reference token '{}'", reference_token)));
-        }
-    }
-
-    return *ptr;
-}
-
-std::vector<std::string> json_pointer::split(std::string_view ref_str)
-{
-    std::vector<std::string> result;
-
-    // special case: empty reference string -> no reference tokens
-    if (ref_str.empty())
-    {
-        return result;
-    }
-
-    // check if nonempty reference string begins with slash
-    if (JSON_UNLIKELY(ref_str[0] != '/'))
-    {
-        JSON_THROW(detail::parse_error::create(107, 1,
-            fmt::format("JSON pointer must be empty or begin with '/' - was: '{}'", ref_str)));
-    }
-
-    // extract the reference tokens:
-    // - slash: position of the last read slash (or end of string)
-    // - start: position after the previous slash
-    for (
-        // search for the first slash after the first character
-        std::size_t slash = ref_str.find_first_of('/', 1),
-        // set the beginning of the first reference token
-        start = 1;
-        // we can stop if start == string::npos+1 = 0
-        start != 0;
-        // set the beginning of the next reference token
-        // (will eventually be 0 if slash == std::string::npos)
-        start = slash + 1,
-        // find next slash
-        slash = ref_str.find_first_of('/', start))
-    {
-        // use the text between the beginning of the reference token
-        // (start) and the last slash (slash).
-        auto reference_token = slice(ref_str, start, slash);
-
-        // check reference tokens are properly escaped
-        for (std::size_t pos = reference_token.find_first_of('~');
-                pos != std::string_view::npos;
-                pos = reference_token.find_first_of('~', pos + 1))
-        {
-            assert(reference_token[pos] == '~');
-
-            // ~ must be followed by 0 or 1
-            if (JSON_UNLIKELY(pos == reference_token.size() - 1 or
-                              (reference_token[pos + 1] != '0' and
-                               reference_token[pos + 1] != '1')))
-            {
-                JSON_THROW(detail::parse_error::create(108, 0, "escape character '~' must be followed with '0' or '1'"));
-            }
-        }
-
-        // finally, store the reference token
-        std::string ref_tok{reference_token};
-        unescape(ref_tok);
-        result.emplace_back(std::move(ref_tok));
-    }
-
-    return result;
-}
-
-void json_pointer::replace_substring(std::string& s, const std::string& f,
-                              const std::string& t)
-{
-    assert(not f.empty());
-    for (auto pos = s.find(f);                // find first occurrence of f
-            pos != std::string::npos;         // make sure f was found
-            s.replace(pos, f.size(), t),      // replace with t, and
-            pos = s.find(f, pos + t.size()))  // find next occurrence of f
-    {}
-}
-
-std::string json_pointer::escape(std::string s)
-{
-    replace_substring(s, "~", "~0");
-    replace_substring(s, "/", "~1");
-    return s;
-}
-
-/// unescape "~1" to tilde and "~0" to slash (order is important!)
-void json_pointer::unescape(std::string& s)
-{
-    replace_substring(s, "~1", "/");
-    replace_substring(s, "~0", "~");
-}
-
-void json_pointer::flatten(std::string_view reference_string,
-                    const json& value,
-                    json& result)
-{
-    switch (value.m_type)
-    {
-        case detail::value_t::array:
-        {
-            if (value.m_value.array->empty())
-            {
-                // flatten empty array as null
-                result[reference_string] = nullptr;
-            }
-            else
-            {
-                // iterate array and use index as reference string
-                for (std::size_t i = 0; i < value.m_value.array->size(); ++i)
-                {
-                    flatten(fmt::format("{}/{}", reference_string, i),
-                            value.m_value.array->operator[](i), result);
-                }
-            }
-            break;
-        }
-
-        case detail::value_t::object:
-        {
-            if (value.m_value.object->empty())
-            {
-                // flatten empty object as null
-                result[reference_string] = nullptr;
-            }
-            else
-            {
-                // iterate object and use keys as reference string
-                for (const auto& element : *value.m_value.object)
-                {
-                    flatten(fmt::format("{}/{}", reference_string, escape(std::string{element.first()})), element.second, result);
-                }
-            }
-            break;
-        }
-
-        default:
-        {
-            // add primitive value with its reference string
-            result[reference_string] = value;
-            break;
-        }
-    }
-}
-
-json
-json_pointer::unflatten(const json& value)
-{
-    if (JSON_UNLIKELY(not value.is_object()))
-    {
-        JSON_THROW(detail::type_error::create(314, "only objects can be unflattened"));
-    }
-
-    // we need to iterate over the object values in sorted key order
-    SmallVector<StringMapConstIterator<json>, 64> sorted;
-    for (auto i = value.m_value.object->begin(),
-         end = value.m_value.object->end(); i != end; ++i)
-    {
-        if (!i->second.is_primitive())
-        {
-            JSON_THROW(detail::type_error::create(315, "values in object must be primitive"));
-        }
-        sorted.push_back(i);
-    }
-    std::sort(sorted.begin(), sorted.end(),
-              [](const StringMapConstIterator<json>& a,
-                 const StringMapConstIterator<json>& b) {
-                return a->getKey() < b->getKey();
-              });
-
-    json result;
-
-    // iterate the sorted JSON object values
-    for (const auto& element : sorted)
-    {
-
-        // assign value to reference pointed to by JSON pointer; Note
-        // that if the JSON pointer is "" (i.e., points to the whole
-        // value), function get_and_create returns a reference to
-        // result itself. An assignment will then create a primitive
-        // value.
-        json_pointer(element->first()).get_and_create(result) = element->second;
-    }
-
-    return result;
-}
-
-}  // namespace wpi
diff --git a/wpiutil/src/main/native/cpp/json_serializer.cpp b/wpiutil/src/main/native/cpp/json_serializer.cpp
deleted file mode 100644
index 1101f66..0000000
--- a/wpiutil/src/main/native/cpp/json_serializer.cpp
+++ /dev/null
@@ -1,1531 +0,0 @@
-/*----------------------------------------------------------------------------*/
-/* Modifications 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.                                                               */
-/*----------------------------------------------------------------------------*/
-/*
-    __ _____ _____ _____
- __|  |   __|     |   | |  JSON for Modern C++
-|  |  |__   |  |  | | | |  version 3.1.2
-|_____|_____|_____|_|___|  https://github.com/nlohmann/json
-
-Licensed under the MIT License <http://opensource.org/licenses/MIT>.
-Copyright (c) 2013-2018 Niels Lohmann <http://nlohmann.me>.
-
-Permission is hereby  granted, free of charge, to any  person obtaining a copy
-of this software and associated  documentation files (the "Software"), to deal
-in the Software  without restriction, including without  limitation the rights
-to  use, copy,  modify, merge,  publish, distribute,  sublicense, and/or  sell
-copies  of  the Software,  and  to  permit persons  to  whom  the Software  is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-THE SOFTWARE  IS PROVIDED "AS  IS", WITHOUT WARRANTY  OF ANY KIND,  EXPRESS OR
-IMPLIED,  INCLUDING BUT  NOT  LIMITED TO  THE  WARRANTIES OF  MERCHANTABILITY,
-FITNESS FOR  A PARTICULAR PURPOSE AND  NONINFRINGEMENT. IN NO EVENT  SHALL THE
-AUTHORS  OR COPYRIGHT  HOLDERS  BE  LIABLE FOR  ANY  CLAIM,  DAMAGES OR  OTHER
-LIABILITY, WHETHER IN AN ACTION OF  CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE  OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
-*/
-#define WPI_JSON_IMPLEMENTATION
-#include "wpi/json.h"
-
-#include "fmt/format.h"
-#include "wpi/SmallString.h"
-#include "wpi/raw_os_ostream.h"
-
-#include "wpi/json_serializer.h"
-
-namespace wpi {
-
-namespace {
-
-/*!
-@brief implements the Grisu2 algorithm for binary to decimal floating-point
-conversion.
-
-This implementation is a slightly modified version of the reference
-implementation which may be obtained from
-http://florian.loitsch.com/publications (bench.tar.gz).
-
-The code is distributed under the MIT license, Copyright (c) 2009 Florian Loitsch.
-
-For a detailed description of the algorithm see:
-
-[1] Loitsch, "Printing Floating-Point Numbers Quickly and Accurately with
-    Integers", Proceedings of the ACM SIGPLAN 2010 Conference on Programming
-    Language Design and Implementation, PLDI 2010
-[2] Burger, Dybvig, "Printing Floating-Point Numbers Quickly and Accurately",
-    Proceedings of the ACM SIGPLAN 1996 Conference on Programming Language
-    Design and Implementation, PLDI 1996
-*/
-namespace dtoa_impl
-{
-
-template <typename Target, typename Source>
-Target reinterpret_bits(const Source source)
-{
-    static_assert(sizeof(Target) == sizeof(Source), "size mismatch");
-
-    Target target;
-    std::memcpy(&target, &source, sizeof(Source));
-    return target;
-}
-
-struct diyfp // f * 2^e
-{
-    static constexpr int kPrecision = 64; // = q
-
-    uint64_t f;
-    int e;
-
-    constexpr diyfp() noexcept : f(0), e(0) {}
-    constexpr diyfp(uint64_t f_, int e_) noexcept : f(f_), e(e_) {}
-
-    /*!
-    @brief returns x - y
-    @pre x.e == y.e and x.f >= y.f
-    */
-    static diyfp sub(const diyfp& x, const diyfp& y) noexcept
-    {
-        assert(x.e == y.e);
-        assert(x.f >= y.f);
-
-        return diyfp(x.f - y.f, x.e);
-    }
-
-    /*!
-    @brief returns x * y
-    @note The result is rounded. (Only the upper q bits are returned.)
-    */
-    static diyfp mul(const diyfp& x, const diyfp& y) noexcept
-    {
-        static_assert(kPrecision == 64, "internal error");
-
-        // Computes:
-        //  f = round((x.f * y.f) / 2^q)
-        //  e = x.e + y.e + q
-
-        // Emulate the 64-bit * 64-bit multiplication:
-        //
-        // p = u * v
-        //   = (u_lo + 2^32 u_hi) (v_lo + 2^32 v_hi)
-        //   = (u_lo v_lo         ) + 2^32 ((u_lo v_hi         ) + (u_hi v_lo         )) + 2^64 (u_hi v_hi         )
-        //   = (p0                ) + 2^32 ((p1                ) + (p2                )) + 2^64 (p3                )
-        //   = (p0_lo + 2^32 p0_hi) + 2^32 ((p1_lo + 2^32 p1_hi) + (p2_lo + 2^32 p2_hi)) + 2^64 (p3                )
-        //   = (p0_lo             ) + 2^32 (p0_hi + p1_lo + p2_lo                      ) + 2^64 (p1_hi + p2_hi + p3)
-        //   = (p0_lo             ) + 2^32 (Q                                          ) + 2^64 (H                 )
-        //   = (p0_lo             ) + 2^32 (Q_lo + 2^32 Q_hi                           ) + 2^64 (H                 )
-        //
-        // (Since Q might be larger than 2^32 - 1)
-        //
-        //   = (p0_lo + 2^32 Q_lo) + 2^64 (Q_hi + H)
-        //
-        // (Q_hi + H does not overflow a 64-bit int)
-        //
-        //   = p_lo + 2^64 p_hi
-
-        const uint64_t u_lo = x.f & 0xFFFFFFFF;
-        const uint64_t u_hi = x.f >> 32;
-        const uint64_t v_lo = y.f & 0xFFFFFFFF;
-        const uint64_t v_hi = y.f >> 32;
-
-        const uint64_t p0 = u_lo * v_lo;
-        const uint64_t p1 = u_lo * v_hi;
-        const uint64_t p2 = u_hi * v_lo;
-        const uint64_t p3 = u_hi * v_hi;
-
-        const uint64_t p0_hi = p0 >> 32;
-        const uint64_t p1_lo = p1 & 0xFFFFFFFF;
-        const uint64_t p1_hi = p1 >> 32;
-        const uint64_t p2_lo = p2 & 0xFFFFFFFF;
-        const uint64_t p2_hi = p2 >> 32;
-
-        uint64_t Q = p0_hi + p1_lo + p2_lo;
-
-        // The full product might now be computed as
-        //
-        // p_hi = p3 + p2_hi + p1_hi + (Q >> 32)
-        // p_lo = p0_lo + (Q << 32)
-        //
-        // But in this particular case here, the full p_lo is not required.
-        // Effectively we only need to add the highest bit in p_lo to p_hi (and
-        // Q_hi + 1 does not overflow).
-
-        Q += uint64_t{1} << (64 - 32 - 1); // round, ties up
-
-        const uint64_t h = p3 + p2_hi + p1_hi + (Q >> 32);
-
-        return diyfp(h, x.e + y.e + 64);
-    }
-
-    /*!
-    @brief normalize x such that the significand is >= 2^(q-1)
-    @pre x.f != 0
-    */
-    static diyfp normalize(diyfp x) noexcept
-    {
-        assert(x.f != 0);
-
-        while ((x.f >> 63) == 0)
-        {
-            x.f <<= 1;
-            x.e--;
-        }
-
-        return x;
-    }
-
-    /*!
-    @brief normalize x such that the result has the exponent E
-    @pre e >= x.e and the upper e - x.e bits of x.f must be zero.
-    */
-    static diyfp normalize_to(const diyfp& x, const int target_exponent) noexcept
-    {
-        const int delta = x.e - target_exponent;
-
-        assert(delta >= 0);
-        assert(((x.f << delta) >> delta) == x.f);
-
-        return diyfp(x.f << delta, target_exponent);
-    }
-};
-
-struct boundaries
-{
-    diyfp w;
-    diyfp minus;
-    diyfp plus;
-};
-
-/*!
-Compute the (normalized) diyfp representing the input number 'value' and its
-boundaries.
-
-@pre value must be finite and positive
-*/
-template <typename FloatType>
-boundaries compute_boundaries(FloatType value)
-{
-    assert(std::isfinite(value));
-    assert(value > 0);
-
-    // Convert the IEEE representation into a diyfp.
-    //
-    // If v is denormal:
-    //      value = 0.F * 2^(1 - bias) = (          F) * 2^(1 - bias - (p-1))
-    // If v is normalized:
-    //      value = 1.F * 2^(E - bias) = (2^(p-1) + F) * 2^(E - bias - (p-1))
-
-    static_assert(std::numeric_limits<FloatType>::is_iec559,
-                  "internal error: dtoa_short requires an IEEE-754 floating-point implementation");
-
-    constexpr int      kPrecision = std::numeric_limits<FloatType>::digits; // = p (includes the hidden bit)
-    constexpr int      kBias      = std::numeric_limits<FloatType>::max_exponent - 1 + (kPrecision - 1);
-    constexpr int      kMinExp    = 1 - kBias;
-    constexpr uint64_t kHiddenBit = uint64_t{1} << (kPrecision - 1); // = 2^(p-1)
-
-    using bits_type = typename std::conditional< kPrecision == 24, uint32_t, uint64_t >::type;
-
-    const uint64_t bits = reinterpret_bits<bits_type>(value);
-    const uint64_t E = bits >> (kPrecision - 1);
-    const uint64_t F = bits & (kHiddenBit - 1);
-
-    const bool is_denormal = (E == 0);
-    const diyfp v = is_denormal
-                    ? diyfp(F, kMinExp)
-                    : diyfp(F + kHiddenBit, static_cast<int>(E) - kBias);
-
-    // Compute the boundaries m- and m+ of the floating-point value
-    // v = f * 2^e.
-    //
-    // Determine v- and v+, the floating-point predecessor and successor if v,
-    // respectively.
-    //
-    //      v- = v - 2^e        if f != 2^(p-1) or e == e_min                (A)
-    //         = v - 2^(e-1)    if f == 2^(p-1) and e > e_min                (B)
-    //
-    //      v+ = v + 2^e
-    //
-    // Let m- = (v- + v) / 2 and m+ = (v + v+) / 2. All real numbers _strictly_
-    // between m- and m+ round to v, regardless of how the input rounding
-    // algorithm breaks ties.
-    //
-    //      ---+-------------+-------------+-------------+-------------+---  (A)
-    //         v-            m-            v             m+            v+
-    //
-    //      -----------------+------+------+-------------+-------------+---  (B)
-    //                       v-     m-     v             m+            v+
-
-    const bool lower_boundary_is_closer = (F == 0 and E > 1);
-    const diyfp m_plus = diyfp(2 * v.f + 1, v.e - 1);
-    const diyfp m_minus = lower_boundary_is_closer
-                          ? diyfp(4 * v.f - 1, v.e - 2)  // (B)
-                          : diyfp(2 * v.f - 1, v.e - 1); // (A)
-
-    // Determine the normalized w+ = m+.
-    const diyfp w_plus = diyfp::normalize(m_plus);
-
-    // Determine w- = m- such that e_(w-) = e_(w+).
-    const diyfp w_minus = diyfp::normalize_to(m_minus, w_plus.e);
-
-    return {diyfp::normalize(v), w_minus, w_plus};
-}
-
-// Given normalized diyfp w, Grisu needs to find a (normalized) cached
-// power-of-ten c, such that the exponent of the product c * w = f * 2^e lies
-// within a certain range [alpha, gamma] (Definition 3.2 from [1])
-//
-//      alpha <= e = e_c + e_w + q <= gamma
-//
-// or
-//
-//      f_c * f_w * 2^alpha <= f_c 2^(e_c) * f_w 2^(e_w) * 2^q
-//                          <= f_c * f_w * 2^gamma
-//
-// Since c and w are normalized, i.e. 2^(q-1) <= f < 2^q, this implies
-//
-//      2^(q-1) * 2^(q-1) * 2^alpha <= c * w * 2^q < 2^q * 2^q * 2^gamma
-//
-// or
-//
-//      2^(q - 2 + alpha) <= c * w < 2^(q + gamma)
-//
-// The choice of (alpha,gamma) determines the size of the table and the form of
-// the digit generation procedure. Using (alpha,gamma)=(-60,-32) works out well
-// in practice:
-//
-// The idea is to cut the number c * w = f * 2^e into two parts, which can be
-// processed independently: An integral part p1, and a fractional part p2:
-//
-//      f * 2^e = ( (f div 2^-e) * 2^-e + (f mod 2^-e) ) * 2^e
-//              = (f div 2^-e) + (f mod 2^-e) * 2^e
-//              = p1 + p2 * 2^e
-//
-// The conversion of p1 into decimal form requires a series of divisions and
-// modulos by (a power of) 10. These operations are faster for 32-bit than for
-// 64-bit integers, so p1 should ideally fit into a 32-bit integer. This can be
-// achieved by choosing
-//
-//      -e >= 32   or   e <= -32 := gamma
-//
-// In order to convert the fractional part
-//
-//      p2 * 2^e = p2 / 2^-e = d[-1] / 10^1 + d[-2] / 10^2 + ...
-//
-// into decimal form, the fraction is repeatedly multiplied by 10 and the digits
-// d[-i] are extracted in order:
-//
-//      (10 * p2) div 2^-e = d[-1]
-//      (10 * p2) mod 2^-e = d[-2] / 10^1 + ...
-//
-// The multiplication by 10 must not overflow. It is sufficient to choose
-//
-//      10 * p2 < 16 * p2 = 2^4 * p2 <= 2^64.
-//
-// Since p2 = f mod 2^-e < 2^-e,
-//
-//      -e <= 60   or   e >= -60 := alpha
-
-constexpr int kAlpha = -60;
-constexpr int kGamma = -32;
-
-struct cached_power // c = f * 2^e ~= 10^k
-{
-    uint64_t f;
-    int e;
-    int k;
-};
-
-/*!
-For a normalized diyfp w = f * 2^e, this function returns a (normalized) cached
-power-of-ten c = f_c * 2^e_c, such that the exponent of the product w * c
-satisfies (Definition 3.2 from [1])
-
-     alpha <= e_c + e + q <= gamma.
-*/
-inline cached_power get_cached_power_for_binary_exponent(int e)
-{
-    // Now
-    //
-    //      alpha <= e_c + e + q <= gamma                                    (1)
-    //      ==> f_c * 2^alpha <= c * 2^e * 2^q
-    //
-    // and since the c's are normalized, 2^(q-1) <= f_c,
-    //
-    //      ==> 2^(q - 1 + alpha) <= c * 2^(e + q)
-    //      ==> 2^(alpha - e - 1) <= c
-    //
-    // If c were an exakt power of ten, i.e. c = 10^k, one may determine k as
-    //
-    //      k = ceil( log_10( 2^(alpha - e - 1) ) )
-    //        = ceil( (alpha - e - 1) * log_10(2) )
-    //
-    // From the paper:
-    // "In theory the result of the procedure could be wrong since c is rounded,
-    //  and the computation itself is approximated [...]. In practice, however,
-    //  this simple function is sufficient."
-    //
-    // For IEEE double precision floating-point numbers converted into
-    // normalized diyfp's w = f * 2^e, with q = 64,
-    //
-    //      e >= -1022      (min IEEE exponent)
-    //           -52        (p - 1)
-    //           -52        (p - 1, possibly normalize denormal IEEE numbers)
-    //           -11        (normalize the diyfp)
-    //         = -1137
-    //
-    // and
-    //
-    //      e <= +1023      (max IEEE exponent)
-    //           -52        (p - 1)
-    //           -11        (normalize the diyfp)
-    //         = 960
-    //
-    // This binary exponent range [-1137,960] results in a decimal exponent
-    // range [-307,324]. One does not need to store a cached power for each
-    // k in this range. For each such k it suffices to find a cached power
-    // such that the exponent of the product lies in [alpha,gamma].
-    // This implies that the difference of the decimal exponents of adjacent
-    // table entries must be less than or equal to
-    //
-    //      floor( (gamma - alpha) * log_10(2) ) = 8.
-    //
-    // (A smaller distance gamma-alpha would require a larger table.)
-
-    // NB:
-    // Actually this function returns c, such that -60 <= e_c + e + 64 <= -34.
-
-    constexpr int kCachedPowersSize = 79;
-    constexpr int kCachedPowersMinDecExp = -300;
-    constexpr int kCachedPowersDecStep = 8;
-
-    static constexpr cached_power kCachedPowers[] =
-    {
-        { 0xAB70FE17C79AC6CA, -1060, -300 },
-        { 0xFF77B1FCBEBCDC4F, -1034, -292 },
-        { 0xBE5691EF416BD60C, -1007, -284 },
-        { 0x8DD01FAD907FFC3C,  -980, -276 },
-        { 0xD3515C2831559A83,  -954, -268 },
-        { 0x9D71AC8FADA6C9B5,  -927, -260 },
-        { 0xEA9C227723EE8BCB,  -901, -252 },
-        { 0xAECC49914078536D,  -874, -244 },
-        { 0x823C12795DB6CE57,  -847, -236 },
-        { 0xC21094364DFB5637,  -821, -228 },
-        { 0x9096EA6F3848984F,  -794, -220 },
-        { 0xD77485CB25823AC7,  -768, -212 },
-        { 0xA086CFCD97BF97F4,  -741, -204 },
-        { 0xEF340A98172AACE5,  -715, -196 },
-        { 0xB23867FB2A35B28E,  -688, -188 },
-        { 0x84C8D4DFD2C63F3B,  -661, -180 },
-        { 0xC5DD44271AD3CDBA,  -635, -172 },
-        { 0x936B9FCEBB25C996,  -608, -164 },
-        { 0xDBAC6C247D62A584,  -582, -156 },
-        { 0xA3AB66580D5FDAF6,  -555, -148 },
-        { 0xF3E2F893DEC3F126,  -529, -140 },
-        { 0xB5B5ADA8AAFF80B8,  -502, -132 },
-        { 0x87625F056C7C4A8B,  -475, -124 },
-        { 0xC9BCFF6034C13053,  -449, -116 },
-        { 0x964E858C91BA2655,  -422, -108 },
-        { 0xDFF9772470297EBD,  -396, -100 },
-        { 0xA6DFBD9FB8E5B88F,  -369,  -92 },
-        { 0xF8A95FCF88747D94,  -343,  -84 },
-        { 0xB94470938FA89BCF,  -316,  -76 },
-        { 0x8A08F0F8BF0F156B,  -289,  -68 },
-        { 0xCDB02555653131B6,  -263,  -60 },
-        { 0x993FE2C6D07B7FAC,  -236,  -52 },
-        { 0xE45C10C42A2B3B06,  -210,  -44 },
-        { 0xAA242499697392D3,  -183,  -36 },
-        { 0xFD87B5F28300CA0E,  -157,  -28 },
-        { 0xBCE5086492111AEB,  -130,  -20 },
-        { 0x8CBCCC096F5088CC,  -103,  -12 },
-        { 0xD1B71758E219652C,   -77,   -4 },
-        { 0x9C40000000000000,   -50,    4 },
-        { 0xE8D4A51000000000,   -24,   12 },
-        { 0xAD78EBC5AC620000,     3,   20 },
-        { 0x813F3978F8940984,    30,   28 },
-        { 0xC097CE7BC90715B3,    56,   36 },
-        { 0x8F7E32CE7BEA5C70,    83,   44 },
-        { 0xD5D238A4ABE98068,   109,   52 },
-        { 0x9F4F2726179A2245,   136,   60 },
-        { 0xED63A231D4C4FB27,   162,   68 },
-        { 0xB0DE65388CC8ADA8,   189,   76 },
-        { 0x83C7088E1AAB65DB,   216,   84 },
-        { 0xC45D1DF942711D9A,   242,   92 },
-        { 0x924D692CA61BE758,   269,  100 },
-        { 0xDA01EE641A708DEA,   295,  108 },
-        { 0xA26DA3999AEF774A,   322,  116 },
-        { 0xF209787BB47D6B85,   348,  124 },
-        { 0xB454E4A179DD1877,   375,  132 },
-        { 0x865B86925B9BC5C2,   402,  140 },
-        { 0xC83553C5C8965D3D,   428,  148 },
-        { 0x952AB45CFA97A0B3,   455,  156 },
-        { 0xDE469FBD99A05FE3,   481,  164 },
-        { 0xA59BC234DB398C25,   508,  172 },
-        { 0xF6C69A72A3989F5C,   534,  180 },
-        { 0xB7DCBF5354E9BECE,   561,  188 },
-        { 0x88FCF317F22241E2,   588,  196 },
-        { 0xCC20CE9BD35C78A5,   614,  204 },
-        { 0x98165AF37B2153DF,   641,  212 },
-        { 0xE2A0B5DC971F303A,   667,  220 },
-        { 0xA8D9D1535CE3B396,   694,  228 },
-        { 0xFB9B7CD9A4A7443C,   720,  236 },
-        { 0xBB764C4CA7A44410,   747,  244 },
-        { 0x8BAB8EEFB6409C1A,   774,  252 },
-        { 0xD01FEF10A657842C,   800,  260 },
-        { 0x9B10A4E5E9913129,   827,  268 },
-        { 0xE7109BFBA19C0C9D,   853,  276 },
-        { 0xAC2820D9623BF429,   880,  284 },
-        { 0x80444B5E7AA7CF85,   907,  292 },
-        { 0xBF21E44003ACDD2D,   933,  300 },
-        { 0x8E679C2F5E44FF8F,   960,  308 },
-        { 0xD433179D9C8CB841,   986,  316 },
-        { 0x9E19DB92B4E31BA9,  1013,  324 },
-    };
-
-    // This computation gives exactly the same results for k as
-    //      k = ceil((kAlpha - e - 1) * 0.30102999566398114)
-    // for |e| <= 1500, but doesn't require floating-point operations.
-    // NB: log_10(2) ~= 78913 / 2^18
-    assert(e >= -1500);
-    assert(e <=  1500);
-    const int f = kAlpha - e - 1;
-    const int k = (f * 78913) / (1 << 18) + (f > 0);
-
-    const int index = (-kCachedPowersMinDecExp + k + (kCachedPowersDecStep - 1)) / kCachedPowersDecStep;
-    assert(index >= 0);
-    assert(index < kCachedPowersSize);
-    static_cast<void>(kCachedPowersSize); // Fix warning.
-
-    const cached_power cached = kCachedPowers[index];
-    assert(kAlpha <= cached.e + e + 64);
-    assert(kGamma >= cached.e + e + 64);
-
-    return cached;
-}
-
-/*!
-For n != 0, returns k, such that pow10 := 10^(k-1) <= n < 10^k.
-For n == 0, returns 1 and sets pow10 := 1.
-*/
-inline int find_largest_pow10(const uint32_t n, uint32_t& pow10)
-{
-    // LCOV_EXCL_START
-    if (n >= 1000000000)
-    {
-        pow10 = 1000000000;
-        return 10;
-    }
-    // LCOV_EXCL_STOP
-    else if (n >= 100000000)
-    {
-        pow10 = 100000000;
-        return  9;
-    }
-    else if (n >= 10000000)
-    {
-        pow10 = 10000000;
-        return  8;
-    }
-    else if (n >= 1000000)
-    {
-        pow10 = 1000000;
-        return  7;
-    }
-    else if (n >= 100000)
-    {
-        pow10 = 100000;
-        return  6;
-    }
-    else if (n >= 10000)
-    {
-        pow10 = 10000;
-        return  5;
-    }
-    else if (n >= 1000)
-    {
-        pow10 = 1000;
-        return  4;
-    }
-    else if (n >= 100)
-    {
-        pow10 = 100;
-        return  3;
-    }
-    else if (n >= 10)
-    {
-        pow10 = 10;
-        return  2;
-    }
-    else
-    {
-        pow10 = 1;
-        return 1;
-    }
-}
-
-inline void grisu2_round(char* buf, int len, uint64_t dist, uint64_t delta,
-                         uint64_t rest, uint64_t ten_k)
-{
-    assert(len >= 1);
-    assert(dist <= delta);
-    assert(rest <= delta);
-    assert(ten_k > 0);
-
-    //               <--------------------------- delta ---->
-    //                                  <---- dist --------->
-    // --------------[------------------+-------------------]--------------
-    //               M-                 w                   M+
-    //
-    //                                  ten_k
-    //                                <------>
-    //                                       <---- rest ---->
-    // --------------[------------------+----+--------------]--------------
-    //                                  w    V
-    //                                       = buf * 10^k
-    //
-    // ten_k represents a unit-in-the-last-place in the decimal representation
-    // stored in buf.
-    // Decrement buf by ten_k while this takes buf closer to w.
-
-    // The tests are written in this order to avoid overflow in unsigned
-    // integer arithmetic.
-
-    while (rest < dist
-            and delta - rest >= ten_k
-            and (rest + ten_k < dist or dist - rest > rest + ten_k - dist))
-    {
-        assert(buf[len - 1] != '0');
-        buf[len - 1]--;
-        rest += ten_k;
-    }
-}
-
-/*!
-Generates V = buffer * 10^decimal_exponent, such that M- <= V <= M+.
-M- and M+ must be normalized and share the same exponent -60 <= e <= -32.
-*/
-inline void grisu2_digit_gen(char* buffer, int& length, int& decimal_exponent,
-                             diyfp M_minus, diyfp w, diyfp M_plus)
-{
-    static_assert(kAlpha >= -60, "internal error");
-    static_assert(kGamma <= -32, "internal error");
-
-    // Generates the digits (and the exponent) of a decimal floating-point
-    // number V = buffer * 10^decimal_exponent in the range [M-, M+]. The diyfp's
-    // w, M- and M+ share the same exponent e, which satisfies alpha <= e <= gamma.
-    //
-    //               <--------------------------- delta ---->
-    //                                  <---- dist --------->
-    // --------------[------------------+-------------------]--------------
-    //               M-                 w                   M+
-    //
-    // Grisu2 generates the digits of M+ from left to right and stops as soon as
-    // V is in [M-,M+].
-
-    assert(M_plus.e >= kAlpha);
-    assert(M_plus.e <= kGamma);
-
-    uint64_t delta = diyfp::sub(M_plus, M_minus).f; // (significand of (M+ - M-), implicit exponent is e)
-    uint64_t dist  = diyfp::sub(M_plus, w      ).f; // (significand of (M+ - w ), implicit exponent is e)
-
-    // Split M+ = f * 2^e into two parts p1 and p2 (note: e < 0):
-    //
-    //      M+ = f * 2^e
-    //         = ((f div 2^-e) * 2^-e + (f mod 2^-e)) * 2^e
-    //         = ((p1        ) * 2^-e + (p2        )) * 2^e
-    //         = p1 + p2 * 2^e
-
-    const diyfp one(uint64_t{1} << -M_plus.e, M_plus.e);
-
-    uint32_t p1 = static_cast<uint32_t>(M_plus.f >> -one.e); // p1 = f div 2^-e (Since -e >= 32, p1 fits into a 32-bit int.)
-    uint64_t p2 = M_plus.f & (one.f - 1);                    // p2 = f mod 2^-e
-
-    // 1)
-    //
-    // Generate the digits of the integral part p1 = d[n-1]...d[1]d[0]
-
-    assert(p1 > 0);
-
-    uint32_t pow10;
-    const int k = find_largest_pow10(p1, pow10);
-
-    //      10^(k-1) <= p1 < 10^k, pow10 = 10^(k-1)
-    //
-    //      p1 = (p1 div 10^(k-1)) * 10^(k-1) + (p1 mod 10^(k-1))
-    //         = (d[k-1]         ) * 10^(k-1) + (p1 mod 10^(k-1))
-    //
-    //      M+ = p1                                             + p2 * 2^e
-    //         = d[k-1] * 10^(k-1) + (p1 mod 10^(k-1))          + p2 * 2^e
-    //         = d[k-1] * 10^(k-1) + ((p1 mod 10^(k-1)) * 2^-e + p2) * 2^e
-    //         = d[k-1] * 10^(k-1) + (                         rest) * 2^e
-    //
-    // Now generate the digits d[n] of p1 from left to right (n = k-1,...,0)
-    //
-    //      p1 = d[k-1]...d[n] * 10^n + d[n-1]...d[0]
-    //
-    // but stop as soon as
-    //
-    //      rest * 2^e = (d[n-1]...d[0] * 2^-e + p2) * 2^e <= delta * 2^e
-
-    int n = k;
-    while (n > 0)
-    {
-        // Invariants:
-        //      M+ = buffer * 10^n + (p1 + p2 * 2^e)    (buffer = 0 for n = k)
-        //      pow10 = 10^(n-1) <= p1 < 10^n
-        //
-        const uint32_t d = p1 / pow10;  // d = p1 div 10^(n-1)
-        const uint32_t r = p1 % pow10;  // r = p1 mod 10^(n-1)
-        //
-        //      M+ = buffer * 10^n + (d * 10^(n-1) + r) + p2 * 2^e
-        //         = (buffer * 10 + d) * 10^(n-1) + (r + p2 * 2^e)
-        //
-        assert(d <= 9);
-        buffer[length++] = static_cast<char>('0' + d); // buffer := buffer * 10 + d
-        //
-        //      M+ = buffer * 10^(n-1) + (r + p2 * 2^e)
-        //
-        p1 = r;
-        n--;
-        //
-        //      M+ = buffer * 10^n + (p1 + p2 * 2^e)
-        //      pow10 = 10^n
-        //
-
-        // Now check if enough digits have been generated.
-        // Compute
-        //
-        //      p1 + p2 * 2^e = (p1 * 2^-e + p2) * 2^e = rest * 2^e
-        //
-        // Note:
-        // Since rest and delta share the same exponent e, it suffices to
-        // compare the significands.
-        const uint64_t rest = (uint64_t{p1} << -one.e) + p2;
-        if (rest <= delta)
-        {
-            // V = buffer * 10^n, with M- <= V <= M+.
-
-            decimal_exponent += n;
-
-            // We may now just stop. But instead look if the buffer could be
-            // decremented to bring V closer to w.
-            //
-            // pow10 = 10^n is now 1 ulp in the decimal representation V.
-            // The rounding procedure works with diyfp's with an implicit
-            // exponent of e.
-            //
-            //      10^n = (10^n * 2^-e) * 2^e = ulp * 2^e
-            //
-            const uint64_t ten_n = uint64_t{pow10} << -one.e;
-            grisu2_round(buffer, length, dist, delta, rest, ten_n);
-
-            return;
-        }
-
-        pow10 /= 10;
-        //
-        //      pow10 = 10^(n-1) <= p1 < 10^n
-        // Invariants restored.
-    }
-
-    // 2)
-    //
-    // The digits of the integral part have been generated:
-    //
-    //      M+ = d[k-1]...d[1]d[0] + p2 * 2^e
-    //         = buffer            + p2 * 2^e
-    //
-    // Now generate the digits of the fractional part p2 * 2^e.
-    //
-    // Note:
-    // No decimal point is generated: the exponent is adjusted instead.
-    //
-    // p2 actually represents the fraction
-    //
-    //      p2 * 2^e
-    //          = p2 / 2^-e
-    //          = d[-1] / 10^1 + d[-2] / 10^2 + ...
-    //
-    // Now generate the digits d[-m] of p1 from left to right (m = 1,2,...)
-    //
-    //      p2 * 2^e = d[-1]d[-2]...d[-m] * 10^-m
-    //                      + 10^-m * (d[-m-1] / 10^1 + d[-m-2] / 10^2 + ...)
-    //
-    // using
-    //
-    //      10^m * p2 = ((10^m * p2) div 2^-e) * 2^-e + ((10^m * p2) mod 2^-e)
-    //                = (                   d) * 2^-e + (                   r)
-    //
-    // or
-    //      10^m * p2 * 2^e = d + r * 2^e
-    //
-    // i.e.
-    //
-    //      M+ = buffer + p2 * 2^e
-    //         = buffer + 10^-m * (d + r * 2^e)
-    //         = (buffer * 10^m + d) * 10^-m + 10^-m * r * 2^e
-    //
-    // and stop as soon as 10^-m * r * 2^e <= delta * 2^e
-
-    assert(p2 > delta);
-
-    int m = 0;
-    for (;;)
-    {
-        // Invariant:
-        //      M+ = buffer * 10^-m + 10^-m * (d[-m-1] / 10 + d[-m-2] / 10^2 + ...) * 2^e
-        //         = buffer * 10^-m + 10^-m * (p2                                 ) * 2^e
-        //         = buffer * 10^-m + 10^-m * (1/10 * (10 * p2)                   ) * 2^e
-        //         = buffer * 10^-m + 10^-m * (1/10 * ((10*p2 div 2^-e) * 2^-e + (10*p2 mod 2^-e)) * 2^e
-        //
-        assert(p2 <= UINT64_MAX / 10);
-        p2 *= 10;
-        const uint64_t d = p2 >> -one.e;     // d = (10 * p2) div 2^-e
-        const uint64_t r = p2 & (one.f - 1); // r = (10 * p2) mod 2^-e
-        //
-        //      M+ = buffer * 10^-m + 10^-m * (1/10 * (d * 2^-e + r) * 2^e
-        //         = buffer * 10^-m + 10^-m * (1/10 * (d + r * 2^e))
-        //         = (buffer * 10 + d) * 10^(-m-1) + 10^(-m-1) * r * 2^e
-        //
-        assert(d <= 9);
-        buffer[length++] = static_cast<char>('0' + d); // buffer := buffer * 10 + d
-        //
-        //      M+ = buffer * 10^(-m-1) + 10^(-m-1) * r * 2^e
-        //
-        p2 = r;
-        m++;
-        //
-        //      M+ = buffer * 10^-m + 10^-m * p2 * 2^e
-        // Invariant restored.
-
-        // Check if enough digits have been generated.
-        //
-        //      10^-m * p2 * 2^e <= delta * 2^e
-        //              p2 * 2^e <= 10^m * delta * 2^e
-        //                    p2 <= 10^m * delta
-        delta *= 10;
-        dist  *= 10;
-        if (p2 <= delta)
-        {
-            break;
-        }
-    }
-
-    // V = buffer * 10^-m, with M- <= V <= M+.
-
-    decimal_exponent -= m;
-
-    // 1 ulp in the decimal representation is now 10^-m.
-    // Since delta and dist are now scaled by 10^m, we need to do the
-    // same with ulp in order to keep the units in sync.
-    //
-    //      10^m * 10^-m = 1 = 2^-e * 2^e = ten_m * 2^e
-    //
-    const uint64_t ten_m = one.f;
-    grisu2_round(buffer, length, dist, delta, p2, ten_m);
-
-    // By construction this algorithm generates the shortest possible decimal
-    // number (Loitsch, Theorem 6.2) which rounds back to w.
-    // For an input number of precision p, at least
-    //
-    //      N = 1 + ceil(p * log_10(2))
-    //
-    // decimal digits are sufficient to identify all binary floating-point
-    // numbers (Matula, "In-and-Out conversions").
-    // This implies that the algorithm does not produce more than N decimal
-    // digits.
-    //
-    //      N = 17 for p = 53 (IEEE double precision)
-    //      N = 9  for p = 24 (IEEE single precision)
-}
-
-/*!
-v = buf * 10^decimal_exponent
-len is the length of the buffer (number of decimal digits)
-The buffer must be large enough, i.e. >= max_digits10.
-*/
-inline void grisu2(char* buf, int& len, int& decimal_exponent,
-                   diyfp m_minus, diyfp v, diyfp m_plus)
-{
-    assert(m_plus.e == m_minus.e);
-    assert(m_plus.e == v.e);
-
-    //  --------(-----------------------+-----------------------)--------    (A)
-    //          m-                      v                       m+
-    //
-    //  --------------------(-----------+-----------------------)--------    (B)
-    //                      m-          v                       m+
-    //
-    // First scale v (and m- and m+) such that the exponent is in the range
-    // [alpha, gamma].
-
-    const cached_power cached = get_cached_power_for_binary_exponent(m_plus.e);
-
-    const diyfp c_minus_k(cached.f, cached.e); // = c ~= 10^-k
-
-    // The exponent of the products is = v.e + c_minus_k.e + q and is in the range [alpha,gamma]
-    const diyfp w       = diyfp::mul(v,       c_minus_k);
-    const diyfp w_minus = diyfp::mul(m_minus, c_minus_k);
-    const diyfp w_plus  = diyfp::mul(m_plus,  c_minus_k);
-
-    //  ----(---+---)---------------(---+---)---------------(---+---)----
-    //          w-                      w                       w+
-    //          = c*m-                  = c*v                   = c*m+
-    //
-    // diyfp::mul rounds its result and c_minus_k is approximated too. w, w- and
-    // w+ are now off by a small amount.
-    // In fact:
-    //
-    //      w - v * 10^k < 1 ulp
-    //
-    // To account for this inaccuracy, add resp. subtract 1 ulp.
-    //
-    //  --------+---[---------------(---+---)---------------]---+--------
-    //          w-  M-                  w                   M+  w+
-    //
-    // Now any number in [M-, M+] (bounds included) will round to w when input,
-    // regardless of how the input rounding algorithm breaks ties.
-    //
-    // And digit_gen generates the shortest possible such number in [M-, M+].
-    // Note that this does not mean that Grisu2 always generates the shortest
-    // possible number in the interval (m-, m+).
-    const diyfp M_minus(w_minus.f + 1, w_minus.e);
-    const diyfp M_plus (w_plus.f  - 1, w_plus.e );
-
-    decimal_exponent = -cached.k; // = -(-k) = k
-
-    grisu2_digit_gen(buf, len, decimal_exponent, M_minus, w, M_plus);
-}
-
-/*!
-v = buf * 10^decimal_exponent
-len is the length of the buffer (number of decimal digits)
-The buffer must be large enough, i.e. >= max_digits10.
-*/
-template <typename FloatType>
-void grisu2(char* buf, int& len, int& decimal_exponent, FloatType value)
-{
-    static_assert(diyfp::kPrecision >= std::numeric_limits<FloatType>::digits + 3,
-                  "internal error: not enough precision");
-
-    assert(std::isfinite(value));
-    assert(value > 0);
-
-    // If the neighbors (and boundaries) of 'value' are always computed for double-precision
-    // numbers, all float's can be recovered using strtod (and strtof). However, the resulting
-    // decimal representations are not exactly "short".
-    //
-    // The documentation for 'std::to_chars' (http://en.cppreference.com/w/cpp/utility/to_chars)
-    // says "value is converted to a string as if by std::sprintf in the default ("C") locale"
-    // and since sprintf promotes float's to double's, I think this is exactly what 'std::to_chars'
-    // does.
-    // On the other hand, the documentation for 'std::to_chars' requires that "parsing the
-    // representation using the corresponding std::from_chars function recovers value exactly". That
-    // indicates that single precision floating-point numbers should be recovered using
-    // 'std::strtof'.
-    //
-    // NB: If the neighbors are computed for single-precision numbers, there is a single float
-    //     (7.0385307e-26f) which can't be recovered using strtod. The resulting double precision
-    //     value is off by 1 ulp.
-#if 0
-    const boundaries w = compute_boundaries(static_cast<double>(value));
-#else
-    const boundaries w = compute_boundaries(value);
-#endif
-
-    grisu2(buf, len, decimal_exponent, w.minus, w.w, w.plus);
-}
-
-/*!
-@brief appends a decimal representation of e to buf
-@return a pointer to the element following the exponent.
-@pre -1000 < e < 1000
-*/
-inline char* append_exponent(char* buf, int e)
-{
-    assert(e > -1000);
-    assert(e <  1000);
-
-    if (e < 0)
-    {
-        e = -e;
-        *buf++ = '-';
-    }
-    else
-    {
-        *buf++ = '+';
-    }
-
-    uint32_t k = static_cast<uint32_t>(e);
-    if (k < 10)
-    {
-        // Always print at least two digits in the exponent.
-        // This is for compatibility with printf("%g").
-        *buf++ = '0';
-        *buf++ = static_cast<char>('0' + k);
-    }
-    else if (k < 100)
-    {
-        *buf++ = static_cast<char>('0' + k / 10);
-        k %= 10;
-        *buf++ = static_cast<char>('0' + k);
-    }
-    else
-    {
-        *buf++ = static_cast<char>('0' + k / 100);
-        k %= 100;
-        *buf++ = static_cast<char>('0' + k / 10);
-        k %= 10;
-        *buf++ = static_cast<char>('0' + k);
-    }
-
-    return buf;
-}
-
-/*!
-@brief prettify v = buf * 10^decimal_exponent
-
-If v is in the range [10^min_exp, 10^max_exp) it will be printed in fixed-point
-notation. Otherwise it will be printed in exponential notation.
-
-@pre min_exp < 0
-@pre max_exp > 0
-*/
-inline char* format_buffer(char* buf, int len, int decimal_exponent,
-                           int min_exp, int max_exp)
-{
-    assert(min_exp < 0);
-    assert(max_exp > 0);
-
-    const int k = len;
-    const int n = len + decimal_exponent;
-
-    // v = buf * 10^(n-k)
-    // k is the length of the buffer (number of decimal digits)
-    // n is the position of the decimal point relative to the start of the buffer.
-
-    if (k <= n and n <= max_exp)
-    {
-        // digits[000]
-        // len <= max_exp + 2
-
-        std::memset(buf + k, '0', static_cast<size_t>(n - k));
-        // Make it look like a floating-point number (#362, #378)
-        buf[n + 0] = '.';
-        buf[n + 1] = '0';
-        return buf + (n + 2);
-    }
-
-    if (0 < n and n <= max_exp)
-    {
-        // dig.its
-        // len <= max_digits10 + 1
-
-        assert(k > n);
-
-        std::memmove(buf + (n + 1), buf + n, static_cast<size_t>(k - n));
-        buf[n] = '.';
-        return buf + (k + 1);
-    }
-
-    if (min_exp < n and n <= 0)
-    {
-        // 0.[000]digits
-        // len <= 2 + (-min_exp - 1) + max_digits10
-
-        std::memmove(buf + (2 + -n), buf, static_cast<size_t>(k));
-        buf[0] = '0';
-        buf[1] = '.';
-        std::memset(buf + 2, '0', static_cast<size_t>(-n));
-        return buf + (2 + (-n) + k);
-    }
-
-    if (k == 1)
-    {
-        // dE+123
-        // len <= 1 + 5
-
-        buf += 1;
-    }
-    else
-    {
-        // d.igitsE+123
-        // len <= max_digits10 + 1 + 5
-
-        std::memmove(buf + 2, buf + 1, static_cast<size_t>(k - 1));
-        buf[1] = '.';
-        buf += 1 + k;
-    }
-
-    *buf++ = 'e';
-    return append_exponent(buf, n - 1);
-}
-
-} // namespace dtoa_impl
-
-/*!
-@brief generates a decimal representation of the floating-point number value in [first, last).
-
-The format of the resulting decimal representation is similar to printf's %g
-format. Returns an iterator pointing past-the-end of the decimal representation.
-
-@note The input number must be finite, i.e. NaN's and Inf's are not supported.
-@note The buffer must be large enough.
-@note The result is NOT null-terminated.
-*/
-template <typename FloatType>
-char* to_chars(char* first, char* last, FloatType value)
-{
-    static_cast<void>(last); // maybe unused - fix warning
-    assert(std::isfinite(value));
-
-    // Use signbit(value) instead of (value < 0) since signbit works for -0.
-    if (std::signbit(value))
-    {
-        value = -value;
-        *first++ = '-';
-    }
-
-    if (value == 0) // +-0
-    {
-        *first++ = '0';
-        // Make it look like a floating-point number (#362, #378)
-        *first++ = '.';
-        *first++ = '0';
-        return first;
-    }
-
-    assert(last - first >= std::numeric_limits<FloatType>::max_digits10);
-
-    // Compute v = buffer * 10^decimal_exponent.
-    // The decimal digits are stored in the buffer, which needs to be interpreted
-    // as an unsigned decimal integer.
-    // len is the length of the buffer, i.e. the number of decimal digits.
-    int len = 0;
-    int decimal_exponent = 0;
-    dtoa_impl::grisu2(first, len, decimal_exponent, value);
-
-    assert(len <= std::numeric_limits<FloatType>::max_digits10);
-
-    // Format the buffer like printf("%.*g", prec, value)
-    constexpr int kMinExp = -4;
-    // Use digits10 here to increase compatibility with version 2.
-    constexpr int kMaxExp = std::numeric_limits<FloatType>::digits10;
-
-    assert(last - first >= kMaxExp + 2);
-    assert(last - first >= 2 + (-kMinExp - 1) + std::numeric_limits<FloatType>::max_digits10);
-    assert(last - first >= std::numeric_limits<FloatType>::max_digits10 + 6);
-
-    return dtoa_impl::format_buffer(first, len, decimal_exponent, kMinExp, kMaxExp);
-}
-
-}  // namespace
-
-void json::serializer::dump(const json& val, const bool pretty_print,
-          const bool ensure_ascii,
-          const unsigned int indent_step,
-          const unsigned int current_indent)
-{
-    switch (val.m_type)
-    {
-        case value_t::object:
-        {
-            if (val.m_value.object->empty())
-            {
-                o << "{}";
-                return;
-            }
-
-            // we need to iterate over the object values in sorted key order
-            SmallVector<StringMapConstIterator<json>, 64> sorted;
-            for (auto i = val.m_value.object->begin(),
-                 end = val.m_value.object->end(); i != end; ++i)
-            {
-                sorted.push_back(i);
-            }
-            std::sort(sorted.begin(), sorted.end(),
-                      [](const StringMapConstIterator<json>& a,
-                         const StringMapConstIterator<json>& b) {
-                        return a->getKey() < b->getKey();
-                      });
-
-            if (pretty_print)
-            {
-                o << "{\n";
-
-                // variable to hold indentation for recursive calls
-                const auto new_indent = current_indent + indent_step;
-                if (JSON_UNLIKELY(indent_string.size() < new_indent))
-                {
-                    indent_string.resize(indent_string.size() * 2, indent_char);
-                }
-
-                // first n-1 elements
-                auto i = sorted.begin();
-                for (std::size_t cnt = 0; cnt < sorted.size() - 1; ++cnt, ++i)
-                {
-                    o.write(indent_string.c_str(), new_indent);
-                    o << '\"';
-                    dump_escaped((*i)->first(), ensure_ascii);
-                    o << "\": ";
-                    dump((*i)->second, true, ensure_ascii, indent_step, new_indent);
-                    o << ",\n";
-                }
-
-                // last element
-                assert(i != sorted.end());
-                //assert(std::next(i) == val.m_value.object->end());
-                o.write(indent_string.c_str(), new_indent);
-                o << '\"';
-                dump_escaped((*i)->first(), ensure_ascii);
-                o << "\": ";
-                dump((*i)->second, true, ensure_ascii, indent_step, new_indent);
-
-                o << '\n';
-                o.write(indent_string.c_str(), current_indent);
-                o << '}';
-            }
-            else
-            {
-                o << '{';
-
-                // first n-1 elements
-                auto i = sorted.begin();
-                for (std::size_t cnt = 0; cnt < sorted.size() - 1; ++cnt, ++i)
-                {
-                    o << '\"';
-                    dump_escaped((*i)->first(), ensure_ascii);
-                    o << "\":";
-                    dump((*i)->second, false, ensure_ascii, indent_step, current_indent);
-                    o << ',';
-                }
-
-                // last element
-                assert(i != sorted.end());
-                //assert(std::next(i) == val.m_value.object->end());
-                o << '\"';
-                dump_escaped((*i)->first(), ensure_ascii);
-                o << "\":";
-                dump((*i)->second, false, ensure_ascii, indent_step, current_indent);
-
-                o << '}';
-            }
-
-            return;
-        }
-
-        case value_t::array:
-        {
-            if (val.m_value.array->empty())
-            {
-                o << "[]";
-                return;
-            }
-
-            if (pretty_print)
-            {
-                o << "[\n";
-
-                // variable to hold indentation for recursive calls
-                const auto new_indent = current_indent + indent_step;
-                if (JSON_UNLIKELY(indent_string.size() < new_indent))
-                {
-                    indent_string.resize(indent_string.size() * 2, indent_char);
-                }
-
-                // first n-1 elements
-                for (auto i = val.m_value.array->cbegin();
-                        i != val.m_value.array->cend() - 1; ++i)
-                {
-                    o.write(indent_string.c_str(), new_indent);
-                    dump(*i, true, ensure_ascii, indent_step, new_indent);
-                    o << ",\n";
-                }
-
-                // last element
-                assert(not val.m_value.array->empty());
-                o.write(indent_string.c_str(), new_indent);
-                dump(val.m_value.array->back(), true, ensure_ascii, indent_step, new_indent);
-
-                o << '\n';
-                o.write(indent_string.c_str(), current_indent);
-                o << ']';
-            }
-            else
-            {
-                o << '[';
-
-                // first n-1 elements
-                for (auto i = val.m_value.array->cbegin();
-                        i != val.m_value.array->cend() - 1; ++i)
-                {
-                    dump(*i, false, ensure_ascii, indent_step, current_indent);
-                    o << ',';
-                }
-
-                // last element
-                assert(not val.m_value.array->empty());
-                dump(val.m_value.array->back(), false, ensure_ascii, indent_step, current_indent);
-
-                o << ']';
-            }
-
-            return;
-        }
-
-        case value_t::string:
-        {
-            o << '\"';
-            dump_escaped(*val.m_value.string, ensure_ascii);
-            o << '\"';
-            return;
-        }
-
-        case value_t::boolean:
-        {
-            if (val.m_value.boolean)
-            {
-                o << "true";
-            }
-            else
-            {
-                o << "false";
-            }
-            return;
-        }
-
-        case value_t::number_integer:
-        {
-            dump_integer(val.m_value.number_integer);
-            return;
-        }
-
-        case value_t::number_unsigned:
-        {
-            dump_integer(val.m_value.number_unsigned);
-            return;
-        }
-
-        case value_t::number_float:
-        {
-            dump_float(val.m_value.number_float);
-            return;
-        }
-
-        case value_t::discarded:
-        {
-            o << "<discarded>";
-            return;
-        }
-
-        case value_t::null:
-        {
-            o << "null";
-            return;
-        }
-    }
-}
-
-void json::serializer::dump_escaped(std::string_view s, const bool ensure_ascii)
-{
-    uint32_t codepoint;
-    uint8_t state = UTF8_ACCEPT;
-
-    for (std::size_t i = 0; i < s.size(); ++i)
-    {
-        const auto byte = static_cast<uint8_t>(s[i]);
-
-        switch (decode(state, codepoint, byte))
-        {
-            case UTF8_ACCEPT:  // decode found a new code point
-            {
-                switch (codepoint)
-                {
-                    case 0x08: // backspace
-                    {
-                        o << '\\' << 'b';
-                        break;
-                    }
-
-                    case 0x09: // horizontal tab
-                    {
-                        o << '\\' << 't';
-                        break;
-                    }
-
-                    case 0x0A: // newline
-                    {
-                        o << '\\' << 'n';
-                        break;
-                    }
-
-                    case 0x0C: // formfeed
-                    {
-                        o << '\\' << 'f';
-                        break;
-                    }
-
-                    case 0x0D: // carriage return
-                    {
-                        o << '\\' << 'r';
-                        break;
-                    }
-
-                    case 0x22: // quotation mark
-                    {
-                        o << '\\' << '\"';
-                        break;
-                    }
-
-                    case 0x5C: // reverse solidus
-                    {
-                        o << '\\' << '\\';
-                        break;
-                    }
-
-                    default:
-                    {
-                        // escape control characters (0x00..0x1F) or, if
-                        // ensure_ascii parameter is used, non-ASCII characters
-                        if ((codepoint <= 0x1F) or (ensure_ascii and (codepoint >= 0x7F)))
-                        {
-                            if (codepoint <= 0xFFFF)
-                            {
-                                o << fmt::format("\\u{:04x}", codepoint);
-                            }
-                            else
-                            {
-                                o << fmt::format("\\u{:04x}", 0xD7C0 + (codepoint >> 10));
-                                o << fmt::format("\\u{:04x}", 0xDC00 + (codepoint & 0x3FF));
-                            }
-                        }
-                        else
-                        {
-                            // copy byte to buffer (all previous bytes
-                            // been copied have in default case above)
-                            o << s[i];
-                        }
-                        break;
-                    }
-                }
-                break;
-            }
-
-            case UTF8_REJECT:  // decode found invalid UTF-8 byte
-            {
-                JSON_THROW(type_error::create(316, fmt::format("invalid UTF-8 byte at index {}: {:#02x}", i, byte)));
-            }
-
-            default:  // decode found yet incomplete multi-byte code point
-            {
-                if (not ensure_ascii)
-                {
-                    // code point will not be escaped - copy byte to buffer
-                    o << s[i];
-                }
-                break;
-            }
-        }
-    }
-
-    if (JSON_UNLIKELY(state != UTF8_ACCEPT))
-    {
-        // we finish reading, but do not accept: string was incomplete
-        JSON_THROW(type_error::create(316, fmt::format("incomplete UTF-8 string; last byte: {:#02x}", s.back())));
-    }
-}
-
-void json::serializer::dump_float(double x)
-{
-    // NaN / inf
-    if (not std::isfinite(x))
-    {
-        o << "null";
-        return;
-    }
-
-    // use the Grisu2 algorithm to produce short numbers which are
-    // guaranteed to round-trip, using strtof and strtod, resp.
-    char* begin = number_buffer.data();
-    char* end = to_chars(begin, begin + number_buffer.size(), x);
-
-    o.write(begin, static_cast<size_t>(end - begin));
-}
-
-uint8_t json::serializer::decode(uint8_t& state, uint32_t& codep, const uint8_t byte) noexcept
-{
-    static const std::array<uint8_t, 400> utf8d =
-    {
-        {
-            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 00..1F
-            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 20..3F
-            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 40..5F
-            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 60..7F
-            1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, // 80..9F
-            7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, // A0..BF
-            8, 8, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // C0..DF
-            0xA, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x4, 0x3, 0x3, // E0..EF
-            0xB, 0x6, 0x6, 0x6, 0x5, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, // F0..FF
-            0x0, 0x1, 0x2, 0x3, 0x5, 0x8, 0x7, 0x1, 0x1, 0x1, 0x4, 0x6, 0x1, 0x1, 0x1, 0x1, // s0..s0
-            1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, // s1..s2
-            1, 2, 1, 1, 1, 1, 1, 2, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, // s3..s4
-            1, 2, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 1, 3, 1, 1, 1, 1, 1, 1, // s5..s6
-            1, 3, 1, 1, 1, 1, 1, 3, 1, 3, 1, 1, 1, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 // s7..s8
-        }
-    };
-
-    const uint8_t type = utf8d[byte];
-
-    codep = (state != UTF8_ACCEPT)
-            ? (byte & 0x3fu) | (codep << 6)
-            : static_cast<uint32_t>(0xff >> type) & (byte);
-
-    state = utf8d[256u + state * 16u + type];
-    return state;
-}
-
-std::string json::dump(const int indent, const char indent_char,
-                 const bool ensure_ascii) const
-{
-    std::string result;
-    raw_string_ostream os(result);
-    dump(os, indent, indent_char, ensure_ascii);
-    return result;
-}
-
-void json::dump(raw_ostream& os, int indent, const char indent_char,
-          const bool ensure_ascii) const
-{
-    serializer s(os, indent_char);
-
-    if (indent >= 0)
-    {
-        s.dump(*this, true, ensure_ascii, static_cast<unsigned int>(indent));
-    }
-    else
-    {
-        s.dump(*this, false, ensure_ascii, 0);
-    }
-
-    os.flush();
-}
-
-raw_ostream& operator<<(raw_ostream& o, const json& j)
-{
-    j.dump(o, 0);
-    return o;
-}
-
-std::ostream& operator<<(std::ostream& o, const json& j)
-{
-    raw_os_ostream os(o);
-    j.dump(os, 0);
-    return o;
-}
-
-}  // namespace wpi
diff --git a/wpiutil/src/main/native/cpp/leb128.cpp b/wpiutil/src/main/native/cpp/leb128.cpp
index 9657883..17fbeb9 100644
--- a/wpiutil/src/main/native/cpp/leb128.cpp
+++ b/wpiutil/src/main/native/cpp/leb128.cpp
@@ -7,7 +7,6 @@
 #include "wpi/SpanExtras.h"
 #include "wpi/raw_istream.h"
 #include "wpi/raw_ostream.h"
-#include "wpi/span.h"
 
 namespace wpi {
 
@@ -98,7 +97,7 @@
   return true;
 }
 
-std::optional<uint64_t> Uleb128Reader::ReadOne(span<const uint8_t>* in) {
+std::optional<uint64_t> Uleb128Reader::ReadOne(std::span<const uint8_t>* in) {
   while (!in->empty()) {
     uint8_t byte = in->front();
     *in = wpi::drop_front(*in);
diff --git a/wpiutil/src/main/native/cpp/llvm/ConvertUTF.cpp b/wpiutil/src/main/native/cpp/llvm/ConvertUTF.cpp
deleted file mode 100644
index 3050e63..0000000
--- a/wpiutil/src/main/native/cpp/llvm/ConvertUTF.cpp
+++ /dev/null
@@ -1,834 +0,0 @@
-/*===--- ConvertUTF.c - Universal Character Names conversions ---------------===
- *
- *                     The LLVM Compiler Infrastructure
- *
- * This file is distributed under the University of Illinois Open Source
- * License. See LICENSE.TXT for details.
- *
- *===------------------------------------------------------------------------=*/
-/*
- * Copyright 2001-2004 Unicode, Inc.
- *
- * Disclaimer
- *
- * This source code is provided as is by Unicode, Inc. No claims are
- * made as to fitness for any particular purpose. No warranties of any
- * kind are expressed or implied. The recipient agrees to determine
- * applicability of information provided. If this file has been
- * purchased on magnetic or optical media from Unicode, Inc., the
- * sole remedy for any claim will be exchange of defective media
- * within 90 days of receipt.
- *
- * Limitations on Rights to Redistribute This Code
- *
- * Unicode, Inc. hereby grants the right to freely use the information
- * supplied in this file in the creation of products supporting the
- * Unicode Standard, and to make copies of this file in any form
- * for internal or external distribution as long as this notice
- * remains attached.
- */
-
-/* ---------------------------------------------------------------------
-
-    Conversions between UTF32, UTF-16, and UTF-8. Source code file.
-    Author: Mark E. Davis, 1994.
-    Rev History: Rick McGowan, fixes & updates May 2001.
-    Sept 2001: fixed const & error conditions per
-        mods suggested by S. Parent & A. Lillich.
-    June 2002: Tim Dodd added detection and handling of incomplete
-        source sequences, enhanced error detection, added casts
-        to eliminate compiler warnings.
-    July 2003: slight mods to back out aggressive FFFE detection.
-    Jan 2004: updated switches in from-UTF8 conversions.
-    Oct 2004: updated to use UNI_MAX_LEGAL_UTF32 in UTF-32 conversions.
-
-    See the header file "ConvertUTF.h" for complete documentation.
-
------------------------------------------------------------------------- */
-
-#include "wpi/ConvertUTF.h"
-#ifdef CVTUTF_DEBUG
-#include <stdio.h>
-#endif
-#include <assert.h>
-
-#ifdef _WIN32
-#include "wpi/WindowsError.h"
-#include "Windows/WindowsSupport.h"
-#endif
-
-/*
- * This code extensively uses fall-through switches.
- * Keep the compiler from warning about that.
- */
-#if defined(__clang__) && defined(__has_warning)
-# if __has_warning("-Wimplicit-fallthrough")
-#  define ConvertUTF_DISABLE_WARNINGS \
-    _Pragma("clang diagnostic push")  \
-    _Pragma("clang diagnostic ignored \"-Wimplicit-fallthrough\"")
-#  define ConvertUTF_RESTORE_WARNINGS \
-    _Pragma("clang diagnostic pop")
-# endif
-#elif defined(__GNUC__) && __GNUC__ > 6
-# define ConvertUTF_DISABLE_WARNINGS \
-   _Pragma("GCC diagnostic push")    \
-   _Pragma("GCC diagnostic ignored \"-Wimplicit-fallthrough\"")
-# define ConvertUTF_RESTORE_WARNINGS \
-   _Pragma("GCC diagnostic pop")
-#endif
-#ifndef ConvertUTF_DISABLE_WARNINGS
-# define ConvertUTF_DISABLE_WARNINGS
-#endif
-#ifndef ConvertUTF_RESTORE_WARNINGS
-# define ConvertUTF_RESTORE_WARNINGS
-#endif
-
-ConvertUTF_DISABLE_WARNINGS
-
-namespace wpi {
-
-static const int halfShift  = 10; /* used for shifting by 10 bits */
-
-static const UTF32 halfBase = 0x0010000UL;
-static const UTF32 halfMask = 0x3FFUL;
-
-#define UNI_SUR_HIGH_START  (UTF32)0xD800
-#define UNI_SUR_HIGH_END    (UTF32)0xDBFF
-#define UNI_SUR_LOW_START   (UTF32)0xDC00
-#define UNI_SUR_LOW_END     (UTF32)0xDFFF
-
-/* --------------------------------------------------------------------- */
-
-/*
- * Index into the table below with the first byte of a UTF-8 sequence to
- * get the number of trailing bytes that are supposed to follow it.
- * Note that *legal* UTF-8 values can't have 4 or 5-bytes. The table is
- * left as-is for anyone who may want to do such conversion, which was
- * allowed in earlier algorithms.
- */
-static const char trailingBytesForUTF8[256] = {
-    0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-    0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-    0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-    0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-    0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-    0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
-    1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
-    2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, 3,3,3,3,3,3,3,3,4,4,4,4,5,5,5,5
-};
-
-/*
- * Magic values subtracted from a buffer value during UTF8 conversion.
- * This table contains as many values as there might be trailing bytes
- * in a UTF-8 sequence.
- */
-static const UTF32 offsetsFromUTF8[6] = { 0x00000000UL, 0x00003080UL, 0x000E2080UL,
-                     0x03C82080UL, 0xFA082080UL, 0x82082080UL };
-
-/*
- * Once the bits are split out into bytes of UTF-8, this is a mask OR-ed
- * into the first byte, depending on how many bytes follow.  There are
- * as many entries in this table as there are UTF-8 sequence types.
- * (I.e., one byte sequence, two byte... etc.). Remember that sequencs
- * for *legal* UTF-8 will be 4 or fewer bytes total.
- */
-static const UTF8 firstByteMark[7] = { 0x00, 0x00, 0xC0, 0xE0, 0xF0, 0xF8, 0xFC };
-
-/* --------------------------------------------------------------------- */
-
-/* The interface converts a whole buffer to avoid function-call overhead.
- * Constants have been gathered. Loops & conditionals have been removed as
- * much as possible for efficiency, in favor of drop-through switches.
- * (See "Note A" at the bottom of the file for equivalent code.)
- * If your compiler supports it, the "isLegalUTF8" call can be turned
- * into an inline function.
- */
-
-
-/* --------------------------------------------------------------------- */
-
-ConversionResult ConvertUTF32toUTF16 (
-        const UTF32** sourceStart, const UTF32* sourceEnd,
-        UTF16** targetStart, UTF16* targetEnd, ConversionFlags flags) {
-    ConversionResult result = conversionOK;
-    const UTF32* source = *sourceStart;
-    UTF16* target = *targetStart;
-    while (source < sourceEnd) {
-        UTF32 ch;
-        if (target >= targetEnd) {
-            result = targetExhausted; break;
-        }
-        ch = *source++;
-        if (ch <= UNI_MAX_BMP) { /* Target is a character <= 0xFFFF */
-            /* UTF-16 surrogate values are illegal in UTF-32; 0xffff or 0xfffe are both reserved values */
-            if (ch >= UNI_SUR_HIGH_START && ch <= UNI_SUR_LOW_END) {
-                if (flags == strictConversion) {
-                    --source; /* return to the illegal value itself */
-                    result = sourceIllegal;
-                    break;
-                } else {
-                    *target++ = UNI_REPLACEMENT_CHAR;
-                }
-            } else {
-                *target++ = (UTF16)ch; /* normal case */
-            }
-        } else if (ch > UNI_MAX_LEGAL_UTF32) {
-            if (flags == strictConversion) {
-                result = sourceIllegal;
-            } else {
-                *target++ = UNI_REPLACEMENT_CHAR;
-            }
-        } else {
-            /* target is a character in range 0xFFFF - 0x10FFFF. */
-            if (target + 1 >= targetEnd) {
-                --source; /* Back up source pointer! */
-                result = targetExhausted; break;
-            }
-            ch -= halfBase;
-            *target++ = (UTF16)((ch >> halfShift) + UNI_SUR_HIGH_START);
-            *target++ = (UTF16)((ch & halfMask) + UNI_SUR_LOW_START);
-        }
-    }
-    *sourceStart = source;
-    *targetStart = target;
-    return result;
-}
-
-/* --------------------------------------------------------------------- */
-
-ConversionResult ConvertUTF16toUTF32 (
-        const UTF16** sourceStart, const UTF16* sourceEnd,
-        UTF32** targetStart, UTF32* targetEnd, ConversionFlags flags) {
-    ConversionResult result = conversionOK;
-    const UTF16* source = *sourceStart;
-    UTF32* target = *targetStart;
-    UTF32 ch, ch2;
-    while (source < sourceEnd) {
-        const UTF16* oldSource = source; /*  In case we have to back up because of target overflow. */
-        ch = *source++;
-        /* If we have a surrogate pair, convert to UTF32 first. */
-        if (ch >= UNI_SUR_HIGH_START && ch <= UNI_SUR_HIGH_END) {
-            /* If the 16 bits following the high surrogate are in the source buffer... */
-            if (source < sourceEnd) {
-                ch2 = *source;
-                /* If it's a low surrogate, convert to UTF32. */
-                if (ch2 >= UNI_SUR_LOW_START && ch2 <= UNI_SUR_LOW_END) {
-                    ch = ((ch - UNI_SUR_HIGH_START) << halfShift)
-                        + (ch2 - UNI_SUR_LOW_START) + halfBase;
-                    ++source;
-                } else if (flags == strictConversion) { /* it's an unpaired high surrogate */
-                    --source; /* return to the illegal value itself */
-                    result = sourceIllegal;
-                    break;
-                }
-            } else { /* We don't have the 16 bits following the high surrogate. */
-                --source; /* return to the high surrogate */
-                result = sourceExhausted;
-                break;
-            }
-        } else if (flags == strictConversion) {
-            /* UTF-16 surrogate values are illegal in UTF-32 */
-            if (ch >= UNI_SUR_LOW_START && ch <= UNI_SUR_LOW_END) {
-                --source; /* return to the illegal value itself */
-                result = sourceIllegal;
-                break;
-            }
-        }
-        if (target >= targetEnd) {
-            source = oldSource; /* Back up source pointer! */
-            result = targetExhausted; break;
-        }
-        *target++ = ch;
-    }
-    *sourceStart = source;
-    *targetStart = target;
-#ifdef CVTUTF_DEBUG
-if (result == sourceIllegal) {
-    fprintf(stderr, "ConvertUTF16toUTF32 illegal seq 0x%04x,%04x\n", ch, ch2);
-    fflush(stderr);
-}
-#endif
-    return result;
-}
-ConversionResult ConvertUTF16toUTF8 (
-        const UTF16** sourceStart, const UTF16* sourceEnd,
-        UTF8** targetStart, UTF8* targetEnd, ConversionFlags flags) {
-    ConversionResult result = conversionOK;
-    const UTF16* source = *sourceStart;
-    UTF8* target = *targetStart;
-    while (source < sourceEnd) {
-        UTF32 ch;
-        unsigned short bytesToWrite = 0;
-        const UTF32 byteMask = 0xBF;
-        const UTF32 byteMark = 0x80;
-        const UTF16* oldSource = source; /* In case we have to back up because of target overflow. */
-        ch = *source++;
-        /* If we have a surrogate pair, convert to UTF32 first. */
-        if (ch >= UNI_SUR_HIGH_START && ch <= UNI_SUR_HIGH_END) {
-            /* If the 16 bits following the high surrogate are in the source buffer... */
-            if (source < sourceEnd) {
-                UTF32 ch2 = *source;
-                /* If it's a low surrogate, convert to UTF32. */
-                if (ch2 >= UNI_SUR_LOW_START && ch2 <= UNI_SUR_LOW_END) {
-                    ch = ((ch - UNI_SUR_HIGH_START) << halfShift)
-                        + (ch2 - UNI_SUR_LOW_START) + halfBase;
-                    ++source;
-                } else if (flags == strictConversion) { /* it's an unpaired high surrogate */
-                    --source; /* return to the illegal value itself */
-                    result = sourceIllegal;
-                    break;
-                }
-            } else { /* We don't have the 16 bits following the high surrogate. */
-                --source; /* return to the high surrogate */
-                result = sourceExhausted;
-                break;
-            }
-        } else if (flags == strictConversion) {
-            /* UTF-16 surrogate values are illegal in UTF-32 */
-            if (ch >= UNI_SUR_LOW_START && ch <= UNI_SUR_LOW_END) {
-                --source; /* return to the illegal value itself */
-                result = sourceIllegal;
-                break;
-            }
-        }
-        /* Figure out how many bytes the result will require */
-        if (ch < (UTF32)0x80) {      bytesToWrite = 1;
-        } else if (ch < (UTF32)0x800) {     bytesToWrite = 2;
-        } else if (ch < (UTF32)0x10000) {   bytesToWrite = 3;
-        } else if (ch < (UTF32)0x110000) {  bytesToWrite = 4;
-        } else {                            bytesToWrite = 3;
-                                            ch = UNI_REPLACEMENT_CHAR;
-        }
-
-        target += bytesToWrite;
-        if (target > targetEnd) {
-            source = oldSource; /* Back up source pointer! */
-            target -= bytesToWrite; result = targetExhausted; break;
-        }
-        switch (bytesToWrite) { /* note: everything falls through. */
-            case 4: *--target = (UTF8)((ch | byteMark) & byteMask); ch >>= 6;
-            case 3: *--target = (UTF8)((ch | byteMark) & byteMask); ch >>= 6;
-            case 2: *--target = (UTF8)((ch | byteMark) & byteMask); ch >>= 6;
-            case 1: *--target =  (UTF8)(ch | firstByteMark[bytesToWrite]);
-        }
-        target += bytesToWrite;
-    }
-    *sourceStart = source;
-    *targetStart = target;
-    return result;
-}
-
-/* --------------------------------------------------------------------- */
-
-ConversionResult ConvertUTF32toUTF8 (
-        const UTF32** sourceStart, const UTF32* sourceEnd,
-        UTF8** targetStart, UTF8* targetEnd, ConversionFlags flags) {
-    ConversionResult result = conversionOK;
-    const UTF32* source = *sourceStart;
-    UTF8* target = *targetStart;
-    while (source < sourceEnd) {
-        UTF32 ch;
-        unsigned short bytesToWrite = 0;
-        const UTF32 byteMask = 0xBF;
-        const UTF32 byteMark = 0x80;
-        ch = *source++;
-        if (flags == strictConversion ) {
-            /* UTF-16 surrogate values are illegal in UTF-32 */
-            if (ch >= UNI_SUR_HIGH_START && ch <= UNI_SUR_LOW_END) {
-                --source; /* return to the illegal value itself */
-                result = sourceIllegal;
-                break;
-            }
-        }
-        /*
-         * Figure out how many bytes the result will require. Turn any
-         * illegally large UTF32 things (> Plane 17) into replacement chars.
-         */
-        if (ch < (UTF32)0x80) {      bytesToWrite = 1;
-        } else if (ch < (UTF32)0x800) {     bytesToWrite = 2;
-        } else if (ch < (UTF32)0x10000) {   bytesToWrite = 3;
-        } else if (ch <= UNI_MAX_LEGAL_UTF32) {  bytesToWrite = 4;
-        } else {                            bytesToWrite = 3;
-                                            ch = UNI_REPLACEMENT_CHAR;
-                                            result = sourceIllegal;
-        }
-
-        target += bytesToWrite;
-        if (target > targetEnd) {
-            --source; /* Back up source pointer! */
-            target -= bytesToWrite; result = targetExhausted; break;
-        }
-        switch (bytesToWrite) { /* note: everything falls through. */
-            case 4: *--target = (UTF8)((ch | byteMark) & byteMask); ch >>= 6;
-            case 3: *--target = (UTF8)((ch | byteMark) & byteMask); ch >>= 6;
-            case 2: *--target = (UTF8)((ch | byteMark) & byteMask); ch >>= 6;
-            case 1: *--target = (UTF8) (ch | firstByteMark[bytesToWrite]);
-        }
-        target += bytesToWrite;
-    }
-    *sourceStart = source;
-    *targetStart = target;
-    return result;
-}
-
-/* --------------------------------------------------------------------- */
-
-/*
- * Utility routine to tell whether a sequence of bytes is legal UTF-8.
- * This must be called with the length pre-determined by the first byte.
- * If not calling this from ConvertUTF8to*, then the length can be set by:
- *  length = trailingBytesForUTF8[*source]+1;
- * and the sequence is illegal right away if there aren't that many bytes
- * available.
- * If presented with a length > 4, this returns false.  The Unicode
- * definition of UTF-8 goes up to 4-byte sequences.
- */
-
-static Boolean isLegalUTF8(const UTF8 *source, int length) {
-    UTF8 a;
-    const UTF8 *srcptr = source+length;
-    switch (length) {
-    default: return false;
-        /* Everything else falls through when "true"... */
-    case 4: if ((a = (*--srcptr)) < 0x80 || a > 0xBF) return false;
-    case 3: if ((a = (*--srcptr)) < 0x80 || a > 0xBF) return false;
-    case 2: if ((a = (*--srcptr)) < 0x80 || a > 0xBF) return false;
-
-        switch (*source) {
-            /* no fall-through in this inner switch */
-            case 0xE0: if (a < 0xA0) return false; break;
-            case 0xED: if (a > 0x9F) return false; break;
-            case 0xF0: if (a < 0x90) return false; break;
-            case 0xF4: if (a > 0x8F) return false; break;
-            default:   if (a < 0x80) return false;
-        }
-
-    case 1: if (*source >= 0x80 && *source < 0xC2) return false;
-    }
-    if (*source > 0xF4) return false;
-    return true;
-}
-
-/* --------------------------------------------------------------------- */
-
-/*
- * Exported function to return whether a UTF-8 sequence is legal or not.
- * This is not used here; it's just exported.
- */
-Boolean isLegalUTF8Sequence(const UTF8 *source, const UTF8 *sourceEnd) {
-    int length = trailingBytesForUTF8[*source]+1;
-    if (length > sourceEnd - source) {
-        return false;
-    }
-    return isLegalUTF8(source, length);
-}
-
-/* --------------------------------------------------------------------- */
-
-static unsigned
-findMaximalSubpartOfIllFormedUTF8Sequence(const UTF8 *source,
-                                          const UTF8 *sourceEnd) {
-  UTF8 b1, b2, b3;
-
-  assert(!isLegalUTF8Sequence(source, sourceEnd));
-
-  /*
-   * Unicode 6.3.0, D93b:
-   *
-   *   Maximal subpart of an ill-formed subsequence: The longest code unit
-   *   subsequence starting at an unconvertible offset that is either:
-   *   a. the initial subsequence of a well-formed code unit sequence, or
-   *   b. a subsequence of length one.
-   */
-
-  if (source == sourceEnd)
-    return 0;
-
-  /*
-   * Perform case analysis.  See Unicode 6.3.0, Table 3-7. Well-Formed UTF-8
-   * Byte Sequences.
-   */
-
-  b1 = *source;
-  ++source;
-  if (b1 >= 0xC2 && b1 <= 0xDF) {
-    /*
-     * First byte is valid, but we know that this code unit sequence is
-     * invalid, so the maximal subpart has to end after the first byte.
-     */
-    return 1;
-  }
-
-  if (source == sourceEnd)
-    return 1;
-
-  b2 = *source;
-  ++source;
-
-  if (b1 == 0xE0) {
-    return (b2 >= 0xA0 && b2 <= 0xBF) ? 2 : 1;
-  }
-  if (b1 >= 0xE1 && b1 <= 0xEC) {
-    return (b2 >= 0x80 && b2 <= 0xBF) ? 2 : 1;
-  }
-  if (b1 == 0xED) {
-    return (b2 >= 0x80 && b2 <= 0x9F) ? 2 : 1;
-  }
-  if (b1 >= 0xEE && b1 <= 0xEF) {
-    return (b2 >= 0x80 && b2 <= 0xBF) ? 2 : 1;
-  }
-  if (b1 == 0xF0) {
-    if (b2 >= 0x90 && b2 <= 0xBF) {
-      if (source == sourceEnd)
-        return 2;
-
-      b3 = *source;
-      return (b3 >= 0x80 && b3 <= 0xBF) ? 3 : 2;
-    }
-    return 1;
-  }
-  if (b1 >= 0xF1 && b1 <= 0xF3) {
-    if (b2 >= 0x80 && b2 <= 0xBF) {
-      if (source == sourceEnd)
-        return 2;
-
-      b3 = *source;
-      return (b3 >= 0x80 && b3 <= 0xBF) ? 3 : 2;
-    }
-    return 1;
-  }
-  if (b1 == 0xF4) {
-    if (b2 >= 0x80 && b2 <= 0x8F) {
-      if (source == sourceEnd)
-        return 2;
-
-      b3 = *source;
-      return (b3 >= 0x80 && b3 <= 0xBF) ? 3 : 2;
-    }
-    return 1;
-  }
-
-  assert((b1 >= 0x80 && b1 <= 0xC1) || b1 >= 0xF5);
-  /*
-   * There are no valid sequences that start with these bytes.  Maximal subpart
-   * is defined to have length 1 in these cases.
-   */
-  return 1;
-}
-
-/* --------------------------------------------------------------------- */
-
-/*
- * Exported function to return the total number of bytes in a codepoint
- * represented in UTF-8, given the value of the first byte.
- */
-unsigned getNumBytesForUTF8(UTF8 first) {
-  return trailingBytesForUTF8[first] + 1;
-}
-
-/* --------------------------------------------------------------------- */
-
-/*
- * Exported function to return whether a UTF-8 string is legal or not.
- * This is not used here; it's just exported.
- */
-Boolean isLegalUTF8String(const UTF8 **source, const UTF8 *sourceEnd) {
-    while (*source != sourceEnd) {
-        int length = trailingBytesForUTF8[**source] + 1;
-        if (length > sourceEnd - *source || !isLegalUTF8(*source, length))
-            return false;
-        *source += length;
-    }
-    return true;
-}
-
-/* --------------------------------------------------------------------- */
-
-ConversionResult ConvertUTF8toUTF16 (
-        const UTF8** sourceStart, const UTF8* sourceEnd,
-        UTF16** targetStart, UTF16* targetEnd, ConversionFlags flags) {
-    ConversionResult result = conversionOK;
-    const UTF8* source = *sourceStart;
-    UTF16* target = *targetStart;
-    while (source < sourceEnd) {
-        UTF32 ch = 0;
-        unsigned short extraBytesToRead = trailingBytesForUTF8[*source];
-        if (extraBytesToRead >= sourceEnd - source) {
-            result = sourceExhausted; break;
-        }
-        /* Do this check whether lenient or strict */
-        if (!isLegalUTF8(source, extraBytesToRead+1)) {
-            result = sourceIllegal;
-            break;
-        }
-        /*
-         * The cases all fall through. See "Note A" below.
-         */
-        switch (extraBytesToRead) {
-            case 5: ch += *source++; ch <<= 6; /* remember, illegal UTF-8 */
-            case 4: ch += *source++; ch <<= 6; /* remember, illegal UTF-8 */
-            case 3: ch += *source++; ch <<= 6;
-            case 2: ch += *source++; ch <<= 6;
-            case 1: ch += *source++; ch <<= 6;
-            case 0: ch += *source++;
-        }
-        ch -= offsetsFromUTF8[extraBytesToRead];
-
-        if (target >= targetEnd) {
-            source -= (extraBytesToRead+1); /* Back up source pointer! */
-            result = targetExhausted; break;
-        }
-        if (ch <= UNI_MAX_BMP) { /* Target is a character <= 0xFFFF */
-            /* UTF-16 surrogate values are illegal in UTF-32 */
-            if (ch >= UNI_SUR_HIGH_START && ch <= UNI_SUR_LOW_END) {
-                if (flags == strictConversion) {
-                    source -= (extraBytesToRead+1); /* return to the illegal value itself */
-                    result = sourceIllegal;
-                    break;
-                } else {
-                    *target++ = UNI_REPLACEMENT_CHAR;
-                }
-            } else {
-                *target++ = (UTF16)ch; /* normal case */
-            }
-        } else if (ch > UNI_MAX_UTF16) {
-            if (flags == strictConversion) {
-                result = sourceIllegal;
-                source -= (extraBytesToRead+1); /* return to the start */
-                break; /* Bail out; shouldn't continue */
-            } else {
-                *target++ = UNI_REPLACEMENT_CHAR;
-            }
-        } else {
-            /* target is a character in range 0xFFFF - 0x10FFFF. */
-            if (target + 1 >= targetEnd) {
-                source -= (extraBytesToRead+1); /* Back up source pointer! */
-                result = targetExhausted; break;
-            }
-            ch -= halfBase;
-            *target++ = (UTF16)((ch >> halfShift) + UNI_SUR_HIGH_START);
-            *target++ = (UTF16)((ch & halfMask) + UNI_SUR_LOW_START);
-        }
-    }
-    *sourceStart = source;
-    *targetStart = target;
-    return result;
-}
-
-/* --------------------------------------------------------------------- */
-
-static ConversionResult ConvertUTF8toUTF32Impl(
-        const UTF8** sourceStart, const UTF8* sourceEnd,
-        UTF32** targetStart, UTF32* targetEnd, ConversionFlags flags,
-        Boolean InputIsPartial) {
-    ConversionResult result = conversionOK;
-    const UTF8* source = *sourceStart;
-    UTF32* target = *targetStart;
-    while (source < sourceEnd) {
-        UTF32 ch = 0;
-        unsigned short extraBytesToRead = trailingBytesForUTF8[*source];
-        if (extraBytesToRead >= sourceEnd - source) {
-            if (flags == strictConversion || InputIsPartial) {
-                result = sourceExhausted;
-                break;
-            } else {
-                result = sourceIllegal;
-
-                /*
-                 * Replace the maximal subpart of ill-formed sequence with
-                 * replacement character.
-                 */
-                source += findMaximalSubpartOfIllFormedUTF8Sequence(source,
-                                                                    sourceEnd);
-                *target++ = UNI_REPLACEMENT_CHAR;
-                continue;
-            }
-        }
-        if (target >= targetEnd) {
-            result = targetExhausted; break;
-        }
-
-        /* Do this check whether lenient or strict */
-        if (!isLegalUTF8(source, extraBytesToRead+1)) {
-            result = sourceIllegal;
-            if (flags == strictConversion) {
-                /* Abort conversion. */
-                break;
-            } else {
-                /*
-                 * Replace the maximal subpart of ill-formed sequence with
-                 * replacement character.
-                 */
-                source += findMaximalSubpartOfIllFormedUTF8Sequence(source,
-                                                                    sourceEnd);
-                *target++ = UNI_REPLACEMENT_CHAR;
-                continue;
-            }
-        }
-        /*
-         * The cases all fall through. See "Note A" below.
-         */
-        switch (extraBytesToRead) {
-            case 5: ch += *source++; ch <<= 6;
-            case 4: ch += *source++; ch <<= 6;
-            case 3: ch += *source++; ch <<= 6;
-            case 2: ch += *source++; ch <<= 6;
-            case 1: ch += *source++; ch <<= 6;
-            case 0: ch += *source++;
-        }
-        ch -= offsetsFromUTF8[extraBytesToRead];
-
-        if (ch <= UNI_MAX_LEGAL_UTF32) {
-            /*
-             * UTF-16 surrogate values are illegal in UTF-32, and anything
-             * over Plane 17 (> 0x10FFFF) is illegal.
-             */
-            if (ch >= UNI_SUR_HIGH_START && ch <= UNI_SUR_LOW_END) {
-                if (flags == strictConversion) {
-                    source -= (extraBytesToRead+1); /* return to the illegal value itself */
-                    result = sourceIllegal;
-                    break;
-                } else {
-                    *target++ = UNI_REPLACEMENT_CHAR;
-                }
-            } else {
-                *target++ = ch;
-            }
-        } else { /* i.e., ch > UNI_MAX_LEGAL_UTF32 */
-            result = sourceIllegal;
-            *target++ = UNI_REPLACEMENT_CHAR;
-        }
-    }
-    *sourceStart = source;
-    *targetStart = target;
-    return result;
-}
-
-ConversionResult ConvertUTF8toUTF32Partial(const UTF8 **sourceStart,
-                                           const UTF8 *sourceEnd,
-                                           UTF32 **targetStart,
-                                           UTF32 *targetEnd,
-                                           ConversionFlags flags) {
-  return ConvertUTF8toUTF32Impl(sourceStart, sourceEnd, targetStart, targetEnd,
-                                flags, /*InputIsPartial=*/true);
-}
-
-ConversionResult ConvertUTF8toUTF32(const UTF8 **sourceStart,
-                                    const UTF8 *sourceEnd, UTF32 **targetStart,
-                                    UTF32 *targetEnd, ConversionFlags flags) {
-  return ConvertUTF8toUTF32Impl(sourceStart, sourceEnd, targetStart, targetEnd,
-                                flags, /*InputIsPartial=*/false);
-}
-
-/* ---------------------------------------------------------------------
-
-    Note A.
-    The fall-through switches in UTF-8 reading code save a
-    temp variable, some decrements & conditionals.  The switches
-    are equivalent to the following loop:
-        {
-            int tmpBytesToRead = extraBytesToRead+1;
-            do {
-                ch += *source++;
-                --tmpBytesToRead;
-                if (tmpBytesToRead) ch <<= 6;
-            } while (tmpBytesToRead > 0);
-        }
-    In UTF-8 writing code, the switches on "bytesToWrite" are
-    similarly unrolled loops.
-
-   --------------------------------------------------------------------- */
-
-#ifdef _WIN32
-
-namespace sys {
-namespace windows {
-std::error_code CodePageToUTF16(unsigned codepage,
-                                std::string_view original,
-                                wpi::SmallVectorImpl<wchar_t> &utf16) {
-  if (!original.empty()) {
-    int len = ::MultiByteToWideChar(codepage, MB_ERR_INVALID_CHARS, original.data(),
-                                    original.size(), utf16.begin(), 0);
-
-    if (len == 0) {
-      return mapWindowsError(::GetLastError());
-    }
-
-    utf16.reserve(len + 1);
-    utf16.set_size(len);
-
-    len = ::MultiByteToWideChar(codepage, MB_ERR_INVALID_CHARS, original.data(),
-                                original.size(), utf16.begin(), utf16.size());
-
-    if (len == 0) {
-      return mapWindowsError(::GetLastError());
-    }
-  }
-
-  // Make utf16 null terminated.
-  utf16.push_back(0);
-  utf16.pop_back();
-
-  return std::error_code();
-}
-
-std::error_code UTF8ToUTF16(std::string_view utf8,
-                            wpi::SmallVectorImpl<wchar_t> &utf16) {
-  return CodePageToUTF16(CP_UTF8, utf8, utf16);
-}
-
-std::error_code CurCPToUTF16(std::string_view curcp,
-                            wpi::SmallVectorImpl<wchar_t> &utf16) {
-  return CodePageToUTF16(CP_ACP, curcp, utf16);
-}
-
-static
-std::error_code UTF16ToCodePage(unsigned codepage, const wchar_t *utf16,
-                                size_t utf16_len,
-                                wpi::SmallVectorImpl<char> &converted) {
-  if (utf16_len) {
-    // Get length.
-    int len = ::WideCharToMultiByte(codepage, 0, utf16, utf16_len, converted.begin(),
-                                    0, NULL, NULL);
-
-    if (len == 0) {
-      return mapWindowsError(::GetLastError());
-    }
-
-    converted.reserve(len);
-    converted.set_size(len);
-
-    // Now do the actual conversion.
-    len = ::WideCharToMultiByte(codepage, 0, utf16, utf16_len, converted.data(),
-                                converted.size(), NULL, NULL);
-
-    if (len == 0) {
-      return mapWindowsError(::GetLastError());
-    }
-  }
-
-  // Make the new string null terminated.
-  converted.push_back(0);
-  converted.pop_back();
-
-  return std::error_code();
-}
-
-std::error_code UTF16ToUTF8(const wchar_t *utf16, size_t utf16_len,
-                            wpi::SmallVectorImpl<char> &utf8) {
-  return UTF16ToCodePage(CP_UTF8, utf16, utf16_len, utf8);
-}
-
-std::error_code UTF16ToCurCP(const wchar_t *utf16, size_t utf16_len,
-                             wpi::SmallVectorImpl<char> &curcp) {
-  return UTF16ToCodePage(CP_ACP, utf16, utf16_len, curcp);
-}
-
-} // end namespace windows
-} // end namespace sys
-
-#endif  // _WIN32
-
-} // namespace llvm
-
-ConvertUTF_RESTORE_WARNINGS
diff --git a/wpiutil/src/main/native/cpp/llvm/ConvertUTFWrapper.cpp b/wpiutil/src/main/native/cpp/llvm/ConvertUTFWrapper.cpp
deleted file mode 100644
index db190d0..0000000
--- a/wpiutil/src/main/native/cpp/llvm/ConvertUTFWrapper.cpp
+++ /dev/null
@@ -1,123 +0,0 @@
-//===-- ConvertUTFWrapper.cpp - Wrap ConvertUTF.h with clang data types -----===
-//
-//                     The LLVM Compiler Infrastructure
-//
-// This file is distributed under the University of Illinois Open Source
-// License. See LICENSE.TXT for details.
-//
-//===----------------------------------------------------------------------===//
-
-#include "wpi/ConvertUTF.h"
-#include "wpi/SmallVector.h"
-#include <string>
-#include <vector>
-
-namespace wpi {
-
-bool ConvertCodePointToUTF8(unsigned Source, char *&ResultPtr) {
-  const UTF32 *SourceStart = &Source;
-  const UTF32 *SourceEnd = SourceStart + 1;
-  UTF8 *TargetStart = reinterpret_cast<UTF8 *>(ResultPtr);
-  UTF8 *TargetEnd = TargetStart + 4;
-  ConversionResult CR = ConvertUTF32toUTF8(&SourceStart, SourceEnd,
-                                           &TargetStart, TargetEnd,
-                                           strictConversion);
-  if (CR != conversionOK)
-    return false;
-
-  ResultPtr = reinterpret_cast<char*>(TargetStart);
-  return true;
-}
-
-bool hasUTF16ByteOrderMark(span<const char> S) {
-  return (S.size() >= 2 &&
-          ((S[0] == '\xff' && S[1] == '\xfe') ||
-           (S[0] == '\xfe' && S[1] == '\xff')));
-}
-
-bool convertUTF16ToUTF8String(span<const UTF16> SrcUTF16,
-                              SmallVectorImpl<char> &DstUTF8) {
-  assert(DstUTF8.empty());
-
-  // Avoid OOB by returning early on empty input.
-  if (SrcUTF16.empty())
-    return true;
-
-  const UTF16 *Src = SrcUTF16.begin();
-  const UTF16 *SrcEnd = SrcUTF16.end();
-
-  // Byteswap if necessary.
-  std::vector<UTF16> ByteSwapped;
-  if (Src[0] == UNI_UTF16_BYTE_ORDER_MARK_SWAPPED) {
-    ByteSwapped.insert(ByteSwapped.end(), Src, SrcEnd);
-    for (unsigned I = 0, E = ByteSwapped.size(); I != E; ++I)
-      ByteSwapped[I] = (ByteSwapped[I] << 8) | (ByteSwapped[I] >> 8);
-    Src = &ByteSwapped[0];
-    SrcEnd = &ByteSwapped[ByteSwapped.size() - 1] + 1;
-  }
-
-  // Skip the BOM for conversion.
-  if (Src[0] == UNI_UTF16_BYTE_ORDER_MARK_NATIVE)
-    Src++;
-
-  // Just allocate enough space up front.  We'll shrink it later.  Allocate
-  // enough that we can fit a null terminator without reallocating.
-  DstUTF8.resize(SrcUTF16.size() * UNI_MAX_UTF8_BYTES_PER_CODE_POINT + 1);
-  UTF8 *Dst = reinterpret_cast<UTF8*>(&DstUTF8[0]);
-  UTF8 *DstEnd = Dst + DstUTF8.size();
-
-  ConversionResult CR =
-      ConvertUTF16toUTF8(&Src, SrcEnd, &Dst, DstEnd, strictConversion);
-  assert(CR != targetExhausted);
-
-  if (CR != conversionOK) {
-    DstUTF8.clear();
-    return false;
-  }
-
-  DstUTF8.resize(reinterpret_cast<char*>(Dst) - &DstUTF8[0]);
-  DstUTF8.push_back(0);
-  DstUTF8.pop_back();
-  return true;
-}
-
-bool convertUTF8ToUTF16String(std::string_view SrcUTF8,
-                              SmallVectorImpl<UTF16> &DstUTF16) {
-  assert(DstUTF16.empty());
-
-  // Avoid OOB by returning early on empty input.
-  if (SrcUTF8.empty()) {
-    DstUTF16.push_back(0);
-    DstUTF16.pop_back();
-    return true;
-  }
-
-  const UTF8 *Src = reinterpret_cast<const UTF8 *>(SrcUTF8.data());
-  const UTF8 *SrcEnd = reinterpret_cast<const UTF8 *>(SrcUTF8.data() + SrcUTF8.size());
-
-  // Allocate the same number of UTF-16 code units as UTF-8 code units. Encoding
-  // as UTF-16 should always require the same amount or less code units than the
-  // UTF-8 encoding.  Allocate one extra byte for the null terminator though,
-  // so that someone calling DstUTF16.data() gets a null terminated string.
-  // We resize down later so we don't have to worry that this over allocates.
-  DstUTF16.resize(SrcUTF8.size()+1);
-  UTF16 *Dst = &DstUTF16[0];
-  UTF16 *DstEnd = Dst + DstUTF16.size();
-
-  ConversionResult CR =
-      ConvertUTF8toUTF16(&Src, SrcEnd, &Dst, DstEnd, strictConversion);
-  assert(CR != targetExhausted);
-
-  if (CR != conversionOK) {
-    DstUTF16.clear();
-    return false;
-  }
-
-  DstUTF16.resize(Dst - &DstUTF16[0]);
-  DstUTF16.push_back(0);
-  DstUTF16.pop_back();
-  return true;
-}
-
-} // end namespace wpi
-
diff --git a/wpiutil/src/main/native/cpp/llvm/ErrorHandling.cpp b/wpiutil/src/main/native/cpp/llvm/ErrorHandling.cpp
deleted file mode 100644
index 22ae636..0000000
--- a/wpiutil/src/main/native/cpp/llvm/ErrorHandling.cpp
+++ /dev/null
@@ -1,250 +0,0 @@
-//===- lib/Support/ErrorHandling.cpp - Callbacks for errors ---------------===//
-//
-//                     The LLVM Compiler Infrastructure
-//
-// This file is distributed under the University of Illinois Open Source
-// License. See LICENSE.TXT for details.
-//
-//===----------------------------------------------------------------------===//
-//
-// This file defines an API used to indicate fatal error conditions.  Non-fatal
-// errors (most of them) should be handled through LLVMContext.
-//
-//===----------------------------------------------------------------------===//
-
-#include "wpi/ErrorHandling.h"
-#include "wpi/SmallVector.h"
-#include "wpi/WindowsError.h"
-#include "fmt/format.h"
-#include <cassert>
-#include <cstdio>
-#include <cstdlib>
-#include <mutex>
-#include <new>
-
-#ifndef _WIN32
-#include <unistd.h>
-#endif
-
-#if defined(_MSC_VER)
-#include <io.h>
-#endif
-
-using namespace wpi;
-
-static fatal_error_handler_t ErrorHandler = nullptr;
-static void *ErrorHandlerUserData = nullptr;
-
-static fatal_error_handler_t BadAllocErrorHandler = nullptr;
-static void *BadAllocErrorHandlerUserData = nullptr;
-
-// Mutexes to synchronize installing error handlers and calling error handlers.
-// Do not use ManagedStatic, or that may allocate memory while attempting to
-// report an OOM.
-//
-// This usage of std::mutex has to be conditionalized behind ifdefs because
-// of this script:
-//   compiler-rt/lib/sanitizer_common/symbolizer/scripts/build_symbolizer.sh
-// That script attempts to statically link the LLVM symbolizer library with the
-// STL and hide all of its symbols with 'opt -internalize'. To reduce size, it
-// cuts out the threading portions of the hermetic copy of libc++ that it
-// builds. We can remove these ifdefs if that script goes away.
-static std::mutex ErrorHandlerMutex;
-static std::mutex BadAllocErrorHandlerMutex;
-
-void wpi::install_fatal_error_handler(fatal_error_handler_t handler,
-                                      void *user_data) {
-  std::scoped_lock Lock(ErrorHandlerMutex);
-  assert(!ErrorHandler && "Error handler already registered!\n");
-  ErrorHandler = handler;
-  ErrorHandlerUserData = user_data;
-}
-
-void wpi::remove_fatal_error_handler() {
-  std::scoped_lock Lock(ErrorHandlerMutex);
-  ErrorHandler = nullptr;
-  ErrorHandlerUserData = nullptr;
-}
-
-void wpi::report_fatal_error(const char *Reason, bool GenCrashDiag) {
-  report_fatal_error(std::string_view(Reason), GenCrashDiag);
-}
-
-void wpi::report_fatal_error(const std::string &Reason, bool GenCrashDiag) {
-  report_fatal_error(std::string_view(Reason), GenCrashDiag);
-}
-
-void wpi::report_fatal_error(std::string_view Reason, bool GenCrashDiag) {
-  wpi::fatal_error_handler_t handler = nullptr;
-  void* handlerData = nullptr;
-  {
-    // Only acquire the mutex while reading the handler, so as not to invoke a
-    // user-supplied callback under a lock.
-    std::scoped_lock Lock(ErrorHandlerMutex);
-    handler = ErrorHandler;
-    handlerData = ErrorHandlerUserData;
-  }
-
-  if (handler) {
-    handler(handlerData, std::string{Reason}, GenCrashDiag);
-  } else {
-    fmt::print(stderr, "LLVM ERROR: {}\n", Reason);
-  }
-
-  exit(1);
-}
-
-void wpi::install_bad_alloc_error_handler(fatal_error_handler_t handler,
-                                          void *user_data) {
-  std::scoped_lock Lock(BadAllocErrorHandlerMutex);
-  assert(!ErrorHandler && "Bad alloc error handler already registered!\n");
-  BadAllocErrorHandler = handler;
-  BadAllocErrorHandlerUserData = user_data;
-}
-
-void wpi::remove_bad_alloc_error_handler() {
-  std::scoped_lock Lock(BadAllocErrorHandlerMutex);
-  BadAllocErrorHandler = nullptr;
-  BadAllocErrorHandlerUserData = nullptr;
-}
-
-void wpi::report_bad_alloc_error(const char *Reason, bool GenCrashDiag) {
-  fatal_error_handler_t Handler = nullptr;
-  void *HandlerData = nullptr;
-  {
-    // Only acquire the mutex while reading the handler, so as not to invoke a
-    // user-supplied callback under a lock.
-    std::scoped_lock Lock(BadAllocErrorHandlerMutex);
-    Handler = BadAllocErrorHandler;
-    HandlerData = BadAllocErrorHandlerUserData;
-  }
-
-  if (Handler) {
-    Handler(HandlerData, Reason, GenCrashDiag);
-    wpi_unreachable("bad alloc handler should not return");
-  }
-
-  // Don't call the normal error handler. It may allocate memory. Directly write
-  // an OOM to stderr and abort.
-  char OOMMessage[] = "LLVM ERROR: out of memory\n";
-#ifdef _WIN32
-  int written = ::_write(2, OOMMessage, strlen(OOMMessage));
-#else
-  ssize_t written = ::write(2, OOMMessage, strlen(OOMMessage));
-#endif
-  (void)written;
-  abort();
-}
-
-// Causes crash on allocation failure. It is called prior to the handler set by
-// 'install_bad_alloc_error_handler'.
-static void out_of_memory_new_handler() {
-  wpi::report_bad_alloc_error("Allocation failed");
-}
-
-// Installs new handler that causes crash on allocation failure. It does not
-// need to be called explicitly, if this file is linked to application, because
-// in this case it is called during construction of 'new_handler_installer'.
-void wpi::install_out_of_memory_new_handler() {
-  static bool out_of_memory_new_handler_installed = false;
-  if (!out_of_memory_new_handler_installed) {
-    std::set_new_handler(out_of_memory_new_handler);
-    out_of_memory_new_handler_installed = true;
-  }
-}
-
-// Static object that causes installation of 'out_of_memory_new_handler' before
-// execution of 'main'.
-static class NewHandlerInstaller {
-public:
-  NewHandlerInstaller() {
-    install_out_of_memory_new_handler();
-  }
-} new_handler_installer;
-
-void wpi::wpi_unreachable_internal(const char *msg, const char *file,
-                                   unsigned line) {
-  // This code intentionally doesn't call the ErrorHandler callback, because
-  // wpi_unreachable is intended to be used to indicate "impossible"
-  // situations, and not legitimate runtime errors.
-  if (msg)
-    fmt::print(stderr, "{}\n", msg);
-  std::fputs("UNREACHABLE executed", stderr);
-  if (file)
-    fmt::print(stderr, " at {}:{}", file, line);
-  fmt::print(stderr, "{}", "!\n");
-  abort();
-#ifdef LLVM_BUILTIN_UNREACHABLE
-  // Windows systems and possibly others don't declare abort() to be noreturn,
-  // so use the unreachable builtin to avoid a Clang self-host warning.
-  LLVM_BUILTIN_UNREACHABLE;
-#endif
-}
-
-#ifdef _WIN32
-
-#include <system_error>
-#include <winerror.h>
-
-// I'd rather not double the line count of the following.
-#define MAP_ERR_TO_COND(x, y)                                                  \
-  case x:                                                                      \
-    return std::make_error_code(std::errc::y)
-
-std::error_code wpi::mapWindowsError(unsigned EV) {
-  switch (EV) {
-    MAP_ERR_TO_COND(ERROR_ACCESS_DENIED, permission_denied);
-    MAP_ERR_TO_COND(ERROR_ALREADY_EXISTS, file_exists);
-    MAP_ERR_TO_COND(ERROR_BAD_UNIT, no_such_device);
-    MAP_ERR_TO_COND(ERROR_BUFFER_OVERFLOW, filename_too_long);
-    MAP_ERR_TO_COND(ERROR_BUSY, device_or_resource_busy);
-    MAP_ERR_TO_COND(ERROR_BUSY_DRIVE, device_or_resource_busy);
-    MAP_ERR_TO_COND(ERROR_CANNOT_MAKE, permission_denied);
-    MAP_ERR_TO_COND(ERROR_CANTOPEN, io_error);
-    MAP_ERR_TO_COND(ERROR_CANTREAD, io_error);
-    MAP_ERR_TO_COND(ERROR_CANTWRITE, io_error);
-    MAP_ERR_TO_COND(ERROR_CURRENT_DIRECTORY, permission_denied);
-    MAP_ERR_TO_COND(ERROR_DEV_NOT_EXIST, no_such_device);
-    MAP_ERR_TO_COND(ERROR_DEVICE_IN_USE, device_or_resource_busy);
-    MAP_ERR_TO_COND(ERROR_DIR_NOT_EMPTY, directory_not_empty);
-    MAP_ERR_TO_COND(ERROR_DIRECTORY, invalid_argument);
-    MAP_ERR_TO_COND(ERROR_DISK_FULL, no_space_on_device);
-    MAP_ERR_TO_COND(ERROR_FILE_EXISTS, file_exists);
-    MAP_ERR_TO_COND(ERROR_FILE_NOT_FOUND, no_such_file_or_directory);
-    MAP_ERR_TO_COND(ERROR_HANDLE_DISK_FULL, no_space_on_device);
-    MAP_ERR_TO_COND(ERROR_INVALID_ACCESS, permission_denied);
-    MAP_ERR_TO_COND(ERROR_INVALID_DRIVE, no_such_device);
-    MAP_ERR_TO_COND(ERROR_INVALID_FUNCTION, function_not_supported);
-    MAP_ERR_TO_COND(ERROR_INVALID_HANDLE, invalid_argument);
-    MAP_ERR_TO_COND(ERROR_INVALID_NAME, invalid_argument);
-    MAP_ERR_TO_COND(ERROR_LOCK_VIOLATION, no_lock_available);
-    MAP_ERR_TO_COND(ERROR_LOCKED, no_lock_available);
-    MAP_ERR_TO_COND(ERROR_NEGATIVE_SEEK, invalid_argument);
-    MAP_ERR_TO_COND(ERROR_NOACCESS, permission_denied);
-    MAP_ERR_TO_COND(ERROR_NOT_ENOUGH_MEMORY, not_enough_memory);
-    MAP_ERR_TO_COND(ERROR_NOT_READY, resource_unavailable_try_again);
-    MAP_ERR_TO_COND(ERROR_OPEN_FAILED, io_error);
-    MAP_ERR_TO_COND(ERROR_OPEN_FILES, device_or_resource_busy);
-    MAP_ERR_TO_COND(ERROR_OUTOFMEMORY, not_enough_memory);
-    MAP_ERR_TO_COND(ERROR_PATH_NOT_FOUND, no_such_file_or_directory);
-    MAP_ERR_TO_COND(ERROR_BAD_NETPATH, no_such_file_or_directory);
-    MAP_ERR_TO_COND(ERROR_READ_FAULT, io_error);
-    MAP_ERR_TO_COND(ERROR_RETRY, resource_unavailable_try_again);
-    MAP_ERR_TO_COND(ERROR_SEEK, io_error);
-    MAP_ERR_TO_COND(ERROR_SHARING_VIOLATION, permission_denied);
-    MAP_ERR_TO_COND(ERROR_TOO_MANY_OPEN_FILES, too_many_files_open);
-    MAP_ERR_TO_COND(ERROR_WRITE_FAULT, io_error);
-    MAP_ERR_TO_COND(ERROR_WRITE_PROTECT, permission_denied);
-    MAP_ERR_TO_COND(WSAEACCES, permission_denied);
-    MAP_ERR_TO_COND(WSAEBADF, bad_file_descriptor);
-    MAP_ERR_TO_COND(WSAEFAULT, bad_address);
-    MAP_ERR_TO_COND(WSAEINTR, interrupted);
-    MAP_ERR_TO_COND(WSAEINVAL, invalid_argument);
-    MAP_ERR_TO_COND(WSAEMFILE, too_many_files_open);
-    MAP_ERR_TO_COND(WSAENAMETOOLONG, filename_too_long);
-  default:
-    return std::error_code(EV, std::system_category());
-  }
-}
-
-#endif
diff --git a/wpiutil/src/main/native/cpp/llvm/Hashing.cpp b/wpiutil/src/main/native/cpp/llvm/Hashing.cpp
deleted file mode 100644
index e916751..0000000
--- a/wpiutil/src/main/native/cpp/llvm/Hashing.cpp
+++ /dev/null
@@ -1,29 +0,0 @@
-//===-------------- lib/Support/Hashing.cpp -------------------------------===//
-//
-//                     The LLVM Compiler Infrastructure
-//
-// This file is distributed under the University of Illinois Open Source
-// License. See LICENSE.TXT for details.
-//
-//===----------------------------------------------------------------------===//
-//
-// This file provides implementation bits for the LLVM common hashing
-// infrastructure. Documentation and most of the other information is in the
-// header file.
-//
-//===----------------------------------------------------------------------===//
-
-#include "wpi/Hashing.h"
-
-using namespace wpi;
-
-// Provide a definition and static initializer for the fixed seed. This
-// initializer should always be zero to ensure its value can never appear to be
-// non-zero, even during dynamic initialization.
-uint64_t wpi::hashing::detail::fixed_seed_override = 0;
-
-// Implement the function for forced setting of the fixed seed.
-// FIXME: Use atomic operations here so that there is no data race.
-void wpi::set_fixed_execution_hash_seed(uint64_t fixed_value) {
-  hashing::detail::fixed_seed_override = fixed_value;
-}
diff --git a/wpiutil/src/main/native/cpp/llvm/ManagedStatic.cpp b/wpiutil/src/main/native/cpp/llvm/ManagedStatic.cpp
deleted file mode 100644
index a8c82bf..0000000
--- a/wpiutil/src/main/native/cpp/llvm/ManagedStatic.cpp
+++ /dev/null
@@ -1,88 +0,0 @@
-//===-- ManagedStatic.cpp - Static Global wrapper -------------------------===//
-//
-//                     The LLVM Compiler Infrastructure
-//
-// This file is distributed under the University of Illinois Open Source
-// License. See LICENSE.TXT for details.
-//
-//===----------------------------------------------------------------------===//
-//
-// This file implements the ManagedStatic class and wpi_shutdown().
-//
-//===----------------------------------------------------------------------===//
-
-#include "wpi/ManagedStatic.h"
-#include "wpi/mutex.h"
-#include <cassert>
-#include <mutex>
-using namespace wpi;
-
-static const ManagedStaticBase *StaticList = nullptr;
-static wpi::mutex *ManagedStaticMutex = nullptr;
-static std::once_flag mutex_init_flag;
-
-static void initializeMutex() {
-  ManagedStaticMutex = new wpi::mutex();
-}
-
-static wpi::mutex* getManagedStaticMutex() {
-  std::call_once(mutex_init_flag, initializeMutex);
-  return ManagedStaticMutex;
-}
-
-void ManagedStaticBase::RegisterManagedStatic(void* created,
-                                              void (*Deleter)(void*)) const {
-  std::scoped_lock Lock(*getManagedStaticMutex());
-
-  if (!Ptr.load(std::memory_order_relaxed)) {
-    void *Tmp = created;
-
-    Ptr.store(Tmp, std::memory_order_release);
-    DeleterFn = Deleter;
-
-    // Add to list of managed statics.
-    Next = StaticList;
-    StaticList = this;
-  }
-}
-
-void ManagedStaticBase::RegisterManagedStatic(void *(*Creator)(),
-                                              void (*Deleter)(void*)) const {
-  assert(Creator);
-  std::scoped_lock Lock(*getManagedStaticMutex());
-
-  if (!Ptr.load(std::memory_order_relaxed)) {
-    void *Tmp = Creator();
-
-    Ptr.store(Tmp, std::memory_order_release);
-    DeleterFn = Deleter;
-
-    // Add to list of managed statics.
-    Next = StaticList;
-    StaticList = this;
-  }
-}
-
-void ManagedStaticBase::destroy() const {
-  assert(DeleterFn && "ManagedStatic not initialized correctly!");
-  assert(StaticList == this &&
-         "Not destroyed in reverse order of construction?");
-  // Unlink from list.
-  StaticList = Next;
-  Next = nullptr;
-
-  // Destroy memory.
-  DeleterFn(Ptr);
-
-  // Cleanup.
-  Ptr = nullptr;
-  DeleterFn = nullptr;
-}
-
-/// wpi_shutdown - Deallocate and destroy all ManagedStatic variables.
-void wpi::wpi_shutdown() {
-  std::scoped_lock Lock(*getManagedStaticMutex());
-
-  while (StaticList)
-    StaticList->destroy();
-}
diff --git a/wpiutil/src/main/native/cpp/llvm/SmallPtrSet.cpp b/wpiutil/src/main/native/cpp/llvm/SmallPtrSet.cpp
deleted file mode 100644
index 1b33f4c..0000000
--- a/wpiutil/src/main/native/cpp/llvm/SmallPtrSet.cpp
+++ /dev/null
@@ -1,271 +0,0 @@
-//===- llvm/ADT/SmallPtrSet.cpp - 'Normally small' pointer set ------------===//
-//
-//                     The LLVM Compiler Infrastructure
-//
-// This file is distributed under the University of Illinois Open Source
-// License. See LICENSE.TXT for details.
-//
-//===----------------------------------------------------------------------===//
-//
-// This file implements the SmallPtrSet class.  See SmallPtrSet.h for an
-// overview of the algorithm.
-//
-//===----------------------------------------------------------------------===//
-
-#include "wpi/SmallPtrSet.h"
-#include "wpi/DenseMapInfo.h"
-#include "wpi/MathExtras.h"
-#include "wpi/MemAlloc.h"
-#include "wpi/ErrorHandling.h"
-#include <algorithm>
-#include <cassert>
-#include <cstdlib>
-
-using namespace wpi;
-
-void SmallPtrSetImplBase::shrink_and_clear() {
-  assert(!isSmall() && "Can't shrink a small set!");
-  free(CurArray);
-
-  // Reduce the number of buckets.
-  unsigned Size = size();
-  CurArraySize = Size > 16 ? 1 << (Log2_32_Ceil(Size) + 1) : 32;
-  NumNonEmpty = NumTombstones = 0;
-
-  // Install the new array.  Clear all the buckets to empty.
-  CurArray = (const void**)safe_malloc(sizeof(void*) * CurArraySize);
-
-  memset(CurArray, -1, CurArraySize*sizeof(void*));
-}
-
-std::pair<const void *const *, bool>
-SmallPtrSetImplBase::insert_imp_big(const void *Ptr) {
-  if (LLVM_UNLIKELY(size() * 4 >= CurArraySize * 3)) {
-    // If more than 3/4 of the array is full, grow.
-    Grow(CurArraySize < 64 ? 128 : CurArraySize * 2);
-  } else if (LLVM_UNLIKELY(CurArraySize - NumNonEmpty < CurArraySize / 8)) {
-    // If fewer of 1/8 of the array is empty (meaning that many are filled with
-    // tombstones), rehash.
-    Grow(CurArraySize);
-  }
-
-  // Okay, we know we have space.  Find a hash bucket.
-  const void **Bucket = const_cast<const void**>(FindBucketFor(Ptr));
-  if (*Bucket == Ptr)
-    return std::make_pair(Bucket, false); // Already inserted, good.
-
-  // Otherwise, insert it!
-  if (*Bucket == getTombstoneMarker())
-    --NumTombstones;
-  else
-    ++NumNonEmpty; // Track density.
-  *Bucket = Ptr;
-  return std::make_pair(Bucket, true);
-}
-
-const void * const *SmallPtrSetImplBase::FindBucketFor(const void *Ptr) const {
-  unsigned Bucket = DenseMapInfo<void *>::getHashValue(Ptr) & (CurArraySize-1);
-  unsigned ArraySize = CurArraySize;
-  unsigned ProbeAmt = 1;
-  const void *const *Array = CurArray;
-  const void *const *Tombstone = nullptr;
-  while (true) {
-    // If we found an empty bucket, the pointer doesn't exist in the set.
-    // Return a tombstone if we've seen one so far, or the empty bucket if
-    // not.
-    if (LLVM_LIKELY(Array[Bucket] == getEmptyMarker()))
-      return Tombstone ? Tombstone : Array+Bucket;
-
-    // Found Ptr's bucket?
-    if (LLVM_LIKELY(Array[Bucket] == Ptr))
-      return Array+Bucket;
-
-    // If this is a tombstone, remember it.  If Ptr ends up not in the set, we
-    // prefer to return it than something that would require more probing.
-    if (Array[Bucket] == getTombstoneMarker() && !Tombstone)
-      Tombstone = Array+Bucket;  // Remember the first tombstone found.
-
-    // It's a hash collision or a tombstone. Reprobe.
-    Bucket = (Bucket + ProbeAmt++) & (ArraySize-1);
-  }
-}
-
-/// Grow - Allocate a larger backing store for the buckets and move it over.
-///
-void SmallPtrSetImplBase::Grow(unsigned NewSize) {
-  const void **OldBuckets = CurArray;
-  const void **OldEnd = EndPointer();
-  bool WasSmall = isSmall();
-
-  // Install the new array.  Clear all the buckets to empty.
-  const void **NewBuckets = (const void**) safe_malloc(sizeof(void*) * NewSize);
-
-  // Reset member only if memory was allocated successfully
-  CurArray = NewBuckets;
-  CurArraySize = NewSize;
-  memset(CurArray, -1, NewSize*sizeof(void*));
-
-  // Copy over all valid entries.
-  for (const void **BucketPtr = OldBuckets; BucketPtr != OldEnd; ++BucketPtr) {
-    // Copy over the element if it is valid.
-    const void *Elt = *BucketPtr;
-    if (Elt != getTombstoneMarker() && Elt != getEmptyMarker())
-      *const_cast<void**>(FindBucketFor(Elt)) = const_cast<void*>(Elt);
-  }
-
-  if (!WasSmall)
-    free(OldBuckets);
-  NumNonEmpty -= NumTombstones;
-  NumTombstones = 0;
-}
-
-SmallPtrSetImplBase::SmallPtrSetImplBase(const void **SmallStorage,
-                                         const SmallPtrSetImplBase &that) {
-  SmallArray = SmallStorage;
-
-  // If we're becoming small, prepare to insert into our stack space
-  if (that.isSmall()) {
-    CurArray = SmallArray;
-  // Otherwise, allocate new heap space (unless we were the same size)
-  } else {
-    CurArray = (const void**)safe_malloc(sizeof(void*) * that.CurArraySize);
-  }
-
-  // Copy over the that array.
-  CopyHelper(that);
-}
-
-SmallPtrSetImplBase::SmallPtrSetImplBase(const void **SmallStorage,
-                                         unsigned SmallSize,
-                                         SmallPtrSetImplBase &&that) {
-  SmallArray = SmallStorage;
-  MoveHelper(SmallSize, std::move(that));
-}
-
-void SmallPtrSetImplBase::CopyFrom(const SmallPtrSetImplBase &RHS) {
-  assert(&RHS != this && "Self-copy should be handled by the caller.");
-
-  if (isSmall() && RHS.isSmall())
-    assert(CurArraySize == RHS.CurArraySize &&
-           "Cannot assign sets with different small sizes");
-
-  // If we're becoming small, prepare to insert into our stack space
-  if (RHS.isSmall()) {
-    if (!isSmall())
-      free(CurArray);
-    CurArray = SmallArray;
-  // Otherwise, allocate new heap space (unless we were the same size)
-  } else if (CurArraySize != RHS.CurArraySize) {
-    if (isSmall())
-      CurArray = (const void**)safe_malloc(sizeof(void*) * RHS.CurArraySize);
-    else {
-      const void **T = (const void**)safe_realloc(CurArray,
-                                             sizeof(void*) * RHS.CurArraySize);
-      CurArray = T;
-    }
-  }
-
-  CopyHelper(RHS);
-}
-
-void SmallPtrSetImplBase::CopyHelper(const SmallPtrSetImplBase &RHS) {
-  // Copy over the new array size
-  CurArraySize = RHS.CurArraySize;
-
-  // Copy over the contents from the other set
-  std::copy(RHS.CurArray, RHS.EndPointer(), CurArray);
-
-  NumNonEmpty = RHS.NumNonEmpty;
-  NumTombstones = RHS.NumTombstones;
-}
-
-void SmallPtrSetImplBase::MoveFrom(unsigned SmallSize,
-                                   SmallPtrSetImplBase &&RHS) {
-  if (!isSmall())
-    free(CurArray);
-  MoveHelper(SmallSize, std::move(RHS));
-}
-
-void SmallPtrSetImplBase::MoveHelper(unsigned SmallSize,
-                                     SmallPtrSetImplBase &&RHS) {
-  assert(&RHS != this && "Self-move should be handled by the caller.");
-
-  if (RHS.isSmall()) {
-    // Copy a small RHS rather than moving.
-    CurArray = SmallArray;
-    std::copy(RHS.CurArray, RHS.CurArray + RHS.NumNonEmpty, CurArray);
-  } else {
-    CurArray = RHS.CurArray;
-    RHS.CurArray = RHS.SmallArray;
-  }
-
-  // Copy the rest of the trivial members.
-  CurArraySize = RHS.CurArraySize;
-  NumNonEmpty = RHS.NumNonEmpty;
-  NumTombstones = RHS.NumTombstones;
-
-  // Make the RHS small and empty.
-  RHS.CurArraySize = SmallSize;
-  assert(RHS.CurArray == RHS.SmallArray);
-  RHS.NumNonEmpty = 0;
-  RHS.NumTombstones = 0;
-}
-
-void SmallPtrSetImplBase::swap(SmallPtrSetImplBase &RHS) {
-  if (this == &RHS) return;
-
-  // We can only avoid copying elements if neither set is small.
-  if (!this->isSmall() && !RHS.isSmall()) {
-    std::swap(this->CurArray, RHS.CurArray);
-    std::swap(this->CurArraySize, RHS.CurArraySize);
-    std::swap(this->NumNonEmpty, RHS.NumNonEmpty);
-    std::swap(this->NumTombstones, RHS.NumTombstones);
-    return;
-  }
-
-  // FIXME: From here on we assume that both sets have the same small size.
-
-  // If only RHS is small, copy the small elements into LHS and move the pointer
-  // from LHS to RHS.
-  if (!this->isSmall() && RHS.isSmall()) {
-    assert(RHS.CurArray == RHS.SmallArray);
-    std::copy(RHS.CurArray, RHS.CurArray + RHS.NumNonEmpty, this->SmallArray);
-    std::swap(RHS.CurArraySize, this->CurArraySize);
-    std::swap(this->NumNonEmpty, RHS.NumNonEmpty);
-    std::swap(this->NumTombstones, RHS.NumTombstones);
-    RHS.CurArray = this->CurArray;
-    this->CurArray = this->SmallArray;
-    return;
-  }
-
-  // If only LHS is small, copy the small elements into RHS and move the pointer
-  // from RHS to LHS.
-  if (this->isSmall() && !RHS.isSmall()) {
-    assert(this->CurArray == this->SmallArray);
-    std::copy(this->CurArray, this->CurArray + this->NumNonEmpty,
-              RHS.SmallArray);
-    std::swap(RHS.CurArraySize, this->CurArraySize);
-    std::swap(RHS.NumNonEmpty, this->NumNonEmpty);
-    std::swap(RHS.NumTombstones, this->NumTombstones);
-    this->CurArray = RHS.CurArray;
-    RHS.CurArray = RHS.SmallArray;
-    return;
-  }
-
-  // Both a small, just swap the small elements.
-  assert(this->isSmall() && RHS.isSmall());
-  unsigned MinNonEmpty = std::min(this->NumNonEmpty, RHS.NumNonEmpty);
-  std::swap_ranges(this->SmallArray, this->SmallArray + MinNonEmpty,
-                   RHS.SmallArray);
-  if (this->NumNonEmpty > MinNonEmpty) {
-    std::copy(this->SmallArray + MinNonEmpty,
-              this->SmallArray + this->NumNonEmpty,
-              RHS.SmallArray + MinNonEmpty);
-  } else {
-    std::copy(RHS.SmallArray + MinNonEmpty, RHS.SmallArray + RHS.NumNonEmpty,
-              this->SmallArray + MinNonEmpty);
-  }
-  assert(this->CurArraySize == RHS.CurArraySize);
-  std::swap(this->NumNonEmpty, RHS.NumNonEmpty);
-  std::swap(this->NumTombstones, RHS.NumTombstones);
-}
diff --git a/wpiutil/src/main/native/cpp/llvm/SmallVector.cpp b/wpiutil/src/main/native/cpp/llvm/SmallVector.cpp
deleted file mode 100644
index 974fec9..0000000
--- a/wpiutil/src/main/native/cpp/llvm/SmallVector.cpp
+++ /dev/null
@@ -1,43 +0,0 @@
-//===- llvm/ADT/SmallVector.cpp - 'Normally small' vectors ----------------===//
-//
-//                     The LLVM Compiler Infrastructure
-//
-// This file is distributed under the University of Illinois Open Source
-// License. See LICENSE.TXT for details.
-//
-//===----------------------------------------------------------------------===//
-//
-// This file implements the SmallVector class.
-//
-//===----------------------------------------------------------------------===//
-
-#include "wpi/SmallVector.h"
-#include "wpi/MemAlloc.h"
-using namespace wpi;
-
-/// grow_pod - This is an implementation of the grow() method which only works
-/// on POD-like datatypes and is out of line to reduce code duplication.
-void SmallVectorBase::grow_pod(void *FirstEl, size_t MinCapacity,
-                               size_t TSize) {
-  // Ensure we can fit the new capacity in 32 bits.
-  if (MinCapacity > UINT32_MAX)
-    report_bad_alloc_error("SmallVector capacity overflow during allocation");
-
-  size_t NewCapacity = 2 * capacity() + 1; // Always grow.
-  NewCapacity =
-      std::min(std::max(NewCapacity, MinCapacity), size_t(UINT32_MAX));
-
-  void *NewElts;
-  if (BeginX == FirstEl) {
-    NewElts = safe_malloc(NewCapacity * TSize);
-
-    // Copy the elements over.  No need to run dtors on PODs.
-    memcpy(NewElts, this->BeginX, size() * TSize);
-  } else {
-    // If this wasn't grown from the inline copy, grow the allocated space.
-    NewElts = safe_realloc(this->BeginX, NewCapacity * TSize);
-  }
-
-  this->BeginX = NewElts;
-  this->Capacity = NewCapacity;
-}
diff --git a/wpiutil/src/main/native/cpp/llvm/StringMap.cpp b/wpiutil/src/main/native/cpp/llvm/StringMap.cpp
deleted file mode 100644
index 768801d..0000000
--- a/wpiutil/src/main/native/cpp/llvm/StringMap.cpp
+++ /dev/null
@@ -1,276 +0,0 @@
-//===--- StringMap.cpp - String Hash table map implementation -------------===//
-//
-//                     The LLVM Compiler Infrastructure
-//
-// This file is distributed under the University of Illinois Open Source
-// License. See LICENSE.TXT for details.
-//
-//===----------------------------------------------------------------------===//
-//
-// This file implements the StringMap class.
-//
-//===----------------------------------------------------------------------===//
-
-#include "wpi/StringMap.h"
-#include "wpi/StringExtras.h"
-#include "wpi/Compiler.h"
-#include "wpi/MathExtras.h"
-#include <cassert>
-
-using namespace wpi;
-
-/// HashString - Hash function for strings.
-///
-/// This is the Bernstein hash function.
-//
-// FIXME: Investigate whether a modified bernstein hash function performs
-// better: http://eternallyconfuzzled.com/tuts/algorithms/jsw_tut_hashing.aspx
-//   X*33+c -> X*33^c
-static inline unsigned HashString(std::string_view str,
-                                  unsigned result = 0) noexcept {
-  for (std::string_view::size_type i = 0, e = str.size(); i != e; ++i) {
-    result = result * 33 + static_cast<unsigned char>(str[i]);
-  }
-  return result;
-}
-
-/// Returns the number of buckets to allocate to ensure that the DenseMap can
-/// accommodate \p NumEntries without need to grow().
-static unsigned getMinBucketToReserveForEntries(unsigned NumEntries) {
-  // Ensure that "NumEntries * 4 < NumBuckets * 3"
-  if (NumEntries == 0)
-    return 0;
-  // +1 is required because of the strict equality.
-  // For example if NumEntries is 48, we need to return 401.
-  return NextPowerOf2(NumEntries * 4 / 3 + 1);
-}
-
-StringMapImpl::StringMapImpl(unsigned InitSize, unsigned itemSize) {
-  ItemSize = itemSize;
-
-  // If a size is specified, initialize the table with that many buckets.
-  if (InitSize) {
-    // The table will grow when the number of entries reach 3/4 of the number of
-    // buckets. To guarantee that "InitSize" number of entries can be inserted
-    // in the table without growing, we allocate just what is needed here.
-    init(getMinBucketToReserveForEntries(InitSize));
-    return;
-  }
-
-  // Otherwise, initialize it with zero buckets to avoid the allocation.
-  TheTable = nullptr;
-  NumBuckets = 0;
-  NumItems = 0;
-  NumTombstones = 0;
-}
-
-void StringMapImpl::init(unsigned InitSize) {
-  assert((InitSize & (InitSize-1)) == 0 &&
-         "Init Size must be a power of 2 or zero!");
-
-  unsigned NewNumBuckets = InitSize ? InitSize : 16;
-  NumItems = 0;
-  NumTombstones = 0;
-
-  TheTable = static_cast<StringMapEntryBase **>(
-      safe_calloc(NewNumBuckets+1,
-                  sizeof(StringMapEntryBase **) + sizeof(unsigned)));
-
-  // Set the member only if TheTable was successfully allocated
-  NumBuckets = NewNumBuckets;
-
-  // Allocate one extra bucket, set it to look filled so the iterators stop at
-  // end.
-  TheTable[NumBuckets] = (StringMapEntryBase*)2;
-}
-
-/// LookupBucketFor - Look up the bucket that the specified string should end
-/// up in.  If it already exists as a key in the map, the Item pointer for the
-/// specified bucket will be non-null.  Otherwise, it will be null.  In either
-/// case, the FullHashValue field of the bucket will be set to the hash value
-/// of the string.
-unsigned StringMapImpl::LookupBucketFor(std::string_view Name) {
-  unsigned HTSize = NumBuckets;
-  if (HTSize == 0) {  // Hash table unallocated so far?
-    init(16);
-    HTSize = NumBuckets;
-  }
-  unsigned FullHashValue = HashString(Name);
-  unsigned BucketNo = FullHashValue & (HTSize-1);
-  unsigned *HashTable = (unsigned *)(TheTable + NumBuckets + 1);
-
-  unsigned ProbeAmt = 1;
-  int FirstTombstone = -1;
-  while (true) {
-    StringMapEntryBase *BucketItem = TheTable[BucketNo];
-    // If we found an empty bucket, this key isn't in the table yet, return it.
-    if (LLVM_LIKELY(!BucketItem)) {
-      // If we found a tombstone, we want to reuse the tombstone instead of an
-      // empty bucket.  This reduces probing.
-      if (FirstTombstone != -1) {
-        HashTable[FirstTombstone] = FullHashValue;
-        return FirstTombstone;
-      }
-
-      HashTable[BucketNo] = FullHashValue;
-      return BucketNo;
-    }
-
-    if (BucketItem == getTombstoneVal()) {
-      // Skip over tombstones.  However, remember the first one we see.
-      if (FirstTombstone == -1) FirstTombstone = BucketNo;
-    } else if (LLVM_LIKELY(HashTable[BucketNo] == FullHashValue)) {
-      // If the full hash value matches, check deeply for a match.  The common
-      // case here is that we are only looking at the buckets (for item info
-      // being non-null and for the full hash value) not at the items.  This
-      // is important for cache locality.
-
-      // Do the comparison like this because Name isn't necessarily
-      // null-terminated!
-      char *ItemStr = (char*)BucketItem+ItemSize;
-      if (Name == std::string_view(ItemStr, BucketItem->getKeyLength())) {
-        // We found a match!
-        return BucketNo;
-      }
-    }
-
-    // Okay, we didn't find the item.  Probe to the next bucket.
-    BucketNo = (BucketNo+ProbeAmt) & (HTSize-1);
-
-    // Use quadratic probing, it has fewer clumping artifacts than linear
-    // probing and has good cache behavior in the common case.
-    ++ProbeAmt;
-  }
-}
-
-/// FindKey - Look up the bucket that contains the specified key. If it exists
-/// in the map, return the bucket number of the key.  Otherwise return -1.
-/// This does not modify the map.
-int StringMapImpl::FindKey(std::string_view Key) const {
-  unsigned HTSize = NumBuckets;
-  if (HTSize == 0) return -1;  // Really empty table?
-  unsigned FullHashValue = HashString(Key);
-  unsigned BucketNo = FullHashValue & (HTSize-1);
-  unsigned *HashTable = (unsigned *)(TheTable + NumBuckets + 1);
-
-  unsigned ProbeAmt = 1;
-  while (true) {
-    StringMapEntryBase *BucketItem = TheTable[BucketNo];
-    // If we found an empty bucket, this key isn't in the table yet, return.
-    if (LLVM_LIKELY(!BucketItem))
-      return -1;
-
-    if (BucketItem == getTombstoneVal()) {
-      // Ignore tombstones.
-    } else if (LLVM_LIKELY(HashTable[BucketNo] == FullHashValue)) {
-      // If the full hash value matches, check deeply for a match.  The common
-      // case here is that we are only looking at the buckets (for item info
-      // being non-null and for the full hash value) not at the items.  This
-      // is important for cache locality.
-
-      // Do the comparison like this because NameStart isn't necessarily
-      // null-terminated!
-      char *ItemStr = (char*)BucketItem+ItemSize;
-      if (Key == std::string_view(ItemStr, BucketItem->getKeyLength())) {
-        // We found a match!
-        return BucketNo;
-      }
-    }
-
-    // Okay, we didn't find the item.  Probe to the next bucket.
-    BucketNo = (BucketNo+ProbeAmt) & (HTSize-1);
-
-    // Use quadratic probing, it has fewer clumping artifacts than linear
-    // probing and has good cache behavior in the common case.
-    ++ProbeAmt;
-  }
-}
-
-/// RemoveKey - Remove the specified StringMapEntry from the table, but do not
-/// delete it.  This aborts if the value isn't in the table.
-void StringMapImpl::RemoveKey(StringMapEntryBase *V) {
-  const char *VStr = (char*)V + ItemSize;
-  StringMapEntryBase *V2 = RemoveKey(std::string_view(VStr, V->getKeyLength()));
-  (void)V2;
-  assert(V == V2 && "Didn't find key?");
-}
-
-/// RemoveKey - Remove the StringMapEntry for the specified key from the
-/// table, returning it.  If the key is not in the table, this returns null.
-StringMapEntryBase *StringMapImpl::RemoveKey(std::string_view Key) {
-  int Bucket = FindKey(Key);
-  if (Bucket == -1) return nullptr;
-
-  StringMapEntryBase *Result = TheTable[Bucket];
-  TheTable[Bucket] = getTombstoneVal();
-  --NumItems;
-  ++NumTombstones;
-  assert(NumItems + NumTombstones <= NumBuckets);
-
-  return Result;
-}
-
-/// RehashTable - Grow the table, redistributing values into the buckets with
-/// the appropriate mod-of-hashtable-size.
-unsigned StringMapImpl::RehashTable(unsigned BucketNo) {
-  unsigned NewSize;
-  unsigned *HashTable = (unsigned *)(TheTable + NumBuckets + 1);
-
-  // If the hash table is now more than 3/4 full, or if fewer than 1/8 of
-  // the buckets are empty (meaning that many are filled with tombstones),
-  // grow/rehash the table.
-  if (LLVM_UNLIKELY(NumItems * 4 > NumBuckets * 3)) {
-    NewSize = NumBuckets*2;
-  } else if (LLVM_UNLIKELY(NumBuckets - (NumItems + NumTombstones) <=
-                           NumBuckets / 8)) {
-    NewSize = NumBuckets;
-  } else {
-    return BucketNo;
-  }
-
-  unsigned NewBucketNo = BucketNo;
-  // Allocate one extra bucket which will always be non-empty.  This allows the
-  // iterators to stop at end.
-  auto NewTableArray = static_cast<StringMapEntryBase **>(
-      safe_calloc(NewSize+1, sizeof(StringMapEntryBase *) + sizeof(unsigned)));
-
-  unsigned *NewHashArray = (unsigned *)(NewTableArray + NewSize + 1);
-  NewTableArray[NewSize] = (StringMapEntryBase*)2;
-
-  // Rehash all the items into their new buckets.  Luckily :) we already have
-  // the hash values available, so we don't have to rehash any strings.
-  for (unsigned I = 0, E = NumBuckets; I != E; ++I) {
-    StringMapEntryBase *Bucket = TheTable[I];
-    if (Bucket && Bucket != getTombstoneVal()) {
-      // Fast case, bucket available.
-      unsigned FullHash = HashTable[I];
-      unsigned NewBucket = FullHash & (NewSize-1);
-      if (!NewTableArray[NewBucket]) {
-        NewTableArray[FullHash & (NewSize-1)] = Bucket;
-        NewHashArray[FullHash & (NewSize-1)] = FullHash;
-        if (I == BucketNo)
-          NewBucketNo = NewBucket;
-        continue;
-      }
-
-      // Otherwise probe for a spot.
-      unsigned ProbeSize = 1;
-      do {
-        NewBucket = (NewBucket + ProbeSize++) & (NewSize-1);
-      } while (NewTableArray[NewBucket]);
-
-      // Finally found a slot.  Fill it in.
-      NewTableArray[NewBucket] = Bucket;
-      NewHashArray[NewBucket] = FullHash;
-      if (I == BucketNo)
-        NewBucketNo = NewBucket;
-    }
-  }
-
-  free(TheTable);
-
-  TheTable = NewTableArray;
-  NumBuckets = NewSize;
-  NumTombstones = 0;
-  return NewBucketNo;
-}
diff --git a/wpiutil/src/main/native/cpp/llvm/Windows/WindowsSupport.h b/wpiutil/src/main/native/cpp/llvm/Windows/WindowsSupport.h
deleted file mode 100644
index 7307337..0000000
--- a/wpiutil/src/main/native/cpp/llvm/Windows/WindowsSupport.h
+++ /dev/null
@@ -1,245 +0,0 @@
-//===- WindowsSupport.h - Common Windows Include File -----------*- C++ -*-===//
-//
-//                     The LLVM Compiler Infrastructure
-//
-// This file is distributed under the University of Illinois Open Source
-// License. See LICENSE.TXT for details.
-//
-//===----------------------------------------------------------------------===//
-//
-// This file defines things specific to Windows implementations.  In addition to
-// providing some helpers for working with win32 APIs, this header wraps
-// <windows.h> with some portability macros.  Always include WindowsSupport.h
-// instead of including <windows.h> directly.
-//
-//===----------------------------------------------------------------------===//
-
-//===----------------------------------------------------------------------===//
-//=== WARNING: Implementation here must contain only generic Win32 code that
-//===          is guaranteed to work on *all* Win32 variants.
-//===----------------------------------------------------------------------===//
-
-#ifndef LLVM_SUPPORT_WINDOWSSUPPORT_H
-#define LLVM_SUPPORT_WINDOWSSUPPORT_H
-
-// mingw-w64 tends to define it as 0x0502 in its headers.
-#undef _WIN32_WINNT
-#undef _WIN32_IE
-
-// Require at least Windows 7 API.
-#define _WIN32_WINNT 0x0601
-#define _WIN32_IE    0x0800 // MinGW at it again. FIXME: verify if still needed.
-#define WIN32_LEAN_AND_MEAN
-#ifndef NOMINMAX
-#define NOMINMAX
-#endif
-
-#include "wpi/SmallVector.h"
-#include "wpi/StringExtras.h"
-#include "wpi/Chrono.h"
-#include "wpi/Compiler.h"
-#include "wpi/VersionTuple.h"
-#include <cassert>
-#include <string>
-#include <string_view>
-#include <system_error>
-#define WIN32_NO_STATUS
-#include <windows.h>
-#undef WIN32_NO_STATUS
-#include <winternl.h>
-#include <ntstatus.h>
-
-namespace wpi {
-
-/// Returns the Windows version as Major.Minor.0.BuildNumber. Uses
-/// RtlGetVersion or GetVersionEx under the hood depending on what is available.
-/// GetVersionEx is deprecated, but this API exposes the build number which can
-/// be useful for working around certain kernel bugs.
-inline wpi::VersionTuple GetWindowsOSVersion() {
-  typedef NTSTATUS(WINAPI* RtlGetVersionPtr)(PRTL_OSVERSIONINFOW);
-  HMODULE hMod = ::GetModuleHandleW(L"ntdll.dll");
-  if (hMod) {
-    auto getVer = (RtlGetVersionPtr)::GetProcAddress(hMod, "RtlGetVersion");
-    if (getVer) {
-      RTL_OSVERSIONINFOEXW info{};
-      info.dwOSVersionInfoSize = sizeof(info);
-      if (getVer((PRTL_OSVERSIONINFOW)&info) == ((NTSTATUS)0x00000000L)) {
-        return wpi::VersionTuple(info.dwMajorVersion, info.dwMinorVersion, 0,
-                                  info.dwBuildNumber);
-      }
-    }
-  }
-  return wpi::VersionTuple(0, 0, 0, 0);
-}
-
-/// Determines if the program is running on Windows 8 or newer. This
-/// reimplements one of the helpers in the Windows 8.1 SDK, which are intended
-/// to supercede raw calls to GetVersionEx. Old SDKs, Cygwin, and MinGW don't
-/// yet have VersionHelpers.h, so we have our own helper.
-inline bool RunningWindows8OrGreater() {
-  // Windows 8 is version 6.2, service pack 0.
-  return GetWindowsOSVersion() >= wpi::VersionTuple(6, 2, 0, 0);
-}
-
-inline bool MakeErrMsg(std::string *ErrMsg, const std::string &prefix) {
-  if (!ErrMsg)
-    return true;
-  char *buffer = NULL;
-  DWORD LastError = GetLastError();
-  DWORD R = FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER |
-                               FORMAT_MESSAGE_FROM_SYSTEM |
-                               FORMAT_MESSAGE_MAX_WIDTH_MASK,
-                           NULL, LastError, 0, (LPSTR)&buffer, 1, NULL);
-  if (R)
-    *ErrMsg = prefix + ": " + buffer;
-  else
-    *ErrMsg = prefix + ": Unknown error";
-  *ErrMsg += " (0x" + wpi::utohexstr(LastError) + ")";
-
-  LocalFree(buffer);
-  return R != 0;
-}
-
-template <typename HandleTraits>
-class ScopedHandle {
-  typedef typename HandleTraits::handle_type handle_type;
-  handle_type Handle;
-
-  ScopedHandle(const ScopedHandle &other) = delete;
-  void operator=(const ScopedHandle &other) = delete;
-public:
-  ScopedHandle()
-    : Handle(HandleTraits::GetInvalid()) {}
-
-  explicit ScopedHandle(handle_type h)
-    : Handle(h) {}
-
-  ~ScopedHandle() {
-    if (HandleTraits::IsValid(Handle))
-      HandleTraits::Close(Handle);
-  }
-
-  handle_type take() {
-    handle_type t = Handle;
-    Handle = HandleTraits::GetInvalid();
-    return t;
-  }
-
-  ScopedHandle &operator=(handle_type h) {
-    if (HandleTraits::IsValid(Handle))
-      HandleTraits::Close(Handle);
-    Handle = h;
-    return *this;
-  }
-
-  // True if Handle is valid.
-  explicit operator bool() const {
-    return HandleTraits::IsValid(Handle) ? true : false;
-  }
-
-  operator handle_type() const {
-    return Handle;
-  }
-};
-
-struct CommonHandleTraits {
-  typedef HANDLE handle_type;
-
-  static handle_type GetInvalid() {
-    return INVALID_HANDLE_VALUE;
-  }
-
-  static void Close(handle_type h) {
-    ::CloseHandle(h);
-  }
-
-  static bool IsValid(handle_type h) {
-    return h != GetInvalid();
-  }
-};
-
-struct JobHandleTraits : CommonHandleTraits {
-  static handle_type GetInvalid() {
-    return NULL;
-  }
-};
-
-struct RegTraits : CommonHandleTraits {
-  typedef HKEY handle_type;
-
-  static handle_type GetInvalid() {
-    return NULL;
-  }
-
-  static void Close(handle_type h) {
-    ::RegCloseKey(h);
-  }
-
-  static bool IsValid(handle_type h) {
-    return h != GetInvalid();
-  }
-};
-
-struct FindHandleTraits : CommonHandleTraits {
-  static void Close(handle_type h) {
-    ::FindClose(h);
-  }
-};
-
-struct FileHandleTraits : CommonHandleTraits {};
-
-typedef ScopedHandle<CommonHandleTraits> ScopedCommonHandle;
-typedef ScopedHandle<FileHandleTraits>   ScopedFileHandle;
-typedef ScopedHandle<RegTraits>          ScopedRegHandle;
-typedef ScopedHandle<FindHandleTraits>   ScopedFindHandle;
-typedef ScopedHandle<JobHandleTraits>    ScopedJobHandle;
-
-template <class T>
-class SmallVectorImpl;
-
-template <class T>
-typename SmallVectorImpl<T>::const_pointer
-c_str(SmallVectorImpl<T> &str) {
-  str.push_back(0);
-  str.pop_back();
-  return str.data();
-}
-
-namespace sys {
-
-inline std::chrono::nanoseconds toDuration(FILETIME Time) {
-  ULARGE_INTEGER TimeInteger;
-  TimeInteger.LowPart = Time.dwLowDateTime;
-  TimeInteger.HighPart = Time.dwHighDateTime;
-
-  // FILETIME's are # of 100 nanosecond ticks (1/10th of a microsecond)
-  return std::chrono::nanoseconds(100 * TimeInteger.QuadPart);
-}
-
-inline TimePoint<> toTimePoint(FILETIME Time) {
-  ULARGE_INTEGER TimeInteger;
-  TimeInteger.LowPart = Time.dwLowDateTime;
-  TimeInteger.HighPart = Time.dwHighDateTime;
-
-  // Adjust for different epoch
-  TimeInteger.QuadPart -= 11644473600ll * 10000000;
-
-  // FILETIME's are # of 100 nanosecond ticks (1/10th of a microsecond)
-  return TimePoint<>(std::chrono::nanoseconds(100 * TimeInteger.QuadPart));
-}
-
-inline FILETIME toFILETIME(TimePoint<> TP) {
-  ULARGE_INTEGER TimeInteger;
-  TimeInteger.QuadPart = TP.time_since_epoch().count() / 100;
-  TimeInteger.QuadPart += 11644473600ll * 10000000;
-
-  FILETIME Time;
-  Time.dwLowDateTime = TimeInteger.LowPart;
-  Time.dwHighDateTime = TimeInteger.HighPart;
-  return Time;
-}
-
-} // end namespace sys
-} // end namespace wpi.
-
-#endif
diff --git a/wpiutil/src/main/native/cpp/llvm/raw_os_ostream.cpp b/wpiutil/src/main/native/cpp/llvm/raw_os_ostream.cpp
deleted file mode 100644
index 1fb6c51..0000000
--- a/wpiutil/src/main/native/cpp/llvm/raw_os_ostream.cpp
+++ /dev/null
@@ -1,30 +0,0 @@
-//===--- raw_os_ostream.cpp - Implement the raw_os_ostream class ----------===//
-//
-//                     The LLVM Compiler Infrastructure
-//
-// This file is distributed under the University of Illinois Open Source
-// License. See LICENSE.TXT for details.
-//
-//===----------------------------------------------------------------------===//
-//
-// This implements support adapting raw_ostream to std::ostream.
-//
-//===----------------------------------------------------------------------===//
-
-#include "wpi/raw_os_ostream.h"
-#include <ostream>
-using namespace wpi;
-
-//===----------------------------------------------------------------------===//
-//  raw_os_ostream
-//===----------------------------------------------------------------------===//
-
-raw_os_ostream::~raw_os_ostream() {
-  flush();
-}
-
-void raw_os_ostream::write_impl(const char *Ptr, size_t Size) {
-  OS.write(Ptr, Size);
-}
-
-uint64_t raw_os_ostream::current_pos() const { return OS.tellp(); }
diff --git a/wpiutil/src/main/native/cpp/llvm/raw_ostream.cpp b/wpiutil/src/main/native/cpp/llvm/raw_ostream.cpp
deleted file mode 100644
index 622e0bf..0000000
--- a/wpiutil/src/main/native/cpp/llvm/raw_ostream.cpp
+++ /dev/null
@@ -1,705 +0,0 @@
-//===--- raw_ostream.cpp - Implement the raw_ostream classes --------------===//
-//
-//                     The LLVM Compiler Infrastructure
-//
-// This file is distributed under the University of Illinois Open Source
-// License. See LICENSE.TXT for details.
-//
-//===----------------------------------------------------------------------===//
-//
-// This implements support for bulk buffered stream output.
-//
-//===----------------------------------------------------------------------===//
-
-#ifdef _WIN32
-#define _CRT_NONSTDC_NO_WARNINGS
-#endif
-
-#include "wpi/raw_ostream.h"
-#include "wpi/SmallString.h"
-#include "wpi/SmallVector.h"
-#include "wpi/StringExtras.h"
-#include "wpi/Compiler.h"
-#include "wpi/ErrorHandling.h"
-#include "wpi/MathExtras.h"
-#include "wpi/WindowsError.h"
-#include "wpi/fs.h"
-#include <algorithm>
-#include <cctype>
-#include <cerrno>
-#include <cstdio>
-#include <iterator>
-#include <sys/stat.h>
-#include <system_error>
-
-// <fcntl.h> may provide O_BINARY.
-#include <fcntl.h>
-
-#ifndef _WIN32
-#include <unistd.h>
-#include <sys/uio.h>
-#endif
-
-#if defined(__CYGWIN__)
-#include <io.h>
-#endif
-
-#if defined(_MSC_VER)
-#include <io.h>
-#ifndef STDIN_FILENO
-# define STDIN_FILENO 0
-#endif
-#ifndef STDOUT_FILENO
-# define STDOUT_FILENO 1
-#endif
-#ifndef STDERR_FILENO
-# define STDERR_FILENO 2
-#endif
-#endif
-
-#ifdef _WIN32
-#include "wpi/ConvertUTF.h"
-#include "Windows/WindowsSupport.h"
-#endif
-
-using namespace wpi;
-
-namespace {
-// Find the length of an array.
-template <class T, std::size_t N>
-constexpr inline size_t array_lengthof(T (&)[N]) {
-  return N;
-}
-}  // namespace
-
-raw_ostream::~raw_ostream() {
-  // raw_ostream's subclasses should take care to flush the buffer
-  // in their destructors.
-  assert(OutBufCur == OutBufStart &&
-         "raw_ostream destructor called with non-empty buffer!");
-
-  if (BufferMode == InternalBuffer)
-    delete [] OutBufStart;
-}
-
-// An out of line virtual method to provide a home for the class vtable.
-void raw_ostream::handle() {}
-
-size_t raw_ostream::preferred_buffer_size() const {
-  // BUFSIZ is intended to be a reasonable default.
-  return BUFSIZ;
-}
-
-void raw_ostream::SetBuffered() {
-  // Ask the subclass to determine an appropriate buffer size.
-  if (size_t Size = preferred_buffer_size())
-    SetBufferSize(Size);
-  else
-    // It may return 0, meaning this stream should be unbuffered.
-    SetUnbuffered();
-}
-
-void raw_ostream::SetBufferAndMode(char *BufferStart, size_t Size,
-                                   BufferKind Mode) {
-  assert(((Mode == Unbuffered && !BufferStart && Size == 0) ||
-          (Mode != Unbuffered && BufferStart && Size != 0)) &&
-         "stream must be unbuffered or have at least one byte");
-  // Make sure the current buffer is free of content (we can't flush here; the
-  // child buffer management logic will be in write_impl).
-  assert(GetNumBytesInBuffer() == 0 && "Current buffer is non-empty!");
-
-  if (BufferMode == InternalBuffer)
-    delete [] OutBufStart;
-  OutBufStart = BufferStart;
-  OutBufEnd = OutBufStart+Size;
-  OutBufCur = OutBufStart;
-  BufferMode = Mode;
-
-  assert(OutBufStart <= OutBufEnd && "Invalid size!");
-}
-
-raw_ostream &raw_ostream::write_escaped(std::string_view Str,
-                                        bool UseHexEscapes) {
-  for (unsigned char c : Str) {
-    switch (c) {
-    case '\\':
-      *this << '\\' << '\\';
-      break;
-    case '\t':
-      *this << '\\' << 't';
-      break;
-    case '\n':
-      *this << '\\' << 'n';
-      break;
-    case '"':
-      *this << '\\' << '"';
-      break;
-    default:
-      if (isPrint(c)) {
-        *this << c;
-        break;
-      }
-
-      // Write out the escaped representation.
-      if (UseHexEscapes) {
-        *this << '\\' << 'x';
-        *this << hexdigit((c >> 4 & 0xF));
-        *this << hexdigit((c >> 0) & 0xF);
-      } else {
-        // Always use a full 3-character octal escape.
-        *this << '\\';
-        *this << char('0' + ((c >> 6) & 7));
-        *this << char('0' + ((c >> 3) & 7));
-        *this << char('0' + ((c >> 0) & 7));
-      }
-    }
-  }
-
-  return *this;
-}
-
-void raw_ostream::flush_nonempty() {
-  assert(OutBufCur > OutBufStart && "Invalid call to flush_nonempty.");
-  size_t Length = OutBufCur - OutBufStart;
-  OutBufCur = OutBufStart;
-  write_impl(OutBufStart, Length);
-}
-
-raw_ostream &raw_ostream::write(unsigned char C) {
-  // Group exceptional cases into a single branch.
-  if (LLVM_UNLIKELY(OutBufCur >= OutBufEnd)) {
-    if (LLVM_UNLIKELY(!OutBufStart)) {
-      if (BufferMode == Unbuffered) {
-        write_impl(reinterpret_cast<char*>(&C), 1);
-        return *this;
-      }
-      // Set up a buffer and start over.
-      SetBuffered();
-      return write(C);
-    }
-
-    flush_nonempty();
-  }
-
-  *OutBufCur++ = C;
-  return *this;
-}
-
-raw_ostream &raw_ostream::write(const char *Ptr, size_t Size) {
-  // Group exceptional cases into a single branch.
-  if (LLVM_UNLIKELY(size_t(OutBufEnd - OutBufCur) < Size)) {
-    if (LLVM_UNLIKELY(!OutBufStart)) {
-      if (BufferMode == Unbuffered) {
-        write_impl(Ptr, Size);
-        return *this;
-      }
-      // Set up a buffer and start over.
-      SetBuffered();
-      return write(Ptr, Size);
-    }
-
-    size_t NumBytes = OutBufEnd - OutBufCur;
-
-    // If the buffer is empty at this point we have a string that is larger
-    // than the buffer. Directly write the chunk that is a multiple of the
-    // preferred buffer size and put the remainder in the buffer.
-    if (LLVM_UNLIKELY(OutBufCur == OutBufStart)) {
-      assert(NumBytes != 0 && "undefined behavior");
-      size_t BytesToWrite = Size - (Size % NumBytes);
-      write_impl(Ptr, BytesToWrite);
-      size_t BytesRemaining = Size - BytesToWrite;
-      if (BytesRemaining > size_t(OutBufEnd - OutBufCur)) {
-        // Too much left over to copy into our buffer.
-        return write(Ptr + BytesToWrite, BytesRemaining);
-      }
-      copy_to_buffer(Ptr + BytesToWrite, BytesRemaining);
-      return *this;
-    }
-
-    // We don't have enough space in the buffer to fit the string in. Insert as
-    // much as possible, flush and start over with the remainder.
-    copy_to_buffer(Ptr, NumBytes);
-    flush_nonempty();
-    return write(Ptr + NumBytes, Size - NumBytes);
-  }
-
-  copy_to_buffer(Ptr, Size);
-
-  return *this;
-}
-
-void raw_ostream::copy_to_buffer(const char *Ptr, size_t Size) {
-  assert(Size <= size_t(OutBufEnd - OutBufCur) && "Buffer overrun!");
-
-  // Handle short strings specially, memcpy isn't very good at very short
-  // strings.
-  switch (Size) {
-  case 4: OutBufCur[3] = Ptr[3]; LLVM_FALLTHROUGH;
-  case 3: OutBufCur[2] = Ptr[2]; LLVM_FALLTHROUGH;
-  case 2: OutBufCur[1] = Ptr[1]; LLVM_FALLTHROUGH;
-  case 1: OutBufCur[0] = Ptr[0]; LLVM_FALLTHROUGH;
-  case 0: break;
-  default:
-    memcpy(OutBufCur, Ptr, Size);
-    break;
-  }
-
-  OutBufCur += Size;
-}
-
-template <char C>
-static raw_ostream &write_padding(raw_ostream &OS, unsigned NumChars) {
-  static const char Chars[] = {C, C, C, C, C, C, C, C, C, C, C, C, C, C, C, C,
-                               C, C, C, C, C, C, C, C, C, C, C, C, C, C, C, C,
-                               C, C, C, C, C, C, C, C, C, C, C, C, C, C, C, C,
-                               C, C, C, C, C, C, C, C, C, C, C, C, C, C, C, C,
-                               C, C, C, C, C, C, C, C, C, C, C, C, C, C, C, C};
-
-  // Usually the indentation is small, handle it with a fastpath.
-  if (NumChars < array_lengthof(Chars))
-    return OS.write(Chars, NumChars);
-
-  while (NumChars) {
-    unsigned NumToWrite = std::min(NumChars,
-                                   (unsigned)array_lengthof(Chars)-1);
-    OS.write(Chars, NumToWrite);
-    NumChars -= NumToWrite;
-  }
-  return OS;
-}
-
-/// indent - Insert 'NumSpaces' spaces.
-raw_ostream &raw_ostream::indent(unsigned NumSpaces) {
-  return write_padding<' '>(*this, NumSpaces);
-}
-
-/// write_zeros - Insert 'NumZeros' nulls.
-raw_ostream &raw_ostream::write_zeros(unsigned NumZeros) {
-  return write_padding<'\0'>(*this, NumZeros);
-}
-
-void raw_ostream::anchor() {}
-
-//===----------------------------------------------------------------------===//
-//  raw_fd_ostream
-//===----------------------------------------------------------------------===//
-
-static int getFD(std::string_view Filename, std::error_code &EC,
-                 fs::CreationDisposition Disp, fs::FileAccess Access,
-                 fs::OpenFlags Flags) {
-  assert((Access & fs::FA_Write) &&
-         "Cannot make a raw_ostream from a read-only descriptor!");
-
-  // Handle "-" as stdout. Note that when we do this, we consider ourself
-  // the owner of stdout and may set the "binary" flag globally based on Flags.
-  if (Filename == "-") {
-    EC = std::error_code();
-    // If user requested binary then put stdout into binary mode if
-    // possible.
-    if (!(Flags & fs::OF_Text)) {
-#if defined(_WIN32)
-      _setmode(_fileno(stdout), _O_BINARY);
-#endif
-    }
-    return STDOUT_FILENO;
-  }
-
-  fs::file_t F;
-  if (Access & fs::FA_Read) {
-    F = fs::OpenFileForReadWrite(fs::path{std::string_view{Filename.data(), Filename.size()}}, EC, Disp, Flags);
-  } else {
-    F = fs::OpenFileForWrite(fs::path{std::string_view{Filename.data(), Filename.size()}}, EC, Disp, Flags);
-  }
-  if (EC)
-    return -1;
-  int FD = fs::FileToFd(F, EC, Flags);
-  if (EC)
-    return -1;
-
-  return FD;
-}
-
-raw_fd_ostream::raw_fd_ostream(std::string_view Filename, std::error_code &EC)
-    : raw_fd_ostream(Filename, EC, fs::CD_CreateAlways, fs::FA_Write,
-                     fs::OF_None) {}
-
-raw_fd_ostream::raw_fd_ostream(std::string_view Filename, std::error_code &EC,
-                               fs::CreationDisposition Disp)
-    : raw_fd_ostream(Filename, EC, Disp, fs::FA_Write, fs::OF_None) {}
-
-raw_fd_ostream::raw_fd_ostream(std::string_view Filename, std::error_code &EC,
-                               fs::FileAccess Access)
-    : raw_fd_ostream(Filename, EC, fs::CD_CreateAlways, Access,
-                     fs::OF_None) {}
-
-raw_fd_ostream::raw_fd_ostream(std::string_view Filename, std::error_code &EC,
-                               fs::OpenFlags Flags)
-    : raw_fd_ostream(Filename, EC, fs::CD_CreateAlways, fs::FA_Write,
-                     Flags) {}
-
-raw_fd_ostream::raw_fd_ostream(std::string_view Filename, std::error_code &EC,
-                               fs::CreationDisposition Disp,
-                               fs::FileAccess Access,
-                               fs::OpenFlags Flags)
-    : raw_fd_ostream(getFD(Filename, EC, Disp, Access, Flags), true) {}
-
-/// FD is the file descriptor that this writes to.  If ShouldClose is true, this
-/// closes the file when the stream is destroyed.
-raw_fd_ostream::raw_fd_ostream(int fd, bool shouldClose, bool unbuffered)
-    : raw_pwrite_stream(unbuffered), FD(fd), ShouldClose(shouldClose) {
-  if (FD < 0 ) {
-    ShouldClose = false;
-    return;
-  }
-
-  // Do not attempt to close stdout or stderr. We used to try to maintain the
-  // property that tools that support writing file to stdout should not also
-  // write informational output to stdout, but in practice we were never able to
-  // maintain this invariant. Many features have been added to LLVM and clang
-  // (-fdump-record-layouts, optimization remarks, etc) that print to stdout, so
-  // users must simply be aware that mixed output and remarks is a possibility.
-  if (FD <= STDERR_FILENO)
-    ShouldClose = false;
-
-#ifdef _WIN32
-  // Check if this is a console device. This is not equivalent to isatty.
-  IsWindowsConsole =
-      ::GetFileType((HANDLE)::_get_osfhandle(fd)) == FILE_TYPE_CHAR;
-#endif
-
-  // Get the starting position.
-  off_t loc = ::lseek(FD, 0, SEEK_CUR);
-#ifdef _WIN32
-  // MSVCRT's _lseek(SEEK_CUR) doesn't return -1 for pipes.
-  SupportsSeeking = loc != (off_t)-1 && ::GetFileType(reinterpret_cast<HANDLE>(::_get_osfhandle(FD))) != FILE_TYPE_PIPE;
-#else
-  SupportsSeeking = loc != (off_t)-1;
-#endif
-  if (!SupportsSeeking)
-    pos = 0;
-  else
-    pos = static_cast<uint64_t>(loc);
-}
-
-raw_fd_ostream::~raw_fd_ostream() {
-  if (FD >= 0) {
-    flush();
-    if (ShouldClose && ::close(FD) < 0)
-      error_detected(std::error_code(errno, std::generic_category()));
-  }
-
-#ifdef __MINGW32__
-  // On mingw, global dtors should not call exit().
-  // report_fatal_error() invokes exit(). We know report_fatal_error()
-  // might not write messages to stderr when any errors were detected
-  // on FD == 2.
-  if (FD == 2) return;
-#endif
-
-  // If there are any pending errors, report them now. Clients wishing
-  // to avoid report_fatal_error calls should check for errors with
-  // has_error() and clear the error flag with clear_error() before
-  // destructing raw_ostream objects which may have errors.
-  if (has_error())
-    report_fatal_error("IO failure on output stream: " + error().message(),
-                       /*GenCrashDiag=*/false);
-}
-
-#if defined(_WIN32)
-// The most reliable way to print unicode in a Windows console is with
-// WriteConsoleW. To use that, first transcode from UTF-8 to UTF-16. This
-// assumes that LLVM programs always print valid UTF-8 to the console. The data
-// might not be UTF-8 for two major reasons:
-// 1. The program is printing binary (-filetype=obj -o -), in which case it
-// would have been gibberish anyway.
-// 2. The program is printing text in a semi-ascii compatible codepage like
-// shift-jis or cp1252.
-//
-// Most LLVM programs don't produce non-ascii text unless they are quoting
-// user source input. A well-behaved LLVM program should either validate that
-// the input is UTF-8 or transcode from the local codepage to UTF-8 before
-// quoting it. If they don't, this may mess up the encoding, but this is still
-// probably the best compromise we can make.
-static bool write_console_impl(int FD, std::string_view Data) {
-  SmallVector<wchar_t, 256> WideText;
-
-  // Fall back to ::write if it wasn't valid UTF-8.
-  if (auto EC = sys::windows::UTF8ToUTF16(Data, WideText))
-    return false;
-
-  // On Windows 7 and earlier, WriteConsoleW has a low maximum amount of data
-  // that can be written to the console at a time.
-  size_t MaxWriteSize = WideText.size();
-  if (!RunningWindows8OrGreater())
-    MaxWriteSize = 32767;
-
-  size_t WCharsWritten = 0;
-  do {
-    size_t WCharsToWrite =
-        std::min(MaxWriteSize, WideText.size() - WCharsWritten);
-    DWORD ActuallyWritten;
-    bool Success =
-        ::WriteConsoleW((HANDLE)::_get_osfhandle(FD), &WideText[WCharsWritten],
-                        WCharsToWrite, &ActuallyWritten,
-                        /*Reserved=*/nullptr);
-
-    // The most likely reason for WriteConsoleW to fail is that FD no longer
-    // points to a console. Fall back to ::write. If this isn't the first loop
-    // iteration, something is truly wrong.
-    if (!Success)
-      return false;
-
-    WCharsWritten += ActuallyWritten;
-  } while (WCharsWritten != WideText.size());
-  return true;
-}
-#endif
-
-void raw_fd_ostream::write_impl(const char *Ptr, size_t Size) {
-  assert(FD >= 0 && "File already closed.");
-  pos += Size;
-
-#if defined(_WIN32)
-  // If this is a Windows console device, try re-encoding from UTF-8 to UTF-16
-  // and using WriteConsoleW. If that fails, fall back to plain write().
-  if (IsWindowsConsole)
-    if (write_console_impl(FD, std::string_view(Ptr, Size)))
-      return;
-#endif
-
-  // The maximum write size is limited to INT32_MAX. A write
-  // greater than SSIZE_MAX is implementation-defined in POSIX,
-  // and Windows _write requires 32 bit input.
-  size_t MaxWriteSize = INT32_MAX;
-
-#if defined(__linux__)
-  // It is observed that Linux returns EINVAL for a very large write (>2G).
-  // Make it a reasonably small value.
-  MaxWriteSize = 1024 * 1024 * 1024;
-#endif
-
-  do {
-    size_t ChunkSize = std::min(Size, MaxWriteSize);
-#ifdef _WIN32
-    int ret = ::_write(FD, Ptr, ChunkSize);
-#else
-    ssize_t ret = ::write(FD, Ptr, ChunkSize);
-#endif
-
-    if (ret < 0) {
-      // If it's a recoverable error, swallow it and retry the write.
-      //
-      // Ideally we wouldn't ever see EAGAIN or EWOULDBLOCK here, since
-      // raw_ostream isn't designed to do non-blocking I/O. However, some
-      // programs, such as old versions of bjam, have mistakenly used
-      // O_NONBLOCK. For compatibility, emulate blocking semantics by
-      // spinning until the write succeeds. If you don't want spinning,
-      // don't use O_NONBLOCK file descriptors with raw_ostream.
-      if (errno == EINTR || errno == EAGAIN
-#ifdef EWOULDBLOCK
-          || errno == EWOULDBLOCK
-#endif
-          )
-        continue;
-
-      // Otherwise it's a non-recoverable error. Note it and quit.
-      error_detected(std::error_code(errno, std::generic_category()));
-      break;
-    }
-
-    // The write may have written some or all of the data. Update the
-    // size and buffer pointer to reflect the remainder that needs
-    // to be written. If there are no bytes left, we're done.
-    Ptr += ret;
-    Size -= ret;
-  } while (Size > 0);
-}
-
-void raw_fd_ostream::close() {
-  assert(ShouldClose);
-  ShouldClose = false;
-  flush();
-  if (::close(FD) < 0)
-    error_detected(std::error_code(errno, std::generic_category()));
-  FD = -1;
-}
-
-uint64_t raw_fd_ostream::seek(uint64_t off) {
-  assert(SupportsSeeking && "Stream does not support seeking!");
-  flush();
-#ifdef _WIN32
-  pos = ::_lseeki64(FD, off, SEEK_SET);
-#else
-  pos = ::lseek(FD, off, SEEK_SET);
-#endif
-  if (pos == (uint64_t)-1)
-    error_detected(std::error_code(errno, std::generic_category()));
-  return pos;
-}
-
-void raw_fd_ostream::pwrite_impl(const char *Ptr, size_t Size,
-                                 uint64_t Offset) {
-  uint64_t Pos = tell();
-  seek(Offset);
-  write(Ptr, Size);
-  seek(Pos);
-}
-
-size_t raw_fd_ostream::preferred_buffer_size() const {
-#if defined(_WIN32)
-  // Disable buffering for console devices. Console output is re-encoded from
-  // UTF-8 to UTF-16 on Windows, and buffering it would require us to split the
-  // buffer on a valid UTF-8 codepoint boundary. Terminal buffering is disabled
-  // below on most other OSs, so do the same thing on Windows and avoid that
-  // complexity.
-  if (IsWindowsConsole)
-    return 0;
-  return raw_ostream::preferred_buffer_size();
-#elif !defined(__minix)
-  // Minix has no st_blksize.
-  assert(FD >= 0 && "File not yet open!");
-  struct stat statbuf;
-  if (fstat(FD, &statbuf) != 0)
-    return 0;
-
-  // If this is a terminal, don't use buffering. Line buffering
-  // would be a more traditional thing to do, but it's not worth
-  // the complexity.
-  if (S_ISCHR(statbuf.st_mode) && isatty(FD))
-    return 0;
-  // Return the preferred block size.
-  return statbuf.st_blksize;
-#else
-  return raw_ostream::preferred_buffer_size();
-#endif
-}
-
-void raw_fd_ostream::anchor() {}
-
-//===----------------------------------------------------------------------===//
-//  outs(), errs(), nulls()
-//===----------------------------------------------------------------------===//
-
-/// outs() - This returns a reference to a raw_ostream for standard output.
-/// Use it like: outs() << "foo" << "bar";
-raw_ostream &wpi::outs() {
-  // Set buffer settings to model stdout behavior.
-  std::error_code EC;
-  static raw_fd_ostream* S = new raw_fd_ostream("-", EC, fs::F_None);
-  assert(!EC);
-  return *S;
-}
-
-/// errs() - This returns a reference to a raw_ostream for standard error.
-/// Use it like: errs() << "foo" << "bar";
-raw_ostream &wpi::errs() {
-  // Set standard error to be unbuffered by default.
-  static raw_fd_ostream* S = new raw_fd_ostream(STDERR_FILENO, false, true);
-  return *S;
-}
-
-/// nulls() - This returns a reference to a raw_ostream which discards output.
-raw_ostream &wpi::nulls() {
-  static raw_null_ostream* S = new raw_null_ostream;
-  return *S;
-}
-
-//===----------------------------------------------------------------------===//
-//  raw_string_ostream
-//===----------------------------------------------------------------------===//
-
-raw_string_ostream::~raw_string_ostream() {
-  flush();
-}
-
-void raw_string_ostream::write_impl(const char *Ptr, size_t Size) {
-  OS.append(Ptr, Size);
-}
-
-//===----------------------------------------------------------------------===//
-//  raw_svector_ostream
-//===----------------------------------------------------------------------===//
-
-uint64_t raw_svector_ostream::current_pos() const { return OS.size(); }
-
-void raw_svector_ostream::write_impl(const char *Ptr, size_t Size) {
-  OS.append(Ptr, Ptr + Size);
-}
-
-void raw_svector_ostream::pwrite_impl(const char *Ptr, size_t Size,
-                                      uint64_t Offset) {
-  memcpy(OS.data() + Offset, Ptr, Size);
-}
-
-//===----------------------------------------------------------------------===//
-//  raw_vector_ostream
-//===----------------------------------------------------------------------===//
-
-uint64_t raw_vector_ostream::current_pos() const { return OS.size(); }
-
-void raw_vector_ostream::write_impl(const char *Ptr, size_t Size) {
-  OS.insert(OS.end(), Ptr, Ptr + Size);
-}
-
-void raw_vector_ostream::pwrite_impl(const char *Ptr, size_t Size,
-                                     uint64_t Offset) {
-  memcpy(OS.data() + Offset, Ptr, Size);
-}
-
-//===----------------------------------------------------------------------===//
-//  raw_usvector_ostream
-//===----------------------------------------------------------------------===//
-
-uint64_t raw_usvector_ostream::current_pos() const { return OS.size(); }
-
-void raw_usvector_ostream::write_impl(const char *Ptr, size_t Size) {
-  OS.append(reinterpret_cast<const uint8_t *>(Ptr),
-            reinterpret_cast<const uint8_t *>(Ptr) + Size);
-}
-
-void raw_usvector_ostream::pwrite_impl(const char *Ptr, size_t Size,
-                                       uint64_t Offset) {
-  memcpy(OS.data() + Offset, Ptr, Size);
-}
-
-//===----------------------------------------------------------------------===//
-//  raw_uvector_ostream
-//===----------------------------------------------------------------------===//
-
-uint64_t raw_uvector_ostream::current_pos() const { return OS.size(); }
-
-void raw_uvector_ostream::write_impl(const char *Ptr, size_t Size) {
-  OS.insert(OS.end(), reinterpret_cast<const uint8_t *>(Ptr),
-            reinterpret_cast<const uint8_t *>(Ptr) + Size);
-}
-
-void raw_uvector_ostream::pwrite_impl(const char *Ptr, size_t Size,
-                                      uint64_t Offset) {
-  memcpy(OS.data() + Offset, Ptr, Size);
-}
-
-//===----------------------------------------------------------------------===//
-//  raw_null_ostream
-//===----------------------------------------------------------------------===//
-
-raw_null_ostream::~raw_null_ostream() {
-#ifndef NDEBUG
-  // ~raw_ostream asserts that the buffer is empty. This isn't necessary
-  // with raw_null_ostream, but it's better to have raw_null_ostream follow
-  // the rules than to change the rules just for raw_null_ostream.
-  flush();
-#endif
-}
-
-void raw_null_ostream::write_impl(const char * /*Ptr*/, size_t /*Size*/) {}
-
-uint64_t raw_null_ostream::current_pos() const {
-  return 0;
-}
-
-void raw_null_ostream::pwrite_impl(const char * /*Ptr*/, size_t /*Size*/,
-                                   uint64_t /*Offset*/) {}
-
-void raw_pwrite_stream::anchor() {}
-
-void buffer_ostream::anchor() {}
diff --git a/wpiutil/src/main/native/cpp/mpack.cpp b/wpiutil/src/main/native/cpp/mpack.cpp
deleted file mode 100644
index fbcb6a4..0000000
--- a/wpiutil/src/main/native/cpp/mpack.cpp
+++ /dev/null
@@ -1,7251 +0,0 @@
-/**
- * The MIT License (MIT)
- * 
- * Copyright (c) 2015-2021 Nicholas Fraser and the MPack authors
- * 
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- * 
- * The above copyright notice and this permission notice shall be included in all
- * copies or substantial portions of the Software.
- * 
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- * SOFTWARE.
- * 
- */
-
-/*
- * This is the MPack 1.1 amalgamation package.
- *
- * http://github.com/ludocode/mpack
- */
-
-#define MPACK_INTERNAL 1
-#define MPACK_EMIT_INLINE_DEFS 0
-
-#include "wpi/mpack.h"
-
-
-/* mpack/mpack-platform.c.c */
-
-
-// We define MPACK_EMIT_INLINE_DEFS and include mpack.h to emit
-// standalone definitions of all (non-static) inline functions in MPack.
-
-#define MPACK_INTERNAL 1
-#define MPACK_EMIT_INLINE_DEFS 0
-
-/* #include "mpack-platform.h" */
-/* #include "mpack.h" */
-
-MPACK_SILENCE_WARNINGS_BEGIN
-namespace mpack {
-
-#if MPACK_DEBUG
-
-#if MPACK_STDIO
-void mpack_assert_fail_format(const char* format, ...) {
-    char buffer[512];
-    va_list args;
-    va_start(args, format);
-    vsnprintf(buffer, sizeof(buffer), format, args);
-    va_end(args);
-    buffer[sizeof(buffer) - 1] = 0;
-    mpack_assert_fail_wrapper(buffer);
-}
-
-void mpack_break_hit_format(const char* format, ...) {
-    char buffer[512];
-    va_list args;
-    va_start(args, format);
-    vsnprintf(buffer, sizeof(buffer), format, args);
-    va_end(args);
-    buffer[sizeof(buffer) - 1] = 0;
-    mpack_break_hit(buffer);
-}
-#endif
-
-#if !MPACK_CUSTOM_ASSERT
-void mpack_assert_fail(const char* message) {
-    MPACK_UNUSED(message);
-
-    #if MPACK_STDIO
-    fprintf(stderr, "%s\n", message);
-    #endif
-}
-#endif
-
-// We split the assert failure from the wrapper so that a
-// custom assert function can return.
-void mpack_assert_fail_wrapper(const char* message) {
-
-    #ifdef MPACK_GCOV
-    // gcov marks even __builtin_unreachable() as an uncovered line. this
-    // silences it.
-    (mpack_assert_fail(message), __builtin_unreachable());
-
-    #else
-    mpack_assert_fail(message);
-
-    // mpack_assert_fail() is not supposed to return. in case it does, we
-    // abort.
-
-    #if !MPACK_NO_BUILTINS
-    #if defined(__GNUC__) || defined(__clang__)
-    __builtin_trap();
-    #elif defined(WIN32)
-    __debugbreak();
-    #endif
-    #endif
-
-    #if (defined(__GNUC__) || defined(__clang__)) && !MPACK_NO_BUILTINS
-    __builtin_abort();
-    #elif MPACK_STDLIB
-    abort();
-    #endif
-
-    MPACK_UNREACHABLE;
-    #endif
-}
-
-#if !MPACK_CUSTOM_BREAK
-
-// If we have a custom assert handler, break wraps it by default.
-// This allows users of MPack to only implement mpack_assert_fail() without
-// having to worry about the difference between assert and break.
-//
-// MPACK_CUSTOM_BREAK is available to define a separate break handler
-// (which is needed by the unit test suite), but this is not offered in
-// mpack-config.h for simplicity.
-
-#if MPACK_CUSTOM_ASSERT
-void mpack_break_hit(const char* message) {
-    mpack_assert_fail_wrapper(message);
-}
-#else
-void mpack_break_hit(const char* message) {
-    MPACK_UNUSED(message);
-
-    #if MPACK_STDIO
-    fprintf(stderr, "%s\n", message);
-    #endif
-
-    #if defined(__GNUC__) || defined(__clang__) && !MPACK_NO_BUILTINS
-    __builtin_trap();
-    #elif defined(WIN32) && !MPACK_NO_BUILTINS
-    __debugbreak();
-    #elif MPACK_STDLIB
-    abort();
-    #endif
-}
-#endif
-
-#endif
-
-#endif
-
-
-
-// The below are adapted from the C wikibook:
-//     https://en.wikibooks.org/wiki/C_Programming/Strings
-
-#ifndef mpack_memcmp
-int mpack_memcmp(const void* s1, const void* s2, size_t n) {
-     const unsigned char *us1 = (const unsigned char *) s1;
-     const unsigned char *us2 = (const unsigned char *) s2;
-     while (n-- != 0) {
-         if (*us1 != *us2)
-             return (*us1 < *us2) ? -1 : +1;
-         us1++;
-         us2++;
-     }
-     return 0;
-}
-#endif
-
-#ifndef mpack_memcpy
-void* mpack_memcpy(void* MPACK_RESTRICT s1, const void* MPACK_RESTRICT s2, size_t n) {
-    char* MPACK_RESTRICT dst = (char *)s1;
-    const char* MPACK_RESTRICT src = (const char *)s2;
-    while (n-- != 0)
-        *dst++ = *src++;
-    return s1;
-}
-#endif
-
-#ifndef mpack_memmove
-void* mpack_memmove(void* s1, const void* s2, size_t n) {
-    char *p1 = (char *)s1;
-    const char *p2 = (const char *)s2;
-    if (p2 < p1 && p1 < p2 + n) {
-        p2 += n;
-        p1 += n;
-        while (n-- != 0)
-            *--p1 = *--p2;
-    } else
-        while (n-- != 0)
-            *p1++ = *p2++;
-    return s1;
-}
-#endif
-
-#ifndef mpack_memset
-void* mpack_memset(void* s, int c, size_t n) {
-    unsigned char *us = (unsigned char *)s;
-    unsigned char uc = (unsigned char)c;
-    while (n-- != 0)
-        *us++ = uc;
-    return s;
-}
-#endif
-
-#ifndef mpack_strlen
-size_t mpack_strlen(const char* s) {
-    const char* p = s;
-    while (*p != '\0')
-        p++;
-    return (size_t)(p - s);
-}
-#endif
-
-
-
-#if defined(MPACK_MALLOC) && !defined(MPACK_REALLOC)
-void* mpack_realloc(void* old_ptr, size_t used_size, size_t new_size) {
-    if (new_size == 0) {
-        if (old_ptr)
-            MPACK_FREE(old_ptr);
-        return NULL;
-    }
-
-    void* new_ptr = MPACK_MALLOC(new_size);
-    if (new_ptr == NULL)
-        return NULL;
-
-    mpack_memcpy(new_ptr, old_ptr, used_size);
-    MPACK_FREE(old_ptr);
-    return new_ptr;
-}
-#endif
-
-MPACK_SILENCE_WARNINGS_END
-
-/* mpack/mpack-common.c.c */
-
-#define MPACK_INTERNAL 1
-
-/* #include "mpack-common.h" */
-
-MPACK_SILENCE_WARNINGS_BEGIN
-
-const char* mpack_error_to_string(mpack_error_t error) {
-    #if MPACK_STRINGS
-    switch (error) {
-        #define MPACK_ERROR_STRING_CASE(e) case e: return #e
-        MPACK_ERROR_STRING_CASE(mpack_ok);
-        MPACK_ERROR_STRING_CASE(mpack_error_io);
-        MPACK_ERROR_STRING_CASE(mpack_error_invalid);
-        MPACK_ERROR_STRING_CASE(mpack_error_unsupported);
-        MPACK_ERROR_STRING_CASE(mpack_error_type);
-        MPACK_ERROR_STRING_CASE(mpack_error_too_big);
-        MPACK_ERROR_STRING_CASE(mpack_error_memory);
-        MPACK_ERROR_STRING_CASE(mpack_error_bug);
-        MPACK_ERROR_STRING_CASE(mpack_error_data);
-        MPACK_ERROR_STRING_CASE(mpack_error_eof);
-        #undef MPACK_ERROR_STRING_CASE
-    }
-    mpack_assert(0, "unrecognized error %i", (int)error);
-    return "(unknown mpack_error_t)";
-    #else
-    MPACK_UNUSED(error);
-    return "";
-    #endif
-}
-
-const char* mpack_type_to_string(mpack_type_t type) {
-    #if MPACK_STRINGS
-    switch (type) {
-        #define MPACK_TYPE_STRING_CASE(e) case e: return #e
-        MPACK_TYPE_STRING_CASE(mpack_type_missing);
-        MPACK_TYPE_STRING_CASE(mpack_type_nil);
-        MPACK_TYPE_STRING_CASE(mpack_type_bool);
-        MPACK_TYPE_STRING_CASE(mpack_type_float);
-        MPACK_TYPE_STRING_CASE(mpack_type_double);
-        MPACK_TYPE_STRING_CASE(mpack_type_int);
-        MPACK_TYPE_STRING_CASE(mpack_type_uint);
-        MPACK_TYPE_STRING_CASE(mpack_type_str);
-        MPACK_TYPE_STRING_CASE(mpack_type_bin);
-        MPACK_TYPE_STRING_CASE(mpack_type_array);
-        MPACK_TYPE_STRING_CASE(mpack_type_map);
-        #if MPACK_EXTENSIONS
-        MPACK_TYPE_STRING_CASE(mpack_type_ext);
-        #endif
-        #undef MPACK_TYPE_STRING_CASE
-    }
-    mpack_assert(0, "unrecognized type %i", (int)type);
-    return "(unknown mpack_type_t)";
-    #else
-    MPACK_UNUSED(type);
-    return "";
-    #endif
-}
-
-int mpack_tag_cmp(mpack_tag_t left, mpack_tag_t right) {
-
-    // positive numbers may be stored as int; convert to uint
-    if (left.type == mpack_type_int && left.v.i >= 0) {
-        left.type = mpack_type_uint;
-        left.v.u = (uint64_t)left.v.i;
-    }
-    if (right.type == mpack_type_int && right.v.i >= 0) {
-        right.type = mpack_type_uint;
-        right.v.u = (uint64_t)right.v.i;
-    }
-
-    if (left.type != right.type)
-        return ((int)left.type < (int)right.type) ? -1 : 1;
-
-    switch (left.type) {
-        case mpack_type_missing: // fallthrough
-        case mpack_type_nil:
-            return 0;
-
-        case mpack_type_bool:
-            return (int)left.v.b - (int)right.v.b;
-
-        case mpack_type_int:
-            if (left.v.i == right.v.i)
-                return 0;
-            return (left.v.i < right.v.i) ? -1 : 1;
-
-        case mpack_type_uint:
-            if (left.v.u == right.v.u)
-                return 0;
-            return (left.v.u < right.v.u) ? -1 : 1;
-
-        case mpack_type_array:
-        case mpack_type_map:
-            if (left.v.n == right.v.n)
-                return 0;
-            return (left.v.n < right.v.n) ? -1 : 1;
-
-        case mpack_type_str:
-        case mpack_type_bin:
-            if (left.v.l == right.v.l)
-                return 0;
-            return (left.v.l < right.v.l) ? -1 : 1;
-
-        #if MPACK_EXTENSIONS
-        case mpack_type_ext:
-            if (left.exttype == right.exttype) {
-                if (left.v.l == right.v.l)
-                    return 0;
-                return (left.v.l < right.v.l) ? -1 : 1;
-            }
-            return (int)left.exttype - (int)right.exttype;
-        #endif
-
-        // floats should not normally be compared for equality. we compare
-        // with memcmp() to silence compiler warnings, but this will return
-        // equal if both are NaNs with the same representation (though we may
-        // want this, for instance if you are for some bizarre reason using
-        // floats as map keys.) i'm not sure what the right thing to
-        // do is here. check for NaN first? always return false if the type
-        // is float? use operator== and pragmas to silence compiler warning?
-        // please send me your suggestions.
-        // note also that we don't convert floats to doubles, so when this is
-        // used for ordering purposes, all floats are ordered before all
-        // doubles.
-        case mpack_type_float:
-            return mpack_memcmp(&left.v.f, &right.v.f, sizeof(left.v.f));
-        case mpack_type_double:
-            return mpack_memcmp(&left.v.d, &right.v.d, sizeof(left.v.d));
-    }
-
-    mpack_assert(0, "unrecognized type %i", (int)left.type);
-    return false;
-}
-
-#if MPACK_DEBUG && MPACK_STDIO
-static char mpack_hex_char(uint8_t hex_value) {
-    // Older compilers (e.g. GCC 4.4.7) promote the result of this ternary to
-    // int and warn under -Wconversion, so we have to cast it back to char.
-    return (char)((hex_value < 10) ? (char)('0' + hex_value) : (char)('a' + (hex_value - 10)));
-}
-
-static void mpack_tag_debug_complete_bin_ext(mpack_tag_t tag, size_t string_length, char* buffer, size_t buffer_size,
-        const char* prefix, size_t prefix_size)
-{
-    // If at any point in this function we run out of space in the buffer, we
-    // bail out. The outer tag print wrapper will make sure we have a
-    // null-terminator.
-
-    if (string_length == 0 || string_length >= buffer_size)
-        return;
-    buffer += string_length;
-    buffer_size -= string_length;
-
-    size_t total = mpack_tag_bytes(&tag);
-    if (total == 0) {
-        strncpy(buffer, ">", buffer_size);
-        return;
-    }
-
-    strncpy(buffer, ": ", buffer_size);
-    if (buffer_size < 2)
-        return;
-    buffer += 2;
-    buffer_size -= 2;
-
-    size_t hex_bytes = 0;
-    size_t i;
-    for (i = 0; i < MPACK_PRINT_BYTE_COUNT && i < prefix_size && buffer_size > 2; ++i) {
-        uint8_t byte = (uint8_t)prefix[i];
-        buffer[0] = mpack_hex_char((uint8_t)(byte >> 4));
-        buffer[1] = mpack_hex_char((uint8_t)(byte & 0xfu));
-        buffer += 2;
-        buffer_size -= 2;
-        ++hex_bytes;
-    }
-
-    if (buffer_size != 0)
-        mpack_snprintf(buffer, buffer_size, "%s>", (total > hex_bytes) ? "..." : "");
-}
-
-static void mpack_tag_debug_pseudo_json_bin(mpack_tag_t tag, char* buffer, size_t buffer_size,
-        const char* prefix, size_t prefix_size)
-{
-    mpack_assert(mpack_tag_type(&tag) == mpack_type_bin);
-    size_t length = (size_t)mpack_snprintf(buffer, buffer_size, "<binary data of length %u", tag.v.l);
-    mpack_tag_debug_complete_bin_ext(tag, length, buffer, buffer_size, prefix, prefix_size);
-}
-
-#if MPACK_EXTENSIONS
-static void mpack_tag_debug_pseudo_json_ext(mpack_tag_t tag, char* buffer, size_t buffer_size,
-        const char* prefix, size_t prefix_size)
-{
-    mpack_assert(mpack_tag_type(&tag) == mpack_type_ext);
-    size_t length = (size_t)mpack_snprintf(buffer, buffer_size, "<ext data of type %i and length %u",
-            mpack_tag_ext_exttype(&tag), mpack_tag_ext_length(&tag));
-    mpack_tag_debug_complete_bin_ext(tag, length, buffer, buffer_size, prefix, prefix_size);
-}
-#endif
-
-static void mpack_tag_debug_pseudo_json_impl(mpack_tag_t tag, char* buffer, size_t buffer_size,
-        const char* prefix, size_t prefix_size)
-{
-    switch (tag.type) {
-        case mpack_type_missing:
-            mpack_snprintf(buffer, buffer_size, "<missing!>");
-            return;
-        case mpack_type_nil:
-            mpack_snprintf(buffer, buffer_size, "null");
-            return;
-        case mpack_type_bool:
-            mpack_snprintf(buffer, buffer_size, tag.v.b ? "true" : "false");
-            return;
-        case mpack_type_int:
-            mpack_snprintf(buffer, buffer_size, "%" PRIi64, tag.v.i);
-            return;
-        case mpack_type_uint:
-            mpack_snprintf(buffer, buffer_size, "%" PRIu64, tag.v.u);
-            return;
-        case mpack_type_float:
-            #if MPACK_FLOAT
-            mpack_snprintf(buffer, buffer_size, "%f", tag.v.f);
-            #else
-            mpack_snprintf(buffer, buffer_size, "<float>");
-            #endif
-            return;
-        case mpack_type_double:
-            #if MPACK_DOUBLE
-            mpack_snprintf(buffer, buffer_size, "%f", tag.v.d);
-            #else
-            mpack_snprintf(buffer, buffer_size, "<double>");
-            #endif
-            return;
-
-        case mpack_type_str:
-            mpack_snprintf(buffer, buffer_size, "<string of %u bytes>", tag.v.l);
-            return;
-        case mpack_type_bin:
-            mpack_tag_debug_pseudo_json_bin(tag, buffer, buffer_size, prefix, prefix_size);
-            return;
-        #if MPACK_EXTENSIONS
-        case mpack_type_ext:
-            mpack_tag_debug_pseudo_json_ext(tag, buffer, buffer_size, prefix, prefix_size);
-            return;
-        #endif
-
-        case mpack_type_array:
-            mpack_snprintf(buffer, buffer_size, "<array of %u elements>", tag.v.n);
-            return;
-        case mpack_type_map:
-            mpack_snprintf(buffer, buffer_size, "<map of %u key-value pairs>", tag.v.n);
-            return;
-    }
-
-    mpack_snprintf(buffer, buffer_size, "<unknown!>");
-}
-
-void mpack_tag_debug_pseudo_json(mpack_tag_t tag, char* buffer, size_t buffer_size,
-        const char* prefix, size_t prefix_size)
-{
-    mpack_assert(buffer_size > 0, "buffer size cannot be zero!");
-    buffer[0] = 0;
-
-    mpack_tag_debug_pseudo_json_impl(tag, buffer, buffer_size, prefix, prefix_size);
-
-    // We always null-terminate the buffer manually just in case the snprintf()
-    // function doesn't null-terminate when the string doesn't fit.
-    buffer[buffer_size - 1] = 0;
-}
-
-static void mpack_tag_debug_describe_impl(mpack_tag_t tag, char* buffer, size_t buffer_size) {
-    switch (tag.type) {
-        case mpack_type_missing:
-            mpack_snprintf(buffer, buffer_size, "missing");
-            return;
-        case mpack_type_nil:
-            mpack_snprintf(buffer, buffer_size, "nil");
-            return;
-        case mpack_type_bool:
-            mpack_snprintf(buffer, buffer_size, tag.v.b ? "true" : "false");
-            return;
-        case mpack_type_int:
-            mpack_snprintf(buffer, buffer_size, "int %" PRIi64, tag.v.i);
-            return;
-        case mpack_type_uint:
-            mpack_snprintf(buffer, buffer_size, "uint %" PRIu64, tag.v.u);
-            return;
-        case mpack_type_float:
-            #if MPACK_FLOAT
-            mpack_snprintf(buffer, buffer_size, "float %f", tag.v.f);
-            #else
-            mpack_snprintf(buffer, buffer_size, "float");
-            #endif
-            return;
-        case mpack_type_double:
-            #if MPACK_DOUBLE
-            mpack_snprintf(buffer, buffer_size, "double %f", tag.v.d);
-            #else
-            mpack_snprintf(buffer, buffer_size, "double");
-            #endif
-            return;
-        case mpack_type_str:
-            mpack_snprintf(buffer, buffer_size, "str of %u bytes", tag.v.l);
-            return;
-        case mpack_type_bin:
-            mpack_snprintf(buffer, buffer_size, "bin of %u bytes", tag.v.l);
-            return;
-        #if MPACK_EXTENSIONS
-        case mpack_type_ext:
-            mpack_snprintf(buffer, buffer_size, "ext of type %i, %u bytes",
-                    mpack_tag_ext_exttype(&tag), mpack_tag_ext_length(&tag));
-            return;
-        #endif
-        case mpack_type_array:
-            mpack_snprintf(buffer, buffer_size, "array of %u elements", tag.v.n);
-            return;
-        case mpack_type_map:
-            mpack_snprintf(buffer, buffer_size, "map of %u key-value pairs", tag.v.n);
-            return;
-    }
-
-    mpack_snprintf(buffer, buffer_size, "unknown!");
-}
-
-void mpack_tag_debug_describe(mpack_tag_t tag, char* buffer, size_t buffer_size) {
-    mpack_assert(buffer_size > 0, "buffer size cannot be zero!");
-    buffer[0] = 0;
-
-    mpack_tag_debug_describe_impl(tag, buffer, buffer_size);
-
-    // We always null-terminate the buffer manually just in case the snprintf()
-    // function doesn't null-terminate when the string doesn't fit.
-    buffer[buffer_size - 1] = 0;
-}
-#endif
-
-
-
-#if MPACK_READ_TRACKING || MPACK_WRITE_TRACKING
-
-#ifndef MPACK_TRACKING_INITIAL_CAPACITY
-// seems like a reasonable number. we grow by doubling, and it only
-// needs to be as long as the maximum depth of the message.
-#define MPACK_TRACKING_INITIAL_CAPACITY 8
-#endif
-
-mpack_error_t mpack_track_init(mpack_track_t* track) {
-    track->count = 0;
-    track->capacity = MPACK_TRACKING_INITIAL_CAPACITY;
-    track->elements = (mpack_track_element_t*)MPACK_MALLOC(sizeof(mpack_track_element_t) * track->capacity);
-    if (track->elements == NULL)
-        return mpack_error_memory;
-    return mpack_ok;
-}
-
-mpack_error_t mpack_track_grow(mpack_track_t* track) {
-    mpack_assert(track->elements, "null track elements!");
-    mpack_assert(track->count == track->capacity, "incorrect growing?");
-
-    size_t new_capacity = track->capacity * 2;
-
-    mpack_track_element_t* new_elements = (mpack_track_element_t*)mpack_realloc(track->elements,
-            sizeof(mpack_track_element_t) * track->count, sizeof(mpack_track_element_t) * new_capacity);
-    if (new_elements == NULL)
-        return mpack_error_memory;
-
-    track->elements = new_elements;
-    track->capacity = new_capacity;
-    return mpack_ok;
-}
-
-mpack_error_t mpack_track_push(mpack_track_t* track, mpack_type_t type, uint32_t count) {
-    mpack_assert(track->elements, "null track elements!");
-    mpack_log("track pushing %s count %i\n", mpack_type_to_string(type), (int)count);
-
-    // grow if needed
-    if (track->count == track->capacity) {
-        mpack_error_t error = mpack_track_grow(track);
-        if (error != mpack_ok)
-            return error;
-    }
-
-    // insert new track
-    track->elements[track->count].type = type;
-    track->elements[track->count].left = count;
-    track->elements[track->count].builder = false;
-    track->elements[track->count].key_needs_value = false;
-    ++track->count;
-    return mpack_ok;
-}
-
-// TODO dedupe this
-mpack_error_t mpack_track_push_builder(mpack_track_t* track, mpack_type_t type) {
-    mpack_assert(track->elements, "null track elements!");
-    mpack_log("track pushing %s builder\n", mpack_type_to_string(type));
-
-    // grow if needed
-    if (track->count == track->capacity) {
-        mpack_error_t error = mpack_track_grow(track);
-        if (error != mpack_ok)
-            return error;
-    }
-
-    // insert new track
-    track->elements[track->count].type = type;
-    track->elements[track->count].left = 0;
-    track->elements[track->count].builder = true;
-    track->elements[track->count].key_needs_value = false;
-    ++track->count;
-    return mpack_ok;
-}
-
-static mpack_error_t mpack_track_pop_impl(mpack_track_t* track, mpack_type_t type, bool builder) {
-    mpack_assert(track->elements, "null track elements!");
-    mpack_log("track popping %s\n", mpack_type_to_string(type));
-
-    if (track->count == 0) {
-        mpack_break("attempting to close a %s but nothing was opened!", mpack_type_to_string(type));
-        return mpack_error_bug;
-    }
-
-    mpack_track_element_t* element = &track->elements[track->count - 1];
-
-    if (element->type != type) {
-        mpack_break("attempting to close a %s but the open element is a %s!",
-                mpack_type_to_string(type), mpack_type_to_string(element->type));
-        return mpack_error_bug;
-    }
-
-    if (element->key_needs_value) {
-        mpack_assert(type == mpack_type_map, "key_needs_value can only be true for maps!");
-        mpack_break("attempting to close a %s but an odd number of elements were written",
-                mpack_type_to_string(type));
-        return mpack_error_bug;
-    }
-
-    if (element->left != 0) {
-        mpack_break("attempting to close a %s but there are %i %s left",
-                mpack_type_to_string(type), element->left,
-                (type == mpack_type_map || type == mpack_type_array) ? "elements" : "bytes");
-        return mpack_error_bug;
-    }
-
-    if (element->builder != builder) {
-        mpack_break("attempting to pop a %sbuilder but the open element is %sa builder",
-                builder ? "" : "non-",
-                element->builder ? "" : "not ");
-        return mpack_error_bug;
-    }
-
-    --track->count;
-    return mpack_ok;
-}
-
-mpack_error_t mpack_track_pop(mpack_track_t* track, mpack_type_t type) {
-    return mpack_track_pop_impl(track, type, false);
-}
-
-mpack_error_t mpack_track_pop_builder(mpack_track_t* track, mpack_type_t type) {
-    return mpack_track_pop_impl(track, type, true);
-}
-
-mpack_error_t mpack_track_peek_element(mpack_track_t* track, bool read) {
-    MPACK_UNUSED(read);
-    mpack_assert(track->elements, "null track elements!");
-
-    // if there are no open elements, that's fine, we can read/write elements at will
-    if (track->count == 0)
-        return mpack_ok;
-
-    mpack_track_element_t* element = &track->elements[track->count - 1];
-
-    if (element->type != mpack_type_map && element->type != mpack_type_array) {
-        mpack_break("elements cannot be %s within an %s", read ? "read" : "written",
-                mpack_type_to_string(element->type));
-        return mpack_error_bug;
-    }
-
-    if (!element->builder && element->left == 0 && !element->key_needs_value) {
-        mpack_break("too many elements %s for %s", read ? "read" : "written",
-                mpack_type_to_string(element->type));
-        return mpack_error_bug;
-    }
-
-    return mpack_ok;
-}
-
-mpack_error_t mpack_track_element(mpack_track_t* track, bool read) {
-    mpack_error_t error = mpack_track_peek_element(track, read);
-    if (track->count == 0 || error != mpack_ok)
-        return error;
-
-    mpack_track_element_t* element = &track->elements[track->count - 1];
-
-    if (element->type == mpack_type_map) {
-        if (!element->key_needs_value) {
-            element->key_needs_value = true;
-            return mpack_ok; // don't decrement
-        }
-        element->key_needs_value = false;
-    }
-
-    if (!element->builder)
-        --element->left;
-    return mpack_ok;
-}
-
-mpack_error_t mpack_track_bytes(mpack_track_t* track, bool read, size_t count) {
-    MPACK_UNUSED(read);
-    mpack_assert(track->elements, "null track elements!");
-
-    if (count > MPACK_UINT32_MAX) {
-        mpack_break("%s more bytes than could possibly fit in a str/bin/ext!",
-                read ? "reading" : "writing");
-        return mpack_error_bug;
-    }
-
-    if (track->count == 0) {
-        mpack_break("bytes cannot be %s with no open bin, str or ext", read ? "read" : "written");
-        return mpack_error_bug;
-    }
-
-    mpack_track_element_t* element = &track->elements[track->count - 1];
-
-    if (element->type == mpack_type_map || element->type == mpack_type_array) {
-        mpack_break("bytes cannot be %s within an %s", read ? "read" : "written",
-                mpack_type_to_string(element->type));
-        return mpack_error_bug;
-    }
-
-    if (element->left < count) {
-        mpack_break("too many bytes %s for %s", read ? "read" : "written",
-                mpack_type_to_string(element->type));
-        return mpack_error_bug;
-    }
-
-    element->left -= (uint32_t)count;
-    return mpack_ok;
-}
-
-mpack_error_t mpack_track_str_bytes_all(mpack_track_t* track, bool read, size_t count) {
-    mpack_error_t error = mpack_track_bytes(track, read, count);
-    if (error != mpack_ok)
-        return error;
-
-    mpack_track_element_t* element = &track->elements[track->count - 1];
-
-    if (element->type != mpack_type_str) {
-        mpack_break("the open type must be a string, not a %s", mpack_type_to_string(element->type));
-        return mpack_error_bug;
-    }
-
-    if (element->left != 0) {
-        mpack_break("not all bytes were read; the wrong byte count was requested for a string read.");
-        return mpack_error_bug;
-    }
-
-    return mpack_ok;
-}
-
-mpack_error_t mpack_track_check_empty(mpack_track_t* track) {
-    if (track->count != 0) {
-        mpack_break("unclosed %s", mpack_type_to_string(track->elements[0].type));
-        return mpack_error_bug;
-    }
-    return mpack_ok;
-}
-
-mpack_error_t mpack_track_destroy(mpack_track_t* track, bool cancel) {
-    mpack_error_t error = cancel ? mpack_ok : mpack_track_check_empty(track);
-    if (track->elements) {
-        MPACK_FREE(track->elements);
-        track->elements = NULL;
-    }
-    return error;
-}
-#endif
-
-
-
-static bool mpack_utf8_check_impl(const uint8_t* str, size_t count, bool allow_null) {
-    while (count > 0) {
-        uint8_t lead = str[0];
-
-        // NUL
-        if (!allow_null && lead == '\0') // we don't allow NUL bytes in MPack C-strings
-            return false;
-
-        // ASCII
-        if (lead <= 0x7F) {
-            ++str;
-            --count;
-
-        // 2-byte sequence
-        } else if ((lead & 0xE0) == 0xC0) {
-            if (count < 2) // truncated sequence
-                return false;
-
-            uint8_t cont = str[1];
-            if ((cont & 0xC0) != 0x80) // not a continuation byte
-                return false;
-
-            str += 2;
-            count -= 2;
-
-            uint32_t z = ((uint32_t)(lead & ~0xE0) << 6) |
-                          (uint32_t)(cont & ~0xC0);
-
-            if (z < 0x80) // overlong sequence
-                return false;
-
-        // 3-byte sequence
-        } else if ((lead & 0xF0) == 0xE0) {
-            if (count < 3) // truncated sequence
-                return false;
-
-            uint8_t cont1 = str[1];
-            if ((cont1 & 0xC0) != 0x80) // not a continuation byte
-                return false;
-            uint8_t cont2 = str[2];
-            if ((cont2 & 0xC0) != 0x80) // not a continuation byte
-                return false;
-
-            str += 3;
-            count -= 3;
-
-            uint32_t z = ((uint32_t)(lead  & ~0xF0) << 12) |
-                         ((uint32_t)(cont1 & ~0xC0) <<  6) |
-                          (uint32_t)(cont2 & ~0xC0);
-
-            if (z < 0x800) // overlong sequence
-                return false;
-            if (z >= 0xD800 && z <= 0xDFFF) // surrogate
-                return false;
-
-        // 4-byte sequence
-        } else if ((lead & 0xF8) == 0xF0) {
-            if (count < 4) // truncated sequence
-                return false;
-
-            uint8_t cont1 = str[1];
-            if ((cont1 & 0xC0) != 0x80) // not a continuation byte
-                return false;
-            uint8_t cont2 = str[2];
-            if ((cont2 & 0xC0) != 0x80) // not a continuation byte
-                return false;
-            uint8_t cont3 = str[3];
-            if ((cont3 & 0xC0) != 0x80) // not a continuation byte
-                return false;
-
-            str += 4;
-            count -= 4;
-
-            uint32_t z = ((uint32_t)(lead  & ~0xF8) << 18) |
-                         ((uint32_t)(cont1 & ~0xC0) << 12) |
-                         ((uint32_t)(cont2 & ~0xC0) <<  6) |
-                          (uint32_t)(cont3 & ~0xC0);
-
-            if (z < 0x10000) // overlong sequence
-                return false;
-            if (z > 0x10FFFF) // codepoint limit
-                return false;
-
-        } else {
-            return false; // continuation byte without a lead, or lead for a 5-byte sequence or longer
-        }
-    }
-    return true;
-}
-
-bool mpack_utf8_check(const char* str, size_t bytes) {
-    return mpack_utf8_check_impl((const uint8_t*)str, bytes, true);
-}
-
-bool mpack_utf8_check_no_null(const char* str, size_t bytes) {
-    return mpack_utf8_check_impl((const uint8_t*)str, bytes, false);
-}
-
-bool mpack_str_check_no_null(const char* str, size_t bytes) {
-    size_t i;
-    for (i = 0; i < bytes; ++i)
-        if (str[i] == '\0')
-            return false;
-    return true;
-}
-
-#if MPACK_DEBUG && MPACK_STDIO
-void mpack_print_append(mpack_print_t* print, const char* data, size_t count) {
-
-    // copy whatever fits into the buffer
-    size_t copy = print->size - print->count;
-    if (copy > count)
-        copy = count;
-    mpack_memcpy(print->buffer + print->count, data, copy);
-    print->count += copy;
-    data += copy;
-    count -= copy;
-
-    // if we don't need to flush or can't flush there's nothing else to do
-    if (count == 0 || print->callback == NULL)
-        return;
-
-    // flush the buffer
-    print->callback(print->context, print->buffer, print->count);
-
-    if (count > print->size / 2) {
-        // flush the rest of the data
-        print->count = 0;
-        print->callback(print->context, data, count);
-    } else {
-        // copy the rest of the data into the buffer
-        mpack_memcpy(print->buffer, data, count);
-        print->count = count;
-    }
-
-}
-
-void mpack_print_flush(mpack_print_t* print) {
-    if (print->count > 0 && print->callback != NULL) {
-        print->callback(print->context, print->buffer, print->count);
-        print->count = 0;
-    }
-}
-
-void mpack_print_file_callback(void* context, const char* data, size_t count) {
-    FILE* file = (FILE*)context;
-    fwrite(data, 1, count, file);
-}
-#endif
-
-MPACK_SILENCE_WARNINGS_END
-
-/* mpack/mpack-writer.c.c */
-
-#define MPACK_INTERNAL 1
-
-/* #include "mpack-writer.h" */
-
-MPACK_SILENCE_WARNINGS_BEGIN
-
-#if MPACK_WRITER
-
-#if MPACK_BUILDER
-static void mpack_builder_flush(mpack_writer_t* writer);
-#endif
-
-#if MPACK_WRITE_TRACKING
-static void mpack_writer_flag_if_error(mpack_writer_t* writer, mpack_error_t error) {
-    if (error != mpack_ok)
-        mpack_writer_flag_error(writer, error);
-}
-
-void mpack_writer_track_push(mpack_writer_t* writer, mpack_type_t type, uint32_t count) {
-    if (writer->error == mpack_ok)
-        mpack_writer_flag_if_error(writer, mpack_track_push(&writer->track, type, count));
-}
-
-void mpack_writer_track_push_builder(mpack_writer_t* writer, mpack_type_t type) {
-    if (writer->error == mpack_ok)
-        mpack_writer_flag_if_error(writer, mpack_track_push_builder(&writer->track, type));
-}
-
-void mpack_writer_track_pop(mpack_writer_t* writer, mpack_type_t type) {
-    if (writer->error == mpack_ok)
-        mpack_writer_flag_if_error(writer, mpack_track_pop(&writer->track, type));
-}
-
-void mpack_writer_track_pop_builder(mpack_writer_t* writer, mpack_type_t type) {
-    if (writer->error == mpack_ok)
-        mpack_writer_flag_if_error(writer, mpack_track_pop_builder(&writer->track, type));
-}
-
-void mpack_writer_track_bytes(mpack_writer_t* writer, size_t count) {
-    if (writer->error == mpack_ok)
-        mpack_writer_flag_if_error(writer, mpack_track_bytes(&writer->track, false, count));
-}
-#endif
-
-// This should probably be renamed. It's not solely used for tracking.
-static inline void mpack_writer_track_element(mpack_writer_t* writer) {
-    (void)writer;
-
-    #if MPACK_WRITE_TRACKING
-    if (writer->error == mpack_ok)
-        mpack_writer_flag_if_error(writer, mpack_track_element(&writer->track, false));
-    #endif
-
-    #if MPACK_BUILDER
-    if (writer->builder.current_build != NULL) {
-        mpack_build_t* build = writer->builder.current_build;
-        // We only track this write if it's not nested within another non-build
-        // map or array.
-        if (build->nested_compound_elements == 0) {
-            if (build->type != mpack_type_map) {
-                ++build->count;
-                mpack_log("adding element to build %p, now %u elements\n", (void*)build, build->count);
-            } else if (build->key_needs_value) {
-                build->key_needs_value = false;
-                ++build->count;
-            } else {
-                build->key_needs_value = true;
-            }
-        }
-    }
-    #endif
-}
-
-static void mpack_writer_clear(mpack_writer_t* writer) {
-    #if MPACK_COMPATIBILITY
-    writer->version = mpack_version_current;
-    #endif
-    writer->flush = NULL;
-    writer->error_fn = NULL;
-    writer->teardown = NULL;
-    writer->context = NULL;
-
-    writer->buffer = NULL;
-    writer->position = NULL;
-    writer->end = NULL;
-    writer->error = mpack_ok;
-
-    #if MPACK_WRITE_TRACKING
-    mpack_memset(&writer->track, 0, sizeof(writer->track));
-    #endif
-
-    #if MPACK_BUILDER
-    writer->builder.current_build = NULL;
-    writer->builder.latest_build = NULL;
-    writer->builder.current_page = NULL;
-    writer->builder.pages = NULL;
-    writer->builder.stash_buffer = NULL;
-    writer->builder.stash_position = NULL;
-    writer->builder.stash_end = NULL;
-    #endif
-}
-
-void mpack_writer_init(mpack_writer_t* writer, char* buffer, size_t size) {
-    mpack_assert(buffer != NULL, "cannot initialize writer with empty buffer");
-    mpack_writer_clear(writer);
-    writer->buffer = buffer;
-    writer->position = buffer;
-    writer->end = writer->buffer + size;
-
-    #if MPACK_WRITE_TRACKING
-    mpack_writer_flag_if_error(writer, mpack_track_init(&writer->track));
-    #endif
-
-    mpack_log("===========================\n");
-    mpack_log("initializing writer with buffer size %i\n", (int)size);
-}
-
-void mpack_writer_init_error(mpack_writer_t* writer, mpack_error_t error) {
-    mpack_writer_clear(writer);
-    writer->error = error;
-
-    mpack_log("===========================\n");
-    mpack_log("initializing writer in error state %i\n", (int)error);
-}
-
-void mpack_writer_set_flush(mpack_writer_t* writer, mpack_writer_flush_t flush) {
-    MPACK_STATIC_ASSERT(MPACK_WRITER_MINIMUM_BUFFER_SIZE >= MPACK_MAXIMUM_TAG_SIZE,
-            "minimum buffer size must fit any tag!");
-    MPACK_STATIC_ASSERT(31 + MPACK_TAG_SIZE_FIXSTR >= MPACK_WRITER_MINIMUM_BUFFER_SIZE,
-            "minimum buffer size must fit the largest possible fixstr!");
-
-    if (mpack_writer_buffer_size(writer) < MPACK_WRITER_MINIMUM_BUFFER_SIZE) {
-        mpack_break("buffer size is %i, but minimum buffer size for flush is %i",
-                (int)mpack_writer_buffer_size(writer), MPACK_WRITER_MINIMUM_BUFFER_SIZE);
-        mpack_writer_flag_error(writer, mpack_error_bug);
-        return;
-    }
-
-    writer->flush = flush;
-}
-
-#ifdef MPACK_MALLOC
-typedef struct mpack_growable_writer_t {
-    char** target_data;
-    size_t* target_size;
-} mpack_growable_writer_t;
-
-static char* mpack_writer_get_reserved(mpack_writer_t* writer) {
-    // This is in a separate function in order to avoid false strict aliasing
-    // warnings. We aren't actually violating strict aliasing (the reserved
-    // space is only ever dereferenced as an mpack_growable_writer_t.)
-    return (char*)writer->reserved;
-}
-
-static void mpack_growable_writer_flush(mpack_writer_t* writer, const char* data, size_t count) {
-
-    // This is an intrusive flush function which modifies the writer's buffer
-    // in response to a flush instead of emptying it in order to add more
-    // capacity for data. This removes the need to copy data from a fixed buffer
-    // into a growable one, improving performance.
-    //
-    // There are three ways flush can be called:
-    //   - flushing the buffer during writing (used is zero, count is all data, data is buffer)
-    //   - flushing extra data during writing (used is all flushed data, count is extra data, data is not buffer)
-    //   - flushing during teardown (used and count are both all flushed data, data is buffer)
-    //
-    // In the first two cases, we grow the buffer by at least double, enough
-    // to ensure that new data will fit. We ignore the teardown flush.
-
-    if (data == writer->buffer) {
-
-        // teardown, do nothing
-        if (mpack_writer_buffer_used(writer) == count)
-            return;
-
-        // otherwise leave the data in the buffer and just grow
-        writer->position = writer->buffer + count;
-        count = 0;
-    }
-
-    size_t used = mpack_writer_buffer_used(writer);
-    size_t size = mpack_writer_buffer_size(writer);
-
-    mpack_log("flush size %i used %i data %p buffer %p\n",
-            (int)count, (int)used, data, writer->buffer);
-
-    mpack_assert(data == writer->buffer || used + count > size,
-            "extra flush for %i but there is %i space left in the buffer! (%i/%i)",
-            (int)count, (int)mpack_writer_buffer_left(writer), (int)used, (int)size);
-
-    // grow to fit the data
-    // TODO: this really needs to correctly test for overflow
-    size_t new_size = size * 2;
-    while (new_size < used + count)
-        new_size *= 2;
-
-    mpack_log("flush growing buffer size from %i to %i\n", (int)size, (int)new_size);
-
-    // grow the buffer
-    char* new_buffer = (char*)mpack_realloc(writer->buffer, used, new_size);
-    if (new_buffer == NULL) {
-        mpack_writer_flag_error(writer, mpack_error_memory);
-        return;
-    }
-    writer->position = new_buffer + used;
-    writer->buffer = new_buffer;
-    writer->end = writer->buffer + new_size;
-
-    // append the extra data
-    if (count > 0) {
-        mpack_memcpy(writer->position, data, count);
-        writer->position += count;
-    }
-
-    mpack_log("new buffer %p, used %i\n", new_buffer, (int)mpack_writer_buffer_used(writer));
-}
-
-static void mpack_growable_writer_teardown(mpack_writer_t* writer) {
-    mpack_growable_writer_t* growable_writer = (mpack_growable_writer_t*)mpack_writer_get_reserved(writer);
-
-    if (mpack_writer_error(writer) == mpack_ok) {
-
-        // shrink the buffer to an appropriate size if the data is
-        // much smaller than the buffer
-        if (mpack_writer_buffer_used(writer) < mpack_writer_buffer_size(writer) / 2) {
-            size_t used = mpack_writer_buffer_used(writer);
-
-            // We always return a non-null pointer that must be freed, even if
-            // nothing was written. malloc() and realloc() do not necessarily
-            // do this so we enforce it ourselves.
-            size_t size = (used != 0) ? used : 1;
-
-            char* buffer = (char*)mpack_realloc(writer->buffer, used, size);
-            if (!buffer) {
-                MPACK_FREE(writer->buffer);
-                mpack_writer_flag_error(writer, mpack_error_memory);
-                return;
-            }
-            writer->buffer = buffer;
-            writer->end = (writer->position = writer->buffer + used);
-        }
-
-        *growable_writer->target_data = writer->buffer;
-        *growable_writer->target_size = mpack_writer_buffer_used(writer);
-        writer->buffer = NULL;
-
-    } else if (writer->buffer) {
-        MPACK_FREE(writer->buffer);
-        writer->buffer = NULL;
-    }
-
-    writer->context = NULL;
-}
-
-void mpack_writer_init_growable(mpack_writer_t* writer, char** target_data, size_t* target_size) {
-    mpack_assert(target_data != NULL, "cannot initialize writer without a destination for the data");
-    mpack_assert(target_size != NULL, "cannot initialize writer without a destination for the size");
-
-    *target_data = NULL;
-    *target_size = 0;
-
-    MPACK_STATIC_ASSERT(sizeof(mpack_growable_writer_t) <= sizeof(writer->reserved),
-            "not enough reserved space for growable writer!");
-    mpack_growable_writer_t* growable_writer = (mpack_growable_writer_t*)mpack_writer_get_reserved(writer);
-
-    growable_writer->target_data = target_data;
-    growable_writer->target_size = target_size;
-
-    size_t capacity = MPACK_BUFFER_SIZE;
-    char* buffer = (char*)MPACK_MALLOC(capacity);
-    if (buffer == NULL) {
-        mpack_writer_init_error(writer, mpack_error_memory);
-        return;
-    }
-
-    mpack_writer_init(writer, buffer, capacity);
-    mpack_writer_set_flush(writer, mpack_growable_writer_flush);
-    mpack_writer_set_teardown(writer, mpack_growable_writer_teardown);
-}
-#endif
-
-#if MPACK_STDIO
-static void mpack_file_writer_flush(mpack_writer_t* writer, const char* buffer, size_t count) {
-    FILE* file = (FILE*)writer->context;
-    size_t written = fwrite((const void*)buffer, 1, count, file);
-    if (written != count)
-        mpack_writer_flag_error(writer, mpack_error_io);
-}
-
-static void mpack_file_writer_teardown(mpack_writer_t* writer) {
-    MPACK_FREE(writer->buffer);
-    writer->buffer = NULL;
-    writer->context = NULL;
-}
-
-static void mpack_file_writer_teardown_close(mpack_writer_t* writer) {
-    FILE* file = (FILE*)writer->context;
-
-    if (file) {
-        int ret = fclose(file);
-        if (ret != 0)
-            mpack_writer_flag_error(writer, mpack_error_io);
-    }
-
-    mpack_file_writer_teardown(writer);
-}
-
-void mpack_writer_init_stdfile(mpack_writer_t* writer, FILE* file, bool close_when_done) {
-    mpack_assert(file != NULL, "file is NULL");
-
-    size_t capacity = MPACK_BUFFER_SIZE;
-    char* buffer = (char*)MPACK_MALLOC(capacity);
-    if (buffer == NULL) {
-        mpack_writer_init_error(writer, mpack_error_memory);
-        if (close_when_done) {
-            fclose(file);
-        }
-        return;
-    }
-
-    mpack_writer_init(writer, buffer, capacity);
-    mpack_writer_set_context(writer, file);
-    mpack_writer_set_flush(writer, mpack_file_writer_flush);
-    mpack_writer_set_teardown(writer, close_when_done ?
-            mpack_file_writer_teardown_close :
-            mpack_file_writer_teardown);
-}
-
-void mpack_writer_init_filename(mpack_writer_t* writer, const char* filename) {
-    mpack_assert(filename != NULL, "filename is NULL");
-
-    FILE* file = fopen(filename, "wb");
-    if (file == NULL) {
-        mpack_writer_init_error(writer, mpack_error_io);
-        return;
-    }
-
-    mpack_writer_init_stdfile(writer, file, true);
-}
-#endif
-
-void mpack_writer_flag_error(mpack_writer_t* writer, mpack_error_t error) {
-    mpack_log("writer %p setting error %i: %s\n", (void*)writer, (int)error, mpack_error_to_string(error));
-
-    if (writer->error == mpack_ok) {
-        writer->error = error;
-        if (writer->error_fn)
-            writer->error_fn(writer, writer->error);
-    }
-}
-
-MPACK_STATIC_INLINE void mpack_writer_flush_unchecked(mpack_writer_t* writer) {
-    // This is a bit ugly; we reset used before calling flush so that
-    // a flush function can distinguish between flushing the buffer
-    // versus flushing external data. see mpack_growable_writer_flush()
-    size_t used = mpack_writer_buffer_used(writer);
-    writer->position = writer->buffer;
-    writer->flush(writer, writer->buffer, used);
-}
-
-void mpack_writer_flush_message(mpack_writer_t* writer) {
-    if (writer->error != mpack_ok)
-        return;
-
-    #if MPACK_WRITE_TRACKING
-    // You cannot flush while there are elements open.
-    mpack_writer_flag_if_error(writer, mpack_track_check_empty(&writer->track));
-    if (writer->error != mpack_ok)
-        return;
-    #endif
-
-    #if MPACK_BUILDER
-    if (writer->builder.current_build != NULL) {
-        mpack_break("cannot call mpack_writer_flush_message() while there are elements open!");
-        mpack_writer_flag_error(writer, mpack_error_bug);
-        return;
-    }
-    #endif
-
-    if (writer->flush == NULL) {
-        mpack_break("cannot call mpack_writer_flush_message() without a flush function!");
-        mpack_writer_flag_error(writer, mpack_error_bug);
-        return;
-    }
-
-    if (mpack_writer_buffer_used(writer) > 0)
-        mpack_writer_flush_unchecked(writer);
-}
-
-// Ensures there are at least count bytes free in the buffer. This
-// will flag an error if the flush function fails to make enough
-// room in the buffer.
-MPACK_NOINLINE static bool mpack_writer_ensure(mpack_writer_t* writer, size_t count) {
-    mpack_assert(count != 0, "cannot ensure zero bytes!");
-    mpack_assert(count <= MPACK_WRITER_MINIMUM_BUFFER_SIZE,
-            "cannot ensure %i bytes, this is more than the minimum buffer size %i!",
-            (int)count, (int)MPACK_WRITER_MINIMUM_BUFFER_SIZE);
-    mpack_assert(count > mpack_writer_buffer_left(writer),
-            "request to ensure %i bytes but there are already %i left in the buffer!",
-            (int)count, (int)mpack_writer_buffer_left(writer));
-
-    mpack_log("ensuring %i bytes, %i left\n", (int)count, (int)mpack_writer_buffer_left(writer));
-
-    if (mpack_writer_error(writer) != mpack_ok)
-        return false;
-
-    #if MPACK_BUILDER
-    // if we have a build in progress, we just ask the builder for a page.
-    // either it will have space for a tag, or it will flag a memory error.
-    if (writer->builder.current_build != NULL) {
-        mpack_builder_flush(writer);
-        return mpack_writer_error(writer) == mpack_ok;
-    }
-    #endif
-
-    if (writer->flush == NULL) {
-        mpack_writer_flag_error(writer, mpack_error_too_big);
-        return false;
-    }
-
-    mpack_writer_flush_unchecked(writer);
-    if (mpack_writer_error(writer) != mpack_ok)
-        return false;
-
-    if (mpack_writer_buffer_left(writer) >= count)
-        return true;
-
-    mpack_writer_flag_error(writer, mpack_error_io);
-    return false;
-}
-
-// Writes encoded bytes to the buffer when we already know the data
-// does not fit in the buffer (i.e. it straddles the edge of the
-// buffer.) If there is a flush function, it is guaranteed to be
-// called; otherwise mpack_error_too_big is raised.
-MPACK_NOINLINE static void mpack_write_native_straddle(mpack_writer_t* writer, const char* p, size_t count) {
-    mpack_assert(count == 0 || p != NULL, "data pointer for %i bytes is NULL", (int)count);
-
-    if (mpack_writer_error(writer) != mpack_ok)
-        return;
-    mpack_log("big write for %i bytes from %p, %i space left in buffer\n",
-            (int)count, p, (int)mpack_writer_buffer_left(writer));
-    mpack_assert(count > mpack_writer_buffer_left(writer),
-            "big write requested for %i bytes, but there is %i available "
-            "space in buffer. should have called mpack_write_native() instead",
-            (int)count, (int)(mpack_writer_buffer_left(writer)));
-
-    #if MPACK_BUILDER
-    // if we have a build in progress, we can't flush. we need to copy all
-    // bytes into as many build buffer pages as it takes.
-    if (writer->builder.current_build != NULL) {
-        while (true) {
-            size_t step = (size_t)(writer->end - writer->position);
-            if (step > count)
-                step = count;
-            mpack_memcpy(writer->position, p, step);
-            writer->position += step;
-            p += step;
-            count -= step;
-
-            if (count == 0)
-                return;
-
-            mpack_builder_flush(writer);
-            if (mpack_writer_error(writer) != mpack_ok)
-                return;
-            mpack_assert(writer->position != writer->end);
-        }
-    }
-    #endif
-
-    // we'll need a flush function
-    if (!writer->flush) {
-        mpack_writer_flag_error(writer, mpack_error_too_big);
-        return;
-    }
-
-    // flush the buffer
-    mpack_writer_flush_unchecked(writer);
-    if (mpack_writer_error(writer) != mpack_ok)
-        return;
-
-    // note that an intrusive flush function (such as mpack_growable_writer_flush())
-    // may have changed size and/or reset used to a non-zero value. we treat both as
-    // though they may have changed, and there may still be data in the buffer.
-
-    // flush the extra data directly if it doesn't fit in the buffer
-    if (count > mpack_writer_buffer_left(writer)) {
-        writer->flush(writer, p, count);
-        if (mpack_writer_error(writer) != mpack_ok)
-            return;
-    } else {
-        mpack_memcpy(writer->position, p, count);
-        writer->position += count;
-    }
-}
-
-// Writes encoded bytes to the buffer, flushing if necessary.
-MPACK_STATIC_INLINE void mpack_write_native(mpack_writer_t* writer, const char* p, size_t count) {
-    mpack_assert(count == 0 || p != NULL, "data pointer for %i bytes is NULL", (int)count);
-
-    if (mpack_writer_buffer_left(writer) < count) {
-        mpack_write_native_straddle(writer, p, count);
-    } else {
-        mpack_memcpy(writer->position, p, count);
-        writer->position += count;
-    }
-}
-
-mpack_error_t mpack_writer_destroy(mpack_writer_t* writer) {
-
-    // clean up tracking, asserting if we're not already in an error state
-    #if MPACK_WRITE_TRACKING
-    mpack_track_destroy(&writer->track, writer->error != mpack_ok);
-    #endif
-
-    // flush any outstanding data
-    if (mpack_writer_error(writer) == mpack_ok && mpack_writer_buffer_used(writer) != 0 && writer->flush != NULL) {
-        writer->flush(writer, writer->buffer, mpack_writer_buffer_used(writer));
-        writer->flush = NULL;
-    }
-
-    if (writer->teardown) {
-        writer->teardown(writer);
-        writer->teardown = NULL;
-    }
-
-    return writer->error;
-}
-
-void mpack_write_tag(mpack_writer_t* writer, mpack_tag_t value) {
-    switch (value.type) {
-        case mpack_type_missing:
-            mpack_break("cannot write a missing value!");
-            mpack_writer_flag_error(writer, mpack_error_bug);
-            return;
-
-        case mpack_type_nil:    mpack_write_nil   (writer);            return;
-        case mpack_type_bool:   mpack_write_bool  (writer, value.v.b); return;
-        case mpack_type_int:    mpack_write_int   (writer, value.v.i); return;
-        case mpack_type_uint:   mpack_write_uint  (writer, value.v.u); return;
-
-        case mpack_type_float:
-            #if MPACK_FLOAT
-            mpack_write_float
-            #else
-            mpack_write_raw_float
-            #endif
-                (writer, value.v.f);
-            return;
-        case mpack_type_double:
-            #if MPACK_DOUBLE
-            mpack_write_double
-            #else
-            mpack_write_raw_double
-            #endif
-                (writer, value.v.d);
-            return;
-
-        case mpack_type_str: mpack_start_str(writer, value.v.l); return;
-        case mpack_type_bin: mpack_start_bin(writer, value.v.l); return;
-
-        #if MPACK_EXTENSIONS
-        case mpack_type_ext:
-            mpack_start_ext(writer, mpack_tag_ext_exttype(&value), mpack_tag_ext_length(&value));
-            return;
-        #endif
-
-        case mpack_type_array: mpack_start_array(writer, value.v.n); return;
-        case mpack_type_map:   mpack_start_map(writer, value.v.n);   return;
-    }
-
-    mpack_break("unrecognized type %i", (int)value.type);
-    mpack_writer_flag_error(writer, mpack_error_bug);
-}
-
-MPACK_STATIC_INLINE void mpack_write_byte_element(mpack_writer_t* writer, char value) {
-    mpack_writer_track_element(writer);
-    if (MPACK_LIKELY(mpack_writer_buffer_left(writer) >= 1) || mpack_writer_ensure(writer, 1))
-        *(writer->position++) = value;
-}
-
-void mpack_write_nil(mpack_writer_t* writer) {
-    mpack_write_byte_element(writer, (char)0xc0);
-}
-
-void mpack_write_bool(mpack_writer_t* writer, bool value) {
-    mpack_write_byte_element(writer, (char)(0xc2 | (value ? 1 : 0)));
-}
-
-void mpack_write_true(mpack_writer_t* writer) {
-    mpack_write_byte_element(writer, (char)0xc3);
-}
-
-void mpack_write_false(mpack_writer_t* writer) {
-    mpack_write_byte_element(writer, (char)0xc2);
-}
-
-void mpack_write_object_bytes(mpack_writer_t* writer, const char* data, size_t bytes) {
-    mpack_writer_track_element(writer);
-    mpack_write_native(writer, data, bytes);
-}
-
-/*
- * Encode functions
- */
-
-MPACK_STATIC_INLINE void mpack_encode_fixuint(char* p, uint8_t value) {
-    mpack_assert(value <= 127);
-    mpack_store_u8(p, value);
-}
-
-MPACK_STATIC_INLINE void mpack_encode_u8(char* p, uint8_t value) {
-    mpack_assert(value > 127);
-    mpack_store_u8(p, 0xcc);
-    mpack_store_u8(p + 1, value);
-}
-
-MPACK_STATIC_INLINE void mpack_encode_u16(char* p, uint16_t value) {
-    mpack_assert(value > MPACK_UINT8_MAX);
-    mpack_store_u8(p, 0xcd);
-    mpack_store_u16(p + 1, value);
-}
-
-MPACK_STATIC_INLINE void mpack_encode_u32(char* p, uint32_t value) {
-    mpack_assert(value > MPACK_UINT16_MAX);
-    mpack_store_u8(p, 0xce);
-    mpack_store_u32(p + 1, value);
-}
-
-MPACK_STATIC_INLINE void mpack_encode_u64(char* p, uint64_t value) {
-    mpack_assert(value > MPACK_UINT32_MAX);
-    mpack_store_u8(p, 0xcf);
-    mpack_store_u64(p + 1, value);
-}
-
-MPACK_STATIC_INLINE void mpack_encode_fixint(char* p, int8_t value) {
-    // this can encode positive or negative fixints
-    mpack_assert(value >= -32);
-    mpack_store_i8(p, value);
-}
-
-MPACK_STATIC_INLINE void mpack_encode_i8(char* p, int8_t value) {
-    mpack_assert(value < -32);
-    mpack_store_u8(p, 0xd0);
-    mpack_store_i8(p + 1, value);
-}
-
-MPACK_STATIC_INLINE void mpack_encode_i16(char* p, int16_t value) {
-    mpack_assert(value < MPACK_INT8_MIN);
-    mpack_store_u8(p, 0xd1);
-    mpack_store_i16(p + 1, value);
-}
-
-MPACK_STATIC_INLINE void mpack_encode_i32(char* p, int32_t value) {
-    mpack_assert(value < MPACK_INT16_MIN);
-    mpack_store_u8(p, 0xd2);
-    mpack_store_i32(p + 1, value);
-}
-
-MPACK_STATIC_INLINE void mpack_encode_i64(char* p, int64_t value) {
-    mpack_assert(value < MPACK_INT32_MIN);
-    mpack_store_u8(p, 0xd3);
-    mpack_store_i64(p + 1, value);
-}
-
-#if MPACK_FLOAT
-MPACK_STATIC_INLINE void mpack_encode_float(char* p, float value) {
-    mpack_store_u8(p, 0xca);
-    mpack_store_float(p + 1, value);
-}
-#else
-MPACK_STATIC_INLINE void mpack_encode_raw_float(char* p, uint32_t value) {
-    mpack_store_u8(p, 0xca);
-    mpack_store_u32(p + 1, value);
-}
-#endif
-
-#if MPACK_DOUBLE
-MPACK_STATIC_INLINE void mpack_encode_double(char* p, double value) {
-    mpack_store_u8(p, 0xcb);
-    mpack_store_double(p + 1, value);
-}
-#else
-MPACK_STATIC_INLINE void mpack_encode_raw_double(char* p, uint64_t value) {
-    mpack_store_u8(p, 0xcb);
-    mpack_store_u64(p + 1, value);
-}
-#endif
-
-MPACK_STATIC_INLINE void mpack_encode_fixarray(char* p, uint8_t count) {
-    mpack_assert(count <= 15);
-    mpack_store_u8(p, (uint8_t)(0x90 | count));
-}
-
-MPACK_STATIC_INLINE void mpack_encode_array16(char* p, uint16_t count) {
-    mpack_assert(count > 15);
-    mpack_store_u8(p, 0xdc);
-    mpack_store_u16(p + 1, count);
-}
-
-MPACK_STATIC_INLINE void mpack_encode_array32(char* p, uint32_t count) {
-    mpack_assert(count > MPACK_UINT16_MAX);
-    mpack_store_u8(p, 0xdd);
-    mpack_store_u32(p + 1, count);
-}
-
-MPACK_STATIC_INLINE void mpack_encode_fixmap(char* p, uint8_t count) {
-    mpack_assert(count <= 15);
-    mpack_store_u8(p, (uint8_t)(0x80 | count));
-}
-
-MPACK_STATIC_INLINE void mpack_encode_map16(char* p, uint16_t count) {
-    mpack_assert(count > 15);
-    mpack_store_u8(p, 0xde);
-    mpack_store_u16(p + 1, count);
-}
-
-MPACK_STATIC_INLINE void mpack_encode_map32(char* p, uint32_t count) {
-    mpack_assert(count > MPACK_UINT16_MAX);
-    mpack_store_u8(p, 0xdf);
-    mpack_store_u32(p + 1, count);
-}
-
-MPACK_STATIC_INLINE void mpack_encode_fixstr(char* p, uint8_t count) {
-    mpack_assert(count <= 31);
-    mpack_store_u8(p, (uint8_t)(0xa0 | count));
-}
-
-MPACK_STATIC_INLINE void mpack_encode_str8(char* p, uint8_t count) {
-    mpack_assert(count > 31);
-    mpack_store_u8(p, 0xd9);
-    mpack_store_u8(p + 1, count);
-}
-
-MPACK_STATIC_INLINE void mpack_encode_str16(char* p, uint16_t count) {
-    // we might be encoding a raw in compatibility mode, so we
-    // allow count to be in the range [32, MPACK_UINT8_MAX].
-    mpack_assert(count > 31);
-    mpack_store_u8(p, 0xda);
-    mpack_store_u16(p + 1, count);
-}
-
-MPACK_STATIC_INLINE void mpack_encode_str32(char* p, uint32_t count) {
-    mpack_assert(count > MPACK_UINT16_MAX);
-    mpack_store_u8(p, 0xdb);
-    mpack_store_u32(p + 1, count);
-}
-
-MPACK_STATIC_INLINE void mpack_encode_bin8(char* p, uint8_t count) {
-    mpack_store_u8(p, 0xc4);
-    mpack_store_u8(p + 1, count);
-}
-
-MPACK_STATIC_INLINE void mpack_encode_bin16(char* p, uint16_t count) {
-    mpack_assert(count > MPACK_UINT8_MAX);
-    mpack_store_u8(p, 0xc5);
-    mpack_store_u16(p + 1, count);
-}
-
-MPACK_STATIC_INLINE void mpack_encode_bin32(char* p, uint32_t count) {
-    mpack_assert(count > MPACK_UINT16_MAX);
-    mpack_store_u8(p, 0xc6);
-    mpack_store_u32(p + 1, count);
-}
-
-#if MPACK_EXTENSIONS
-MPACK_STATIC_INLINE void mpack_encode_fixext1(char* p, int8_t exttype) {
-    mpack_store_u8(p, 0xd4);
-    mpack_store_i8(p + 1, exttype);
-}
-
-MPACK_STATIC_INLINE void mpack_encode_fixext2(char* p, int8_t exttype) {
-    mpack_store_u8(p, 0xd5);
-    mpack_store_i8(p + 1, exttype);
-}
-
-MPACK_STATIC_INLINE void mpack_encode_fixext4(char* p, int8_t exttype) {
-    mpack_store_u8(p, 0xd6);
-    mpack_store_i8(p + 1, exttype);
-}
-
-MPACK_STATIC_INLINE void mpack_encode_fixext8(char* p, int8_t exttype) {
-    mpack_store_u8(p, 0xd7);
-    mpack_store_i8(p + 1, exttype);
-}
-
-MPACK_STATIC_INLINE void mpack_encode_fixext16(char* p, int8_t exttype) {
-    mpack_store_u8(p, 0xd8);
-    mpack_store_i8(p + 1, exttype);
-}
-
-MPACK_STATIC_INLINE void mpack_encode_ext8(char* p, int8_t exttype, uint8_t count) {
-    mpack_assert(count != 1 && count != 2 && count != 4 && count != 8 && count != 16);
-    mpack_store_u8(p, 0xc7);
-    mpack_store_u8(p + 1, count);
-    mpack_store_i8(p + 2, exttype);
-}
-
-MPACK_STATIC_INLINE void mpack_encode_ext16(char* p, int8_t exttype, uint16_t count) {
-    mpack_assert(count > MPACK_UINT8_MAX);
-    mpack_store_u8(p, 0xc8);
-    mpack_store_u16(p + 1, count);
-    mpack_store_i8(p + 3, exttype);
-}
-
-MPACK_STATIC_INLINE void mpack_encode_ext32(char* p, int8_t exttype, uint32_t count) {
-    mpack_assert(count > MPACK_UINT16_MAX);
-    mpack_store_u8(p, 0xc9);
-    mpack_store_u32(p + 1, count);
-    mpack_store_i8(p + 5, exttype);
-}
-
-MPACK_STATIC_INLINE void mpack_encode_timestamp_4(char* p, uint32_t seconds) {
-    mpack_encode_fixext4(p, MPACK_EXTTYPE_TIMESTAMP);
-    mpack_store_u32(p + MPACK_TAG_SIZE_FIXEXT4, seconds);
-}
-
-MPACK_STATIC_INLINE void mpack_encode_timestamp_8(char* p, int64_t seconds, uint32_t nanoseconds) {
-    mpack_assert(nanoseconds <= MPACK_TIMESTAMP_NANOSECONDS_MAX);
-    mpack_encode_fixext8(p, MPACK_EXTTYPE_TIMESTAMP);
-    uint64_t encoded = ((uint64_t)nanoseconds << 34) | (uint64_t)seconds;
-    mpack_store_u64(p + MPACK_TAG_SIZE_FIXEXT8, encoded);
-}
-
-MPACK_STATIC_INLINE void mpack_encode_timestamp_12(char* p, int64_t seconds, uint32_t nanoseconds) {
-    mpack_assert(nanoseconds <= MPACK_TIMESTAMP_NANOSECONDS_MAX);
-    mpack_encode_ext8(p, MPACK_EXTTYPE_TIMESTAMP, 12);
-    mpack_store_u32(p + MPACK_TAG_SIZE_EXT8, nanoseconds);
-    mpack_store_i64(p + MPACK_TAG_SIZE_EXT8 + 4, seconds);
-}
-#endif
-
-
-
-/*
- * Write functions
- */
-
-// This is a macro wrapper to the encode functions to encode
-// directly into the buffer. If mpack_writer_ensure() fails
-// it will flag an error so we don't have to do anything.
-#define MPACK_WRITE_ENCODED(encode_fn, size, ...) do {                                                 \
-    if (MPACK_LIKELY(mpack_writer_buffer_left(writer) >= size) || mpack_writer_ensure(writer, size)) { \
-        MPACK_EXPAND(encode_fn(writer->position, __VA_ARGS__));                                        \
-        writer->position += size;                                                                      \
-    }                                                                                                  \
-} while (0)
-
-void mpack_write_u8(mpack_writer_t* writer, uint8_t value) {
-    #if MPACK_OPTIMIZE_FOR_SIZE
-    mpack_write_u64(writer, value);
-    #else
-    mpack_writer_track_element(writer);
-    if (value <= 127) {
-        MPACK_WRITE_ENCODED(mpack_encode_fixuint, MPACK_TAG_SIZE_FIXUINT, value);
-    } else {
-        MPACK_WRITE_ENCODED(mpack_encode_u8, MPACK_TAG_SIZE_U8, value);
-    }
-    #endif
-}
-
-void mpack_write_u16(mpack_writer_t* writer, uint16_t value) {
-    #if MPACK_OPTIMIZE_FOR_SIZE
-    mpack_write_u64(writer, value);
-    #else
-    mpack_writer_track_element(writer);
-    if (value <= 127) {
-        MPACK_WRITE_ENCODED(mpack_encode_fixuint, MPACK_TAG_SIZE_FIXUINT, (uint8_t)value);
-    } else if (value <= MPACK_UINT8_MAX) {
-        MPACK_WRITE_ENCODED(mpack_encode_u8, MPACK_TAG_SIZE_U8, (uint8_t)value);
-    } else {
-        MPACK_WRITE_ENCODED(mpack_encode_u16, MPACK_TAG_SIZE_U16, value);
-    }
-    #endif
-}
-
-void mpack_write_u32(mpack_writer_t* writer, uint32_t value) {
-    #if MPACK_OPTIMIZE_FOR_SIZE
-    mpack_write_u64(writer, value);
-    #else
-    mpack_writer_track_element(writer);
-    if (value <= 127) {
-        MPACK_WRITE_ENCODED(mpack_encode_fixuint, MPACK_TAG_SIZE_FIXUINT, (uint8_t)value);
-    } else if (value <= MPACK_UINT8_MAX) {
-        MPACK_WRITE_ENCODED(mpack_encode_u8, MPACK_TAG_SIZE_U8, (uint8_t)value);
-    } else if (value <= MPACK_UINT16_MAX) {
-        MPACK_WRITE_ENCODED(mpack_encode_u16, MPACK_TAG_SIZE_U16, (uint16_t)value);
-    } else {
-        MPACK_WRITE_ENCODED(mpack_encode_u32, MPACK_TAG_SIZE_U32, value);
-    }
-    #endif
-}
-
-void mpack_write_u64(mpack_writer_t* writer, uint64_t value) {
-    mpack_writer_track_element(writer);
-
-    if (value <= 127) {
-        MPACK_WRITE_ENCODED(mpack_encode_fixuint, MPACK_TAG_SIZE_FIXUINT, (uint8_t)value);
-    } else if (value <= MPACK_UINT8_MAX) {
-        MPACK_WRITE_ENCODED(mpack_encode_u8, MPACK_TAG_SIZE_U8, (uint8_t)value);
-    } else if (value <= MPACK_UINT16_MAX) {
-        MPACK_WRITE_ENCODED(mpack_encode_u16, MPACK_TAG_SIZE_U16, (uint16_t)value);
-    } else if (value <= MPACK_UINT32_MAX) {
-        MPACK_WRITE_ENCODED(mpack_encode_u32, MPACK_TAG_SIZE_U32, (uint32_t)value);
-    } else {
-        MPACK_WRITE_ENCODED(mpack_encode_u64, MPACK_TAG_SIZE_U64, value);
-    }
-}
-
-void mpack_write_i8(mpack_writer_t* writer, int8_t value) {
-    #if MPACK_OPTIMIZE_FOR_SIZE
-    mpack_write_i64(writer, value);
-    #else
-    mpack_writer_track_element(writer);
-    if (value >= -32) {
-        // we encode positive and negative fixints together
-        MPACK_WRITE_ENCODED(mpack_encode_fixint, MPACK_TAG_SIZE_FIXINT, (int8_t)value);
-    } else {
-        MPACK_WRITE_ENCODED(mpack_encode_i8, MPACK_TAG_SIZE_I8, (int8_t)value);
-    }
-    #endif
-}
-
-void mpack_write_i16(mpack_writer_t* writer, int16_t value) {
-    #if MPACK_OPTIMIZE_FOR_SIZE
-    mpack_write_i64(writer, value);
-    #else
-    mpack_writer_track_element(writer);
-    if (value >= -32) {
-        if (value <= 127) {
-            // we encode positive and negative fixints together
-            MPACK_WRITE_ENCODED(mpack_encode_fixint, MPACK_TAG_SIZE_FIXINT, (int8_t)value);
-        } else if (value <= MPACK_UINT8_MAX) {
-            MPACK_WRITE_ENCODED(mpack_encode_u8, MPACK_TAG_SIZE_U8, (uint8_t)value);
-        } else {
-            MPACK_WRITE_ENCODED(mpack_encode_u16, MPACK_TAG_SIZE_U16, (uint16_t)value);
-        }
-    } else if (value >= MPACK_INT8_MIN) {
-        MPACK_WRITE_ENCODED(mpack_encode_i8, MPACK_TAG_SIZE_I8, (int8_t)value);
-    } else {
-        MPACK_WRITE_ENCODED(mpack_encode_i16, MPACK_TAG_SIZE_I16, (int16_t)value);
-    }
-    #endif
-}
-
-void mpack_write_i32(mpack_writer_t* writer, int32_t value) {
-    #if MPACK_OPTIMIZE_FOR_SIZE
-    mpack_write_i64(writer, value);
-    #else
-    mpack_writer_track_element(writer);
-    if (value >= -32) {
-        if (value <= 127) {
-            // we encode positive and negative fixints together
-            MPACK_WRITE_ENCODED(mpack_encode_fixint, MPACK_TAG_SIZE_FIXINT, (int8_t)value);
-        } else if (value <= MPACK_UINT8_MAX) {
-            MPACK_WRITE_ENCODED(mpack_encode_u8, MPACK_TAG_SIZE_U8, (uint8_t)value);
-        } else if (value <= MPACK_UINT16_MAX) {
-            MPACK_WRITE_ENCODED(mpack_encode_u16, MPACK_TAG_SIZE_U16, (uint16_t)value);
-        } else {
-            MPACK_WRITE_ENCODED(mpack_encode_u32, MPACK_TAG_SIZE_U32, (uint32_t)value);
-        }
-    } else if (value >= MPACK_INT8_MIN) {
-        MPACK_WRITE_ENCODED(mpack_encode_i8, MPACK_TAG_SIZE_I8, (int8_t)value);
-    } else if (value >= MPACK_INT16_MIN) {
-        MPACK_WRITE_ENCODED(mpack_encode_i16, MPACK_TAG_SIZE_I16, (int16_t)value);
-    } else {
-        MPACK_WRITE_ENCODED(mpack_encode_i32, MPACK_TAG_SIZE_I32, value);
-    }
-    #endif
-}
-
-void mpack_write_i64(mpack_writer_t* writer, int64_t value) {
-    #if MPACK_OPTIMIZE_FOR_SIZE
-    if (value > 127) {
-        // for non-fix positive ints we call the u64 writer to save space
-        mpack_write_u64(writer, (uint64_t)value);
-        return;
-    }
-    #endif
-
-    mpack_writer_track_element(writer);
-    if (value >= -32) {
-        #if MPACK_OPTIMIZE_FOR_SIZE
-        MPACK_WRITE_ENCODED(mpack_encode_fixint, MPACK_TAG_SIZE_FIXINT, (int8_t)value);
-        #else
-        if (value <= 127) {
-            MPACK_WRITE_ENCODED(mpack_encode_fixint, MPACK_TAG_SIZE_FIXINT, (int8_t)value);
-        } else if (value <= MPACK_UINT8_MAX) {
-            MPACK_WRITE_ENCODED(mpack_encode_u8, MPACK_TAG_SIZE_U8, (uint8_t)value);
-        } else if (value <= MPACK_UINT16_MAX) {
-            MPACK_WRITE_ENCODED(mpack_encode_u16, MPACK_TAG_SIZE_U16, (uint16_t)value);
-        } else if (value <= MPACK_UINT32_MAX) {
-            MPACK_WRITE_ENCODED(mpack_encode_u32, MPACK_TAG_SIZE_U32, (uint32_t)value);
-        } else {
-            MPACK_WRITE_ENCODED(mpack_encode_u64, MPACK_TAG_SIZE_U64, (uint64_t)value);
-        }
-        #endif
-    } else if (value >= MPACK_INT8_MIN) {
-        MPACK_WRITE_ENCODED(mpack_encode_i8, MPACK_TAG_SIZE_I8, (int8_t)value);
-    } else if (value >= MPACK_INT16_MIN) {
-        MPACK_WRITE_ENCODED(mpack_encode_i16, MPACK_TAG_SIZE_I16, (int16_t)value);
-    } else if (value >= MPACK_INT32_MIN) {
-        MPACK_WRITE_ENCODED(mpack_encode_i32, MPACK_TAG_SIZE_I32, (int32_t)value);
-    } else {
-        MPACK_WRITE_ENCODED(mpack_encode_i64, MPACK_TAG_SIZE_I64, value);
-    }
-}
-
-#if MPACK_FLOAT
-void mpack_write_float(mpack_writer_t* writer, float value) {
-    mpack_writer_track_element(writer);
-    MPACK_WRITE_ENCODED(mpack_encode_float, MPACK_TAG_SIZE_FLOAT, value);
-}
-#else
-void mpack_write_raw_float(mpack_writer_t* writer, uint32_t value) {
-    mpack_writer_track_element(writer);
-    MPACK_WRITE_ENCODED(mpack_encode_raw_float, MPACK_TAG_SIZE_FLOAT, value);
-}
-#endif
-
-#if MPACK_DOUBLE
-void mpack_write_double(mpack_writer_t* writer, double value) {
-    mpack_writer_track_element(writer);
-    MPACK_WRITE_ENCODED(mpack_encode_double, MPACK_TAG_SIZE_DOUBLE, value);
-}
-#else
-void mpack_write_raw_double(mpack_writer_t* writer, uint64_t value) {
-    mpack_writer_track_element(writer);
-    MPACK_WRITE_ENCODED(mpack_encode_raw_double, MPACK_TAG_SIZE_DOUBLE, value);
-}
-#endif
-
-#if MPACK_EXTENSIONS
-void mpack_write_timestamp(mpack_writer_t* writer, int64_t seconds, uint32_t nanoseconds) {
-    #if MPACK_COMPATIBILITY
-    if (writer->version <= mpack_version_v4) {
-        mpack_break("Timestamps require spec version v5 or later. This writer is in v%i mode.", (int)writer->version);
-        mpack_writer_flag_error(writer, mpack_error_bug);
-        return;
-    }
-    #endif
-
-    if (nanoseconds > MPACK_TIMESTAMP_NANOSECONDS_MAX) {
-        mpack_break("timestamp nanoseconds out of bounds: %u", nanoseconds);
-        mpack_writer_flag_error(writer, mpack_error_bug);
-        return;
-    }
-
-    mpack_writer_track_element(writer);
-
-    if (seconds < 0 || seconds >= (MPACK_INT64_C(1) << 34)) {
-        MPACK_WRITE_ENCODED(mpack_encode_timestamp_12, MPACK_EXT_SIZE_TIMESTAMP12, seconds, nanoseconds);
-    } else if (seconds > MPACK_UINT32_MAX || nanoseconds > 0) {
-        MPACK_WRITE_ENCODED(mpack_encode_timestamp_8, MPACK_EXT_SIZE_TIMESTAMP8, seconds, nanoseconds);
-    } else {
-        MPACK_WRITE_ENCODED(mpack_encode_timestamp_4, MPACK_EXT_SIZE_TIMESTAMP4, (uint32_t)seconds);
-    }
-}
-#endif
-
-static void mpack_write_array_notrack(mpack_writer_t* writer, uint32_t count) {
-    if (count <= 15) {
-        MPACK_WRITE_ENCODED(mpack_encode_fixarray, MPACK_TAG_SIZE_FIXARRAY, (uint8_t)count);
-    } else if (count <= MPACK_UINT16_MAX) {
-        MPACK_WRITE_ENCODED(mpack_encode_array16, MPACK_TAG_SIZE_ARRAY16, (uint16_t)count);
-    } else {
-        MPACK_WRITE_ENCODED(mpack_encode_array32, MPACK_TAG_SIZE_ARRAY32, (uint32_t)count);
-    }
-}
-
-static void mpack_write_map_notrack(mpack_writer_t* writer, uint32_t count) {
-    if (count <= 15) {
-        MPACK_WRITE_ENCODED(mpack_encode_fixmap, MPACK_TAG_SIZE_FIXMAP, (uint8_t)count);
-    } else if (count <= MPACK_UINT16_MAX) {
-        MPACK_WRITE_ENCODED(mpack_encode_map16, MPACK_TAG_SIZE_MAP16, (uint16_t)count);
-    } else {
-        MPACK_WRITE_ENCODED(mpack_encode_map32, MPACK_TAG_SIZE_MAP32, (uint32_t)count);
-    }
-}
-
-void mpack_start_array(mpack_writer_t* writer, uint32_t count) {
-    mpack_writer_track_element(writer);
-    mpack_write_array_notrack(writer, count);
-    mpack_writer_track_push(writer, mpack_type_array, count);
-    mpack_builder_compound_push(writer);
-}
-
-void mpack_start_map(mpack_writer_t* writer, uint32_t count) {
-    mpack_writer_track_element(writer);
-    mpack_write_map_notrack(writer, count);
-    mpack_writer_track_push(writer, mpack_type_map, count);
-    mpack_builder_compound_push(writer);
-}
-
-static void mpack_start_str_notrack(mpack_writer_t* writer, uint32_t count) {
-    if (count <= 31) {
-        MPACK_WRITE_ENCODED(mpack_encode_fixstr, MPACK_TAG_SIZE_FIXSTR, (uint8_t)count);
-
-    // str8 is only supported in v5 or later.
-    } else if (count <= MPACK_UINT8_MAX
-            #if MPACK_COMPATIBILITY
-            && writer->version >= mpack_version_v5
-            #endif
-            ) {
-        MPACK_WRITE_ENCODED(mpack_encode_str8, MPACK_TAG_SIZE_STR8, (uint8_t)count);
-
-    } else if (count <= MPACK_UINT16_MAX) {
-        MPACK_WRITE_ENCODED(mpack_encode_str16, MPACK_TAG_SIZE_STR16, (uint16_t)count);
-    } else {
-        MPACK_WRITE_ENCODED(mpack_encode_str32, MPACK_TAG_SIZE_STR32, (uint32_t)count);
-    }
-}
-
-static void mpack_start_bin_notrack(mpack_writer_t* writer, uint32_t count) {
-    #if MPACK_COMPATIBILITY
-    // In the v4 spec, there was only the raw type for any kind of
-    // variable-length data. In v4 mode, we support the bin functions,
-    // but we produce an old-style raw.
-    if (writer->version <= mpack_version_v4) {
-        mpack_start_str_notrack(writer, count);
-        return;
-    }
-    #endif
-
-    if (count <= MPACK_UINT8_MAX) {
-        MPACK_WRITE_ENCODED(mpack_encode_bin8, MPACK_TAG_SIZE_BIN8, (uint8_t)count);
-    } else if (count <= MPACK_UINT16_MAX) {
-        MPACK_WRITE_ENCODED(mpack_encode_bin16, MPACK_TAG_SIZE_BIN16, (uint16_t)count);
-    } else {
-        MPACK_WRITE_ENCODED(mpack_encode_bin32, MPACK_TAG_SIZE_BIN32, (uint32_t)count);
-    }
-}
-
-void mpack_start_str(mpack_writer_t* writer, uint32_t count) {
-    mpack_writer_track_element(writer);
-    mpack_start_str_notrack(writer, count);
-    mpack_writer_track_push(writer, mpack_type_str, count);
-}
-
-void mpack_start_bin(mpack_writer_t* writer, uint32_t count) {
-    mpack_writer_track_element(writer);
-    mpack_start_bin_notrack(writer, count);
-    mpack_writer_track_push(writer, mpack_type_bin, count);
-}
-
-#if MPACK_EXTENSIONS
-void mpack_start_ext(mpack_writer_t* writer, int8_t exttype, uint32_t count) {
-    #if MPACK_COMPATIBILITY
-    if (writer->version <= mpack_version_v4) {
-        mpack_break("Ext types require spec version v5 or later. This writer is in v%i mode.", (int)writer->version);
-        mpack_writer_flag_error(writer, mpack_error_bug);
-        return;
-    }
-    #endif
-
-    mpack_writer_track_element(writer);
-
-    if (count == 1) {
-        MPACK_WRITE_ENCODED(mpack_encode_fixext1, MPACK_TAG_SIZE_FIXEXT1, exttype);
-    } else if (count == 2) {
-        MPACK_WRITE_ENCODED(mpack_encode_fixext2, MPACK_TAG_SIZE_FIXEXT2, exttype);
-    } else if (count == 4) {
-        MPACK_WRITE_ENCODED(mpack_encode_fixext4, MPACK_TAG_SIZE_FIXEXT4, exttype);
-    } else if (count == 8) {
-        MPACK_WRITE_ENCODED(mpack_encode_fixext8, MPACK_TAG_SIZE_FIXEXT8, exttype);
-    } else if (count == 16) {
-        MPACK_WRITE_ENCODED(mpack_encode_fixext16, MPACK_TAG_SIZE_FIXEXT16, exttype);
-    } else if (count <= MPACK_UINT8_MAX) {
-        MPACK_WRITE_ENCODED(mpack_encode_ext8, MPACK_TAG_SIZE_EXT8, exttype, (uint8_t)count);
-    } else if (count <= MPACK_UINT16_MAX) {
-        MPACK_WRITE_ENCODED(mpack_encode_ext16, MPACK_TAG_SIZE_EXT16, exttype, (uint16_t)count);
-    } else {
-        MPACK_WRITE_ENCODED(mpack_encode_ext32, MPACK_TAG_SIZE_EXT32, exttype, (uint32_t)count);
-    }
-
-    mpack_writer_track_push(writer, mpack_type_ext, count);
-}
-#endif
-
-
-
-/*
- * Compound helpers and other functions
- */
-
-void mpack_write_str(mpack_writer_t* writer, const char* data, uint32_t count) {
-    mpack_assert(data != NULL, "data for string of length %i is NULL", (int)count);
-
-    #if MPACK_OPTIMIZE_FOR_SIZE
-    mpack_writer_track_element(writer);
-    mpack_start_str_notrack(writer, count);
-    mpack_write_native(writer, data, count);
-    #else
-
-    mpack_writer_track_element(writer);
-
-    if (count <= 31) {
-        // The minimum buffer size when using a flush function is guaranteed to
-        // fit the largest possible fixstr.
-        size_t size = count + MPACK_TAG_SIZE_FIXSTR;
-        if (MPACK_LIKELY(mpack_writer_buffer_left(writer) >= size) || mpack_writer_ensure(writer, size)) {
-            char* MPACK_RESTRICT p = writer->position;
-            mpack_encode_fixstr(p, (uint8_t)count);
-            mpack_memcpy(p + MPACK_TAG_SIZE_FIXSTR, data, count);
-            writer->position += count + MPACK_TAG_SIZE_FIXSTR;
-        }
-        return;
-    }
-
-    if (count <= MPACK_UINT8_MAX
-            #if MPACK_COMPATIBILITY
-            && writer->version >= mpack_version_v5
-            #endif
-            ) {
-        if (count + MPACK_TAG_SIZE_STR8 <= mpack_writer_buffer_left(writer)) {
-            char* MPACK_RESTRICT p = writer->position;
-            mpack_encode_str8(p, (uint8_t)count);
-            mpack_memcpy(p + MPACK_TAG_SIZE_STR8, data, count);
-            writer->position += count + MPACK_TAG_SIZE_STR8;
-        } else {
-            MPACK_WRITE_ENCODED(mpack_encode_str8, MPACK_TAG_SIZE_STR8, (uint8_t)count);
-            mpack_write_native(writer, data, count);
-        }
-        return;
-    }
-
-    // str16 and str32 are likely to be a significant fraction of the buffer
-    // size, so we don't bother with a combined space check in order to
-    // minimize code size.
-    if (count <= MPACK_UINT16_MAX) {
-        MPACK_WRITE_ENCODED(mpack_encode_str16, MPACK_TAG_SIZE_STR16, (uint16_t)count);
-        mpack_write_native(writer, data, count);
-    } else {
-        MPACK_WRITE_ENCODED(mpack_encode_str32, MPACK_TAG_SIZE_STR32, (uint32_t)count);
-        mpack_write_native(writer, data, count);
-    }
-
-    #endif
-}
-
-void mpack_write_bin(mpack_writer_t* writer, const char* data, uint32_t count) {
-    mpack_assert(data != NULL, "data pointer for bin of %i bytes is NULL", (int)count);
-    mpack_start_bin(writer, count);
-    mpack_write_bytes(writer, data, count);
-    mpack_finish_bin(writer);
-}
-
-#if MPACK_EXTENSIONS
-void mpack_write_ext(mpack_writer_t* writer, int8_t exttype, const char* data, uint32_t count) {
-    mpack_assert(data != NULL, "data pointer for ext of type %i and %i bytes is NULL", exttype, (int)count);
-    mpack_start_ext(writer, exttype, count);
-    mpack_write_bytes(writer, data, count);
-    mpack_finish_ext(writer);
-}
-#endif
-
-void mpack_write_bytes(mpack_writer_t* writer, const char* data, size_t count) {
-    mpack_assert(data != NULL, "data pointer for %i bytes is NULL", (int)count);
-    mpack_writer_track_bytes(writer, count);
-    mpack_write_native(writer, data, count);
-}
-
-void mpack_write_cstr(mpack_writer_t* writer, const char* cstr) {
-    mpack_assert(cstr != NULL, "cstr pointer is NULL");
-    size_t length = mpack_strlen(cstr);
-    if (length > MPACK_UINT32_MAX)
-        mpack_writer_flag_error(writer, mpack_error_invalid);
-    mpack_write_str(writer, cstr, (uint32_t)length);
-}
-
-void mpack_write_cstr_or_nil(mpack_writer_t* writer, const char* cstr) {
-    if (cstr)
-        mpack_write_cstr(writer, cstr);
-    else
-        mpack_write_nil(writer);
-}
-
-void mpack_write_utf8(mpack_writer_t* writer, const char* str, uint32_t length) {
-    mpack_assert(str != NULL, "data for string of length %i is NULL", (int)length);
-    if (!mpack_utf8_check(str, length)) {
-        mpack_writer_flag_error(writer, mpack_error_invalid);
-        return;
-    }
-    mpack_write_str(writer, str, length);
-}
-
-void mpack_write_utf8_cstr(mpack_writer_t* writer, const char* cstr) {
-    mpack_assert(cstr != NULL, "cstr pointer is NULL");
-    size_t length = mpack_strlen(cstr);
-    if (length > MPACK_UINT32_MAX) {
-        mpack_writer_flag_error(writer, mpack_error_invalid);
-        return;
-    }
-    mpack_write_utf8(writer, cstr, (uint32_t)length);
-}
-
-void mpack_write_utf8_cstr_or_nil(mpack_writer_t* writer, const char* cstr) {
-    if (cstr)
-        mpack_write_utf8_cstr(writer, cstr);
-    else
-        mpack_write_nil(writer);
-}
-
-/*
- * Builder implementation
- *
- * When a writer is in build mode, it diverts writes to an internal growable
- * buffer. All elements other than builder start tags are encoded as normal
- * into the builder buffer (even nested maps and arrays of known size, e.g.
- * `mpack_start_array()`.) But for compound elements of unknown size, an
- * mpack_build_t is written to the buffer instead.
- *
- * The mpack_build_t tracks everything needed to re-constitute the final
- * message once all sizes are known. When the last build element is completed,
- * the builder resolves the build by walking through the builds, outputting the
- * final encoded tag, and copying everything in between to the writer's true
- * buffer.
- *
- * To make things extra complicated, the builder buffer is not contiguous. It's
- * allocated in pages, where the first page may be an internal page in the
- * writer. But, each mpack_build_t must itself be contiguous and aligned
- * properly within the buffer. This means bytes can be skipped (and wasted)
- * before the builds or at the end of pages.
- *
- * To keep track of this, builds store both their element count and the number
- * of encoded bytes that follow, and pages store the number of bytes used. As
- * elements are written, each element adds to the count in the current open
- * build, and the number of bytes written adds to the current page and the byte
- * count in the last started build (whether or not it is completed.)
- */
-
-#if MPACK_BUILDER
-
-#ifdef MPACK_ALIGNOF
-    #define MPACK_BUILD_ALIGNMENT MPACK_ALIGNOF(mpack_build_t)
-#else
-    // without alignof, we just align to the greater of size_t, void* and uint64_t.
-    // (we do this even though we don't have uint64_t in it in case we add it later.)
-    #define MPACK_BUILD_ALIGNMENT_MAX(x, y) ((x) > (y) ? (x) : (y))
-    #define MPACK_BUILD_ALIGNMENT (MPACK_BUILD_ALIGNMENT_MAX(sizeof(void*), \
-                MPACK_BUILD_ALIGNMENT_MAX(sizeof(size_t), sizeof(uint64_t))))
-#endif
-
-static inline void mpack_builder_check_sizes(mpack_writer_t* writer) {
-
-    // We check internal and page sizes here so that we don't have to check
-    // them again. A new page with a build in it will have a page header,
-    // build, and minimum space for a tag. This will perform horribly and waste
-    // tons of memory if the page size is small, so you're best off just
-    // sticking with the defaults.
-    //
-    // These are all known at compile time, so if they are large
-    // enough this function should trivially optimize to a no-op.
-
-    #if MPACK_BUILDER_INTERNAL_STORAGE
-    // make sure the internal storage is big enough to be useful
-    MPACK_STATIC_ASSERT(MPACK_BUILDER_INTERNAL_STORAGE_SIZE >= (sizeof(mpack_builder_page_t) +
-            sizeof(mpack_build_t) + MPACK_WRITER_MINIMUM_BUFFER_SIZE),
-            "MPACK_BUILDER_INTERNAL_STORAGE_SIZE is too small to be useful!");
-    if (MPACK_BUILDER_INTERNAL_STORAGE_SIZE < (sizeof(mpack_builder_page_t) +
-            sizeof(mpack_build_t) + MPACK_WRITER_MINIMUM_BUFFER_SIZE))
-    {
-        mpack_break("MPACK_BUILDER_INTERNAL_STORAGE_SIZE is too small to be useful!");
-        mpack_writer_flag_error(writer, mpack_error_bug);
-    }
-    #endif
-
-    // make sure the builder page size is big enough to be useful
-    MPACK_STATIC_ASSERT(MPACK_BUILDER_PAGE_SIZE >= (sizeof(mpack_builder_page_t) +
-            sizeof(mpack_build_t) + MPACK_WRITER_MINIMUM_BUFFER_SIZE),
-            "MPACK_BUILDER_PAGE_SIZE is too small to be useful!");
-    if (MPACK_BUILDER_PAGE_SIZE < (sizeof(mpack_builder_page_t) +
-            sizeof(mpack_build_t) + MPACK_WRITER_MINIMUM_BUFFER_SIZE))
-    {
-        mpack_break("MPACK_BUILDER_PAGE_SIZE is too small to be useful!");
-        mpack_writer_flag_error(writer, mpack_error_bug);
-    }
-}
-
-static inline size_t mpack_builder_page_size(mpack_writer_t* writer, mpack_builder_page_t* page) {
-    #if MPACK_BUILDER_INTERNAL_STORAGE
-    if ((char*)page == writer->builder.internal)
-        return sizeof(writer->builder.internal);
-    #else
-    (void)writer;
-    (void)page;
-    #endif
-    return MPACK_BUILDER_PAGE_SIZE;
-}
-
-static inline size_t mpack_builder_align_build(size_t bytes_used) {
-    size_t offset = bytes_used;
-    offset += MPACK_BUILD_ALIGNMENT - 1;
-    offset -= offset % MPACK_BUILD_ALIGNMENT;
-    mpack_log("aligned %zi to %zi\n", bytes_used, offset);
-    return offset;
-}
-
-static inline void mpack_builder_free_page(mpack_writer_t* writer, mpack_builder_page_t* page) {
-    mpack_log("freeing page %p\n", (void*)page);
-    #if MPACK_BUILDER_INTERNAL_STORAGE
-    if ((char*)page == writer->builder.internal)
-        return;
-    #else
-    (void)writer;
-    #endif
-    MPACK_FREE(page);
-}
-
-static inline size_t mpack_builder_page_remaining(mpack_writer_t* writer, mpack_builder_page_t* page) {
-    return mpack_builder_page_size(writer, page) - page->bytes_used;
-}
-
-static void mpack_builder_configure_buffer(mpack_writer_t* writer) {
-    if (mpack_writer_error(writer) != mpack_ok)
-        return;
-    mpack_builder_t* builder = &writer->builder;
-
-    mpack_builder_page_t* page = builder->current_page;
-    mpack_assert(page != NULL, "page is null??");
-
-    // This diverts the writer into the remainder of the current page of our
-    // build buffer.
-    writer->buffer = (char*)page + page->bytes_used;
-    writer->position = (char*)page + page->bytes_used;
-    writer->end = (char*)page + mpack_builder_page_size(writer, page);
-    mpack_log("configuring buffer from %p to %p\n", (void*)writer->position, (void*)writer->end);
-}
-
-static void mpack_builder_add_page(mpack_writer_t* writer) {
-    mpack_builder_t* builder = &writer->builder;
-    mpack_assert(writer->error == mpack_ok);
-
-    mpack_log("adding a page.\n");
-    mpack_builder_page_t* page = (mpack_builder_page_t*)MPACK_MALLOC(MPACK_BUILDER_PAGE_SIZE);
-    if (page == NULL) {
-        mpack_writer_flag_error(writer, mpack_error_memory);
-        return;
-    }
-
-    page->next = NULL;
-    page->bytes_used = sizeof(mpack_builder_page_t);
-    builder->current_page->next = page;
-    builder->current_page = page;
-}
-
-// Checks how many bytes the writer wrote to the page, adding it to the page's
-// bytes_used. This must be followed up with mpack_builder_configure_buffer()
-// (after adding a new page, build, etc) to reset the writer's buffer pointers.
-static void mpack_builder_apply_writes(mpack_writer_t* writer) {
-    mpack_assert(writer->error == mpack_ok);
-    mpack_builder_t* builder = &writer->builder;
-    mpack_log("latest build is %p\n", (void*)builder->latest_build);
-
-    // The difference between buffer and current is the number of bytes that
-    // were written to the page.
-    size_t bytes_written = (size_t)(writer->position - writer->buffer);
-    mpack_log("applying write of %zi bytes to build %p\n", bytes_written, (void*)builder->latest_build);
-
-    mpack_assert(builder->current_page != NULL);
-    mpack_assert(builder->latest_build != NULL);
-    builder->current_page->bytes_used += bytes_written;
-    builder->latest_build->bytes += bytes_written;
-    mpack_log("latest build %p now has %zi bytes\n", (void*)builder->latest_build, builder->latest_build->bytes);
-}
-
-static void mpack_builder_flush(mpack_writer_t* writer) {
-    mpack_assert(writer->error == mpack_ok);
-    mpack_builder_apply_writes(writer);
-    mpack_builder_add_page(writer);
-    mpack_builder_configure_buffer(writer);
-}
-
-MPACK_NOINLINE static void mpack_builder_begin(mpack_writer_t* writer) {
-    mpack_builder_t* builder = &writer->builder;
-    mpack_assert(writer->error == mpack_ok);
-    mpack_assert(builder->current_build == NULL);
-    mpack_assert(builder->latest_build == NULL);
-    mpack_assert(builder->pages == NULL);
-
-    // If this is the first build, we need to stash the real buffer backing our
-    // writer. We'll be diverting the writer to our build buffer.
-    builder->stash_buffer = writer->buffer;
-    builder->stash_position = writer->position;
-    builder->stash_end = writer->end;
-
-    mpack_builder_page_t* page;
-
-    // we've checked that both these sizes are large enough above.
-    #if MPACK_BUILDER_INTERNAL_STORAGE
-    page = (mpack_builder_page_t*)builder->internal;
-    mpack_log("beginning builder with internal storage %p\n", (void*)page);
-    #else
-    page = (mpack_builder_page_t*)MPACK_MALLOC(MPACK_BUILDER_PAGE_SIZE);
-    if (page == NULL) {
-        mpack_writer_flag_error(writer, mpack_error_memory);
-        return;
-    }
-    mpack_log("beginning builder with allocated page %p\n", (void*)page);
-    #endif
-
-    page->next = NULL;
-    page->bytes_used = sizeof(mpack_builder_page_t);
-    builder->pages = page;
-    builder->current_page = page;
-}
-
-static void mpack_builder_build(mpack_writer_t* writer, mpack_type_t type) {
-    mpack_builder_check_sizes(writer);
-    if (mpack_writer_error(writer) != mpack_ok)
-        return;
-
-    mpack_writer_track_element(writer);
-    mpack_writer_track_push_builder(writer, type);
-
-    mpack_builder_t* builder = &writer->builder;
-
-    if (builder->current_build == NULL) {
-        mpack_builder_begin(writer);
-    } else {
-        mpack_builder_apply_writes(writer);
-    }
-    if (mpack_writer_error(writer) != mpack_ok)
-        return;
-
-    // find aligned space for a new build. if there isn't enough space in the
-    // current page, we discard the remaining space in it and allocate a new
-    // page.
-    size_t offset = mpack_builder_align_build(builder->current_page->bytes_used);
-    if (offset + sizeof(mpack_build_t) > mpack_builder_page_size(writer, builder->current_page)) {
-        mpack_log("not enough space for a build. %zi bytes used of %zi in this page\n",
-                builder->current_page->bytes_used, mpack_builder_page_size(writer, builder->current_page));
-        mpack_builder_add_page(writer);
-        // there is always enough space in a fresh page.
-        offset = mpack_builder_align_build(builder->current_page->bytes_used);
-    }
-
-    // allocate the build within the page. note that we don't keep track of the
-    // space wasted due to the offset. instead the previous build has stored
-    // how many bytes follow it, and we'll redo this offset calculation to find
-    // this build after it.
-    mpack_builder_page_t* page = builder->current_page;
-    page->bytes_used = offset + sizeof(mpack_build_t);
-    mpack_assert(page->bytes_used <= mpack_builder_page_size(writer, page));
-    mpack_build_t* build = (mpack_build_t*)((char*)page + offset);
-    mpack_log("created new build %p within page %p, which now has %zi bytes used\n",
-            (void*)build, (void*)page, page->bytes_used);
-
-    // configure the new build
-    build->parent = builder->current_build;
-    build->bytes = 0;
-    build->count = 0;
-    build->type = type;
-    build->key_needs_value = false;
-    build->nested_compound_elements = 0;
-
-    mpack_log("setting current and latest build to new build %p\n", (void*)build);
-    builder->current_build = build;
-    builder->latest_build = build;
-
-    // we always need to provide a buffer that meets the minimum buffer size.
-    // if there isn't enough space, we discard the remaining space in the
-    // current page and allocate a new one.
-    if (mpack_builder_page_remaining(writer, page) < MPACK_WRITER_MINIMUM_BUFFER_SIZE) {
-        mpack_log("less than minimum buffer size in current page. %zi bytes used of %zi in this page\n",
-                builder->current_page->bytes_used, mpack_builder_page_size(writer, builder->current_page));
-        mpack_builder_add_page(writer);
-        if (mpack_writer_error(writer) != mpack_ok)
-            return;
-    }
-    mpack_assert(mpack_builder_page_remaining(writer, builder->current_page) >= MPACK_WRITER_MINIMUM_BUFFER_SIZE);
-    mpack_builder_configure_buffer(writer);
-}
-
-MPACK_NOINLINE
-static void mpack_builder_resolve(mpack_writer_t* writer) {
-    mpack_builder_t* builder = &writer->builder;
-
-    // The starting page is the internal storage (if we have it), otherwise
-    // it's the first page in the array
-    mpack_builder_page_t* page =
-        #if MPACK_BUILDER_INTERNAL_STORAGE
-        (mpack_builder_page_t*)builder->internal
-        #else
-        builder->pages
-        #endif
-        ;
-
-    // We start by restoring the writer's original buffer so we can write the
-    // data for real.
-    writer->buffer = builder->stash_buffer;
-    writer->position = builder->stash_position;
-    writer->end = builder->stash_end;
-
-    // We can also close out the build now.
-    builder->current_build = NULL;
-    builder->latest_build = NULL;
-    builder->current_page = NULL;
-    builder->pages = NULL;
-
-    // the starting page always starts with the first build
-    size_t offset = mpack_builder_align_build(sizeof(mpack_builder_page_t));
-    mpack_build_t* build = (mpack_build_t*)((char*)page + offset);
-    mpack_log("starting resolve with build %p in page %p\n", (void*)build, (void*)page);
-
-    // encoded data immediately follows the build
-    offset += sizeof(mpack_build_t);
-
-    // Walk the list of builds, writing everything out in the buffer. Note that
-    // we don't check for errors anywhere. The lower-level write functions will
-    // all check for errors. We need to walk all pages anyway to free them, so
-    // there's not much point in optimizing an error path at the expense of the
-    // normal path.
-    while (true) {
-
-        // write out the container tag
-        mpack_log("writing out an %s with count %u followed by %zi bytes\n",
-                mpack_type_to_string(build->type), build->count, build->bytes);
-        switch (build->type) {
-            case mpack_type_map:
-                mpack_write_map_notrack(writer, build->count);
-                break;
-            case mpack_type_array:
-                mpack_write_array_notrack(writer, build->count);
-                break;
-            default:
-                mpack_break("invalid type in builder?");
-                mpack_writer_flag_error(writer, mpack_error_bug);
-                return;
-        }
-
-        // figure out how many bytes follow this container. we're going to be
-        // freeing pages as we write, so we need to be done with this build.
-        size_t left = build->bytes;
-        build = NULL;
-
-        // write out all bytes following this container
-        while (left > 0) {
-            size_t bytes_used = page->bytes_used;
-            if (offset < bytes_used) {
-                size_t step = bytes_used - offset;
-                if (step > left)
-                    step = left;
-                mpack_log("writing out %zi bytes starting at %p in page %p\n",
-                        step, (void*)((char*)page + offset), (void*)page);
-                mpack_write_native(writer, (char*)page + offset, step);
-                offset += step;
-                left -= step;
-            }
-
-            if (left == 0) {
-                mpack_log("done writing bytes for this build\n");
-                break;
-            }
-
-            // still need to write more bytes. free this page and jump to the
-            // next one.
-            mpack_builder_page_t* next_page = page->next;
-            mpack_builder_free_page(writer, page);
-            page = next_page;
-            // bytes on the next page immediately follow the header.
-            offset = sizeof(mpack_builder_page_t);
-        }
-
-        // now see if we can find another build.
-        offset = mpack_builder_align_build(offset);
-        if (offset + sizeof(mpack_build_t) >= mpack_builder_page_size(writer, page)) {
-            mpack_log("not enough room in this page for another build\n");
-            mpack_builder_page_t* next_page = page->next;
-            mpack_builder_free_page(writer, page);
-            page = next_page;
-            if (page == NULL) {
-                mpack_log("no more pages\n");
-                // there are no more pages. we're done.
-                break;
-            }
-            offset = mpack_builder_align_build(sizeof(mpack_builder_page_t));
-        }
-        if (offset + sizeof(mpack_build_t) > page->bytes_used) {
-            // there is no more data. we're done.
-            mpack_log("no more data\n");
-            mpack_builder_free_page(writer, page);
-            break;
-        }
-
-        // we've found another build. loop around!
-        build = (mpack_build_t*)((char*)page + offset);
-        offset += sizeof(mpack_build_t);
-        mpack_log("found build %p\n", (void*)build);
-    }
-
-    mpack_log("done resolve.\n");
-}
-
-static void mpack_builder_complete(mpack_writer_t* writer, mpack_type_t type) {
-    if (mpack_writer_error(writer) != mpack_ok)
-        return;
-
-    mpack_writer_track_pop_builder(writer, type);
-    mpack_builder_t* builder = &writer->builder;
-    mpack_assert(builder->current_build != NULL, "no build in progress!");
-    mpack_assert(builder->latest_build != NULL, "missing latest build!");
-    mpack_assert(builder->current_build->type == type, "completing wrong type!");
-    mpack_log("completing build %p\n", (void*)builder->current_build);
-
-    if (builder->current_build->key_needs_value) {
-        mpack_break("an odd number of elements were written in a map!");
-        mpack_writer_flag_error(writer, mpack_error_bug);
-        return;
-    }
-
-    if (builder->current_build->nested_compound_elements != 0) {
-        mpack_break("there is a nested unfinished non-build map or array in this build.");
-        mpack_writer_flag_error(writer, mpack_error_bug);
-        return;
-    }
-
-    // We need to apply whatever writes have been made to the current build
-    // before popping it.
-    mpack_builder_apply_writes(writer);
-
-    // For a nested build, we just switch the current build back to its parent.
-    if (builder->current_build->parent != NULL) {
-        mpack_log("setting current build to parent build %p. latest is still %p.\n",
-                (void*)builder->current_build->parent, (void*)builder->latest_build);
-        builder->current_build = builder->current_build->parent;
-        mpack_builder_configure_buffer(writer);
-    } else {
-        // We're completing the final build.
-        mpack_builder_resolve(writer);
-    }
-}
-
-void mpack_build_map(mpack_writer_t* writer) {
-    mpack_builder_build(writer, mpack_type_map);
-}
-
-void mpack_build_array(mpack_writer_t* writer) {
-    mpack_builder_build(writer, mpack_type_array);
-}
-
-void mpack_complete_map(mpack_writer_t* writer) {
-    mpack_builder_complete(writer, mpack_type_map);
-}
-
-void mpack_complete_array(mpack_writer_t* writer) {
-    mpack_builder_complete(writer, mpack_type_array);
-}
-
-#endif // MPACK_BUILDER
-#endif // MPACK_WRITER
-
-MPACK_SILENCE_WARNINGS_END
-
-/* mpack/mpack-reader.c.c */
-
-#define MPACK_INTERNAL 1
-
-/* #include "mpack-reader.h" */
-
-MPACK_SILENCE_WARNINGS_BEGIN
-
-#if MPACK_READER
-
-static void mpack_reader_skip_using_fill(mpack_reader_t* reader, size_t count);
-
-void mpack_reader_init(mpack_reader_t* reader, char* buffer, size_t size, size_t count) {
-    mpack_assert(buffer != NULL, "buffer is NULL");
-
-    mpack_memset(reader, 0, sizeof(*reader));
-    reader->buffer = buffer;
-    reader->size = size;
-    reader->data = buffer;
-    reader->end = buffer + count;
-
-    #if MPACK_READ_TRACKING
-    mpack_reader_flag_if_error(reader, mpack_track_init(&reader->track));
-    #endif
-
-    mpack_log("===========================\n");
-    mpack_log("initializing reader with buffer size %i\n", (int)size);
-}
-
-void mpack_reader_init_error(mpack_reader_t* reader, mpack_error_t error) {
-    mpack_memset(reader, 0, sizeof(*reader));
-    reader->error = error;
-
-    mpack_log("===========================\n");
-    mpack_log("initializing reader error state %i\n", (int)error);
-}
-
-void mpack_reader_init_data(mpack_reader_t* reader, const char* data, size_t count) {
-    mpack_assert(data != NULL, "data is NULL");
-
-    mpack_memset(reader, 0, sizeof(*reader));
-    reader->data = data;
-    reader->end = data + count;
-
-    #if MPACK_READ_TRACKING
-    mpack_reader_flag_if_error(reader, mpack_track_init(&reader->track));
-    #endif
-
-    mpack_log("===========================\n");
-    mpack_log("initializing reader with data size %i\n", (int)count);
-}
-
-void mpack_reader_set_fill(mpack_reader_t* reader, mpack_reader_fill_t fill) {
-    MPACK_STATIC_ASSERT(MPACK_READER_MINIMUM_BUFFER_SIZE >= MPACK_MAXIMUM_TAG_SIZE,
-            "minimum buffer size must fit any tag!");
-
-    if (reader->size == 0) {
-        mpack_break("cannot use fill function without a writeable buffer!");
-        mpack_reader_flag_error(reader, mpack_error_bug);
-        return;
-    }
-
-    if (reader->size < MPACK_READER_MINIMUM_BUFFER_SIZE) {
-        mpack_break("buffer size is %i, but minimum buffer size for fill is %i",
-                (int)reader->size, MPACK_READER_MINIMUM_BUFFER_SIZE);
-        mpack_reader_flag_error(reader, mpack_error_bug);
-        return;
-    }
-
-    reader->fill = fill;
-}
-
-void mpack_reader_set_skip(mpack_reader_t* reader, mpack_reader_skip_t skip) {
-    mpack_assert(reader->size != 0, "cannot use skip function without a writeable buffer!");
-    reader->skip = skip;
-}
-
-#if MPACK_STDIO
-static size_t mpack_file_reader_fill(mpack_reader_t* reader, char* buffer, size_t count) {
-    if (feof((FILE *)reader->context)) {
-       mpack_reader_flag_error(reader, mpack_error_eof);
-       return 0;
-    }
-    return fread((void*)buffer, 1, count, (FILE*)reader->context);
-}
-
-static void mpack_file_reader_skip(mpack_reader_t* reader, size_t count) {
-    if (mpack_reader_error(reader) != mpack_ok)
-        return;
-    FILE* file = (FILE*)reader->context;
-
-    // We call ftell() to test whether the stream is seekable
-    // without causing a file error.
-    if (ftell(file) >= 0) {
-        mpack_log("seeking forward %i bytes\n", (int)count);
-        if (fseek(file, (long int)count, SEEK_CUR) == 0)
-            return;
-        mpack_log("fseek() didn't return zero!\n");
-        if (ferror(file)) {
-            mpack_reader_flag_error(reader, mpack_error_io);
-            return;
-        }
-    }
-
-    // If the stream is not seekable, fall back to the fill function.
-    mpack_reader_skip_using_fill(reader, count);
-}
-
-static void mpack_file_reader_teardown(mpack_reader_t* reader) {
-    MPACK_FREE(reader->buffer);
-    reader->buffer = NULL;
-    reader->context = NULL;
-    reader->size = 0;
-    reader->fill = NULL;
-    reader->skip = NULL;
-    reader->teardown = NULL;
-}
-
-static void mpack_file_reader_teardown_close(mpack_reader_t* reader) {
-    FILE* file = (FILE*)reader->context;
-
-    if (file) {
-        int ret = fclose(file);
-        if (ret != 0)
-            mpack_reader_flag_error(reader, mpack_error_io);
-    }
-
-    mpack_file_reader_teardown(reader);
-}
-
-void mpack_reader_init_stdfile(mpack_reader_t* reader, FILE* file, bool close_when_done) {
-    mpack_assert(file != NULL, "file is NULL");
-
-    size_t capacity = MPACK_BUFFER_SIZE;
-    char* buffer = (char*)MPACK_MALLOC(capacity);
-    if (buffer == NULL) {
-        mpack_reader_init_error(reader, mpack_error_memory);
-        if (close_when_done) {
-            fclose(file);
-        }
-        return;
-    }
-
-    mpack_reader_init(reader, buffer, capacity, 0);
-    mpack_reader_set_context(reader, file);
-    mpack_reader_set_fill(reader, mpack_file_reader_fill);
-    mpack_reader_set_skip(reader, mpack_file_reader_skip);
-    mpack_reader_set_teardown(reader, close_when_done ?
-            mpack_file_reader_teardown_close :
-            mpack_file_reader_teardown);
-}
-
-void mpack_reader_init_filename(mpack_reader_t* reader, const char* filename) {
-    mpack_assert(filename != NULL, "filename is NULL");
-
-    FILE* file = fopen(filename, "rb");
-    if (file == NULL) {
-        mpack_reader_init_error(reader, mpack_error_io);
-        return;
-    }
-
-    mpack_reader_init_stdfile(reader, file, true);
-}
-#endif
-
-mpack_error_t mpack_reader_destroy(mpack_reader_t* reader) {
-
-    // clean up tracking, asserting if we're not already in an error state
-    #if MPACK_READ_TRACKING
-    mpack_reader_flag_if_error(reader, mpack_track_destroy(&reader->track, mpack_reader_error(reader) != mpack_ok));
-    #endif
-
-    if (reader->teardown)
-        reader->teardown(reader);
-    reader->teardown = NULL;
-
-    return reader->error;
-}
-
-size_t mpack_reader_remaining(mpack_reader_t* reader, const char** data) {
-    if (mpack_reader_error(reader) != mpack_ok)
-        return 0;
-
-    #if MPACK_READ_TRACKING
-    if (mpack_reader_flag_if_error(reader, mpack_track_check_empty(&reader->track)) != mpack_ok)
-        return 0;
-    #endif
-
-    if (data)
-        *data = reader->data;
-    return (size_t)(reader->end - reader->data);
-}
-
-void mpack_reader_flag_error(mpack_reader_t* reader, mpack_error_t error) {
-    mpack_log("reader %p setting error %i: %s\n", (void*)reader, (int)error, mpack_error_to_string(error));
-
-    if (reader->error == mpack_ok) {
-        reader->error = error;
-        reader->end = reader->data;
-        if (reader->error_fn)
-            reader->error_fn(reader, error);
-    }
-}
-
-// Loops on the fill function, reading between the minimum and
-// maximum number of bytes and flagging an error if it fails.
-MPACK_NOINLINE static size_t mpack_fill_range(mpack_reader_t* reader, char* p, size_t min_bytes, size_t max_bytes) {
-    mpack_assert(reader->fill != NULL, "mpack_fill_range() called with no fill function?");
-    mpack_assert(min_bytes > 0, "cannot fill zero bytes!");
-    mpack_assert(max_bytes >= min_bytes, "min_bytes %i cannot be larger than max_bytes %i!",
-            (int)min_bytes, (int)max_bytes);
-
-    size_t count = 0;
-    while (count < min_bytes) {
-        size_t read = reader->fill(reader, p + count, max_bytes - count);
-
-        // Reader fill functions can flag an error or return 0 on failure. We
-        // also guard against functions that return -1 just in case.
-        if (mpack_reader_error(reader) != mpack_ok)
-            return 0;
-        if (read == 0 || read == ((size_t)(-1))) {
-            mpack_reader_flag_error(reader, mpack_error_io);
-            return 0;
-        }
-
-        count += read;
-    }
-    return count;
-}
-
-MPACK_NOINLINE bool mpack_reader_ensure_straddle(mpack_reader_t* reader, size_t count) {
-    mpack_assert(count != 0, "cannot ensure zero bytes!");
-    mpack_assert(reader->error == mpack_ok, "reader cannot be in an error state!");
-
-    mpack_assert(count > (size_t)(reader->end - reader->data),
-            "straddling ensure requested for %i bytes, but there are %i bytes "
-            "left in buffer. call mpack_reader_ensure() instead",
-            (int)count, (int)(reader->end - reader->data));
-
-    // we'll need a fill function to get more data. if there's no
-    // fill function, the buffer should contain an entire MessagePack
-    // object, so we raise mpack_error_invalid instead of mpack_error_io
-    // on truncated data.
-    if (reader->fill == NULL) {
-        mpack_reader_flag_error(reader, mpack_error_invalid);
-        return false;
-    }
-
-    // we need enough space in the buffer. if the buffer is not
-    // big enough, we return mpack_error_too_big (since this is
-    // for an in-place read larger than the buffer size.)
-    if (count > reader->size) {
-        mpack_reader_flag_error(reader, mpack_error_too_big);
-        return false;
-    }
-
-    // move the existing data to the start of the buffer
-    size_t left = (size_t)(reader->end - reader->data);
-    mpack_memmove(reader->buffer, reader->data, left);
-    reader->end -= reader->data - reader->buffer;
-    reader->data = reader->buffer;
-
-    // read at least the necessary number of bytes, accepting up to the
-    // buffer size
-    size_t read = mpack_fill_range(reader, reader->buffer + left,
-            count - left, reader->size - left);
-    if (mpack_reader_error(reader) != mpack_ok)
-        return false;
-    reader->end += read;
-    return true;
-}
-
-// Reads count bytes into p. Used when there are not enough bytes
-// left in the buffer to satisfy a read.
-MPACK_NOINLINE void mpack_read_native_straddle(mpack_reader_t* reader, char* p, size_t count) {
-    mpack_assert(count == 0 || p != NULL, "data pointer for %i bytes is NULL", (int)count);
-
-    if (mpack_reader_error(reader) != mpack_ok) {
-        mpack_memset(p, 0, count);
-        return;
-    }
-
-    size_t left = (size_t)(reader->end - reader->data);
-    mpack_log("big read for %i bytes into %p, %i left in buffer, buffer size %i\n",
-            (int)count, p, (int)left, (int)reader->size);
-
-    if (count <= left) {
-        mpack_assert(0,
-                "big read requested for %i bytes, but there are %i bytes "
-                "left in buffer. call mpack_read_native() instead",
-                (int)count, (int)left);
-        mpack_reader_flag_error(reader, mpack_error_bug);
-        mpack_memset(p, 0, count);
-        return;
-    }
-
-    // we'll need a fill function to get more data. if there's no
-    // fill function, the buffer should contain an entire MessagePack
-    // object, so we raise mpack_error_invalid instead of mpack_error_io
-    // on truncated data.
-    if (reader->fill == NULL) {
-        mpack_reader_flag_error(reader, mpack_error_invalid);
-        mpack_memset(p, 0, count);
-        return;
-    }
-
-    if (reader->size == 0) {
-        // somewhat debatable what error should be returned here. when
-        // initializing a reader with an in-memory buffer it's not
-        // necessarily a bug if the data is blank; it might just have
-        // been truncated to zero. for this reason we return the same
-        // error as if the data was truncated.
-        mpack_reader_flag_error(reader, mpack_error_io);
-        mpack_memset(p, 0, count);
-        return;
-    }
-
-    // flush what's left of the buffer
-    if (left > 0) {
-        mpack_log("flushing %i bytes remaining in buffer\n", (int)left);
-        mpack_memcpy(p, reader->data, left);
-        count -= left;
-        p += left;
-        reader->data += left;
-    }
-
-    // if the remaining data needed is some small fraction of the
-    // buffer size, we'll try to fill the buffer as much as possible
-    // and copy the needed data out.
-    if (count <= reader->size / MPACK_READER_SMALL_FRACTION_DENOMINATOR) {
-        size_t read = mpack_fill_range(reader, reader->buffer, count, reader->size);
-        if (mpack_reader_error(reader) != mpack_ok)
-            return;
-        mpack_memcpy(p, reader->buffer, count);
-        reader->data = reader->buffer + count;
-        reader->end = reader->buffer + read;
-
-    // otherwise we read the remaining data directly into the target.
-    } else {
-        mpack_log("reading %i additional bytes\n", (int)count);
-        mpack_fill_range(reader, p, count, count);
-    }
-}
-
-MPACK_NOINLINE static void mpack_skip_bytes_straddle(mpack_reader_t* reader, size_t count) {
-
-    // we'll need at least a fill function to skip more data. if there's
-    // no fill function, the buffer should contain an entire MessagePack
-    // object, so we raise mpack_error_invalid instead of mpack_error_io
-    // on truncated data. (see mpack_read_native_straddle())
-    if (reader->fill == NULL) {
-        mpack_log("reader has no fill function!\n");
-        mpack_reader_flag_error(reader, mpack_error_invalid);
-        return;
-    }
-
-    // discard whatever's left in the buffer
-    size_t left = (size_t)(reader->end - reader->data);
-    mpack_log("discarding %i bytes still in buffer\n", (int)left);
-    count -= left;
-    reader->data = reader->end;
-
-    // use the skip function if we've got one, and if we're trying
-    // to skip a lot of data. if we only need to skip some tiny
-    // fraction of the buffer size, it's probably better to just
-    // fill the buffer and skip from it instead of trying to seek.
-    if (reader->skip && count > reader->size / 16) {
-        mpack_log("calling skip function for %i bytes\n", (int)count);
-        reader->skip(reader, count);
-        return;
-    }
-
-    mpack_reader_skip_using_fill(reader, count);
-}
-
-void mpack_skip_bytes(mpack_reader_t* reader, size_t count) {
-    if (mpack_reader_error(reader) != mpack_ok)
-        return;
-    mpack_log("skip requested for %i bytes\n", (int)count);
-
-    mpack_reader_track_bytes(reader, count);
-
-    // check if we have enough in the buffer already
-    size_t left = (size_t)(reader->end - reader->data);
-    if (left >= count) {
-        mpack_log("skipping %u bytes still in buffer\n", (uint32_t)count);
-        reader->data += count;
-        return;
-    }
-
-    mpack_skip_bytes_straddle(reader, count);
-}
-
-MPACK_NOINLINE static void mpack_reader_skip_using_fill(mpack_reader_t* reader, size_t count) {
-    mpack_assert(reader->fill != NULL, "missing fill function!");
-    mpack_assert(reader->data == reader->end, "there are bytes left in the buffer!");
-    mpack_assert(reader->error == mpack_ok, "should not have called this in an error state (%i)", reader->error);
-    mpack_log("skip using fill for %i bytes\n", (int)count);
-
-    // fill and discard multiples of the buffer size
-    while (count > reader->size) {
-        mpack_log("filling and discarding buffer of %i bytes\n", (int)reader->size);
-        if (mpack_fill_range(reader, reader->buffer, reader->size, reader->size) < reader->size) {
-            mpack_reader_flag_error(reader, mpack_error_io);
-            return;
-        }
-        count -= reader->size;
-    }
-
-    // fill the buffer as much as possible
-    reader->data = reader->buffer;
-    size_t read = mpack_fill_range(reader, reader->buffer, count, reader->size);
-    if (read < count) {
-        mpack_reader_flag_error(reader, mpack_error_io);
-        return;
-    }
-    reader->end = reader->data + read;
-    mpack_log("filled %i bytes into buffer; discarding %i bytes\n", (int)read, (int)count);
-    reader->data += count;
-}
-
-void mpack_read_bytes(mpack_reader_t* reader, char* p, size_t count) {
-    mpack_assert(p != NULL, "destination for read of %i bytes is NULL", (int)count);
-    mpack_reader_track_bytes(reader, count);
-    mpack_read_native(reader, p, count);
-}
-
-void mpack_read_utf8(mpack_reader_t* reader, char* p, size_t byte_count) {
-    mpack_assert(p != NULL, "destination for read of %i bytes is NULL", (int)byte_count);
-    mpack_reader_track_str_bytes_all(reader, byte_count);
-    mpack_read_native(reader, p, byte_count);
-
-    if (mpack_reader_error(reader) == mpack_ok && !mpack_utf8_check(p, byte_count))
-        mpack_reader_flag_error(reader, mpack_error_type);
-}
-
-static void mpack_read_cstr_unchecked(mpack_reader_t* reader, char* buf, size_t buffer_size, size_t byte_count) {
-    mpack_assert(buf != NULL, "destination for read of %i bytes is NULL", (int)byte_count);
-    mpack_assert(buffer_size >= 1, "buffer size is zero; you must have room for at least a null-terminator");
-
-    if (mpack_reader_error(reader)) {
-        buf[0] = 0;
-        return;
-    }
-
-    if (byte_count > buffer_size - 1) {
-        mpack_reader_flag_error(reader, mpack_error_too_big);
-        buf[0] = 0;
-        return;
-    }
-
-    mpack_reader_track_str_bytes_all(reader, byte_count);
-    mpack_read_native(reader, buf, byte_count);
-    buf[byte_count] = 0;
-}
-
-void mpack_read_cstr(mpack_reader_t* reader, char* buf, size_t buffer_size, size_t byte_count) {
-    mpack_read_cstr_unchecked(reader, buf, buffer_size, byte_count);
-
-    // check for null bytes
-    if (mpack_reader_error(reader) == mpack_ok && !mpack_str_check_no_null(buf, byte_count)) {
-        buf[0] = 0;
-        mpack_reader_flag_error(reader, mpack_error_type);
-    }
-}
-
-void mpack_read_utf8_cstr(mpack_reader_t* reader, char* buf, size_t buffer_size, size_t byte_count) {
-    mpack_read_cstr_unchecked(reader, buf, buffer_size, byte_count);
-
-    // check encoding
-    if (mpack_reader_error(reader) == mpack_ok && !mpack_utf8_check_no_null(buf, byte_count)) {
-        buf[0] = 0;
-        mpack_reader_flag_error(reader, mpack_error_type);
-    }
-}
-
-#ifdef MPACK_MALLOC
-// Reads native bytes with error callback disabled. This allows MPack reader functions
-// to hold an allocated buffer and read native data into it without leaking it in
-// case of a non-local jump (longjmp, throw) out of an error handler.
-static void mpack_read_native_noerrorfn(mpack_reader_t* reader, char* p, size_t count) {
-    mpack_assert(reader->error == mpack_ok, "cannot call if an error is already flagged!");
-    mpack_reader_error_t error_fn = reader->error_fn;
-    reader->error_fn = NULL;
-    mpack_read_native(reader, p, count);
-    reader->error_fn = error_fn;
-}
-
-char* mpack_read_bytes_alloc_impl(mpack_reader_t* reader, size_t count, bool null_terminated) {
-
-    // track the bytes first in case it jumps
-    mpack_reader_track_bytes(reader, count);
-    if (mpack_reader_error(reader) != mpack_ok)
-        return NULL;
-
-    // cannot allocate zero bytes. this is not an error.
-    if (count == 0 && null_terminated == false)
-        return NULL;
-
-    // allocate data
-    char* data = (char*)MPACK_MALLOC(count + (null_terminated ? 1 : 0)); // TODO: can this overflow?
-    if (data == NULL) {
-        mpack_reader_flag_error(reader, mpack_error_memory);
-        return NULL;
-    }
-
-    // read with error callback disabled so we don't leak our buffer
-    mpack_read_native_noerrorfn(reader, data, count);
-
-    // report flagged errors
-    if (mpack_reader_error(reader) != mpack_ok) {
-        MPACK_FREE(data);
-        if (reader->error_fn)
-            reader->error_fn(reader, mpack_reader_error(reader));
-        return NULL;
-    }
-
-    if (null_terminated)
-        data[count] = '\0';
-    return data;
-}
-#endif
-
-// read inplace without tracking (since there are different
-// tracking modes for different inplace readers)
-static const char* mpack_read_bytes_inplace_notrack(mpack_reader_t* reader, size_t count) {
-    if (mpack_reader_error(reader) != mpack_ok)
-        return NULL;
-
-    // if we have enough bytes already in the buffer, we can return it directly.
-    if ((size_t)(reader->end - reader->data) >= count) {
-        const char* bytes = reader->data;
-        reader->data += count;
-        return bytes;
-    }
-
-    if (!mpack_reader_ensure(reader, count))
-        return NULL;
-
-    const char* bytes = reader->data;
-    reader->data += count;
-    return bytes;
-}
-
-const char* mpack_read_bytes_inplace(mpack_reader_t* reader, size_t count) {
-    mpack_reader_track_bytes(reader, count);
-    return mpack_read_bytes_inplace_notrack(reader, count);
-}
-
-const char* mpack_read_utf8_inplace(mpack_reader_t* reader, size_t count) {
-    mpack_reader_track_str_bytes_all(reader, count);
-    const char* str = mpack_read_bytes_inplace_notrack(reader, count);
-
-    if (mpack_reader_error(reader) == mpack_ok && !mpack_utf8_check(str, count)) {
-        mpack_reader_flag_error(reader, mpack_error_type);
-        return NULL;
-    }
-
-    return str;
-}
-
-static size_t mpack_parse_tag(mpack_reader_t* reader, mpack_tag_t* tag) {
-    mpack_assert(reader->error == mpack_ok, "reader cannot be in an error state!");
-
-    if (!mpack_reader_ensure(reader, 1))
-        return 0;
-    uint8_t type = mpack_load_u8(reader->data);
-
-    // unfortunately, by far the fastest way to parse a tag is to switch
-    // on the first byte, and to explicitly list every possible byte. so for
-    // infix types, the list of cases is quite large.
-    //
-    // in size-optimized builds, we switch on the top four bits first to
-    // handle most infix types with a smaller jump table to save space.
-
-    #if MPACK_OPTIMIZE_FOR_SIZE
-    switch (type >> 4) {
-
-        // positive fixnum
-        case 0x0: case 0x1: case 0x2: case 0x3:
-        case 0x4: case 0x5: case 0x6: case 0x7:
-            *tag = mpack_tag_make_uint(type);
-            return 1;
-
-        // negative fixnum
-        case 0xe: case 0xf:
-            *tag = mpack_tag_make_int((int8_t)type);
-            return 1;
-
-        // fixmap
-        case 0x8:
-            *tag = mpack_tag_make_map(type & ~0xf0u);
-            return 1;
-
-        // fixarray
-        case 0x9:
-            *tag = mpack_tag_make_array(type & ~0xf0u);
-            return 1;
-
-        // fixstr
-        case 0xa: case 0xb:
-            *tag = mpack_tag_make_str(type & ~0xe0u);
-            return 1;
-
-        // not one of the common infix types
-        default:
-            break;
-
-    }
-    #endif
-
-    // handle individual type tags
-    switch (type) {
-
-        #if !MPACK_OPTIMIZE_FOR_SIZE
-        // positive fixnum
-        case 0x00: case 0x01: case 0x02: case 0x03: case 0x04: case 0x05: case 0x06: case 0x07:
-        case 0x08: case 0x09: case 0x0a: case 0x0b: case 0x0c: case 0x0d: case 0x0e: case 0x0f:
-        case 0x10: case 0x11: case 0x12: case 0x13: case 0x14: case 0x15: case 0x16: case 0x17:
-        case 0x18: case 0x19: case 0x1a: case 0x1b: case 0x1c: case 0x1d: case 0x1e: case 0x1f:
-        case 0x20: case 0x21: case 0x22: case 0x23: case 0x24: case 0x25: case 0x26: case 0x27:
-        case 0x28: case 0x29: case 0x2a: case 0x2b: case 0x2c: case 0x2d: case 0x2e: case 0x2f:
-        case 0x30: case 0x31: case 0x32: case 0x33: case 0x34: case 0x35: case 0x36: case 0x37:
-        case 0x38: case 0x39: case 0x3a: case 0x3b: case 0x3c: case 0x3d: case 0x3e: case 0x3f:
-        case 0x40: case 0x41: case 0x42: case 0x43: case 0x44: case 0x45: case 0x46: case 0x47:
-        case 0x48: case 0x49: case 0x4a: case 0x4b: case 0x4c: case 0x4d: case 0x4e: case 0x4f:
-        case 0x50: case 0x51: case 0x52: case 0x53: case 0x54: case 0x55: case 0x56: case 0x57:
-        case 0x58: case 0x59: case 0x5a: case 0x5b: case 0x5c: case 0x5d: case 0x5e: case 0x5f:
-        case 0x60: case 0x61: case 0x62: case 0x63: case 0x64: case 0x65: case 0x66: case 0x67:
-        case 0x68: case 0x69: case 0x6a: case 0x6b: case 0x6c: case 0x6d: case 0x6e: case 0x6f:
-        case 0x70: case 0x71: case 0x72: case 0x73: case 0x74: case 0x75: case 0x76: case 0x77:
-        case 0x78: case 0x79: case 0x7a: case 0x7b: case 0x7c: case 0x7d: case 0x7e: case 0x7f:
-            *tag = mpack_tag_make_uint(type);
-            return 1;
-
-        // negative fixnum
-        case 0xe0: case 0xe1: case 0xe2: case 0xe3: case 0xe4: case 0xe5: case 0xe6: case 0xe7:
-        case 0xe8: case 0xe9: case 0xea: case 0xeb: case 0xec: case 0xed: case 0xee: case 0xef:
-        case 0xf0: case 0xf1: case 0xf2: case 0xf3: case 0xf4: case 0xf5: case 0xf6: case 0xf7:
-        case 0xf8: case 0xf9: case 0xfa: case 0xfb: case 0xfc: case 0xfd: case 0xfe: case 0xff:
-            *tag = mpack_tag_make_int((int8_t)type);
-            return 1;
-
-        // fixmap
-        case 0x80: case 0x81: case 0x82: case 0x83: case 0x84: case 0x85: case 0x86: case 0x87:
-        case 0x88: case 0x89: case 0x8a: case 0x8b: case 0x8c: case 0x8d: case 0x8e: case 0x8f:
-            *tag = mpack_tag_make_map(type & ~0xf0u);
-            return 1;
-
-        // fixarray
-        case 0x90: case 0x91: case 0x92: case 0x93: case 0x94: case 0x95: case 0x96: case 0x97:
-        case 0x98: case 0x99: case 0x9a: case 0x9b: case 0x9c: case 0x9d: case 0x9e: case 0x9f:
-            *tag = mpack_tag_make_array(type & ~0xf0u);
-            return 1;
-
-        // fixstr
-        case 0xa0: case 0xa1: case 0xa2: case 0xa3: case 0xa4: case 0xa5: case 0xa6: case 0xa7:
-        case 0xa8: case 0xa9: case 0xaa: case 0xab: case 0xac: case 0xad: case 0xae: case 0xaf:
-        case 0xb0: case 0xb1: case 0xb2: case 0xb3: case 0xb4: case 0xb5: case 0xb6: case 0xb7:
-        case 0xb8: case 0xb9: case 0xba: case 0xbb: case 0xbc: case 0xbd: case 0xbe: case 0xbf:
-            *tag = mpack_tag_make_str(type & ~0xe0u);
-            return 1;
-        #endif
-
-        // nil
-        case 0xc0:
-            *tag = mpack_tag_make_nil();
-            return 1;
-
-        // bool
-        case 0xc2: case 0xc3:
-            *tag = mpack_tag_make_bool((bool)(type & 1));
-            return 1;
-
-        // bin8
-        case 0xc4:
-            if (!mpack_reader_ensure(reader, MPACK_TAG_SIZE_BIN8))
-                return 0;
-            *tag = mpack_tag_make_bin(mpack_load_u8(reader->data + 1));
-            return MPACK_TAG_SIZE_BIN8;
-
-        // bin16
-        case 0xc5:
-            if (!mpack_reader_ensure(reader, MPACK_TAG_SIZE_BIN16))
-                return 0;
-            *tag = mpack_tag_make_bin(mpack_load_u16(reader->data + 1));
-            return MPACK_TAG_SIZE_BIN16;
-
-        // bin32
-        case 0xc6:
-            if (!mpack_reader_ensure(reader, MPACK_TAG_SIZE_BIN32))
-                return 0;
-            *tag = mpack_tag_make_bin(mpack_load_u32(reader->data + 1));
-            return MPACK_TAG_SIZE_BIN32;
-
-        #if MPACK_EXTENSIONS
-        // ext8
-        case 0xc7:
-            if (!mpack_reader_ensure(reader, MPACK_TAG_SIZE_EXT8))
-                return 0;
-            *tag = mpack_tag_make_ext(mpack_load_i8(reader->data + 2), mpack_load_u8(reader->data + 1));
-            return MPACK_TAG_SIZE_EXT8;
-
-        // ext16
-        case 0xc8:
-            if (!mpack_reader_ensure(reader, MPACK_TAG_SIZE_EXT16))
-                return 0;
-            *tag = mpack_tag_make_ext(mpack_load_i8(reader->data + 3), mpack_load_u16(reader->data + 1));
-            return MPACK_TAG_SIZE_EXT16;
-
-        // ext32
-        case 0xc9:
-            if (!mpack_reader_ensure(reader, MPACK_TAG_SIZE_EXT32))
-                return 0;
-            *tag = mpack_tag_make_ext(mpack_load_i8(reader->data + 5), mpack_load_u32(reader->data + 1));
-            return MPACK_TAG_SIZE_EXT32;
-        #endif
-
-        // float
-        case 0xca:
-            if (!mpack_reader_ensure(reader, MPACK_TAG_SIZE_FLOAT))
-                return 0;
-            #if MPACK_FLOAT
-            *tag = mpack_tag_make_float(mpack_load_float(reader->data + 1));
-            #else
-            *tag = mpack_tag_make_raw_float(mpack_load_u32(reader->data + 1));
-            #endif
-            return MPACK_TAG_SIZE_FLOAT;
-
-        // double
-        case 0xcb:
-            if (!mpack_reader_ensure(reader, MPACK_TAG_SIZE_DOUBLE))
-                return 0;
-            #if MPACK_DOUBLE
-            *tag = mpack_tag_make_double(mpack_load_double(reader->data + 1));
-            #else
-            *tag = mpack_tag_make_raw_double(mpack_load_u64(reader->data + 1));
-            #endif
-            return MPACK_TAG_SIZE_DOUBLE;
-
-        // uint8
-        case 0xcc:
-            if (!mpack_reader_ensure(reader, MPACK_TAG_SIZE_U8))
-                return 0;
-            *tag = mpack_tag_make_uint(mpack_load_u8(reader->data + 1));
-            return MPACK_TAG_SIZE_U8;
-
-        // uint16
-        case 0xcd:
-            if (!mpack_reader_ensure(reader, MPACK_TAG_SIZE_U16))
-                return 0;
-            *tag = mpack_tag_make_uint(mpack_load_u16(reader->data + 1));
-            return MPACK_TAG_SIZE_U16;
-
-        // uint32
-        case 0xce:
-            if (!mpack_reader_ensure(reader, MPACK_TAG_SIZE_U32))
-                return 0;
-            *tag = mpack_tag_make_uint(mpack_load_u32(reader->data + 1));
-            return MPACK_TAG_SIZE_U32;
-
-        // uint64
-        case 0xcf:
-            if (!mpack_reader_ensure(reader, MPACK_TAG_SIZE_U64))
-                return 0;
-            *tag = mpack_tag_make_uint(mpack_load_u64(reader->data + 1));
-            return MPACK_TAG_SIZE_U64;
-
-        // int8
-        case 0xd0:
-            if (!mpack_reader_ensure(reader, MPACK_TAG_SIZE_I8))
-                return 0;
-            *tag = mpack_tag_make_int(mpack_load_i8(reader->data + 1));
-            return MPACK_TAG_SIZE_I8;
-
-        // int16
-        case 0xd1:
-            if (!mpack_reader_ensure(reader, MPACK_TAG_SIZE_I16))
-                return 0;
-            *tag = mpack_tag_make_int(mpack_load_i16(reader->data + 1));
-            return MPACK_TAG_SIZE_I16;
-
-        // int32
-        case 0xd2:
-            if (!mpack_reader_ensure(reader, MPACK_TAG_SIZE_I32))
-                return 0;
-            *tag = mpack_tag_make_int(mpack_load_i32(reader->data + 1));
-            return MPACK_TAG_SIZE_I32;
-
-        // int64
-        case 0xd3:
-            if (!mpack_reader_ensure(reader, MPACK_TAG_SIZE_I64))
-                return 0;
-            *tag = mpack_tag_make_int(mpack_load_i64(reader->data + 1));
-            return MPACK_TAG_SIZE_I64;
-
-        #if MPACK_EXTENSIONS
-        // fixext1
-        case 0xd4:
-            if (!mpack_reader_ensure(reader, MPACK_TAG_SIZE_FIXEXT1))
-                return 0;
-            *tag = mpack_tag_make_ext(mpack_load_i8(reader->data + 1), 1);
-            return MPACK_TAG_SIZE_FIXEXT1;
-
-        // fixext2
-        case 0xd5:
-            if (!mpack_reader_ensure(reader, MPACK_TAG_SIZE_FIXEXT2))
-                return 0;
-            *tag = mpack_tag_make_ext(mpack_load_i8(reader->data + 1), 2);
-            return MPACK_TAG_SIZE_FIXEXT2;
-
-        // fixext4
-        case 0xd6:
-            if (!mpack_reader_ensure(reader, MPACK_TAG_SIZE_FIXEXT4))
-                return 0;
-            *tag = mpack_tag_make_ext(mpack_load_i8(reader->data + 1), 4);
-            return 2;
-
-        // fixext8
-        case 0xd7:
-            if (!mpack_reader_ensure(reader, MPACK_TAG_SIZE_FIXEXT8))
-                return 0;
-            *tag = mpack_tag_make_ext(mpack_load_i8(reader->data + 1), 8);
-            return MPACK_TAG_SIZE_FIXEXT8;
-
-        // fixext16
-        case 0xd8:
-            if (!mpack_reader_ensure(reader, MPACK_TAG_SIZE_FIXEXT16))
-                return 0;
-            *tag = mpack_tag_make_ext(mpack_load_i8(reader->data + 1), 16);
-            return MPACK_TAG_SIZE_FIXEXT16;
-        #endif
-
-        // str8
-        case 0xd9:
-            if (!mpack_reader_ensure(reader, MPACK_TAG_SIZE_STR8))
-                return 0;
-            *tag = mpack_tag_make_str(mpack_load_u8(reader->data + 1));
-            return MPACK_TAG_SIZE_STR8;
-
-        // str16
-        case 0xda:
-            if (!mpack_reader_ensure(reader, MPACK_TAG_SIZE_STR16))
-                return 0;
-            *tag = mpack_tag_make_str(mpack_load_u16(reader->data + 1));
-            return MPACK_TAG_SIZE_STR16;
-
-        // str32
-        case 0xdb:
-            if (!mpack_reader_ensure(reader, MPACK_TAG_SIZE_STR32))
-                return 0;
-            *tag = mpack_tag_make_str(mpack_load_u32(reader->data + 1));
-            return MPACK_TAG_SIZE_STR32;
-
-        // array16
-        case 0xdc:
-            if (!mpack_reader_ensure(reader, MPACK_TAG_SIZE_ARRAY16))
-                return 0;
-            *tag = mpack_tag_make_array(mpack_load_u16(reader->data + 1));
-            return MPACK_TAG_SIZE_ARRAY16;
-
-        // array32
-        case 0xdd:
-            if (!mpack_reader_ensure(reader, MPACK_TAG_SIZE_ARRAY32))
-                return 0;
-            *tag = mpack_tag_make_array(mpack_load_u32(reader->data + 1));
-            return MPACK_TAG_SIZE_ARRAY32;
-
-        // map16
-        case 0xde:
-            if (!mpack_reader_ensure(reader, MPACK_TAG_SIZE_MAP16))
-                return 0;
-            *tag = mpack_tag_make_map(mpack_load_u16(reader->data + 1));
-            return MPACK_TAG_SIZE_MAP16;
-
-        // map32
-        case 0xdf:
-            if (!mpack_reader_ensure(reader, MPACK_TAG_SIZE_MAP32))
-                return 0;
-            *tag = mpack_tag_make_map(mpack_load_u32(reader->data + 1));
-            return MPACK_TAG_SIZE_MAP32;
-
-        // reserved
-        case 0xc1:
-            mpack_reader_flag_error(reader, mpack_error_invalid);
-            return 0;
-
-        #if !MPACK_EXTENSIONS
-        // ext
-        case 0xc7: // fallthrough
-        case 0xc8: // fallthrough
-        case 0xc9: // fallthrough
-        // fixext
-        case 0xd4: // fallthrough
-        case 0xd5: // fallthrough
-        case 0xd6: // fallthrough
-        case 0xd7: // fallthrough
-        case 0xd8:
-            mpack_reader_flag_error(reader, mpack_error_unsupported);
-            return 0;
-        #endif
-
-        #if MPACK_OPTIMIZE_FOR_SIZE
-        // any other bytes should have been handled by the infix switch
-        default:
-            break;
-        #endif
-    }
-
-    mpack_assert(0, "unreachable");
-    return 0;
-}
-
-mpack_tag_t mpack_read_tag(mpack_reader_t* reader) {
-    mpack_log("reading tag\n");
-
-    // make sure we can read a tag
-    if (mpack_reader_error(reader) != mpack_ok)
-        return mpack_tag_nil();
-    if (mpack_reader_track_element(reader) != mpack_ok)
-        return mpack_tag_nil();
-
-    mpack_tag_t tag = MPACK_TAG_ZERO;
-    size_t count = mpack_parse_tag(reader, &tag);
-    if (count == 0)
-        return mpack_tag_nil();
-
-    #if MPACK_READ_TRACKING
-    mpack_error_t track_error = mpack_ok;
-
-    switch (tag.type) {
-        case mpack_type_map:
-        case mpack_type_array:
-            track_error = mpack_track_push(&reader->track, tag.type, tag.v.n);
-            break;
-        #if MPACK_EXTENSIONS
-        case mpack_type_ext:
-        #endif
-        case mpack_type_str:
-        case mpack_type_bin:
-            track_error = mpack_track_push(&reader->track, tag.type, tag.v.l);
-            break;
-        default:
-            break;
-    }
-
-    if (track_error != mpack_ok) {
-        mpack_reader_flag_error(reader, track_error);
-        return mpack_tag_nil();
-    }
-    #endif
-
-    reader->data += count;
-    return tag;
-}
-
-mpack_tag_t mpack_peek_tag(mpack_reader_t* reader) {
-    mpack_log("peeking tag\n");
-
-    // make sure we can peek a tag
-    if (mpack_reader_error(reader) != mpack_ok)
-        return mpack_tag_nil();
-    if (mpack_reader_track_peek_element(reader) != mpack_ok)
-        return mpack_tag_nil();
-
-    mpack_tag_t tag = MPACK_TAG_ZERO;
-    if (mpack_parse_tag(reader, &tag) == 0)
-        return mpack_tag_nil();
-    return tag;
-}
-
-void mpack_discard(mpack_reader_t* reader) {
-    mpack_tag_t var = mpack_read_tag(reader);
-    if (mpack_reader_error(reader))
-        return;
-    switch (var.type) {
-        case mpack_type_str:
-            mpack_skip_bytes(reader, var.v.l);
-            mpack_done_str(reader);
-            break;
-        case mpack_type_bin:
-            mpack_skip_bytes(reader, var.v.l);
-            mpack_done_bin(reader);
-            break;
-        #if MPACK_EXTENSIONS
-        case mpack_type_ext:
-            mpack_skip_bytes(reader, var.v.l);
-            mpack_done_ext(reader);
-            break;
-        #endif
-        case mpack_type_array: {
-            for (; var.v.n > 0; --var.v.n) {
-                mpack_discard(reader);
-                if (mpack_reader_error(reader))
-                    break;
-            }
-            mpack_done_array(reader);
-            break;
-        }
-        case mpack_type_map: {
-            for (; var.v.n > 0; --var.v.n) {
-                mpack_discard(reader);
-                mpack_discard(reader);
-                if (mpack_reader_error(reader))
-                    break;
-            }
-            mpack_done_map(reader);
-            break;
-        }
-        default:
-            break;
-    }
-}
-
-#if MPACK_EXTENSIONS
-mpack_timestamp_t mpack_read_timestamp(mpack_reader_t* reader, size_t size) {
-    mpack_timestamp_t timestamp = {0, 0};
-
-    if (size != 4 && size != 8 && size != 12) {
-        mpack_reader_flag_error(reader, mpack_error_invalid);
-        return timestamp;
-    }
-
-    char buf[12];
-    mpack_read_bytes(reader, buf, size);
-    mpack_done_ext(reader);
-    if (mpack_reader_error(reader) != mpack_ok)
-        return timestamp;
-
-    switch (size) {
-        case 4:
-            timestamp.seconds = (int64_t)(uint64_t)mpack_load_u32(buf);
-            break;
-
-        case 8: {
-            uint64_t packed = mpack_load_u64(buf);
-            timestamp.seconds = (int64_t)(packed & ((MPACK_UINT64_C(1) << 34) - 1));
-            timestamp.nanoseconds = (uint32_t)(packed >> 34);
-            break;
-        }
-
-        case 12:
-            timestamp.nanoseconds = mpack_load_u32(buf);
-            timestamp.seconds = mpack_load_i64(buf + 4);
-            break;
-
-        default:
-            mpack_assert(false, "unreachable");
-            break;
-    }
-
-    if (timestamp.nanoseconds > MPACK_TIMESTAMP_NANOSECONDS_MAX) {
-        mpack_reader_flag_error(reader, mpack_error_invalid);
-        mpack_timestamp_t zero = {0, 0};
-        return zero;
-    }
-
-    return timestamp;
-}
-#endif
-
-#if MPACK_READ_TRACKING
-void mpack_done_type(mpack_reader_t* reader, mpack_type_t type) {
-    if (mpack_reader_error(reader) == mpack_ok)
-        mpack_reader_flag_if_error(reader, mpack_track_pop(&reader->track, type));
-}
-#endif
-
-#if MPACK_DEBUG && MPACK_STDIO
-static size_t mpack_print_read_prefix(mpack_reader_t* reader, size_t length, char* buffer, size_t buffer_size) {
-    if (length == 0)
-        return 0;
-
-    size_t read = (length < buffer_size) ? length : buffer_size;
-    mpack_read_bytes(reader, buffer, read);
-    if (mpack_reader_error(reader) != mpack_ok)
-        return 0;
-
-    mpack_skip_bytes(reader, length - read);
-    return read;
-}
-
-static void mpack_print_element(mpack_reader_t* reader, mpack_print_t* print, size_t depth) {
-    mpack_tag_t val = mpack_read_tag(reader);
-    if (mpack_reader_error(reader) != mpack_ok)
-        return;
-
-    // We read some bytes from bin and ext so we can print its prefix in hex.
-    char buffer[MPACK_PRINT_BYTE_COUNT];
-    size_t count = 0;
-    size_t i, j;
-
-    switch (val.type) {
-        case mpack_type_str:
-            mpack_print_append_cstr(print, "\"");
-            for (i = 0; i < val.v.l; ++i) {
-                char c;
-                mpack_read_bytes(reader, &c, 1);
-                if (mpack_reader_error(reader) != mpack_ok)
-                    return;
-                switch (c) {
-                    case '\n': mpack_print_append_cstr(print, "\\n"); break;
-                    case '\\': mpack_print_append_cstr(print, "\\\\"); break;
-                    case '"': mpack_print_append_cstr(print, "\\\""); break;
-                    default: mpack_print_append(print, &c, 1); break;
-                }
-            }
-            mpack_print_append_cstr(print, "\"");
-            mpack_done_str(reader);
-            return;
-
-        case mpack_type_array:
-            mpack_print_append_cstr(print, "[\n");
-            for (i = 0; i < val.v.n; ++i) {
-                for (j = 0; j < depth + 1; ++j)
-                    mpack_print_append_cstr(print, "    ");
-                mpack_print_element(reader, print, depth + 1);
-                if (mpack_reader_error(reader) != mpack_ok)
-                    return;
-                if (i != val.v.n - 1)
-                    mpack_print_append_cstr(print, ",");
-                mpack_print_append_cstr(print, "\n");
-            }
-            for (i = 0; i < depth; ++i)
-                mpack_print_append_cstr(print, "    ");
-            mpack_print_append_cstr(print, "]");
-            mpack_done_array(reader);
-            return;
-
-        case mpack_type_map:
-            mpack_print_append_cstr(print, "{\n");
-            for (i = 0; i < val.v.n; ++i) {
-                for (j = 0; j < depth + 1; ++j)
-                    mpack_print_append_cstr(print, "    ");
-                mpack_print_element(reader, print, depth + 1);
-                if (mpack_reader_error(reader) != mpack_ok)
-                    return;
-                mpack_print_append_cstr(print, ": ");
-                mpack_print_element(reader, print, depth + 1);
-                if (mpack_reader_error(reader) != mpack_ok)
-                    return;
-                if (i != val.v.n - 1)
-                    mpack_print_append_cstr(print, ",");
-                mpack_print_append_cstr(print, "\n");
-            }
-            for (i = 0; i < depth; ++i)
-                mpack_print_append_cstr(print, "    ");
-            mpack_print_append_cstr(print, "}");
-            mpack_done_map(reader);
-            return;
-
-        // The above cases return so as not to print a pseudo-json value. The
-        // below cases break and print pseudo-json.
-
-        case mpack_type_bin:
-            count = mpack_print_read_prefix(reader, mpack_tag_bin_length(&val), buffer, sizeof(buffer));
-            mpack_done_bin(reader);
-            break;
-
-        #if MPACK_EXTENSIONS
-        case mpack_type_ext:
-            count = mpack_print_read_prefix(reader, mpack_tag_ext_length(&val), buffer, sizeof(buffer));
-            mpack_done_ext(reader);
-            break;
-        #endif
-
-        default:
-            break;
-    }
-
-    char buf[256];
-    mpack_tag_debug_pseudo_json(val, buf, sizeof(buf), buffer, count);
-    mpack_print_append_cstr(print, buf);
-}
-
-static void mpack_print_and_destroy(mpack_reader_t* reader, mpack_print_t* print, size_t depth) {
-    size_t i;
-    for (i = 0; i < depth; ++i)
-        mpack_print_append_cstr(print, "    ");
-    mpack_print_element(reader, print, depth);
-
-    size_t remaining = mpack_reader_remaining(reader, NULL);
-
-    char buf[256];
-    if (mpack_reader_destroy(reader) != mpack_ok) {
-        mpack_snprintf(buf, sizeof(buf), "\n<mpack parsing error %s>", mpack_error_to_string(mpack_reader_error(reader)));
-        buf[sizeof(buf) - 1] = '\0';
-        mpack_print_append_cstr(print, buf);
-    } else if (remaining > 0) {
-        mpack_snprintf(buf, sizeof(buf), "\n<%i extra bytes at end of message>", (int)remaining);
-        buf[sizeof(buf) - 1] = '\0';
-        mpack_print_append_cstr(print, buf);
-    }
-}
-
-static void mpack_print_data(const char* data, size_t len, mpack_print_t* print, size_t depth) {
-    mpack_reader_t reader;
-    mpack_reader_init_data(&reader, data, len);
-    mpack_print_and_destroy(&reader, print, depth);
-}
-
-void mpack_print_data_to_buffer(const char* data, size_t data_size, char* buffer, size_t buffer_size) {
-    if (buffer_size == 0) {
-        mpack_assert(false, "buffer size is zero!");
-        return;
-    }
-
-    mpack_print_t print;
-    mpack_memset(&print, 0, sizeof(print));
-    print.buffer = buffer;
-    print.size = buffer_size;
-    mpack_print_data(data, data_size, &print, 0);
-    mpack_print_append(&print, "",  1); // null-terminator
-    mpack_print_flush(&print);
-
-    // we always make sure there's a null-terminator at the end of the buffer
-    // in case we ran out of space.
-    print.buffer[print.size - 1] = '\0';
-}
-
-void mpack_print_data_to_callback(const char* data, size_t size, mpack_print_callback_t callback, void* context) {
-    char buffer[1024];
-    mpack_print_t print;
-    mpack_memset(&print, 0, sizeof(print));
-    print.buffer = buffer;
-    print.size = sizeof(buffer);
-    print.callback = callback;
-    print.context = context;
-    mpack_print_data(data, size, &print, 0);
-    mpack_print_flush(&print);
-}
-
-void mpack_print_data_to_file(const char* data, size_t len, FILE* file) {
-    mpack_assert(data != NULL, "data is NULL");
-    mpack_assert(file != NULL, "file is NULL");
-
-    char buffer[1024];
-    mpack_print_t print;
-    mpack_memset(&print, 0, sizeof(print));
-    print.buffer = buffer;
-    print.size = sizeof(buffer);
-    print.callback = &mpack_print_file_callback;
-    print.context = file;
-
-    mpack_print_data(data, len, &print, 2);
-    mpack_print_append_cstr(&print, "\n");
-    mpack_print_flush(&print);
-}
-
-void mpack_print_stdfile_to_callback(FILE* file, mpack_print_callback_t callback, void* context) {
-    char buffer[1024];
-    mpack_print_t print;
-    mpack_memset(&print, 0, sizeof(print));
-    print.buffer = buffer;
-    print.size = sizeof(buffer);
-    print.callback = callback;
-    print.context = context;
-
-    mpack_reader_t reader;
-    mpack_reader_init_stdfile(&reader, file, false);
-    mpack_print_and_destroy(&reader, &print, 0);
-    mpack_print_flush(&print);
-}
-#endif
-
-#endif
-
-MPACK_SILENCE_WARNINGS_END
-
-/* mpack/mpack-expect.c.c */
-
-#define MPACK_INTERNAL 1
-
-/* #include "mpack-expect.h" */
-
-MPACK_SILENCE_WARNINGS_BEGIN
-
-#if MPACK_EXPECT
-
-
-// Helpers
-
-MPACK_STATIC_INLINE uint8_t mpack_expect_native_u8(mpack_reader_t* reader) {
-    if (mpack_reader_error(reader) != mpack_ok)
-        return 0;
-    uint8_t type;
-    if (!mpack_reader_ensure(reader, sizeof(type)))
-        return 0;
-    type = mpack_load_u8(reader->data);
-    reader->data += sizeof(type);
-    return type;
-}
-
-#if !MPACK_OPTIMIZE_FOR_SIZE
-MPACK_STATIC_INLINE uint16_t mpack_expect_native_u16(mpack_reader_t* reader) {
-    if (mpack_reader_error(reader) != mpack_ok)
-        return 0;
-    uint16_t type;
-    if (!mpack_reader_ensure(reader, sizeof(type)))
-        return 0;
-    type = mpack_load_u16(reader->data);
-    reader->data += sizeof(type);
-    return type;
-}
-
-MPACK_STATIC_INLINE uint32_t mpack_expect_native_u32(mpack_reader_t* reader) {
-    if (mpack_reader_error(reader) != mpack_ok)
-        return 0;
-    uint32_t type;
-    if (!mpack_reader_ensure(reader, sizeof(type)))
-        return 0;
-    type = mpack_load_u32(reader->data);
-    reader->data += sizeof(type);
-    return type;
-}
-#endif
-
-MPACK_STATIC_INLINE uint8_t mpack_expect_type_byte(mpack_reader_t* reader) {
-    mpack_reader_track_element(reader);
-    return mpack_expect_native_u8(reader);
-}
-
-
-// Basic Number Functions
-
-uint8_t mpack_expect_u8(mpack_reader_t* reader) {
-    mpack_tag_t var = mpack_read_tag(reader);
-    if (var.type == mpack_type_uint) {
-        if (var.v.u <= MPACK_UINT8_MAX)
-            return (uint8_t)var.v.u;
-    } else if (var.type == mpack_type_int) {
-        if (var.v.i >= 0 && var.v.i <= MPACK_UINT8_MAX)
-            return (uint8_t)var.v.i;
-    }
-    mpack_reader_flag_error(reader, mpack_error_type);
-    return 0;
-}
-
-uint16_t mpack_expect_u16(mpack_reader_t* reader) {
-    mpack_tag_t var = mpack_read_tag(reader);
-    if (var.type == mpack_type_uint) {
-        if (var.v.u <= MPACK_UINT16_MAX)
-            return (uint16_t)var.v.u;
-    } else if (var.type == mpack_type_int) {
-        if (var.v.i >= 0 && var.v.i <= MPACK_UINT16_MAX)
-            return (uint16_t)var.v.i;
-    }
-    mpack_reader_flag_error(reader, mpack_error_type);
-    return 0;
-}
-
-uint32_t mpack_expect_u32(mpack_reader_t* reader) {
-    mpack_tag_t var = mpack_read_tag(reader);
-    if (var.type == mpack_type_uint) {
-        if (var.v.u <= MPACK_UINT32_MAX)
-            return (uint32_t)var.v.u;
-    } else if (var.type == mpack_type_int) {
-        if (var.v.i >= 0 && var.v.i <= MPACK_UINT32_MAX)
-            return (uint32_t)var.v.i;
-    }
-    mpack_reader_flag_error(reader, mpack_error_type);
-    return 0;
-}
-
-uint64_t mpack_expect_u64(mpack_reader_t* reader) {
-    mpack_tag_t var = mpack_read_tag(reader);
-    if (var.type == mpack_type_uint) {
-        return var.v.u;
-    } else if (var.type == mpack_type_int) {
-        if (var.v.i >= 0)
-            return (uint64_t)var.v.i;
-    }
-    mpack_reader_flag_error(reader, mpack_error_type);
-    return 0;
-}
-
-int8_t mpack_expect_i8(mpack_reader_t* reader) {
-    mpack_tag_t var = mpack_read_tag(reader);
-    if (var.type == mpack_type_uint) {
-        if (var.v.u <= MPACK_INT8_MAX)
-            return (int8_t)var.v.u;
-    } else if (var.type == mpack_type_int) {
-        if (var.v.i >= MPACK_INT8_MIN && var.v.i <= MPACK_INT8_MAX)
-            return (int8_t)var.v.i;
-    }
-    mpack_reader_flag_error(reader, mpack_error_type);
-    return 0;
-}
-
-int16_t mpack_expect_i16(mpack_reader_t* reader) {
-    mpack_tag_t var = mpack_read_tag(reader);
-    if (var.type == mpack_type_uint) {
-        if (var.v.u <= MPACK_INT16_MAX)
-            return (int16_t)var.v.u;
-    } else if (var.type == mpack_type_int) {
-        if (var.v.i >= MPACK_INT16_MIN && var.v.i <= MPACK_INT16_MAX)
-            return (int16_t)var.v.i;
-    }
-    mpack_reader_flag_error(reader, mpack_error_type);
-    return 0;
-}
-
-int32_t mpack_expect_i32(mpack_reader_t* reader) {
-    mpack_tag_t var = mpack_read_tag(reader);
-    if (var.type == mpack_type_uint) {
-        if (var.v.u <= MPACK_INT32_MAX)
-            return (int32_t)var.v.u;
-    } else if (var.type == mpack_type_int) {
-        if (var.v.i >= MPACK_INT32_MIN && var.v.i <= MPACK_INT32_MAX)
-            return (int32_t)var.v.i;
-    }
-    mpack_reader_flag_error(reader, mpack_error_type);
-    return 0;
-}
-
-int64_t mpack_expect_i64(mpack_reader_t* reader) {
-    mpack_tag_t var = mpack_read_tag(reader);
-    if (var.type == mpack_type_uint) {
-        if (var.v.u <= MPACK_INT64_MAX)
-            return (int64_t)var.v.u;
-    } else if (var.type == mpack_type_int) {
-        return var.v.i;
-    }
-    mpack_reader_flag_error(reader, mpack_error_type);
-    return 0;
-}
-
-#if MPACK_FLOAT
-float mpack_expect_float(mpack_reader_t* reader) {
-    mpack_tag_t var = mpack_read_tag(reader);
-    if (var.type == mpack_type_uint)
-        return (float)var.v.u;
-    if (var.type == mpack_type_int)
-        return (float)var.v.i;
-    if (var.type == mpack_type_float)
-        return var.v.f;
-
-    if (var.type == mpack_type_double) {
-        #if MPACK_DOUBLE
-        return (float)var.v.d;
-        #else
-        return mpack_shorten_raw_double_to_float(var.v.d);
-        #endif
-    }
-
-    mpack_reader_flag_error(reader, mpack_error_type);
-    return 0.0f;
-}
-#endif
-
-#if MPACK_DOUBLE
-double mpack_expect_double(mpack_reader_t* reader) {
-    mpack_tag_t var = mpack_read_tag(reader);
-    if (var.type == mpack_type_uint)
-        return (double)var.v.u;
-    else if (var.type == mpack_type_int)
-        return (double)var.v.i;
-    else if (var.type == mpack_type_float)
-        return (double)var.v.f;
-    else if (var.type == mpack_type_double)
-        return var.v.d;
-    mpack_reader_flag_error(reader, mpack_error_type);
-    return 0.0;
-}
-#endif
-
-#if MPACK_FLOAT
-float mpack_expect_float_strict(mpack_reader_t* reader) {
-    mpack_tag_t var = mpack_read_tag(reader);
-    if (var.type == mpack_type_float)
-        return var.v.f;
-    mpack_reader_flag_error(reader, mpack_error_type);
-    return 0.0f;
-}
-#endif
-
-#if MPACK_DOUBLE
-double mpack_expect_double_strict(mpack_reader_t* reader) {
-    mpack_tag_t var = mpack_read_tag(reader);
-    if (var.type == mpack_type_float)
-        return (double)var.v.f;
-    else if (var.type == mpack_type_double)
-        return var.v.d;
-    mpack_reader_flag_error(reader, mpack_error_type);
-    return 0.0;
-}
-#endif
-
-#if !MPACK_FLOAT
-uint32_t mpack_expect_raw_float(mpack_reader_t* reader) {
-    mpack_tag_t var = mpack_read_tag(reader);
-    if (var.type == mpack_type_float)
-        return var.v.f;
-    mpack_reader_flag_error(reader, mpack_error_type);
-    return 0;
-}
-#endif
-
-#if !MPACK_DOUBLE
-uint64_t mpack_expect_raw_double(mpack_reader_t* reader) {
-    mpack_tag_t var = mpack_read_tag(reader);
-    if (var.type == mpack_type_double)
-        return var.v.d;
-    mpack_reader_flag_error(reader, mpack_error_type);
-    return 0;
-}
-#endif
-
-
-// Ranged Number Functions
-//
-// All ranged functions are identical other than the type, so we
-// define their content with a macro. The prototypes are still written
-// out in full to support ctags/IDE tools.
-
-#define MPACK_EXPECT_RANGE_IMPL(name, type_t)                           \
-                                                                        \
-    /* make sure the range is sensible */                               \
-    mpack_assert(min_value <= max_value,                                \
-            "min_value %i must be less than or equal to max_value %i",  \
-            min_value, max_value);                                      \
-                                                                        \
-    /* read the value */                                                \
-    type_t val = mpack_expect_##name(reader);                           \
-    if (mpack_reader_error(reader) != mpack_ok)                         \
-        return min_value;                                               \
-                                                                        \
-    /* make sure it fits */                                             \
-    if (val < min_value || val > max_value) {                           \
-        mpack_reader_flag_error(reader, mpack_error_type);              \
-        return min_value;                                               \
-    }                                                                   \
-                                                                        \
-    return val;
-
-uint8_t mpack_expect_u8_range(mpack_reader_t* reader, uint8_t min_value, uint8_t max_value) {MPACK_EXPECT_RANGE_IMPL(u8, uint8_t)}
-uint16_t mpack_expect_u16_range(mpack_reader_t* reader, uint16_t min_value, uint16_t max_value) {MPACK_EXPECT_RANGE_IMPL(u16, uint16_t)}
-uint32_t mpack_expect_u32_range(mpack_reader_t* reader, uint32_t min_value, uint32_t max_value) {MPACK_EXPECT_RANGE_IMPL(u32, uint32_t)}
-uint64_t mpack_expect_u64_range(mpack_reader_t* reader, uint64_t min_value, uint64_t max_value) {MPACK_EXPECT_RANGE_IMPL(u64, uint64_t)}
-
-int8_t mpack_expect_i8_range(mpack_reader_t* reader, int8_t min_value, int8_t max_value) {MPACK_EXPECT_RANGE_IMPL(i8, int8_t)}
-int16_t mpack_expect_i16_range(mpack_reader_t* reader, int16_t min_value, int16_t max_value) {MPACK_EXPECT_RANGE_IMPL(i16, int16_t)}
-int32_t mpack_expect_i32_range(mpack_reader_t* reader, int32_t min_value, int32_t max_value) {MPACK_EXPECT_RANGE_IMPL(i32, int32_t)}
-int64_t mpack_expect_i64_range(mpack_reader_t* reader, int64_t min_value, int64_t max_value) {MPACK_EXPECT_RANGE_IMPL(i64, int64_t)}
-
-#if MPACK_FLOAT
-float mpack_expect_float_range(mpack_reader_t* reader, float min_value, float max_value) {MPACK_EXPECT_RANGE_IMPL(float, float)}
-#endif
-#if MPACK_DOUBLE
-double mpack_expect_double_range(mpack_reader_t* reader, double min_value, double max_value) {MPACK_EXPECT_RANGE_IMPL(double, double)}
-#endif
-
-uint32_t mpack_expect_map_range(mpack_reader_t* reader, uint32_t min_value, uint32_t max_value) {MPACK_EXPECT_RANGE_IMPL(map, uint32_t)}
-uint32_t mpack_expect_array_range(mpack_reader_t* reader, uint32_t min_value, uint32_t max_value) {MPACK_EXPECT_RANGE_IMPL(array, uint32_t)}
-
-
-// Matching Number Functions
-
-void mpack_expect_uint_match(mpack_reader_t* reader, uint64_t value) {
-    if (mpack_expect_u64(reader) != value)
-        mpack_reader_flag_error(reader, mpack_error_type);
-}
-
-void mpack_expect_int_match(mpack_reader_t* reader, int64_t value) {
-    if (mpack_expect_i64(reader) != value)
-        mpack_reader_flag_error(reader, mpack_error_type);
-}
-
-
-// Other Basic Types
-
-void mpack_expect_nil(mpack_reader_t* reader) {
-    if (mpack_expect_type_byte(reader) != 0xc0)
-        mpack_reader_flag_error(reader, mpack_error_type);
-}
-
-bool mpack_expect_bool(mpack_reader_t* reader) {
-    uint8_t type = mpack_expect_type_byte(reader);
-    if ((type & ~1) != 0xc2)
-        mpack_reader_flag_error(reader, mpack_error_type);
-    return (bool)(type & 1);
-}
-
-void mpack_expect_true(mpack_reader_t* reader) {
-    if (mpack_expect_bool(reader) != true)
-        mpack_reader_flag_error(reader, mpack_error_type);
-}
-
-void mpack_expect_false(mpack_reader_t* reader) {
-    if (mpack_expect_bool(reader) != false)
-        mpack_reader_flag_error(reader, mpack_error_type);
-}
-
-#if MPACK_EXTENSIONS
-mpack_timestamp_t mpack_expect_timestamp(mpack_reader_t* reader) {
-    mpack_timestamp_t zero = {0, 0};
-
-    mpack_tag_t tag = mpack_read_tag(reader);
-    if (tag.type != mpack_type_ext) {
-        mpack_reader_flag_error(reader, mpack_error_type);
-        return zero;
-    }
-    if (mpack_tag_ext_exttype(&tag) != MPACK_EXTTYPE_TIMESTAMP) {
-        mpack_reader_flag_error(reader, mpack_error_type);
-        return zero;
-    }
-
-    return mpack_read_timestamp(reader, mpack_tag_ext_length(&tag));
-}
-
-int64_t mpack_expect_timestamp_truncate(mpack_reader_t* reader) {
-    return mpack_expect_timestamp(reader).seconds;
-}
-#endif
-
-
-// Compound Types
-
-uint32_t mpack_expect_map(mpack_reader_t* reader) {
-    mpack_tag_t var = mpack_read_tag(reader);
-    if (var.type == mpack_type_map)
-        return var.v.n;
-    mpack_reader_flag_error(reader, mpack_error_type);
-    return 0;
-}
-
-void mpack_expect_map_match(mpack_reader_t* reader, uint32_t count) {
-    if (mpack_expect_map(reader) != count)
-        mpack_reader_flag_error(reader, mpack_error_type);
-}
-
-bool mpack_expect_map_or_nil(mpack_reader_t* reader, uint32_t* count) {
-    mpack_assert(count != NULL, "count cannot be NULL");
-
-    mpack_tag_t var = mpack_read_tag(reader);
-    if (var.type == mpack_type_nil) {
-        *count = 0;
-        return false;
-    }
-    if (var.type == mpack_type_map) {
-        *count = var.v.n;
-        return true;
-    }
-    mpack_reader_flag_error(reader, mpack_error_type);
-    *count = 0;
-    return false;
-}
-
-bool mpack_expect_map_max_or_nil(mpack_reader_t* reader, uint32_t max_count, uint32_t* count) {
-    mpack_assert(count != NULL, "count cannot be NULL");
-
-    bool has_map = mpack_expect_map_or_nil(reader, count);
-    if (has_map && *count > max_count) {
-        *count = 0;
-        mpack_reader_flag_error(reader, mpack_error_type);
-        return false;
-    }
-    return has_map;
-}
-
-uint32_t mpack_expect_array(mpack_reader_t* reader) {
-    mpack_tag_t var = mpack_read_tag(reader);
-    if (var.type == mpack_type_array)
-        return var.v.n;
-    mpack_reader_flag_error(reader, mpack_error_type);
-    return 0;
-}
-
-void mpack_expect_array_match(mpack_reader_t* reader, uint32_t count) {
-    if (mpack_expect_array(reader) != count)
-        mpack_reader_flag_error(reader, mpack_error_type);
-}
-
-bool mpack_expect_array_or_nil(mpack_reader_t* reader, uint32_t* count) {
-    mpack_assert(count != NULL, "count cannot be NULL");
-
-    mpack_tag_t var = mpack_read_tag(reader);
-    if (var.type == mpack_type_nil) {
-        *count = 0;
-        return false;
-    }
-    if (var.type == mpack_type_array) {
-        *count = var.v.n;
-        return true;
-    }
-    mpack_reader_flag_error(reader, mpack_error_type);
-    *count = 0;
-    return false;
-}
-
-bool mpack_expect_array_max_or_nil(mpack_reader_t* reader, uint32_t max_count, uint32_t* count) {
-    mpack_assert(count != NULL, "count cannot be NULL");
-
-    bool has_array = mpack_expect_array_or_nil(reader, count);
-    if (has_array && *count > max_count) {
-        *count = 0;
-        mpack_reader_flag_error(reader, mpack_error_type);
-        return false;
-    }
-    return has_array;
-}
-
-#ifdef MPACK_MALLOC
-void* mpack_expect_array_alloc_impl(mpack_reader_t* reader, size_t element_size, uint32_t max_count, uint32_t* out_count, bool allow_nil) {
-    mpack_assert(out_count != NULL, "out_count cannot be NULL");
-    *out_count = 0;
-
-    uint32_t count;
-    bool has_array = true;
-    if (allow_nil)
-        has_array = mpack_expect_array_max_or_nil(reader, max_count, &count);
-    else
-        count = mpack_expect_array_max(reader, max_count);
-    if (mpack_reader_error(reader))
-        return NULL;
-
-    // size 0 is not an error; we return NULL for no elements.
-    if (count == 0) {
-        // we call mpack_done_array() automatically ONLY if we are using
-        // the _or_nil variant. this is the only way to allow nil and empty
-        // to work the same way.
-        if (allow_nil && has_array)
-            mpack_done_array(reader);
-        return NULL;
-    }
-
-    void* p = MPACK_MALLOC(element_size * count);
-    if (p == NULL) {
-        mpack_reader_flag_error(reader, mpack_error_memory);
-        return NULL;
-    }
-
-    *out_count = count;
-    return p;
-}
-#endif
-
-
-// Str, Bin and Ext Functions
-
-uint32_t mpack_expect_str(mpack_reader_t* reader) {
-    #if MPACK_OPTIMIZE_FOR_SIZE
-    mpack_tag_t var = mpack_read_tag(reader);
-    if (var.type == mpack_type_str)
-        return var.v.l;
-    mpack_reader_flag_error(reader, mpack_error_type);
-    return 0;
-    #else
-    uint8_t type = mpack_expect_type_byte(reader);
-    uint32_t count;
-
-    if ((type >> 5) == 5) {
-        count = type & (uint8_t)~0xe0;
-    } else if (type == 0xd9) {
-        count = mpack_expect_native_u8(reader);
-    } else if (type == 0xda) {
-        count = mpack_expect_native_u16(reader);
-    } else if (type == 0xdb) {
-        count = mpack_expect_native_u32(reader);
-    } else {
-        mpack_reader_flag_error(reader, mpack_error_type);
-        return 0;
-    }
-
-    #if MPACK_READ_TRACKING
-    mpack_reader_flag_if_error(reader, mpack_track_push(&reader->track, mpack_type_str, count));
-    #endif
-    return count;
-    #endif
-}
-
-size_t mpack_expect_str_buf(mpack_reader_t* reader, char* buf, size_t bufsize) {
-    mpack_assert(buf != NULL, "buf cannot be NULL");
-
-    size_t length = mpack_expect_str(reader);
-    if (mpack_reader_error(reader))
-        return 0;
-
-    if (length > bufsize) {
-        mpack_reader_flag_error(reader, mpack_error_too_big);
-        return 0;
-    }
-
-    mpack_read_bytes(reader, buf, length);
-    if (mpack_reader_error(reader))
-        return 0;
-
-    mpack_done_str(reader);
-    return length;
-}
-
-size_t mpack_expect_utf8(mpack_reader_t* reader, char* buf, size_t size) {
-    mpack_assert(buf != NULL, "buf cannot be NULL");
-
-    size_t length = mpack_expect_str_buf(reader, buf, size);
-
-    if (!mpack_utf8_check(buf, length)) {
-        mpack_reader_flag_error(reader, mpack_error_type);
-        return 0;
-    }
-
-    return length;
-}
-
-uint32_t mpack_expect_bin(mpack_reader_t* reader) {
-    mpack_tag_t var = mpack_read_tag(reader);
-    if (var.type == mpack_type_bin)
-        return var.v.l;
-    mpack_reader_flag_error(reader, mpack_error_type);
-    return 0;
-}
-
-size_t mpack_expect_bin_buf(mpack_reader_t* reader, char* buf, size_t bufsize) {
-    mpack_assert(buf != NULL, "buf cannot be NULL");
-
-    size_t binsize = mpack_expect_bin(reader);
-    if (mpack_reader_error(reader))
-        return 0;
-    if (binsize > bufsize) {
-        mpack_reader_flag_error(reader, mpack_error_too_big);
-        return 0;
-    }
-    mpack_read_bytes(reader, buf, binsize);
-    if (mpack_reader_error(reader))
-        return 0;
-    mpack_done_bin(reader);
-    return binsize;
-}
-
-void mpack_expect_bin_size_buf(mpack_reader_t* reader, char* buf, uint32_t size) {
-    mpack_assert(buf != NULL, "buf cannot be NULL");
-    mpack_expect_bin_size(reader, size);
-    mpack_read_bytes(reader, buf, size);
-    mpack_done_bin(reader);
-}
-
-#if MPACK_EXTENSIONS
-uint32_t mpack_expect_ext(mpack_reader_t* reader, int8_t* type) {
-    mpack_tag_t var = mpack_read_tag(reader);
-    if (var.type == mpack_type_ext) {
-        *type = mpack_tag_ext_exttype(&var);
-        return mpack_tag_ext_length(&var);
-    }
-    *type = 0;
-    mpack_reader_flag_error(reader, mpack_error_type);
-    return 0;
-}
-
-size_t mpack_expect_ext_buf(mpack_reader_t* reader, int8_t* type, char* buf, size_t bufsize) {
-    mpack_assert(buf != NULL, "buf cannot be NULL");
-
-    size_t extsize = mpack_expect_ext(reader, type);
-    if (mpack_reader_error(reader))
-        return 0;
-    if (extsize > bufsize) {
-        *type = 0;
-        mpack_reader_flag_error(reader, mpack_error_too_big);
-        return 0;
-    }
-    mpack_read_bytes(reader, buf, extsize);
-    if (mpack_reader_error(reader)) {
-        *type = 0;
-        return 0;
-    }
-    mpack_done_ext(reader);
-    return extsize;
-}
-#endif
-
-void mpack_expect_cstr(mpack_reader_t* reader, char* buf, size_t bufsize) {
-    uint32_t length = mpack_expect_str(reader);
-    mpack_read_cstr(reader, buf, bufsize, length);
-    mpack_done_str(reader);
-}
-
-void mpack_expect_utf8_cstr(mpack_reader_t* reader, char* buf, size_t bufsize) {
-    uint32_t length = mpack_expect_str(reader);
-    mpack_read_utf8_cstr(reader, buf, bufsize, length);
-    mpack_done_str(reader);
-}
-
-#ifdef MPACK_MALLOC
-static char* mpack_expect_cstr_alloc_unchecked(mpack_reader_t* reader, size_t maxsize, size_t* out_length) {
-    mpack_assert(out_length != NULL, "out_length cannot be NULL");
-    *out_length = 0;
-
-    // make sure argument makes sense
-    if (maxsize < 1) {
-        mpack_break("maxsize is zero; you must have room for at least a null-terminator");
-        mpack_reader_flag_error(reader, mpack_error_bug);
-        return NULL;
-    }
-
-    if (SIZE_MAX < MPACK_UINT32_MAX) {
-        if (maxsize > SIZE_MAX)
-            maxsize = SIZE_MAX;
-    } else {
-        if (maxsize > (size_t)MPACK_UINT32_MAX)
-            maxsize = (size_t)MPACK_UINT32_MAX;
-    }
-
-    size_t length = mpack_expect_str_max(reader, (uint32_t)maxsize - 1);
-    char* str = mpack_read_bytes_alloc_impl(reader, length, true);
-    mpack_done_str(reader);
-
-    if (str)
-        *out_length = length;
-    return str;
-}
-
-char* mpack_expect_cstr_alloc(mpack_reader_t* reader, size_t maxsize) {
-    size_t length;
-    char* str = mpack_expect_cstr_alloc_unchecked(reader, maxsize, &length);
-
-    if (str && !mpack_str_check_no_null(str, length)) {
-        MPACK_FREE(str);
-        mpack_reader_flag_error(reader, mpack_error_type);
-        return NULL;
-    }
-
-    return str;
-}
-
-char* mpack_expect_utf8_cstr_alloc(mpack_reader_t* reader, size_t maxsize) {
-    size_t length;
-    char* str = mpack_expect_cstr_alloc_unchecked(reader, maxsize, &length);
-
-    if (str && !mpack_utf8_check_no_null(str, length)) {
-        MPACK_FREE(str);
-        mpack_reader_flag_error(reader, mpack_error_type);
-        return NULL;
-    }
-
-    return str;
-}
-#endif
-
-void mpack_expect_str_match(mpack_reader_t* reader, const char* str, size_t len) {
-    mpack_assert(str != NULL, "str cannot be NULL");
-
-    // expect a str the correct length
-    if (len > MPACK_UINT32_MAX)
-        mpack_reader_flag_error(reader, mpack_error_type);
-    mpack_expect_str_length(reader, (uint32_t)len);
-    if (mpack_reader_error(reader))
-        return;
-    mpack_reader_track_bytes(reader, (uint32_t)len);
-
-    // check each byte one by one (matched strings are likely to be very small)
-    for (; len > 0; --len) {
-        if (mpack_expect_native_u8(reader) != *str++) {
-            mpack_reader_flag_error(reader, mpack_error_type);
-            return;
-        }
-    }
-
-    mpack_done_str(reader);
-}
-
-void mpack_expect_tag(mpack_reader_t* reader, mpack_tag_t expected) {
-    mpack_tag_t actual = mpack_read_tag(reader);
-    if (!mpack_tag_equal(actual, expected))
-        mpack_reader_flag_error(reader, mpack_error_type);
-}
-
-#ifdef MPACK_MALLOC
-char* mpack_expect_bin_alloc(mpack_reader_t* reader, size_t maxsize, size_t* size) {
-    mpack_assert(size != NULL, "size cannot be NULL");
-    *size = 0;
-
-    if (SIZE_MAX < MPACK_UINT32_MAX) {
-        if (maxsize > SIZE_MAX)
-            maxsize = SIZE_MAX;
-    } else {
-        if (maxsize > (size_t)MPACK_UINT32_MAX)
-            maxsize = (size_t)MPACK_UINT32_MAX;
-    }
-
-    size_t length = mpack_expect_bin_max(reader, (uint32_t)maxsize);
-    if (mpack_reader_error(reader))
-        return NULL;
-
-    char* data = mpack_read_bytes_alloc(reader, length);
-    mpack_done_bin(reader);
-
-    if (data)
-        *size = length;
-    return data;
-}
-#endif
-
-#if MPACK_EXTENSIONS && defined(MPACK_MALLOC)
-char* mpack_expect_ext_alloc(mpack_reader_t* reader, int8_t* type, size_t maxsize, size_t* size) {
-    mpack_assert(size != NULL, "size cannot be NULL");
-    *size = 0;
-
-    if (SIZE_MAX < MPACK_UINT32_MAX) {
-        if (maxsize > SIZE_MAX)
-            maxsize = SIZE_MAX;
-    } else {
-        if (maxsize > (size_t)MPACK_UINT32_MAX)
-            maxsize = (size_t)MPACK_UINT32_MAX;
-    }
-
-    size_t length = mpack_expect_ext_max(reader, type, (uint32_t)maxsize);
-    if (mpack_reader_error(reader))
-        return NULL;
-
-    char* data = mpack_read_bytes_alloc(reader, length);
-    mpack_done_ext(reader);
-
-    if (data) {
-        *size = length;
-    } else {
-        *type = 0;
-    }
-    return data;
-}
-#endif
-
-size_t mpack_expect_enum(mpack_reader_t* reader, const char* strings[], size_t count) {
-
-    // read the string in-place
-    size_t keylen = mpack_expect_str(reader);
-    const char* key = mpack_read_bytes_inplace(reader, keylen);
-    mpack_done_str(reader);
-    if (mpack_reader_error(reader) != mpack_ok)
-        return count;
-
-    // find what key it matches
-    size_t i;
-    for (i = 0; i < count; ++i) {
-        const char* other = strings[i];
-        size_t otherlen = mpack_strlen(other);
-        if (keylen == otherlen && mpack_memcmp(key, other, keylen) == 0)
-            return i;
-    }
-
-    // no matches
-    mpack_reader_flag_error(reader, mpack_error_type);
-    return count;
-}
-
-size_t mpack_expect_enum_optional(mpack_reader_t* reader, const char* strings[], size_t count) {
-    if (mpack_reader_error(reader) != mpack_ok)
-        return count;
-
-    mpack_assert(count != 0, "count cannot be zero; no strings are valid!");
-    mpack_assert(strings != NULL, "strings cannot be NULL");
-
-    // the key is only recognized if it is a string
-    if (mpack_peek_tag(reader).type != mpack_type_str) {
-        mpack_discard(reader);
-        return count;
-    }
-
-    // read the string in-place
-    size_t keylen = mpack_expect_str(reader);
-    const char* key = mpack_read_bytes_inplace(reader, keylen);
-    mpack_done_str(reader);
-    if (mpack_reader_error(reader) != mpack_ok)
-        return count;
-
-    // find what key it matches
-    size_t i;
-    for (i = 0; i < count; ++i) {
-        const char* other = strings[i];
-        size_t otherlen = mpack_strlen(other);
-        if (keylen == otherlen && mpack_memcmp(key, other, keylen) == 0)
-            return i;
-    }
-
-    // no matches
-    return count;
-}
-
-size_t mpack_expect_key_uint(mpack_reader_t* reader, bool found[], size_t count) {
-    if (mpack_reader_error(reader) != mpack_ok)
-        return count;
-
-    if (count == 0) {
-        mpack_break("count cannot be zero; no keys are valid!");
-        mpack_reader_flag_error(reader, mpack_error_bug);
-        return count;
-    }
-    mpack_assert(found != NULL, "found cannot be NULL");
-
-    // the key is only recognized if it is an unsigned int
-    if (mpack_peek_tag(reader).type != mpack_type_uint) {
-        mpack_discard(reader);
-        return count;
-    }
-
-    // read the key
-    uint64_t value = mpack_expect_u64(reader);
-    if (mpack_reader_error(reader) != mpack_ok)
-        return count;
-
-    // unrecognized keys are fine, we just return count
-    if (value >= count)
-        return count;
-
-    // check if this key is a duplicate
-    if (found[value]) {
-        mpack_reader_flag_error(reader, mpack_error_invalid);
-        return count;
-    }
-
-    found[value] = true;
-    return (size_t)value;
-}
-
-size_t mpack_expect_key_cstr(mpack_reader_t* reader, const char* keys[], bool found[], size_t count) {
-    size_t i = mpack_expect_enum_optional(reader, keys, count);
-
-    // unrecognized keys are fine, we just return count
-    if (i == count)
-        return count;
-
-    // check if this key is a duplicate
-    mpack_assert(found != NULL, "found cannot be NULL");
-    if (found[i]) {
-        mpack_reader_flag_error(reader, mpack_error_invalid);
-        return count;
-    }
-
-    found[i] = true;
-    return i;
-}
-
-#endif
-
-MPACK_SILENCE_WARNINGS_END
-
-/* mpack/mpack-node.c.c */
-
-#define MPACK_INTERNAL 1
-
-/* #include "mpack-node.h" */
-
-MPACK_SILENCE_WARNINGS_BEGIN
-
-#if MPACK_NODE
-
-MPACK_STATIC_INLINE const char* mpack_node_data_unchecked(mpack_node_t node) {
-    mpack_assert(mpack_node_error(node) == mpack_ok, "tree is in an error state!");
-
-    mpack_type_t type = node.data->type;
-    MPACK_UNUSED(type);
-    #if MPACK_EXTENSIONS
-    mpack_assert(type == mpack_type_str || type == mpack_type_bin || type == mpack_type_ext,
-            "node of type %i (%s) is not a data type!", type, mpack_type_to_string(type));
-    #else
-    mpack_assert(type == mpack_type_str || type == mpack_type_bin,
-            "node of type %i (%s) is not a data type!", type, mpack_type_to_string(type));
-    #endif
-
-    return node.tree->data + node.data->value.offset;
-}
-
-#if MPACK_EXTENSIONS
-MPACK_STATIC_INLINE int8_t mpack_node_exttype_unchecked(mpack_node_t node) {
-    mpack_assert(mpack_node_error(node) == mpack_ok, "tree is in an error state!");
-
-    mpack_type_t type = node.data->type;
-    MPACK_UNUSED(type);
-    mpack_assert(type == mpack_type_ext, "node of type %i (%s) is not an ext type!",
-            type, mpack_type_to_string(type));
-
-    // the exttype of an ext node is stored in the byte preceding the data
-    return mpack_load_i8(mpack_node_data_unchecked(node) - 1);
-}
-#endif
-
-
-
-/*
- * Tree Parsing
- */
-
-#ifdef MPACK_MALLOC
-
-// fix up the alloc size to make sure it exactly fits the
-// maximum number of nodes it can contain (the allocator will
-// waste it back anyway, but we round it down just in case)
-
-#define MPACK_NODES_PER_PAGE \
-    ((MPACK_NODE_PAGE_SIZE - sizeof(mpack_tree_page_t)) / sizeof(mpack_node_data_t) + 1)
-
-#define MPACK_PAGE_ALLOC_SIZE \
-    (sizeof(mpack_tree_page_t) + sizeof(mpack_node_data_t) * (MPACK_NODES_PER_PAGE - 1))
-
-#endif
-
-#ifdef MPACK_MALLOC
-/*
- * Fills the tree until we have at least enough bytes for the current node.
- */
-static bool mpack_tree_reserve_fill(mpack_tree_t* tree) {
-    mpack_assert(tree->parser.state == mpack_tree_parse_state_in_progress);
-
-    size_t bytes = tree->parser.current_node_reserved;
-    mpack_assert(bytes > tree->parser.possible_nodes_left,
-            "there are already enough bytes! call mpack_tree_ensure() instead.");
-    mpack_log("filling to reserve %i bytes\n", (int)bytes);
-
-    // if the necessary bytes would put us over the maximum tree
-    // size, fail right away.
-    // TODO: check for overflow?
-    if (tree->data_length + bytes > tree->max_size) {
-        mpack_tree_flag_error(tree, mpack_error_too_big);
-        return false;
-    }
-
-    // we'll need a read function to fetch more data. if there's
-    // no read function, the data should contain an entire message
-    // (or messages), so we flag it as invalid.
-    if (tree->read_fn == NULL) {
-        mpack_log("tree has no read function!\n");
-        mpack_tree_flag_error(tree, mpack_error_invalid);
-        return false;
-    }
-
-    // expand the buffer if needed
-    if (tree->data_length + bytes > tree->buffer_capacity) {
-
-        // TODO: check for overflow?
-        size_t new_capacity = (tree->buffer_capacity == 0) ? MPACK_BUFFER_SIZE : tree->buffer_capacity;
-        while (new_capacity < tree->data_length + bytes)
-            new_capacity *= 2;
-        if (new_capacity > tree->max_size)
-            new_capacity = tree->max_size;
-
-        mpack_log("expanding buffer from %i to %i\n", (int)tree->buffer_capacity, (int)new_capacity);
-
-        char* new_buffer;
-        if (tree->buffer == NULL)
-            new_buffer = (char*)MPACK_MALLOC(new_capacity);
-        else
-            new_buffer = (char*)mpack_realloc(tree->buffer, tree->data_length, new_capacity);
-
-        if (new_buffer == NULL) {
-            mpack_tree_flag_error(tree, mpack_error_memory);
-            return false;
-        }
-
-        tree->data = new_buffer;
-        tree->buffer = new_buffer;
-        tree->buffer_capacity = new_capacity;
-    }
-
-    // request as much data as possible, looping until we have
-    // all the data we need
-    do {
-        size_t read = tree->read_fn(tree, tree->buffer + tree->data_length, tree->buffer_capacity - tree->data_length);
-
-        // If the fill function encounters an error, it should flag an error on
-        // the tree.
-        if (mpack_tree_error(tree) != mpack_ok)
-            return false;
-
-        // We guard against fill functions that return -1 just in case.
-        if (read == (size_t)(-1)) {
-            mpack_tree_flag_error(tree, mpack_error_io);
-            return false;
-        }
-
-        // If the fill function returns 0, the data is not available yet. We
-        // return false to stop parsing the current node.
-        if (read == 0) {
-            mpack_log("not enough data.\n");
-            return false;
-        }
-
-        mpack_log("read %u more bytes\n", (uint32_t)read);
-        tree->data_length += read;
-        tree->parser.possible_nodes_left += read;
-    } while (tree->parser.possible_nodes_left < bytes);
-
-    return true;
-}
-#endif
-
-/*
- * Ensures there are enough additional bytes in the tree for the current node
- * (including reserved bytes for the children of this node, and in addition to
- * the reserved bytes for children of previous compound nodes), reading more
- * data if needed.
- *
- * extra_bytes is the number of additional bytes to reserve for the current
- * node beyond the type byte (since one byte is already reserved for each node
- * by its parent array or map.)
- *
- * This may reallocate the tree, which means the tree->data pointer may change!
- *
- * Returns false if not enough bytes could be read.
- */
-MPACK_STATIC_INLINE bool mpack_tree_reserve_bytes(mpack_tree_t* tree, size_t extra_bytes) {
-    mpack_assert(tree->parser.state == mpack_tree_parse_state_in_progress);
-
-    // We guard against overflow here. A compound type could declare more than
-    // MPACK_UINT32_MAX contents which overflows SIZE_MAX on 32-bit platforms. We
-    // flag mpack_error_invalid instead of mpack_error_too_big since it's far
-    // more likely that the message is corrupt than that the data is valid but
-    // not parseable on this architecture (see test_read_node_possible() in
-    // test-node.c .)
-    if ((uint64_t)tree->parser.current_node_reserved + (uint64_t)extra_bytes > SIZE_MAX) {
-        mpack_tree_flag_error(tree, mpack_error_invalid);
-        return false;
-    }
-
-    tree->parser.current_node_reserved += extra_bytes;
-
-    // Note that possible_nodes_left already accounts for reserved bytes for
-    // children of previous compound nodes. So even if there are hundreds of
-    // bytes left in the buffer, we might need to read anyway.
-    if (tree->parser.current_node_reserved <= tree->parser.possible_nodes_left)
-        return true;
-
-    #ifdef MPACK_MALLOC
-    return mpack_tree_reserve_fill(tree);
-    #else
-    return false;
-    #endif
-}
-
-MPACK_STATIC_INLINE size_t mpack_tree_parser_stack_capacity(mpack_tree_t* tree) {
-    #ifdef MPACK_MALLOC
-    return tree->parser.stack_capacity;
-    #else
-    return sizeof(tree->parser.stack) / sizeof(tree->parser.stack[0]);
-    #endif
-}
-
-static bool mpack_tree_push_stack(mpack_tree_t* tree, mpack_node_data_t* first_child, size_t total) {
-    mpack_tree_parser_t* parser = &tree->parser;
-    mpack_assert(parser->state == mpack_tree_parse_state_in_progress);
-
-    // No need to push empty containers
-    if (total == 0)
-        return true;
-
-    // Make sure we have enough room in the stack
-    if (parser->level + 1 == mpack_tree_parser_stack_capacity(tree)) {
-        #ifdef MPACK_MALLOC
-        size_t new_capacity = parser->stack_capacity * 2;
-        mpack_log("growing parse stack to capacity %i\n", (int)new_capacity);
-
-        // Replace the stack-allocated parsing stack
-        if (!parser->stack_owned) {
-            mpack_level_t* new_stack = (mpack_level_t*)MPACK_MALLOC(sizeof(mpack_level_t) * new_capacity);
-            if (!new_stack) {
-                mpack_tree_flag_error(tree, mpack_error_memory);
-                return false;
-            }
-            mpack_memcpy(new_stack, parser->stack, sizeof(mpack_level_t) * parser->stack_capacity);
-            parser->stack = new_stack;
-            parser->stack_owned = true;
-
-        // Realloc the allocated parsing stack
-        } else {
-            mpack_level_t* new_stack = (mpack_level_t*)mpack_realloc(parser->stack,
-                    sizeof(mpack_level_t) * parser->stack_capacity, sizeof(mpack_level_t) * new_capacity);
-            if (!new_stack) {
-                mpack_tree_flag_error(tree, mpack_error_memory);
-                return false;
-            }
-            parser->stack = new_stack;
-        }
-        parser->stack_capacity = new_capacity;
-        #else
-        mpack_tree_flag_error(tree, mpack_error_too_big);
-        return false;
-        #endif
-    }
-
-    // Push the contents of this node onto the parsing stack
-    ++parser->level;
-    parser->stack[parser->level].child = first_child;
-    parser->stack[parser->level].left = total;
-    return true;
-}
-
-static bool mpack_tree_parse_children(mpack_tree_t* tree, mpack_node_data_t* node) {
-    mpack_tree_parser_t* parser = &tree->parser;
-    mpack_assert(parser->state == mpack_tree_parse_state_in_progress);
-
-    mpack_type_t type = node->type;
-    size_t total = node->len;
-
-    // Calculate total elements to read
-    if (type == mpack_type_map) {
-        if ((uint64_t)total * 2 > SIZE_MAX) {
-            mpack_tree_flag_error(tree, mpack_error_too_big);
-            return false;
-        }
-        total *= 2;
-    }
-
-    // Make sure we are under our total node limit (TODO can this overflow?)
-    tree->node_count += total;
-    if (tree->node_count > tree->max_nodes) {
-        mpack_tree_flag_error(tree, mpack_error_too_big);
-        return false;
-    }
-
-    // Each node is at least one byte. Count these bytes now to make
-    // sure there is enough data left.
-    if (!mpack_tree_reserve_bytes(tree, total))
-        return false;
-
-    // If there are enough nodes left in the current page, no need to grow
-    if (total <= parser->nodes_left) {
-        node->value.children = parser->nodes;
-        parser->nodes += total;
-        parser->nodes_left -= total;
-
-    } else {
-
-        #ifdef MPACK_MALLOC
-
-        // We can't grow if we're using a fixed pool (i.e. we didn't start with a page)
-        if (!tree->next) {
-            mpack_tree_flag_error(tree, mpack_error_too_big);
-            return false;
-        }
-
-        // Otherwise we need to grow, and the node's children need to be contiguous.
-        // This is a heuristic to decide whether we should waste the remaining space
-        // in the current page and start a new one, or give the children their
-        // own page. With a fraction of 1/8, this causes at most 12% additional
-        // waste. Note that reducing this too much causes less cache coherence and
-        // more malloc() overhead due to smaller allocations, so there's a tradeoff
-        // here. This heuristic could use some improvement, especially with custom
-        // page sizes.
-
-        mpack_tree_page_t* page;
-
-        if (total > MPACK_NODES_PER_PAGE || parser->nodes_left > MPACK_NODES_PER_PAGE / 8) {
-            // TODO: this should check for overflow
-            page = (mpack_tree_page_t*)MPACK_MALLOC(
-                    sizeof(mpack_tree_page_t) + sizeof(mpack_node_data_t) * (total - 1));
-            if (page == NULL) {
-                mpack_tree_flag_error(tree, mpack_error_memory);
-                return false;
-            }
-            mpack_log("allocated seperate page %p for %i children, %i left in page of %i total\n",
-                    (void*)page, (int)total, (int)parser->nodes_left, (int)MPACK_NODES_PER_PAGE);
-
-            node->value.children = page->nodes;
-
-        } else {
-            page = (mpack_tree_page_t*)MPACK_MALLOC(MPACK_PAGE_ALLOC_SIZE);
-            if (page == NULL) {
-                mpack_tree_flag_error(tree, mpack_error_memory);
-                return false;
-            }
-            mpack_log("allocated new page %p for %i children, wasting %i in page of %i total\n",
-                    (void*)page, (int)total, (int)parser->nodes_left, (int)MPACK_NODES_PER_PAGE);
-
-            node->value.children = page->nodes;
-            parser->nodes = page->nodes + total;
-            parser->nodes_left = MPACK_NODES_PER_PAGE - total;
-        }
-
-        page->next = tree->next;
-        tree->next = page;
-
-        #else
-        // We can't grow if we don't have an allocator
-        mpack_tree_flag_error(tree, mpack_error_too_big);
-        return false;
-        #endif
-    }
-
-    return mpack_tree_push_stack(tree, node->value.children, total);
-}
-
-static bool mpack_tree_parse_bytes(mpack_tree_t* tree, mpack_node_data_t* node) {
-    node->value.offset = tree->size + tree->parser.current_node_reserved + 1;
-    return mpack_tree_reserve_bytes(tree, node->len);
-}
-
-#if MPACK_EXTENSIONS
-static bool mpack_tree_parse_ext(mpack_tree_t* tree, mpack_node_data_t* node) {
-    // reserve space for exttype
-    tree->parser.current_node_reserved += sizeof(int8_t);
-    node->type = mpack_type_ext;
-    return mpack_tree_parse_bytes(tree, node);
-}
-#endif
-
-static bool mpack_tree_parse_node_contents(mpack_tree_t* tree, mpack_node_data_t* node) {
-    mpack_assert(tree->parser.state == mpack_tree_parse_state_in_progress);
-    mpack_assert(node != NULL, "null node?");
-
-    // read the type. we've already accounted for this byte in
-    // possible_nodes_left, so we already know it is in bounds, and we don't
-    // need to reserve it for this node.
-    mpack_assert(tree->data_length > tree->size);
-    uint8_t type = mpack_load_u8(tree->data + tree->size);
-    mpack_log("node type %x\n", type);
-    tree->parser.current_node_reserved = 0;
-
-    // as with mpack_read_tag(), the fastest way to parse a node is to switch
-    // on the first byte, and to explicitly list every possible byte. we switch
-    // on the first four bits in size-optimized builds.
-
-    #if MPACK_OPTIMIZE_FOR_SIZE
-    switch (type >> 4) {
-
-        // positive fixnum
-        case 0x0: case 0x1: case 0x2: case 0x3:
-        case 0x4: case 0x5: case 0x6: case 0x7:
-            node->type = mpack_type_uint;
-            node->value.u = type;
-            return true;
-
-        // negative fixnum
-        case 0xe: case 0xf:
-            node->type = mpack_type_int;
-            node->value.i = (int8_t)type;
-            return true;
-
-        // fixmap
-        case 0x8:
-            node->type = mpack_type_map;
-            node->len = (uint32_t)(type & ~0xf0);
-            return mpack_tree_parse_children(tree, node);
-
-        // fixarray
-        case 0x9:
-            node->type = mpack_type_array;
-            node->len = (uint32_t)(type & ~0xf0);
-            return mpack_tree_parse_children(tree, node);
-
-        // fixstr
-        case 0xa: case 0xb:
-            node->type = mpack_type_str;
-            node->len = (uint32_t)(type & ~0xe0);
-            return mpack_tree_parse_bytes(tree, node);
-
-        // not one of the common infix types
-        default:
-            break;
-    }
-    #endif
-
-    switch (type) {
-
-        #if !MPACK_OPTIMIZE_FOR_SIZE
-        // positive fixnum
-        case 0x00: case 0x01: case 0x02: case 0x03: case 0x04: case 0x05: case 0x06: case 0x07:
-        case 0x08: case 0x09: case 0x0a: case 0x0b: case 0x0c: case 0x0d: case 0x0e: case 0x0f:
-        case 0x10: case 0x11: case 0x12: case 0x13: case 0x14: case 0x15: case 0x16: case 0x17:
-        case 0x18: case 0x19: case 0x1a: case 0x1b: case 0x1c: case 0x1d: case 0x1e: case 0x1f:
-        case 0x20: case 0x21: case 0x22: case 0x23: case 0x24: case 0x25: case 0x26: case 0x27:
-        case 0x28: case 0x29: case 0x2a: case 0x2b: case 0x2c: case 0x2d: case 0x2e: case 0x2f:
-        case 0x30: case 0x31: case 0x32: case 0x33: case 0x34: case 0x35: case 0x36: case 0x37:
-        case 0x38: case 0x39: case 0x3a: case 0x3b: case 0x3c: case 0x3d: case 0x3e: case 0x3f:
-        case 0x40: case 0x41: case 0x42: case 0x43: case 0x44: case 0x45: case 0x46: case 0x47:
-        case 0x48: case 0x49: case 0x4a: case 0x4b: case 0x4c: case 0x4d: case 0x4e: case 0x4f:
-        case 0x50: case 0x51: case 0x52: case 0x53: case 0x54: case 0x55: case 0x56: case 0x57:
-        case 0x58: case 0x59: case 0x5a: case 0x5b: case 0x5c: case 0x5d: case 0x5e: case 0x5f:
-        case 0x60: case 0x61: case 0x62: case 0x63: case 0x64: case 0x65: case 0x66: case 0x67:
-        case 0x68: case 0x69: case 0x6a: case 0x6b: case 0x6c: case 0x6d: case 0x6e: case 0x6f:
-        case 0x70: case 0x71: case 0x72: case 0x73: case 0x74: case 0x75: case 0x76: case 0x77:
-        case 0x78: case 0x79: case 0x7a: case 0x7b: case 0x7c: case 0x7d: case 0x7e: case 0x7f:
-            node->type = mpack_type_uint;
-            node->value.u = type;
-            return true;
-
-        // negative fixnum
-        case 0xe0: case 0xe1: case 0xe2: case 0xe3: case 0xe4: case 0xe5: case 0xe6: case 0xe7:
-        case 0xe8: case 0xe9: case 0xea: case 0xeb: case 0xec: case 0xed: case 0xee: case 0xef:
-        case 0xf0: case 0xf1: case 0xf2: case 0xf3: case 0xf4: case 0xf5: case 0xf6: case 0xf7:
-        case 0xf8: case 0xf9: case 0xfa: case 0xfb: case 0xfc: case 0xfd: case 0xfe: case 0xff:
-            node->type = mpack_type_int;
-            node->value.i = (int8_t)type;
-            return true;
-
-        // fixmap
-        case 0x80: case 0x81: case 0x82: case 0x83: case 0x84: case 0x85: case 0x86: case 0x87:
-        case 0x88: case 0x89: case 0x8a: case 0x8b: case 0x8c: case 0x8d: case 0x8e: case 0x8f:
-            node->type = mpack_type_map;
-            node->len = (uint32_t)(type & ~0xf0);
-            return mpack_tree_parse_children(tree, node);
-
-        // fixarray
-        case 0x90: case 0x91: case 0x92: case 0x93: case 0x94: case 0x95: case 0x96: case 0x97:
-        case 0x98: case 0x99: case 0x9a: case 0x9b: case 0x9c: case 0x9d: case 0x9e: case 0x9f:
-            node->type = mpack_type_array;
-            node->len = (uint32_t)(type & ~0xf0);
-            return mpack_tree_parse_children(tree, node);
-
-        // fixstr
-        case 0xa0: case 0xa1: case 0xa2: case 0xa3: case 0xa4: case 0xa5: case 0xa6: case 0xa7:
-        case 0xa8: case 0xa9: case 0xaa: case 0xab: case 0xac: case 0xad: case 0xae: case 0xaf:
-        case 0xb0: case 0xb1: case 0xb2: case 0xb3: case 0xb4: case 0xb5: case 0xb6: case 0xb7:
-        case 0xb8: case 0xb9: case 0xba: case 0xbb: case 0xbc: case 0xbd: case 0xbe: case 0xbf:
-            node->type = mpack_type_str;
-            node->len = (uint32_t)(type & ~0xe0);
-            return mpack_tree_parse_bytes(tree, node);
-        #endif
-
-        // nil
-        case 0xc0:
-            node->type = mpack_type_nil;
-            return true;
-
-        // bool
-        case 0xc2: case 0xc3:
-            node->type = mpack_type_bool;
-            node->value.b = type & 1;
-            return true;
-
-        // bin8
-        case 0xc4:
-            node->type = mpack_type_bin;
-            if (!mpack_tree_reserve_bytes(tree, sizeof(uint8_t)))
-                return false;
-            node->len = mpack_load_u8(tree->data + tree->size + 1);
-            return mpack_tree_parse_bytes(tree, node);
-
-        // bin16
-        case 0xc5:
-            node->type = mpack_type_bin;
-            if (!mpack_tree_reserve_bytes(tree, sizeof(uint16_t)))
-                return false;
-            node->len = mpack_load_u16(tree->data + tree->size + 1);
-            return mpack_tree_parse_bytes(tree, node);
-
-        // bin32
-        case 0xc6:
-            node->type = mpack_type_bin;
-            if (!mpack_tree_reserve_bytes(tree, sizeof(uint32_t)))
-                return false;
-            node->len = mpack_load_u32(tree->data + tree->size + 1);
-            return mpack_tree_parse_bytes(tree, node);
-
-        #if MPACK_EXTENSIONS
-        // ext8
-        case 0xc7:
-            if (!mpack_tree_reserve_bytes(tree, sizeof(uint8_t)))
-                return false;
-            node->len = mpack_load_u8(tree->data + tree->size + 1);
-            return mpack_tree_parse_ext(tree, node);
-
-        // ext16
-        case 0xc8:
-            if (!mpack_tree_reserve_bytes(tree, sizeof(uint16_t)))
-                return false;
-            node->len = mpack_load_u16(tree->data + tree->size + 1);
-            return mpack_tree_parse_ext(tree, node);
-
-        // ext32
-        case 0xc9:
-            if (!mpack_tree_reserve_bytes(tree, sizeof(uint32_t)))
-                return false;
-            node->len = mpack_load_u32(tree->data + tree->size + 1);
-            return mpack_tree_parse_ext(tree, node);
-        #endif
-
-        // float
-        case 0xca:
-            #if MPACK_FLOAT
-            if (!mpack_tree_reserve_bytes(tree, sizeof(float)))
-                return false;
-            node->value.f = mpack_load_float(tree->data + tree->size + 1);
-            #else
-            if (!mpack_tree_reserve_bytes(tree, sizeof(uint32_t)))
-                return false;
-            node->value.f = mpack_load_u32(tree->data + tree->size + 1);
-            #endif
-            node->type = mpack_type_float;
-            return true;
-
-        // double
-        case 0xcb:
-            #if MPACK_DOUBLE
-            if (!mpack_tree_reserve_bytes(tree, sizeof(double)))
-                return false;
-            node->value.d = mpack_load_double(tree->data + tree->size + 1);
-            #else
-            if (!mpack_tree_reserve_bytes(tree, sizeof(uint64_t)))
-                return false;
-            node->value.d = mpack_load_u64(tree->data + tree->size + 1);
-            #endif
-            node->type = mpack_type_double;
-            return true;
-
-        // uint8
-        case 0xcc:
-            node->type = mpack_type_uint;
-            if (!mpack_tree_reserve_bytes(tree, sizeof(uint8_t)))
-                return false;
-            node->value.u = mpack_load_u8(tree->data + tree->size + 1);
-            return true;
-
-        // uint16
-        case 0xcd:
-            node->type = mpack_type_uint;
-            if (!mpack_tree_reserve_bytes(tree, sizeof(uint16_t)))
-                return false;
-            node->value.u = mpack_load_u16(tree->data + tree->size + 1);
-            return true;
-
-        // uint32
-        case 0xce:
-            node->type = mpack_type_uint;
-            if (!mpack_tree_reserve_bytes(tree, sizeof(uint32_t)))
-                return false;
-            node->value.u = mpack_load_u32(tree->data + tree->size + 1);
-            return true;
-
-        // uint64
-        case 0xcf:
-            node->type = mpack_type_uint;
-            if (!mpack_tree_reserve_bytes(tree, sizeof(uint64_t)))
-                return false;
-            node->value.u = mpack_load_u64(tree->data + tree->size + 1);
-            return true;
-
-        // int8
-        case 0xd0:
-            node->type = mpack_type_int;
-            if (!mpack_tree_reserve_bytes(tree, sizeof(int8_t)))
-                return false;
-            node->value.i = mpack_load_i8(tree->data + tree->size + 1);
-            return true;
-
-        // int16
-        case 0xd1:
-            node->type = mpack_type_int;
-            if (!mpack_tree_reserve_bytes(tree, sizeof(int16_t)))
-                return false;
-            node->value.i = mpack_load_i16(tree->data + tree->size + 1);
-            return true;
-
-        // int32
-        case 0xd2:
-            node->type = mpack_type_int;
-            if (!mpack_tree_reserve_bytes(tree, sizeof(int32_t)))
-                return false;
-            node->value.i = mpack_load_i32(tree->data + tree->size + 1);
-            return true;
-
-        // int64
-        case 0xd3:
-            node->type = mpack_type_int;
-            if (!mpack_tree_reserve_bytes(tree, sizeof(int64_t)))
-                return false;
-            node->value.i = mpack_load_i64(tree->data + tree->size + 1);
-            return true;
-
-        #if MPACK_EXTENSIONS
-        // fixext1
-        case 0xd4:
-            node->len = 1;
-            return mpack_tree_parse_ext(tree, node);
-
-        // fixext2
-        case 0xd5:
-            node->len = 2;
-            return mpack_tree_parse_ext(tree, node);
-
-        // fixext4
-        case 0xd6:
-            node->len = 4;
-            return mpack_tree_parse_ext(tree, node);
-
-        // fixext8
-        case 0xd7:
-            node->len = 8;
-            return mpack_tree_parse_ext(tree, node);
-
-        // fixext16
-        case 0xd8:
-            node->len = 16;
-            return mpack_tree_parse_ext(tree, node);
-        #endif
-
-        // str8
-        case 0xd9:
-            if (!mpack_tree_reserve_bytes(tree, sizeof(uint8_t)))
-                return false;
-            node->len = mpack_load_u8(tree->data + tree->size + 1);
-            node->type = mpack_type_str;
-            return mpack_tree_parse_bytes(tree, node);
-
-        // str16
-        case 0xda:
-            if (!mpack_tree_reserve_bytes(tree, sizeof(uint16_t)))
-                return false;
-            node->len = mpack_load_u16(tree->data + tree->size + 1);
-            node->type = mpack_type_str;
-            return mpack_tree_parse_bytes(tree, node);
-
-        // str32
-        case 0xdb:
-            if (!mpack_tree_reserve_bytes(tree, sizeof(uint32_t)))
-                return false;
-            node->len = mpack_load_u32(tree->data + tree->size + 1);
-            node->type = mpack_type_str;
-            return mpack_tree_parse_bytes(tree, node);
-
-        // array16
-        case 0xdc:
-            if (!mpack_tree_reserve_bytes(tree, sizeof(uint16_t)))
-                return false;
-            node->len = mpack_load_u16(tree->data + tree->size + 1);
-            node->type = mpack_type_array;
-            return mpack_tree_parse_children(tree, node);
-
-        // array32
-        case 0xdd:
-            if (!mpack_tree_reserve_bytes(tree, sizeof(uint32_t)))
-                return false;
-            node->len = mpack_load_u32(tree->data + tree->size + 1);
-            node->type = mpack_type_array;
-            return mpack_tree_parse_children(tree, node);
-
-        // map16
-        case 0xde:
-            if (!mpack_tree_reserve_bytes(tree, sizeof(uint16_t)))
-                return false;
-            node->len = mpack_load_u16(tree->data + tree->size + 1);
-            node->type = mpack_type_map;
-            return mpack_tree_parse_children(tree, node);
-
-        // map32
-        case 0xdf:
-            if (!mpack_tree_reserve_bytes(tree, sizeof(uint32_t)))
-                return false;
-            node->len = mpack_load_u32(tree->data + tree->size + 1);
-            node->type = mpack_type_map;
-            return mpack_tree_parse_children(tree, node);
-
-        // reserved
-        case 0xc1:
-            mpack_tree_flag_error(tree, mpack_error_invalid);
-            return false;
-
-        #if !MPACK_EXTENSIONS
-        // ext
-        case 0xc7: // fallthrough
-        case 0xc8: // fallthrough
-        case 0xc9: // fallthrough
-        // fixext
-        case 0xd4: // fallthrough
-        case 0xd5: // fallthrough
-        case 0xd6: // fallthrough
-        case 0xd7: // fallthrough
-        case 0xd8:
-            mpack_tree_flag_error(tree, mpack_error_unsupported);
-            return false;
-        #endif
-
-        #if MPACK_OPTIMIZE_FOR_SIZE
-        // any other bytes should have been handled by the infix switch
-        default:
-            break;
-        #endif
-    }
-
-    mpack_assert(0, "unreachable");
-    return false;
-}
-
-static bool mpack_tree_parse_node(mpack_tree_t* tree, mpack_node_data_t* node) {
-    mpack_log("parsing a node at position %i in level %i\n",
-            (int)tree->size, (int)tree->parser.level);
-
-    if (!mpack_tree_parse_node_contents(tree, node)) {
-        mpack_log("node parsing returned false\n");
-        return false;
-    }
-
-    tree->parser.possible_nodes_left -= tree->parser.current_node_reserved;
-
-    // The reserve for the current node does not include the initial byte
-    // previously reserved as part of its parent.
-    size_t node_size = tree->parser.current_node_reserved + 1;
-
-    // If the parsed type is a map or array, the reserve includes one byte for
-    // each child. We want to subtract these out of possible_nodes_left, but
-    // not out of the current size of the tree.
-    if (node->type == mpack_type_array)
-        node_size -= node->len;
-    else if (node->type == mpack_type_map)
-        node_size -= node->len * 2;
-    tree->size += node_size;
-
-    mpack_log("parsed a node of type %s of %i bytes and "
-            "%i additional bytes reserved for children.\n",
-            mpack_type_to_string(node->type), (int)node_size,
-            (int)tree->parser.current_node_reserved + 1 - (int)node_size);
-
-    return true;
-}
-
-/*
- * We read nodes in a loop instead of recursively for maximum performance. The
- * stack holds the amount of children left to read in each level of the tree.
- * Parsing can pause and resume when more data becomes available.
- */
-static bool mpack_tree_continue_parsing(mpack_tree_t* tree) {
-    if (mpack_tree_error(tree) != mpack_ok)
-        return false;
-
-    mpack_tree_parser_t* parser = &tree->parser;
-    mpack_assert(parser->state == mpack_tree_parse_state_in_progress);
-    mpack_log("parsing tree elements, %i bytes in buffer\n", (int)tree->data_length);
-
-    // we loop parsing nodes until the parse stack is empty. we break
-    // by returning out of the function.
-    while (true) {
-        mpack_node_data_t* node = parser->stack[parser->level].child;
-        size_t level = parser->level;
-        if (!mpack_tree_parse_node(tree, node))
-            return false;
-        --parser->stack[level].left;
-        ++parser->stack[level].child;
-
-        mpack_assert(mpack_tree_error(tree) == mpack_ok,
-                "mpack_tree_parse_node() should have returned false due to error!");
-
-        // pop empty stack levels, exiting the outer loop when the stack is empty.
-        // (we could tail-optimize containers by pre-emptively popping empty
-        // stack levels before reading the new element, this way we wouldn't
-        // have to loop. but we eventually want to use the parse stack to give
-        // better error messages that contain the location of the error, so
-        // it needs to be complete.)
-        while (parser->stack[parser->level].left == 0) {
-            if (parser->level == 0)
-                return true;
-            --parser->level;
-        }
-    }
-}
-
-static void mpack_tree_cleanup(mpack_tree_t* tree) {
-    MPACK_UNUSED(tree);
-
-    #ifdef MPACK_MALLOC
-    if (tree->parser.stack_owned) {
-        MPACK_FREE(tree->parser.stack);
-        tree->parser.stack = NULL;
-        tree->parser.stack_owned = false;
-    }
-
-    mpack_tree_page_t* page = tree->next;
-    while (page != NULL) {
-        mpack_tree_page_t* next = page->next;
-        mpack_log("freeing page %p\n", (void*)page);
-        MPACK_FREE(page);
-        page = next;
-    }
-    tree->next = NULL;
-    #endif
-}
-
-static bool mpack_tree_parse_start(mpack_tree_t* tree) {
-    if (mpack_tree_error(tree) != mpack_ok)
-        return false;
-
-    mpack_tree_parser_t* parser = &tree->parser;
-    mpack_assert(parser->state != mpack_tree_parse_state_in_progress,
-            "previous parsing was not finished!");
-
-    if (parser->state == mpack_tree_parse_state_parsed)
-        mpack_tree_cleanup(tree);
-
-    mpack_log("starting parse\n");
-    tree->parser.state = mpack_tree_parse_state_in_progress;
-    tree->parser.current_node_reserved = 0;
-
-    // check if we previously parsed a tree
-    if (tree->size > 0) {
-        #ifdef MPACK_MALLOC
-        // if we're buffered, move the remaining data back to the
-        // start of the buffer
-        // TODO: This is not ideal performance-wise. We should only move data
-        // when we need to call the fill function.
-        // TODO: We could consider shrinking the buffer here, especially if we
-        // determine that the fill function is providing less than a quarter of
-        // the buffer size or if messages take up less than a quarter of the
-        // buffer size. Maybe this should be configurable.
-        if (tree->buffer != NULL) {
-            mpack_memmove(tree->buffer, tree->buffer + tree->size, tree->data_length - tree->size);
-        }
-        else
-        #endif
-        // otherwise advance past the parsed data
-        {
-            tree->data += tree->size;
-        }
-        tree->data_length -= tree->size;
-        tree->size = 0;
-        tree->node_count = 0;
-    }
-
-    // make sure we have at least one byte available before allocating anything
-    parser->possible_nodes_left = tree->data_length;
-    if (!mpack_tree_reserve_bytes(tree, sizeof(uint8_t))) {
-        tree->parser.state = mpack_tree_parse_state_not_started;
-        return false;
-    }
-    mpack_log("parsing tree at %p starting with byte %x\n", tree->data, (uint8_t)tree->data[0]);
-    parser->possible_nodes_left -= 1;
-    tree->node_count = 1;
-
-    #ifdef MPACK_MALLOC
-    parser->stack = parser->stack_local;
-    parser->stack_owned = false;
-    parser->stack_capacity = sizeof(parser->stack_local) / sizeof(*parser->stack_local);
-
-    if (tree->pool == NULL) {
-
-        // allocate first page
-        mpack_tree_page_t* page = (mpack_tree_page_t*)MPACK_MALLOC(MPACK_PAGE_ALLOC_SIZE);
-        mpack_log("allocated initial page %p of size %i count %i\n",
-                (void*)page, (int)MPACK_PAGE_ALLOC_SIZE, (int)MPACK_NODES_PER_PAGE);
-        if (page == NULL) {
-            tree->error = mpack_error_memory;
-            return false;
-        }
-        page->next = NULL;
-        tree->next = page;
-
-        parser->nodes = page->nodes;
-        parser->nodes_left = MPACK_NODES_PER_PAGE;
-    }
-    else
-    #endif
-    {
-        // otherwise use the provided pool
-        mpack_assert(tree->pool != NULL, "no pool provided?");
-        parser->nodes = tree->pool;
-        parser->nodes_left = tree->pool_count;
-    }
-
-    tree->root = parser->nodes;
-    ++parser->nodes;
-    --parser->nodes_left;
-
-    parser->level = 0;
-    parser->stack[0].child = tree->root;
-    parser->stack[0].left = 1;
-
-    return true;
-}
-
-void mpack_tree_parse(mpack_tree_t* tree) {
-    if (mpack_tree_error(tree) != mpack_ok)
-        return;
-
-    if (tree->parser.state != mpack_tree_parse_state_in_progress) {
-        if (!mpack_tree_parse_start(tree)) {
-            mpack_tree_flag_error(tree, (tree->read_fn == NULL) ?
-                    mpack_error_invalid : mpack_error_io);
-            return;
-        }
-    }
-
-    if (!mpack_tree_continue_parsing(tree)) {
-        if (mpack_tree_error(tree) != mpack_ok)
-            return;
-
-        // We're parsing synchronously on a blocking fill function. If we
-        // didn't completely finish parsing the tree, it's an error.
-        mpack_log("tree parsing incomplete. flagging error.\n");
-        mpack_tree_flag_error(tree, (tree->read_fn == NULL) ?
-                mpack_error_invalid : mpack_error_io);
-        return;
-    }
-
-    mpack_assert(mpack_tree_error(tree) == mpack_ok);
-    mpack_assert(tree->parser.level == 0);
-    tree->parser.state = mpack_tree_parse_state_parsed;
-    mpack_log("parsed tree of %i bytes, %i bytes left\n", (int)tree->size, (int)tree->parser.possible_nodes_left);
-    mpack_log("%i nodes in final page\n", (int)tree->parser.nodes_left);
-}
-
-bool mpack_tree_try_parse(mpack_tree_t* tree) {
-    if (mpack_tree_error(tree) != mpack_ok)
-        return false;
-
-    if (tree->parser.state != mpack_tree_parse_state_in_progress)
-        if (!mpack_tree_parse_start(tree))
-            return false;
-
-    if (!mpack_tree_continue_parsing(tree))
-        return false;
-
-    mpack_assert(mpack_tree_error(tree) == mpack_ok);
-    mpack_assert(tree->parser.level == 0);
-    tree->parser.state = mpack_tree_parse_state_parsed;
-    return true;
-}
-
-
-
-/*
- * Tree functions
- */
-
-mpack_node_t mpack_tree_root(mpack_tree_t* tree) {
-    if (mpack_tree_error(tree) != mpack_ok)
-        return mpack_tree_nil_node(tree);
-
-    // We check that a tree was parsed successfully and assert if not. You must
-    // call mpack_tree_parse() (or mpack_tree_try_parse() with a success
-    // result) in order to access the root node.
-    if (tree->parser.state != mpack_tree_parse_state_parsed) {
-        mpack_break("Tree has not been parsed! "
-                "Did you call mpack_tree_parse() or mpack_tree_try_parse()?");
-        mpack_tree_flag_error(tree, mpack_error_bug);
-        return mpack_tree_nil_node(tree);
-    }
-
-    return mpack_node(tree, tree->root);
-}
-
-static void mpack_tree_init_clear(mpack_tree_t* tree) {
-    mpack_memset(tree, 0, sizeof(*tree));
-    tree->nil_node.type = mpack_type_nil;
-    tree->missing_node.type = mpack_type_missing;
-    tree->max_size = SIZE_MAX;
-    tree->max_nodes = SIZE_MAX;
-}
-
-#ifdef MPACK_MALLOC
-void mpack_tree_init_data(mpack_tree_t* tree, const char* data, size_t length) {
-    mpack_tree_init_clear(tree);
-
-    MPACK_STATIC_ASSERT(MPACK_NODE_PAGE_SIZE >= sizeof(mpack_tree_page_t),
-            "MPACK_NODE_PAGE_SIZE is too small");
-
-    MPACK_STATIC_ASSERT(MPACK_PAGE_ALLOC_SIZE <= MPACK_NODE_PAGE_SIZE,
-            "incorrect page rounding?");
-
-    tree->data = data;
-    tree->data_length = length;
-    tree->pool = NULL;
-    tree->pool_count = 0;
-    tree->next = NULL;
-
-    mpack_log("===========================\n");
-    mpack_log("initializing tree with data of size %i\n", (int)length);
-}
-#endif
-
-void mpack_tree_init_pool(mpack_tree_t* tree, const char* data, size_t length,
-        mpack_node_data_t* node_pool, size_t node_pool_count)
-{
-    mpack_tree_init_clear(tree);
-    #ifdef MPACK_MALLOC
-    tree->next = NULL;
-    #endif
-
-    if (node_pool_count == 0) {
-        mpack_break("initial page has no nodes!");
-        mpack_tree_flag_error(tree, mpack_error_bug);
-        return;
-    }
-
-    tree->data = data;
-    tree->data_length = length;
-    tree->pool = node_pool;
-    tree->pool_count = node_pool_count;
-
-    mpack_log("===========================\n");
-    mpack_log("initializing tree with data of size %i and pool of count %i\n",
-            (int)length, (int)node_pool_count);
-}
-
-void mpack_tree_init_error(mpack_tree_t* tree, mpack_error_t error) {
-    mpack_tree_init_clear(tree);
-    tree->error = error;
-
-    mpack_log("===========================\n");
-    mpack_log("initializing tree error state %i\n", (int)error);
-}
-
-#ifdef MPACK_MALLOC
-void mpack_tree_init_stream(mpack_tree_t* tree, mpack_tree_read_t read_fn, void* context,
-        size_t max_message_size, size_t max_message_nodes) {
-    mpack_tree_init_clear(tree);
-
-    tree->read_fn = read_fn;
-    tree->context = context;
-
-    mpack_tree_set_limits(tree, max_message_size, max_message_nodes);
-    tree->max_size = max_message_size;
-    tree->max_nodes = max_message_nodes;
-
-    mpack_log("===========================\n");
-    mpack_log("initializing tree with stream, max size %i max nodes %i\n",
-            (int)max_message_size, (int)max_message_nodes);
-}
-#endif
-
-void mpack_tree_set_limits(mpack_tree_t* tree, size_t max_message_size, size_t max_message_nodes) {
-    mpack_assert(max_message_size > 0);
-    mpack_assert(max_message_nodes > 0);
-    tree->max_size = max_message_size;
-    tree->max_nodes = max_message_nodes;
-}
-
-#if MPACK_STDIO
-typedef struct mpack_file_tree_t {
-    char* data;
-    size_t size;
-    char buffer[MPACK_BUFFER_SIZE];
-} mpack_file_tree_t;
-
-static void mpack_file_tree_teardown(mpack_tree_t* tree) {
-    mpack_file_tree_t* file_tree = (mpack_file_tree_t*)tree->context;
-    MPACK_FREE(file_tree->data);
-    MPACK_FREE(file_tree);
-}
-
-static bool mpack_file_tree_read(mpack_tree_t* tree, mpack_file_tree_t* file_tree, FILE* file, size_t max_bytes) {
-
-    // get the file size
-    errno = 0;
-    int error = 0;
-    fseek(file, 0, SEEK_END);
-    error |= errno;
-    long size = ftell(file);
-    error |= errno;
-    fseek(file, 0, SEEK_SET);
-    error |= errno;
-
-    // check for errors
-    if (error != 0 || size < 0) {
-        mpack_tree_init_error(tree, mpack_error_io);
-        return false;
-    }
-    if (size == 0) {
-        mpack_tree_init_error(tree, mpack_error_invalid);
-        return false;
-    }
-
-    // make sure the size is less than max_bytes
-    // (this mess exists to safely convert between long and size_t regardless of their widths)
-    if (max_bytes != 0 && (((uint64_t)LONG_MAX > (uint64_t)SIZE_MAX && size > (long)SIZE_MAX) || (size_t)size > max_bytes)) {
-        mpack_tree_init_error(tree, mpack_error_too_big);
-        return false;
-    }
-
-    // allocate data
-    file_tree->data = (char*)MPACK_MALLOC((size_t)size);
-    if (file_tree->data == NULL) {
-        mpack_tree_init_error(tree, mpack_error_memory);
-        return false;
-    }
-
-    // read the file
-    long total = 0;
-    while (total < size) {
-        size_t read = fread(file_tree->data + total, 1, (size_t)(size - total), file);
-        if (read <= 0) {
-            mpack_tree_init_error(tree, mpack_error_io);
-            MPACK_FREE(file_tree->data);
-            return false;
-        }
-        total += (long)read;
-    }
-
-    file_tree->size = (size_t)size;
-    return true;
-}
-
-static bool mpack_tree_file_check_max_bytes(mpack_tree_t* tree, size_t max_bytes) {
-
-    // the C STDIO family of file functions use long (e.g. ftell)
-    if (max_bytes > LONG_MAX) {
-        mpack_break("max_bytes of %" PRIu64 " is invalid, maximum is LONG_MAX", (uint64_t)max_bytes);
-        mpack_tree_init_error(tree, mpack_error_bug);
-        return false;
-    }
-
-    return true;
-}
-
-static void mpack_tree_init_stdfile_noclose(mpack_tree_t* tree, FILE* stdfile, size_t max_bytes) {
-
-    // allocate file tree
-    mpack_file_tree_t* file_tree = (mpack_file_tree_t*) MPACK_MALLOC(sizeof(mpack_file_tree_t));
-    if (file_tree == NULL) {
-        mpack_tree_init_error(tree, mpack_error_memory);
-        return;
-    }
-
-    // read all data
-    if (!mpack_file_tree_read(tree, file_tree, stdfile, max_bytes)) {
-        MPACK_FREE(file_tree);
-        return;
-    }
-
-    mpack_tree_init_data(tree, file_tree->data, file_tree->size);
-    mpack_tree_set_context(tree, file_tree);
-    mpack_tree_set_teardown(tree, mpack_file_tree_teardown);
-}
-
-void mpack_tree_init_stdfile(mpack_tree_t* tree, FILE* stdfile, size_t max_bytes, bool close_when_done) {
-    if (!mpack_tree_file_check_max_bytes(tree, max_bytes))
-        return;
-
-    mpack_tree_init_stdfile_noclose(tree, stdfile, max_bytes);
-
-    if (close_when_done)
-        fclose(stdfile);
-}
-
-void mpack_tree_init_filename(mpack_tree_t* tree, const char* filename, size_t max_bytes) {
-    if (!mpack_tree_file_check_max_bytes(tree, max_bytes))
-        return;
-
-    // open the file
-    FILE* file = fopen(filename, "rb");
-    if (!file) {
-        mpack_tree_init_error(tree, mpack_error_io);
-        return;
-    }
-
-    mpack_tree_init_stdfile(tree, file, max_bytes, true);
-}
-#endif
-
-mpack_error_t mpack_tree_destroy(mpack_tree_t* tree) {
-    mpack_tree_cleanup(tree);
-
-    #ifdef MPACK_MALLOC
-    if (tree->buffer)
-        MPACK_FREE(tree->buffer);
-    #endif
-
-    if (tree->teardown)
-        tree->teardown(tree);
-    tree->teardown = NULL;
-
-    return tree->error;
-}
-
-void mpack_tree_flag_error(mpack_tree_t* tree, mpack_error_t error) {
-    if (tree->error == mpack_ok) {
-        mpack_log("tree %p setting error %i: %s\n", (void*)tree, (int)error, mpack_error_to_string(error));
-        tree->error = error;
-        if (tree->error_fn)
-            tree->error_fn(tree, error);
-    }
-
-}
-
-
-
-/*
- * Node misc functions
- */
-
-void mpack_node_flag_error(mpack_node_t node, mpack_error_t error) {
-    mpack_tree_flag_error(node.tree, error);
-}
-
-mpack_tag_t mpack_node_tag(mpack_node_t node) {
-    if (mpack_node_error(node) != mpack_ok)
-        return mpack_tag_nil();
-
-    mpack_tag_t tag = MPACK_TAG_ZERO;
-
-    tag.type = node.data->type;
-    switch (node.data->type) {
-        case mpack_type_missing:
-            // If a node is missing, I don't know if it makes sense to ask for
-            // a tag for it. We'll return a missing tag to match the missing
-            // node I guess, but attempting to use the tag for anything (like
-            // writing it for example) will flag mpack_error_bug.
-            break;
-        case mpack_type_nil:                                            break;
-        case mpack_type_bool:    tag.v.b = node.data->value.b;          break;
-        case mpack_type_float:   tag.v.f = node.data->value.f;          break;
-        case mpack_type_double:  tag.v.d = node.data->value.d;          break;
-        case mpack_type_int:     tag.v.i = node.data->value.i;          break;
-        case mpack_type_uint:    tag.v.u = node.data->value.u;          break;
-
-        case mpack_type_str:     tag.v.l = node.data->len;     break;
-        case mpack_type_bin:     tag.v.l = node.data->len;     break;
-
-        #if MPACK_EXTENSIONS
-        case mpack_type_ext:
-            tag.v.l = node.data->len;
-            tag.exttype = mpack_node_exttype_unchecked(node);
-            break;
-        #endif
-
-        case mpack_type_array:   tag.v.n = node.data->len;  break;
-        case mpack_type_map:     tag.v.n = node.data->len;  break;
-
-        default:
-            mpack_assert(0, "unrecognized type %i", (int)node.data->type);
-            break;
-    }
-    return tag;
-}
-
-#if MPACK_DEBUG && MPACK_STDIO
-static void mpack_node_print_element(mpack_node_t node, mpack_print_t* print, size_t depth) {
-    mpack_node_data_t* data = node.data;
-    size_t i,j;
-    switch (data->type) {
-        case mpack_type_str:
-            {
-                mpack_print_append_cstr(print, "\"");
-                const char* bytes = mpack_node_data_unchecked(node);
-                for (i = 0; i < data->len; ++i) {
-                    char c = bytes[i];
-                    switch (c) {
-                        case '\n': mpack_print_append_cstr(print, "\\n"); break;
-                        case '\\': mpack_print_append_cstr(print, "\\\\"); break;
-                        case '"': mpack_print_append_cstr(print, "\\\""); break;
-                        default: mpack_print_append(print, &c, 1); break;
-                    }
-                }
-                mpack_print_append_cstr(print, "\"");
-            }
-            break;
-
-        case mpack_type_array:
-            mpack_print_append_cstr(print, "[\n");
-            for (i = 0; i < data->len; ++i) {
-                for (j = 0; j < depth + 1; ++j)
-                    mpack_print_append_cstr(print, "    ");
-                mpack_node_print_element(mpack_node_array_at(node, i), print, depth + 1);
-                if (i != data->len - 1)
-                    mpack_print_append_cstr(print, ",");
-                mpack_print_append_cstr(print, "\n");
-            }
-            for (i = 0; i < depth; ++i)
-                mpack_print_append_cstr(print, "    ");
-            mpack_print_append_cstr(print, "]");
-            break;
-
-        case mpack_type_map:
-            mpack_print_append_cstr(print, "{\n");
-            for (i = 0; i < data->len; ++i) {
-                for (j = 0; j < depth + 1; ++j)
-                    mpack_print_append_cstr(print, "    ");
-                mpack_node_print_element(mpack_node_map_key_at(node, i), print, depth + 1);
-                mpack_print_append_cstr(print, ": ");
-                mpack_node_print_element(mpack_node_map_value_at(node, i), print, depth + 1);
-                if (i != data->len - 1)
-                    mpack_print_append_cstr(print, ",");
-                mpack_print_append_cstr(print, "\n");
-            }
-            for (i = 0; i < depth; ++i)
-                mpack_print_append_cstr(print, "    ");
-            mpack_print_append_cstr(print, "}");
-            break;
-
-        default:
-            {
-                const char* prefix = NULL;
-                size_t prefix_length = 0;
-                if (mpack_node_type(node) == mpack_type_bin
-                        #if MPACK_EXTENSIONS
-                        || mpack_node_type(node) == mpack_type_ext
-                        #endif
-                ) {
-                    prefix = mpack_node_data(node);
-                    prefix_length = mpack_node_data_len(node);
-                }
-
-                char buf[256];
-                mpack_tag_t tag = mpack_node_tag(node);
-                mpack_tag_debug_pseudo_json(tag, buf, sizeof(buf), prefix, prefix_length);
-                mpack_print_append_cstr(print, buf);
-            }
-            break;
-    }
-}
-
-void mpack_node_print_to_buffer(mpack_node_t node, char* buffer, size_t buffer_size) {
-    if (buffer_size == 0) {
-        mpack_assert(false, "buffer size is zero!");
-        return;
-    }
-
-    mpack_print_t print;
-    mpack_memset(&print, 0, sizeof(print));
-    print.buffer = buffer;
-    print.size = buffer_size;
-    mpack_node_print_element(node, &print, 0);
-    mpack_print_append(&print, "",  1); // null-terminator
-    mpack_print_flush(&print);
-
-    // we always make sure there's a null-terminator at the end of the buffer
-    // in case we ran out of space.
-    print.buffer[print.size - 1] = '\0';
-}
-
-void mpack_node_print_to_callback(mpack_node_t node, mpack_print_callback_t callback, void* context) {
-    char buffer[1024];
-    mpack_print_t print;
-    mpack_memset(&print, 0, sizeof(print));
-    print.buffer = buffer;
-    print.size = sizeof(buffer);
-    print.callback = callback;
-    print.context = context;
-    mpack_node_print_element(node, &print, 0);
-    mpack_print_flush(&print);
-}
-
-void mpack_node_print_to_file(mpack_node_t node, FILE* file) {
-    mpack_assert(file != NULL, "file is NULL");
-
-    char buffer[1024];
-    mpack_print_t print;
-    mpack_memset(&print, 0, sizeof(print));
-    print.buffer = buffer;
-    print.size = sizeof(buffer);
-    print.callback = &mpack_print_file_callback;
-    print.context = file;
-
-    size_t depth = 2;
-    size_t i;
-    for (i = 0; i < depth; ++i)
-        mpack_print_append_cstr(&print, "    ");
-    mpack_node_print_element(node, &print, depth);
-    mpack_print_append_cstr(&print, "\n");
-    mpack_print_flush(&print);
-}
-#endif
-
-
-
-/*
- * Node Value Functions
- */
-
-#if MPACK_EXTENSIONS
-mpack_timestamp_t mpack_node_timestamp(mpack_node_t node) {
-    mpack_timestamp_t timestamp = {0, 0};
-
-    // we'll let mpack_node_exttype() do most checks
-    if (mpack_node_exttype(node) != MPACK_EXTTYPE_TIMESTAMP) {
-        mpack_log("exttype %i\n", mpack_node_exttype(node));
-        mpack_node_flag_error(node, mpack_error_type);
-        return timestamp;
-    }
-
-    const char* p = mpack_node_data_unchecked(node);
-
-    switch (node.data->len) {
-        case 4:
-            timestamp.nanoseconds = 0;
-            timestamp.seconds = mpack_load_u32(p);
-            break;
-
-        case 8: {
-            uint64_t value = mpack_load_u64(p);
-            timestamp.nanoseconds = (uint32_t)(value >> 34);
-            timestamp.seconds = value & ((MPACK_UINT64_C(1) << 34) - 1);
-            break;
-        }
-
-        case 12:
-            timestamp.nanoseconds = mpack_load_u32(p);
-            timestamp.seconds = mpack_load_i64(p + 4);
-            break;
-
-        default:
-            mpack_tree_flag_error(node.tree, mpack_error_invalid);
-            return timestamp;
-    }
-
-    if (timestamp.nanoseconds > MPACK_TIMESTAMP_NANOSECONDS_MAX) {
-        mpack_tree_flag_error(node.tree, mpack_error_invalid);
-        mpack_timestamp_t zero = {0, 0};
-        return zero;
-    }
-
-    return timestamp;
-}
-
-int64_t mpack_node_timestamp_seconds(mpack_node_t node) {
-    return mpack_node_timestamp(node).seconds;
-}
-
-uint32_t mpack_node_timestamp_nanoseconds(mpack_node_t node) {
-    return mpack_node_timestamp(node).nanoseconds;
-}
-#endif
-
-
-
-/*
- * Node Data Functions
- */
-
-void mpack_node_check_utf8(mpack_node_t node) {
-    if (mpack_node_error(node) != mpack_ok)
-        return;
-    mpack_node_data_t* data = node.data;
-    if (data->type != mpack_type_str || !mpack_utf8_check(mpack_node_data_unchecked(node), data->len))
-        mpack_node_flag_error(node, mpack_error_type);
-}
-
-void mpack_node_check_utf8_cstr(mpack_node_t node) {
-    if (mpack_node_error(node) != mpack_ok)
-        return;
-    mpack_node_data_t* data = node.data;
-    if (data->type != mpack_type_str || !mpack_utf8_check_no_null(mpack_node_data_unchecked(node), data->len))
-        mpack_node_flag_error(node, mpack_error_type);
-}
-
-size_t mpack_node_copy_data(mpack_node_t node, char* buffer, size_t bufsize) {
-    if (mpack_node_error(node) != mpack_ok)
-        return 0;
-
-    mpack_assert(bufsize == 0 || buffer != NULL, "buffer is NULL for maximum of %i bytes", (int)bufsize);
-
-    mpack_type_t type = node.data->type;
-    if (type != mpack_type_str && type != mpack_type_bin
-            #if MPACK_EXTENSIONS
-            && type != mpack_type_ext
-            #endif
-    ) {
-        mpack_node_flag_error(node, mpack_error_type);
-        return 0;
-    }
-
-    if (node.data->len > bufsize) {
-        mpack_node_flag_error(node, mpack_error_too_big);
-        return 0;
-    }
-
-    mpack_memcpy(buffer, mpack_node_data_unchecked(node), node.data->len);
-    return (size_t)node.data->len;
-}
-
-size_t mpack_node_copy_utf8(mpack_node_t node, char* buffer, size_t bufsize) {
-    if (mpack_node_error(node) != mpack_ok)
-        return 0;
-
-    mpack_assert(bufsize == 0 || buffer != NULL, "buffer is NULL for maximum of %i bytes", (int)bufsize);
-
-    mpack_type_t type = node.data->type;
-    if (type != mpack_type_str) {
-        mpack_node_flag_error(node, mpack_error_type);
-        return 0;
-    }
-
-    if (node.data->len > bufsize) {
-        mpack_node_flag_error(node, mpack_error_too_big);
-        return 0;
-    }
-
-    if (!mpack_utf8_check(mpack_node_data_unchecked(node), node.data->len)) {
-        mpack_node_flag_error(node, mpack_error_type);
-        return 0;
-    }
-
-    mpack_memcpy(buffer, mpack_node_data_unchecked(node), node.data->len);
-    return (size_t)node.data->len;
-}
-
-void mpack_node_copy_cstr(mpack_node_t node, char* buffer, size_t bufsize) {
-
-    // we can't break here because the error isn't recoverable; we
-    // have to add a null-terminator.
-    mpack_assert(buffer != NULL, "buffer is NULL");
-    mpack_assert(bufsize >= 1, "buffer size is zero; you must have room for at least a null-terminator");
-
-    if (mpack_node_error(node) != mpack_ok) {
-        buffer[0] = '\0';
-        return;
-    }
-
-    if (node.data->type != mpack_type_str) {
-        buffer[0] = '\0';
-        mpack_node_flag_error(node, mpack_error_type);
-        return;
-    }
-
-    if (node.data->len > bufsize - 1) {
-        buffer[0] = '\0';
-        mpack_node_flag_error(node, mpack_error_too_big);
-        return;
-    }
-
-    if (!mpack_str_check_no_null(mpack_node_data_unchecked(node), node.data->len)) {
-        buffer[0] = '\0';
-        mpack_node_flag_error(node, mpack_error_type);
-        return;
-    }
-
-    mpack_memcpy(buffer, mpack_node_data_unchecked(node), node.data->len);
-    buffer[node.data->len] = '\0';
-}
-
-void mpack_node_copy_utf8_cstr(mpack_node_t node, char* buffer, size_t bufsize) {
-
-    // we can't break here because the error isn't recoverable; we
-    // have to add a null-terminator.
-    mpack_assert(buffer != NULL, "buffer is NULL");
-    mpack_assert(bufsize >= 1, "buffer size is zero; you must have room for at least a null-terminator");
-
-    if (mpack_node_error(node) != mpack_ok) {
-        buffer[0] = '\0';
-        return;
-    }
-
-    if (node.data->type != mpack_type_str) {
-        buffer[0] = '\0';
-        mpack_node_flag_error(node, mpack_error_type);
-        return;
-    }
-
-    if (node.data->len > bufsize - 1) {
-        buffer[0] = '\0';
-        mpack_node_flag_error(node, mpack_error_too_big);
-        return;
-    }
-
-    if (!mpack_utf8_check_no_null(mpack_node_data_unchecked(node), node.data->len)) {
-        buffer[0] = '\0';
-        mpack_node_flag_error(node, mpack_error_type);
-        return;
-    }
-
-    mpack_memcpy(buffer, mpack_node_data_unchecked(node), node.data->len);
-    buffer[node.data->len] = '\0';
-}
-
-#ifdef MPACK_MALLOC
-char* mpack_node_data_alloc(mpack_node_t node, size_t maxlen) {
-    if (mpack_node_error(node) != mpack_ok)
-        return NULL;
-
-    // make sure this is a valid data type
-    mpack_type_t type = node.data->type;
-    if (type != mpack_type_str && type != mpack_type_bin
-            #if MPACK_EXTENSIONS
-            && type != mpack_type_ext
-            #endif
-    ) {
-        mpack_node_flag_error(node, mpack_error_type);
-        return NULL;
-    }
-
-    if (node.data->len > maxlen) {
-        mpack_node_flag_error(node, mpack_error_too_big);
-        return NULL;
-    }
-
-    char* ret = (char*) MPACK_MALLOC((size_t)node.data->len);
-    if (ret == NULL) {
-        mpack_node_flag_error(node, mpack_error_memory);
-        return NULL;
-    }
-
-    mpack_memcpy(ret, mpack_node_data_unchecked(node), node.data->len);
-    return ret;
-}
-
-char* mpack_node_cstr_alloc(mpack_node_t node, size_t maxlen) {
-    if (mpack_node_error(node) != mpack_ok)
-        return NULL;
-
-    // make sure maxlen makes sense
-    if (maxlen < 1) {
-        mpack_break("maxlen is zero; you must have room for at least a null-terminator");
-        mpack_node_flag_error(node, mpack_error_bug);
-        return NULL;
-    }
-
-    if (node.data->type != mpack_type_str) {
-        mpack_node_flag_error(node, mpack_error_type);
-        return NULL;
-    }
-
-    if (node.data->len > maxlen - 1) {
-        mpack_node_flag_error(node, mpack_error_too_big);
-        return NULL;
-    }
-
-    if (!mpack_str_check_no_null(mpack_node_data_unchecked(node), node.data->len)) {
-        mpack_node_flag_error(node, mpack_error_type);
-        return NULL;
-    }
-
-    char* ret = (char*) MPACK_MALLOC((size_t)(node.data->len + 1));
-    if (ret == NULL) {
-        mpack_node_flag_error(node, mpack_error_memory);
-        return NULL;
-    }
-
-    mpack_memcpy(ret, mpack_node_data_unchecked(node), node.data->len);
-    ret[node.data->len] = '\0';
-    return ret;
-}
-
-char* mpack_node_utf8_cstr_alloc(mpack_node_t node, size_t maxlen) {
-    if (mpack_node_error(node) != mpack_ok)
-        return NULL;
-
-    // make sure maxlen makes sense
-    if (maxlen < 1) {
-        mpack_break("maxlen is zero; you must have room for at least a null-terminator");
-        mpack_node_flag_error(node, mpack_error_bug);
-        return NULL;
-    }
-
-    if (node.data->type != mpack_type_str) {
-        mpack_node_flag_error(node, mpack_error_type);
-        return NULL;
-    }
-
-    if (node.data->len > maxlen - 1) {
-        mpack_node_flag_error(node, mpack_error_too_big);
-        return NULL;
-    }
-
-    if (!mpack_utf8_check_no_null(mpack_node_data_unchecked(node), node.data->len)) {
-        mpack_node_flag_error(node, mpack_error_type);
-        return NULL;
-    }
-
-    char* ret = (char*) MPACK_MALLOC((size_t)(node.data->len + 1));
-    if (ret == NULL) {
-        mpack_node_flag_error(node, mpack_error_memory);
-        return NULL;
-    }
-
-    mpack_memcpy(ret, mpack_node_data_unchecked(node), node.data->len);
-    ret[node.data->len] = '\0';
-    return ret;
-}
-#endif
-
-
-/*
- * Compound Node Functions
- */
-
-static mpack_node_data_t* mpack_node_map_int_impl(mpack_node_t node, int64_t num) {
-    if (mpack_node_error(node) != mpack_ok)
-        return NULL;
-
-    if (node.data->type != mpack_type_map) {
-        mpack_node_flag_error(node, mpack_error_type);
-        return NULL;
-    }
-
-    mpack_node_data_t* found = NULL;
-
-    size_t i;
-    for (i = 0; i < node.data->len; ++i) {
-        mpack_node_data_t* key = mpack_node_child(node, i * 2);
-
-        if ((key->type == mpack_type_int && key->value.i == num) ||
-            (key->type == mpack_type_uint && num >= 0 && key->value.u == (uint64_t)num))
-        {
-            if (found) {
-                mpack_node_flag_error(node, mpack_error_data);
-                return NULL;
-            }
-            found = mpack_node_child(node, i * 2 + 1);
-        }
-    }
-
-    if (found)
-        return found;
-
-    return NULL;
-}
-
-static mpack_node_data_t* mpack_node_map_uint_impl(mpack_node_t node, uint64_t num) {
-    if (mpack_node_error(node) != mpack_ok)
-        return NULL;
-
-    if (node.data->type != mpack_type_map) {
-        mpack_node_flag_error(node, mpack_error_type);
-        return NULL;
-    }
-
-    mpack_node_data_t* found = NULL;
-
-    size_t i;
-    for (i = 0; i < node.data->len; ++i) {
-        mpack_node_data_t* key = mpack_node_child(node, i * 2);
-
-        if ((key->type == mpack_type_uint && key->value.u == num) ||
-            (key->type == mpack_type_int && key->value.i >= 0 && (uint64_t)key->value.i == num))
-        {
-            if (found) {
-                mpack_node_flag_error(node, mpack_error_data);
-                return NULL;
-            }
-            found = mpack_node_child(node, i * 2 + 1);
-        }
-    }
-
-    if (found)
-        return found;
-
-    return NULL;
-}
-
-static mpack_node_data_t* mpack_node_map_str_impl(mpack_node_t node, const char* str, size_t length) {
-    if (mpack_node_error(node) != mpack_ok)
-        return NULL;
-
-    mpack_assert(length == 0 || str != NULL, "str of length %i is NULL", (int)length);
-
-    if (node.data->type != mpack_type_map) {
-        mpack_node_flag_error(node, mpack_error_type);
-        return NULL;
-    }
-
-    mpack_tree_t* tree = node.tree;
-    mpack_node_data_t* found = NULL;
-
-    size_t i;
-    for (i = 0; i < node.data->len; ++i) {
-        mpack_node_data_t* key = mpack_node_child(node, i * 2);
-
-        if (key->type == mpack_type_str && key->len == length &&
-                mpack_memcmp(str, mpack_node_data_unchecked(mpack_node(tree, key)), length) == 0) {
-            if (found) {
-                mpack_node_flag_error(node, mpack_error_data);
-                return NULL;
-            }
-            found = mpack_node_child(node, i * 2 + 1);
-        }
-    }
-
-    if (found)
-        return found;
-
-    return NULL;
-}
-
-static mpack_node_t mpack_node_wrap_lookup(mpack_tree_t* tree, mpack_node_data_t* data) {
-    if (!data) {
-        if (tree->error == mpack_ok)
-            mpack_tree_flag_error(tree, mpack_error_data);
-        return mpack_tree_nil_node(tree);
-    }
-    return mpack_node(tree, data);
-}
-
-static mpack_node_t mpack_node_wrap_lookup_optional(mpack_tree_t* tree, mpack_node_data_t* data) {
-    if (!data) {
-        if (tree->error == mpack_ok)
-            return mpack_tree_missing_node(tree);
-        return mpack_tree_nil_node(tree);
-    }
-    return mpack_node(tree, data);
-}
-
-mpack_node_t mpack_node_map_int(mpack_node_t node, int64_t num) {
-    return mpack_node_wrap_lookup(node.tree, mpack_node_map_int_impl(node, num));
-}
-
-mpack_node_t mpack_node_map_int_optional(mpack_node_t node, int64_t num) {
-    return mpack_node_wrap_lookup_optional(node.tree, mpack_node_map_int_impl(node, num));
-}
-
-mpack_node_t mpack_node_map_uint(mpack_node_t node, uint64_t num) {
-    return mpack_node_wrap_lookup(node.tree, mpack_node_map_uint_impl(node, num));
-}
-
-mpack_node_t mpack_node_map_uint_optional(mpack_node_t node, uint64_t num) {
-    return mpack_node_wrap_lookup_optional(node.tree, mpack_node_map_uint_impl(node, num));
-}
-
-mpack_node_t mpack_node_map_str(mpack_node_t node, const char* str, size_t length) {
-    return mpack_node_wrap_lookup(node.tree, mpack_node_map_str_impl(node, str, length));
-}
-
-mpack_node_t mpack_node_map_str_optional(mpack_node_t node, const char* str, size_t length) {
-    return mpack_node_wrap_lookup_optional(node.tree, mpack_node_map_str_impl(node, str, length));
-}
-
-mpack_node_t mpack_node_map_cstr(mpack_node_t node, const char* cstr) {
-    mpack_assert(cstr != NULL, "cstr is NULL");
-    return mpack_node_map_str(node, cstr, mpack_strlen(cstr));
-}
-
-mpack_node_t mpack_node_map_cstr_optional(mpack_node_t node, const char* cstr) {
-    mpack_assert(cstr != NULL, "cstr is NULL");
-    return mpack_node_map_str_optional(node, cstr, mpack_strlen(cstr));
-}
-
-bool mpack_node_map_contains_int(mpack_node_t node, int64_t num) {
-    return mpack_node_map_int_impl(node, num) != NULL;
-}
-
-bool mpack_node_map_contains_uint(mpack_node_t node, uint64_t num) {
-    return mpack_node_map_uint_impl(node, num) != NULL;
-}
-
-bool mpack_node_map_contains_str(mpack_node_t node, const char* str, size_t length) {
-    return mpack_node_map_str_impl(node, str, length) != NULL;
-}
-
-bool mpack_node_map_contains_cstr(mpack_node_t node, const char* cstr) {
-    mpack_assert(cstr != NULL, "cstr is NULL");
-    return mpack_node_map_contains_str(node, cstr, mpack_strlen(cstr));
-}
-
-size_t mpack_node_enum_optional(mpack_node_t node, const char* strings[], size_t count) {
-    if (mpack_node_error(node) != mpack_ok)
-        return count;
-
-    // the value is only recognized if it is a string
-    if (mpack_node_type(node) != mpack_type_str)
-        return count;
-
-    // fetch the string
-    const char* key = mpack_node_str(node);
-    size_t keylen = mpack_node_strlen(node);
-    mpack_assert(mpack_node_error(node) == mpack_ok, "these should not fail");
-
-    // find what key it matches
-    size_t i;
-    for (i = 0; i < count; ++i) {
-        const char* other = strings[i];
-        size_t otherlen = mpack_strlen(other);
-        if (keylen == otherlen && mpack_memcmp(key, other, keylen) == 0)
-            return i;
-    }
-
-    // no matches
-    return count;
-}
-
-size_t mpack_node_enum(mpack_node_t node, const char* strings[], size_t count) {
-    size_t value = mpack_node_enum_optional(node, strings, count);
-    if (value == count)
-        mpack_node_flag_error(node, mpack_error_type);
-    return value;
-}
-
-mpack_type_t mpack_node_type(mpack_node_t node) {
-    if (mpack_node_error(node) != mpack_ok)
-        return mpack_type_nil;
-    return node.data->type;
-}
-
-bool mpack_node_is_nil(mpack_node_t node) {
-    if (mpack_node_error(node) != mpack_ok) {
-        // All nodes are treated as nil nodes when we are in error.
-        return true;
-    }
-    return node.data->type == mpack_type_nil;
-}
-
-bool mpack_node_is_missing(mpack_node_t node) {
-    if (mpack_node_error(node) != mpack_ok) {
-        // errors still return nil nodes, not missing nodes.
-        return false;
-    }
-    return node.data->type == mpack_type_missing;
-}
-
-void mpack_node_nil(mpack_node_t node) {
-    if (mpack_node_error(node) != mpack_ok)
-        return;
-    if (node.data->type != mpack_type_nil)
-        mpack_node_flag_error(node, mpack_error_type);
-}
-
-void mpack_node_missing(mpack_node_t node) {
-    if (mpack_node_error(node) != mpack_ok)
-        return;
-    if (node.data->type != mpack_type_missing)
-        mpack_node_flag_error(node, mpack_error_type);
-}
-
-bool mpack_node_bool(mpack_node_t node) {
-    if (mpack_node_error(node) != mpack_ok)
-        return false;
-
-    if (node.data->type == mpack_type_bool)
-        return node.data->value.b;
-
-    mpack_node_flag_error(node, mpack_error_type);
-    return false;
-}
-
-void mpack_node_true(mpack_node_t node) {
-    if (mpack_node_bool(node) != true)
-        mpack_node_flag_error(node, mpack_error_type);
-}
-
-void mpack_node_false(mpack_node_t node) {
-    if (mpack_node_bool(node) != false)
-        mpack_node_flag_error(node, mpack_error_type);
-}
-
-uint8_t mpack_node_u8(mpack_node_t node) {
-    if (mpack_node_error(node) != mpack_ok)
-        return 0;
-
-    if (node.data->type == mpack_type_uint) {
-        if (node.data->value.u <= MPACK_UINT8_MAX)
-            return (uint8_t)node.data->value.u;
-    } else if (node.data->type == mpack_type_int) {
-        if (node.data->value.i >= 0 && node.data->value.i <= MPACK_UINT8_MAX)
-            return (uint8_t)node.data->value.i;
-    }
-
-    mpack_node_flag_error(node, mpack_error_type);
-    return 0;
-}
-
-int8_t mpack_node_i8(mpack_node_t node) {
-    if (mpack_node_error(node) != mpack_ok)
-        return 0;
-
-    if (node.data->type == mpack_type_uint) {
-        if (node.data->value.u <= MPACK_INT8_MAX)
-            return (int8_t)node.data->value.u;
-    } else if (node.data->type == mpack_type_int) {
-        if (node.data->value.i >= MPACK_INT8_MIN && node.data->value.i <= MPACK_INT8_MAX)
-            return (int8_t)node.data->value.i;
-    }
-
-    mpack_node_flag_error(node, mpack_error_type);
-    return 0;
-}
-
-uint16_t mpack_node_u16(mpack_node_t node) {
-    if (mpack_node_error(node) != mpack_ok)
-        return 0;
-
-    if (node.data->type == mpack_type_uint) {
-        if (node.data->value.u <= MPACK_UINT16_MAX)
-            return (uint16_t)node.data->value.u;
-    } else if (node.data->type == mpack_type_int) {
-        if (node.data->value.i >= 0 && node.data->value.i <= MPACK_UINT16_MAX)
-            return (uint16_t)node.data->value.i;
-    }
-
-    mpack_node_flag_error(node, mpack_error_type);
-    return 0;
-}
-
-int16_t mpack_node_i16(mpack_node_t node) {
-    if (mpack_node_error(node) != mpack_ok)
-        return 0;
-
-    if (node.data->type == mpack_type_uint) {
-        if (node.data->value.u <= MPACK_INT16_MAX)
-            return (int16_t)node.data->value.u;
-    } else if (node.data->type == mpack_type_int) {
-        if (node.data->value.i >= MPACK_INT16_MIN && node.data->value.i <= MPACK_INT16_MAX)
-            return (int16_t)node.data->value.i;
-    }
-
-    mpack_node_flag_error(node, mpack_error_type);
-    return 0;
-}
-
-uint32_t mpack_node_u32(mpack_node_t node) {
-    if (mpack_node_error(node) != mpack_ok)
-        return 0;
-
-    if (node.data->type == mpack_type_uint) {
-        if (node.data->value.u <= MPACK_UINT32_MAX)
-            return (uint32_t)node.data->value.u;
-    } else if (node.data->type == mpack_type_int) {
-        if (node.data->value.i >= 0 && node.data->value.i <= MPACK_UINT32_MAX)
-            return (uint32_t)node.data->value.i;
-    }
-
-    mpack_node_flag_error(node, mpack_error_type);
-    return 0;
-}
-
-int32_t mpack_node_i32(mpack_node_t node) {
-    if (mpack_node_error(node) != mpack_ok)
-        return 0;
-
-    if (node.data->type == mpack_type_uint) {
-        if (node.data->value.u <= MPACK_INT32_MAX)
-            return (int32_t)node.data->value.u;
-    } else if (node.data->type == mpack_type_int) {
-        if (node.data->value.i >= MPACK_INT32_MIN && node.data->value.i <= MPACK_INT32_MAX)
-            return (int32_t)node.data->value.i;
-    }
-
-    mpack_node_flag_error(node, mpack_error_type);
-    return 0;
-}
-
-uint64_t mpack_node_u64(mpack_node_t node) {
-    if (mpack_node_error(node) != mpack_ok)
-        return 0;
-
-    if (node.data->type == mpack_type_uint) {
-        return node.data->value.u;
-    } else if (node.data->type == mpack_type_int) {
-        if (node.data->value.i >= 0)
-            return (uint64_t)node.data->value.i;
-    }
-
-    mpack_node_flag_error(node, mpack_error_type);
-    return 0;
-}
-
-int64_t mpack_node_i64(mpack_node_t node) {
-    if (mpack_node_error(node) != mpack_ok)
-        return 0;
-
-    if (node.data->type == mpack_type_uint) {
-        if (node.data->value.u <= (uint64_t)MPACK_INT64_MAX)
-            return (int64_t)node.data->value.u;
-    } else if (node.data->type == mpack_type_int) {
-        return node.data->value.i;
-    }
-
-    mpack_node_flag_error(node, mpack_error_type);
-    return 0;
-}
-
-unsigned int mpack_node_uint(mpack_node_t node) {
-
-    // This should be true at compile-time, so this just wraps the 32-bit function.
-    if (sizeof(unsigned int) == 4)
-        return (unsigned int)mpack_node_u32(node);
-
-    // Otherwise we use u64 and check the range.
-    uint64_t val = mpack_node_u64(node);
-    if (val <= MPACK_UINT_MAX)
-        return (unsigned int)val;
-
-    mpack_node_flag_error(node, mpack_error_type);
-    return 0;
-}
-
-int mpack_node_int(mpack_node_t node) {
-
-    // This should be true at compile-time, so this just wraps the 32-bit function.
-    if (sizeof(int) == 4)
-        return (int)mpack_node_i32(node);
-
-    // Otherwise we use i64 and check the range.
-    int64_t val = mpack_node_i64(node);
-    if (val >= MPACK_INT_MIN && val <= MPACK_INT_MAX)
-        return (int)val;
-
-    mpack_node_flag_error(node, mpack_error_type);
-    return 0;
-}
-
-#if MPACK_FLOAT
-float mpack_node_float(mpack_node_t node) {
-    if (mpack_node_error(node) != mpack_ok)
-        return 0.0f;
-
-    if (node.data->type == mpack_type_uint)
-        return (float)node.data->value.u;
-    if (node.data->type == mpack_type_int)
-        return (float)node.data->value.i;
-    if (node.data->type == mpack_type_float)
-        return node.data->value.f;
-
-    if (node.data->type == mpack_type_double) {
-        #if MPACK_DOUBLE
-        return (float)node.data->value.d;
-        #else
-        return mpack_shorten_raw_double_to_float(node.data->value.d);
-        #endif
-    }
-
-    mpack_node_flag_error(node, mpack_error_type);
-    return 0.0f;
-}
-#endif
-
-#if MPACK_DOUBLE
-double mpack_node_double(mpack_node_t node) {
-    if (mpack_node_error(node) != mpack_ok)
-        return 0.0;
-
-    if (node.data->type == mpack_type_uint)
-        return (double)node.data->value.u;
-    else if (node.data->type == mpack_type_int)
-        return (double)node.data->value.i;
-    else if (node.data->type == mpack_type_float)
-        return (double)node.data->value.f;
-    else if (node.data->type == mpack_type_double)
-        return node.data->value.d;
-
-    mpack_node_flag_error(node, mpack_error_type);
-    return 0.0;
-}
-#endif
-
-#if MPACK_FLOAT
-float mpack_node_float_strict(mpack_node_t node) {
-    if (mpack_node_error(node) != mpack_ok)
-        return 0.0f;
-
-    if (node.data->type == mpack_type_float)
-        return node.data->value.f;
-
-    mpack_node_flag_error(node, mpack_error_type);
-    return 0.0f;
-}
-#endif
-
-#if MPACK_DOUBLE
-double mpack_node_double_strict(mpack_node_t node) {
-    if (mpack_node_error(node) != mpack_ok)
-        return 0.0;
-
-    if (node.data->type == mpack_type_float)
-        return (double)node.data->value.f;
-    else if (node.data->type == mpack_type_double)
-        return node.data->value.d;
-
-    mpack_node_flag_error(node, mpack_error_type);
-    return 0.0;
-}
-#endif
-
-#if !MPACK_FLOAT
-uint32_t mpack_node_raw_float(mpack_node_t node) {
-    if (mpack_node_error(node) != mpack_ok)
-        return 0;
-
-    if (node.data->type == mpack_type_float)
-        return node.data->value.f;
-
-    mpack_node_flag_error(node, mpack_error_type);
-    return 0;
-}
-#endif
-
-#if !MPACK_DOUBLE
-uint64_t mpack_node_raw_double(mpack_node_t node) {
-    if (mpack_node_error(node) != mpack_ok)
-        return 0;
-
-    if (node.data->type == mpack_type_double)
-        return node.data->value.d;
-
-    mpack_node_flag_error(node, mpack_error_type);
-    return 0;
-}
-#endif
-
-#if MPACK_EXTENSIONS
-int8_t mpack_node_exttype(mpack_node_t node) {
-    if (mpack_node_error(node) != mpack_ok)
-        return 0;
-
-    if (node.data->type == mpack_type_ext)
-        return mpack_node_exttype_unchecked(node);
-
-    mpack_node_flag_error(node, mpack_error_type);
-    return 0;
-}
-#endif
-
-uint32_t mpack_node_data_len(mpack_node_t node) {
-    if (mpack_node_error(node) != mpack_ok)
-        return 0;
-
-    mpack_type_t type = node.data->type;
-    if (type == mpack_type_str || type == mpack_type_bin
-            #if MPACK_EXTENSIONS
-            || type == mpack_type_ext
-            #endif
-            )
-        return (uint32_t)node.data->len;
-
-    mpack_node_flag_error(node, mpack_error_type);
-    return 0;
-}
-
-size_t mpack_node_strlen(mpack_node_t node) {
-    if (mpack_node_error(node) != mpack_ok)
-        return 0;
-
-    if (node.data->type == mpack_type_str)
-        return (size_t)node.data->len;
-
-    mpack_node_flag_error(node, mpack_error_type);
-    return 0;
-}
-
-const char* mpack_node_str(mpack_node_t node) {
-    if (mpack_node_error(node) != mpack_ok)
-        return NULL;
-
-    mpack_type_t type = node.data->type;
-    if (type == mpack_type_str)
-        return mpack_node_data_unchecked(node);
-
-    mpack_node_flag_error(node, mpack_error_type);
-    return NULL;
-}
-
-const char* mpack_node_data(mpack_node_t node) {
-    if (mpack_node_error(node) != mpack_ok)
-        return NULL;
-
-    mpack_type_t type = node.data->type;
-    if (type == mpack_type_str || type == mpack_type_bin
-            #if MPACK_EXTENSIONS
-            || type == mpack_type_ext
-            #endif
-            )
-        return mpack_node_data_unchecked(node);
-
-    mpack_node_flag_error(node, mpack_error_type);
-    return NULL;
-}
-
-const char* mpack_node_bin_data(mpack_node_t node) {
-    if (mpack_node_error(node) != mpack_ok)
-        return NULL;
-
-    if (node.data->type == mpack_type_bin)
-        return mpack_node_data_unchecked(node);
-
-    mpack_node_flag_error(node, mpack_error_type);
-    return NULL;
-}
-
-size_t mpack_node_bin_size(mpack_node_t node) {
-    if (mpack_node_error(node) != mpack_ok)
-        return 0;
-
-    if (node.data->type == mpack_type_bin)
-        return (size_t)node.data->len;
-
-    mpack_node_flag_error(node, mpack_error_type);
-    return 0;
-}
-
-size_t mpack_node_array_length(mpack_node_t node) {
-    if (mpack_node_error(node) != mpack_ok)
-        return 0;
-
-    if (node.data->type != mpack_type_array) {
-        mpack_node_flag_error(node, mpack_error_type);
-        return 0;
-    }
-
-    return (size_t)node.data->len;
-}
-
-mpack_node_t mpack_node_array_at(mpack_node_t node, size_t index) {
-    if (mpack_node_error(node) != mpack_ok)
-        return mpack_tree_nil_node(node.tree);
-
-    if (node.data->type != mpack_type_array) {
-        mpack_node_flag_error(node, mpack_error_type);
-        return mpack_tree_nil_node(node.tree);
-    }
-
-    if (index >= node.data->len) {
-        mpack_node_flag_error(node, mpack_error_data);
-        return mpack_tree_nil_node(node.tree);
-    }
-
-    return mpack_node(node.tree, mpack_node_child(node, index));
-}
-
-size_t mpack_node_map_count(mpack_node_t node) {
-    if (mpack_node_error(node) != mpack_ok)
-        return 0;
-
-    if (node.data->type != mpack_type_map) {
-        mpack_node_flag_error(node, mpack_error_type);
-        return 0;
-    }
-
-    return node.data->len;
-}
-
-// internal node map lookup
-static mpack_node_t mpack_node_map_at(mpack_node_t node, size_t index, size_t offset) {
-    if (mpack_node_error(node) != mpack_ok)
-        return mpack_tree_nil_node(node.tree);
-
-    if (node.data->type != mpack_type_map) {
-        mpack_node_flag_error(node, mpack_error_type);
-        return mpack_tree_nil_node(node.tree);
-    }
-
-    if (index >= node.data->len) {
-        mpack_node_flag_error(node, mpack_error_data);
-        return mpack_tree_nil_node(node.tree);
-    }
-
-    return mpack_node(node.tree, mpack_node_child(node, index * 2 + offset));
-}
-
-mpack_node_t mpack_node_map_key_at(mpack_node_t node, size_t index) {
-    return mpack_node_map_at(node, index, 0);
-}
-
-mpack_node_t mpack_node_map_value_at(mpack_node_t node, size_t index) {
-    return mpack_node_map_at(node, index, 1);
-}
-
-#endif
-
-}  // namespace wpi
-MPACK_SILENCE_WARNINGS_END
diff --git a/wpiutil/src/main/native/cpp/raw_socket_istream.cpp b/wpiutil/src/main/native/cpp/raw_socket_istream.cpp
deleted file mode 100644
index c6b759e..0000000
--- a/wpiutil/src/main/native/cpp/raw_socket_istream.cpp
+++ /dev/null
@@ -1,33 +0,0 @@
-// Copyright (c) FIRST and other WPILib contributors.
-// Open Source Software; you can modify and/or share it under the terms of
-// the WPILib BSD license file in the root directory of this project.
-
-#include "wpi/raw_socket_istream.h"
-
-#include "wpi/NetworkStream.h"
-
-using namespace wpi;
-
-void raw_socket_istream::read_impl(void* data, size_t len) {
-  char* cdata = static_cast<char*>(data);
-  size_t pos = 0;
-
-  while (pos < len) {
-    NetworkStream::Error err;
-    size_t count = m_stream.receive(&cdata[pos], len - pos, &err, m_timeout);
-    if (count == 0) {
-      error_detected();
-      break;
-    }
-    pos += count;
-  }
-  set_read_count(pos);
-}
-
-void raw_socket_istream::close() {
-  m_stream.close();
-}
-
-size_t raw_socket_istream::in_avail() const {
-  return 0;
-}
diff --git a/wpiutil/src/main/native/cpp/raw_socket_ostream.cpp b/wpiutil/src/main/native/cpp/raw_socket_ostream.cpp
deleted file mode 100644
index af01c84..0000000
--- a/wpiutil/src/main/native/cpp/raw_socket_ostream.cpp
+++ /dev/null
@@ -1,42 +0,0 @@
-// Copyright (c) FIRST and other WPILib contributors.
-// Open Source Software; you can modify and/or share it under the terms of
-// the WPILib BSD license file in the root directory of this project.
-
-#include "wpi/raw_socket_ostream.h"
-
-#include "wpi/NetworkStream.h"
-
-using namespace wpi;
-
-raw_socket_ostream::~raw_socket_ostream() {
-  flush();
-  if (m_shouldClose) {
-    close();
-  }
-}
-
-void raw_socket_ostream::write_impl(const char* data, size_t len) {
-  size_t pos = 0;
-
-  while (pos < len) {
-    NetworkStream::Error err;
-    size_t count = m_stream.send(&data[pos], len - pos, &err);
-    if (count == 0) {
-      error_detected();
-      return;
-    }
-    pos += count;
-  }
-}
-
-uint64_t raw_socket_ostream::current_pos() const {
-  return 0;
-}
-
-void raw_socket_ostream::close() {
-  if (!m_shouldClose) {
-    return;
-  }
-  flush();
-  m_stream.close();
-}
diff --git a/wpiutil/src/main/native/cpp/raw_uv_ostream.cpp b/wpiutil/src/main/native/cpp/raw_uv_ostream.cpp
deleted file mode 100644
index f055a2a..0000000
--- a/wpiutil/src/main/native/cpp/raw_uv_ostream.cpp
+++ /dev/null
@@ -1,39 +0,0 @@
-// Copyright (c) FIRST and other WPILib contributors.
-// Open Source Software; you can modify and/or share it under the terms of
-// the WPILib BSD license file in the root directory of this project.
-
-#include "wpi/raw_uv_ostream.h"
-
-#include <cstring>
-
-using namespace wpi;
-
-void raw_uv_ostream::write_impl(const char* data, size_t len) {
-  while (len > 0) {
-    // allocate additional buffers as required
-    if (m_left == 0) {
-      m_bufs.emplace_back(m_alloc());
-      // we want bufs() to always be valid, so set len=0 and keep track of the
-      // amount of space remaining separately
-      m_left = m_bufs.back().len;
-      m_bufs.back().len = 0;
-      assert(m_left != 0);
-    }
-
-    size_t amt = (std::min)(m_left, len);
-    auto& buf = m_bufs.back();
-    std::memcpy(buf.base + buf.len, data, amt);
-    data += amt;
-    len -= amt;
-    buf.len += amt;
-    m_left -= amt;
-  }
-}
-
-uint64_t raw_uv_ostream::current_pos() const {
-  uint64_t size = 0;
-  for (auto&& buf : m_bufs) {
-    size += buf.len;
-  }
-  return size;
-}
diff --git a/wpiutil/src/main/native/cpp/sha1.cpp b/wpiutil/src/main/native/cpp/sha1.cpp
index 8ec7cb1..98dc543 100644
--- a/wpiutil/src/main/native/cpp/sha1.cpp
+++ b/wpiutil/src/main/native/cpp/sha1.cpp
@@ -217,7 +217,7 @@
 }
 
 void SHA1::Update(std::string_view s) {
-  raw_mem_istream is(span<const char>(s.data(), s.size()));
+  raw_mem_istream is(std::span<const char>(s.data(), s.size()));
   Update(is);
 }
 
diff --git a/wpiutil/src/main/native/cpp/uv/Async.cpp b/wpiutil/src/main/native/cpp/uv/Async.cpp
deleted file mode 100644
index c9d698e..0000000
--- a/wpiutil/src/main/native/cpp/uv/Async.cpp
+++ /dev/null
@@ -1,33 +0,0 @@
-// Copyright (c) FIRST and other WPILib contributors.
-// Open Source Software; you can modify and/or share it under the terms of
-// the WPILib BSD license file in the root directory of this project.
-
-#include "wpi/uv/Async.h"
-
-#include "wpi/uv/Loop.h"
-
-namespace wpi::uv {
-
-Async<>::~Async() noexcept {
-  if (auto loop = m_loop.lock()) {
-    Close();
-  } else {
-    ForceClosed();
-  }
-}
-
-std::shared_ptr<Async<>> Async<>::Create(const std::shared_ptr<Loop>& loop) {
-  auto h = std::make_shared<Async>(loop, private_init{});
-  int err = uv_async_init(loop->GetRaw(), h->GetRaw(), [](uv_async_t* handle) {
-    Async& h = *static_cast<Async*>(handle->data);
-    h.wakeup();
-  });
-  if (err < 0) {
-    loop->ReportError(err);
-    return nullptr;
-  }
-  h->Keep();
-  return h;
-}
-
-}  // namespace wpi::uv
diff --git a/wpiutil/src/main/native/cpp/uv/Check.cpp b/wpiutil/src/main/native/cpp/uv/Check.cpp
deleted file mode 100644
index 97265d4..0000000
--- a/wpiutil/src/main/native/cpp/uv/Check.cpp
+++ /dev/null
@@ -1,29 +0,0 @@
-// Copyright (c) FIRST and other WPILib contributors.
-// Open Source Software; you can modify and/or share it under the terms of
-// the WPILib BSD license file in the root directory of this project.
-
-#include "wpi/uv/Check.h"
-
-#include "wpi/uv/Loop.h"
-
-namespace wpi::uv {
-
-std::shared_ptr<Check> Check::Create(Loop& loop) {
-  auto h = std::make_shared<Check>(private_init{});
-  int err = uv_check_init(loop.GetRaw(), h->GetRaw());
-  if (err < 0) {
-    loop.ReportError(err);
-    return nullptr;
-  }
-  h->Keep();
-  return h;
-}
-
-void Check::Start() {
-  Invoke(&uv_check_start, GetRaw(), [](uv_check_t* handle) {
-    Check& h = *static_cast<Check*>(handle->data);
-    h.check();
-  });
-}
-
-}  // namespace wpi::uv
diff --git a/wpiutil/src/main/native/cpp/uv/FsEvent.cpp b/wpiutil/src/main/native/cpp/uv/FsEvent.cpp
deleted file mode 100644
index 3c83d1d..0000000
--- a/wpiutil/src/main/native/cpp/uv/FsEvent.cpp
+++ /dev/null
@@ -1,63 +0,0 @@
-// Copyright (c) FIRST and other WPILib contributors.
-// Open Source Software; you can modify and/or share it under the terms of
-// the WPILib BSD license file in the root directory of this project.
-
-#include "wpi/uv/FsEvent.h"
-
-#include <cstdlib>
-
-#include "wpi/SmallString.h"
-#include "wpi/uv/Loop.h"
-
-namespace wpi::uv {
-
-std::shared_ptr<FsEvent> FsEvent::Create(Loop& loop) {
-  auto h = std::make_shared<FsEvent>(private_init{});
-  int err = uv_fs_event_init(loop.GetRaw(), h->GetRaw());
-  if (err < 0) {
-    loop.ReportError(err);
-    return nullptr;
-  }
-  h->Keep();
-  return h;
-}
-
-void FsEvent::Start(std::string_view path, unsigned int flags) {
-  SmallString<128> pathBuf{path};
-  Invoke(
-      &uv_fs_event_start, GetRaw(),
-      [](uv_fs_event_t* handle, const char* filename, int events, int status) {
-        FsEvent& h = *static_cast<FsEvent*>(handle->data);
-        if (status < 0) {
-          h.ReportError(status);
-        } else {
-          h.fsEvent(filename, events);
-        }
-      },
-      pathBuf.c_str(), flags);
-}
-
-std::string FsEvent::GetPath() {
-  // Per the libuv docs, GetPath() always gives us a null-terminated string.
-  // common case should be small
-  char buf[128];
-  size_t size = 128;
-  int r = uv_fs_event_getpath(GetRaw(), buf, &size);
-  if (r == 0) {
-    return buf;
-  } else if (r == UV_ENOBUFS) {
-    // need to allocate a big enough buffer
-    char* buf2 = static_cast<char*>(std::malloc(size));
-    r = uv_fs_event_getpath(GetRaw(), buf2, &size);
-    if (r == 0) {
-      std::string out{buf2};
-      std::free(buf2);
-      return out;
-    }
-    std::free(buf2);
-  }
-  ReportError(r);
-  return std::string{};
-}
-
-}  // namespace wpi::uv
diff --git a/wpiutil/src/main/native/cpp/uv/GetAddrInfo.cpp b/wpiutil/src/main/native/cpp/uv/GetAddrInfo.cpp
deleted file mode 100644
index 2e6e38f..0000000
--- a/wpiutil/src/main/native/cpp/uv/GetAddrInfo.cpp
+++ /dev/null
@@ -1,51 +0,0 @@
-// Copyright (c) FIRST and other WPILib contributors.
-// Open Source Software; you can modify and/or share it under the terms of
-// the WPILib BSD license file in the root directory of this project.
-
-#include "wpi/uv/GetAddrInfo.h"
-
-#include "wpi/SmallString.h"
-#include "wpi/uv/Loop.h"
-#include "wpi/uv/util.h"
-
-namespace wpi::uv {
-
-GetAddrInfoReq::GetAddrInfoReq() {
-  error = [this](Error err) { GetLoop().error(err); };
-}
-
-void GetAddrInfo(Loop& loop, const std::shared_ptr<GetAddrInfoReq>& req,
-                 std::string_view node, std::string_view service,
-                 const addrinfo* hints) {
-  SmallString<128> nodeStr{node};
-  SmallString<128> serviceStr{service};
-  int err = uv_getaddrinfo(
-      loop.GetRaw(), req->GetRaw(),
-      [](uv_getaddrinfo_t* req, int status, addrinfo* res) {
-        auto& h = *static_cast<GetAddrInfoReq*>(req->data);
-        if (status < 0) {
-          h.ReportError(status);
-        } else {
-          h.resolved(*res);
-        }
-        uv_freeaddrinfo(res);
-        h.Release();  // this is always a one-shot
-      },
-      node.empty() ? nullptr : nodeStr.c_str(),
-      service.empty() ? nullptr : serviceStr.c_str(), hints);
-  if (err < 0) {
-    loop.ReportError(err);
-  } else {
-    req->Keep();
-  }
-}
-
-void GetAddrInfo(Loop& loop, std::function<void(const addrinfo&)> callback,
-                 std::string_view node, std::string_view service,
-                 const addrinfo* hints) {
-  auto req = std::make_shared<GetAddrInfoReq>();
-  req->resolved.connect(std::move(callback));
-  GetAddrInfo(loop, req, node, service, hints);
-}
-
-}  // namespace wpi::uv
diff --git a/wpiutil/src/main/native/cpp/uv/GetNameInfo.cpp b/wpiutil/src/main/native/cpp/uv/GetNameInfo.cpp
deleted file mode 100644
index 4e662f3..0000000
--- a/wpiutil/src/main/native/cpp/uv/GetNameInfo.cpp
+++ /dev/null
@@ -1,94 +0,0 @@
-// Copyright (c) FIRST and other WPILib contributors.
-// Open Source Software; you can modify and/or share it under the terms of
-// the WPILib BSD license file in the root directory of this project.
-
-#include "wpi/uv/GetNameInfo.h"
-
-#include "wpi/uv/Loop.h"
-#include "wpi/uv/util.h"
-
-namespace wpi::uv {
-
-GetNameInfoReq::GetNameInfoReq() {
-  error = [this](Error err) { GetLoop().error(err); };
-}
-
-void GetNameInfo(Loop& loop, const std::shared_ptr<GetNameInfoReq>& req,
-                 const sockaddr& addr, int flags) {
-  int err = uv_getnameinfo(
-      loop.GetRaw(), req->GetRaw(),
-      [](uv_getnameinfo_t* req, int status, const char* hostname,
-         const char* service) {
-        auto& h = *static_cast<GetNameInfoReq*>(req->data);
-        if (status < 0) {
-          h.ReportError(status);
-        } else {
-          h.resolved(hostname, service);
-        }
-        h.Release();  // this is always a one-shot
-      },
-      &addr, flags);
-  if (err < 0) {
-    loop.ReportError(err);
-  } else {
-    req->Keep();
-  }
-}
-
-void GetNameInfo(Loop& loop,
-                 std::function<void(const char*, const char*)> callback,
-                 const sockaddr& addr, int flags) {
-  auto req = std::make_shared<GetNameInfoReq>();
-  req->resolved.connect(std::move(callback));
-  GetNameInfo(loop, req, addr, flags);
-}
-
-void GetNameInfo4(Loop& loop, const std::shared_ptr<GetNameInfoReq>& req,
-                  std::string_view ip, unsigned int port, int flags) {
-  sockaddr_in addr;
-  int err = NameToAddr(ip, port, &addr);
-  if (err < 0) {
-    loop.ReportError(err);
-  } else {
-    GetNameInfo(loop, req, reinterpret_cast<const sockaddr&>(addr), flags);
-  }
-}
-
-void GetNameInfo4(Loop& loop,
-                  std::function<void(const char*, const char*)> callback,
-                  std::string_view ip, unsigned int port, int flags) {
-  sockaddr_in addr;
-  int err = NameToAddr(ip, port, &addr);
-  if (err < 0) {
-    loop.ReportError(err);
-  } else {
-    GetNameInfo(loop, std::move(callback),
-                reinterpret_cast<const sockaddr&>(addr), flags);
-  }
-}
-
-void GetNameInfo6(Loop& loop, const std::shared_ptr<GetNameInfoReq>& req,
-                  std::string_view ip, unsigned int port, int flags) {
-  sockaddr_in6 addr;
-  int err = NameToAddr(ip, port, &addr);
-  if (err < 0) {
-    loop.ReportError(err);
-  } else {
-    GetNameInfo(loop, req, reinterpret_cast<const sockaddr&>(addr), flags);
-  }
-}
-
-void GetNameInfo6(Loop& loop,
-                  std::function<void(const char*, const char*)> callback,
-                  std::string_view ip, unsigned int port, int flags) {
-  sockaddr_in6 addr;
-  int err = NameToAddr(ip, port, &addr);
-  if (err < 0) {
-    loop.ReportError(err);
-  } else {
-    GetNameInfo(loop, std::move(callback),
-                reinterpret_cast<const sockaddr&>(addr), flags);
-  }
-}
-
-}  // namespace wpi::uv
diff --git a/wpiutil/src/main/native/cpp/uv/Handle.cpp b/wpiutil/src/main/native/cpp/uv/Handle.cpp
deleted file mode 100644
index 74c4c60..0000000
--- a/wpiutil/src/main/native/cpp/uv/Handle.cpp
+++ /dev/null
@@ -1,35 +0,0 @@
-// Copyright (c) FIRST and other WPILib contributors.
-// Open Source Software; you can modify and/or share it under the terms of
-// the WPILib BSD license file in the root directory of this project.
-
-#include "wpi/uv/Handle.h"
-
-using namespace wpi::uv;
-
-Handle::~Handle() noexcept {
-  if (!m_closed && m_uv_handle->type != UV_UNKNOWN_HANDLE) {
-    uv_close(m_uv_handle, [](uv_handle_t* uv_handle) { std::free(uv_handle); });
-  } else {
-    std::free(m_uv_handle);
-  }
-}
-
-void Handle::Close() noexcept {
-  if (!IsClosing()) {
-    uv_close(m_uv_handle, [](uv_handle_t* handle) {
-      Handle& h = *static_cast<Handle*>(handle->data);
-      h.closed();
-      h.Release();  // free ourselves
-    });
-    m_closed = true;
-  }
-}
-
-void Handle::AllocBuf(uv_handle_t* handle, size_t size, uv_buf_t* buf) {
-  auto& h = *static_cast<Handle*>(handle->data);
-  *buf = h.m_allocBuf(size);
-}
-
-void Handle::DefaultFreeBuf(Buffer& buf) {
-  buf.Deallocate();
-}
diff --git a/wpiutil/src/main/native/cpp/uv/Idle.cpp b/wpiutil/src/main/native/cpp/uv/Idle.cpp
deleted file mode 100644
index 6bf8602..0000000
--- a/wpiutil/src/main/native/cpp/uv/Idle.cpp
+++ /dev/null
@@ -1,29 +0,0 @@
-// Copyright (c) FIRST and other WPILib contributors.
-// Open Source Software; you can modify and/or share it under the terms of
-// the WPILib BSD license file in the root directory of this project.
-
-#include "wpi/uv/Idle.h"
-
-#include "wpi/uv/Loop.h"
-
-namespace wpi::uv {
-
-std::shared_ptr<Idle> Idle::Create(Loop& loop) {
-  auto h = std::make_shared<Idle>(private_init{});
-  int err = uv_idle_init(loop.GetRaw(), h->GetRaw());
-  if (err < 0) {
-    loop.ReportError(err);
-    return nullptr;
-  }
-  h->Keep();
-  return h;
-}
-
-void Idle::Start() {
-  Invoke(&uv_idle_start, GetRaw(), [](uv_idle_t* handle) {
-    Idle& h = *static_cast<Idle*>(handle->data);
-    h.idle();
-  });
-}
-
-}  // namespace wpi::uv
diff --git a/wpiutil/src/main/native/cpp/uv/Loop.cpp b/wpiutil/src/main/native/cpp/uv/Loop.cpp
deleted file mode 100644
index c5b7163..0000000
--- a/wpiutil/src/main/native/cpp/uv/Loop.cpp
+++ /dev/null
@@ -1,70 +0,0 @@
-// Copyright (c) FIRST and other WPILib contributors.
-// Open Source Software; you can modify and/or share it under the terms of
-// the WPILib BSD license file in the root directory of this project.
-
-#include "wpi/uv/Loop.h"
-
-using namespace wpi::uv;
-
-Loop::Loop(const private_init&) noexcept {
-#ifndef _WIN32
-  // Ignore SIGPIPE (see https://github.com/joyent/libuv/issues/1254)
-  static bool once = []() {
-    signal(SIGPIPE, SIG_IGN);
-    return true;
-  }();
-  (void)once;
-#endif
-}
-
-Loop::~Loop() noexcept {
-  if (m_loop) {
-    m_loop->data = nullptr;
-    Close();
-  }
-}
-
-std::shared_ptr<Loop> Loop::Create() {
-  auto loop = std::make_shared<Loop>(private_init{});
-  if (uv_loop_init(&loop->m_loopStruct) < 0) {
-    return nullptr;
-  }
-  loop->m_loop = &loop->m_loopStruct;
-  loop->m_loop->data = loop.get();
-  return loop;
-}
-
-std::shared_ptr<Loop> Loop::GetDefault() {
-  static std::shared_ptr<Loop> loop = std::make_shared<Loop>(private_init{});
-  loop->m_loop = uv_default_loop();
-  if (!loop->m_loop) {
-    return nullptr;
-  }
-  loop->m_loop->data = loop.get();
-  return loop;
-}
-
-void Loop::Close() {
-  int err = uv_loop_close(m_loop);
-  if (err < 0) {
-    ReportError(err);
-  }
-}
-
-void Loop::Walk(function_ref<void(Handle&)> callback) {
-  uv_walk(
-      m_loop,
-      [](uv_handle_t* handle, void* func) {
-        auto& h = *static_cast<Handle*>(handle->data);
-        auto& f = *static_cast<function_ref<void(Handle&)>*>(func);
-        f(h);
-      },
-      &callback);
-}
-
-void Loop::Fork() {
-  int err = uv_loop_fork(m_loop);
-  if (err < 0) {
-    ReportError(err);
-  }
-}
diff --git a/wpiutil/src/main/native/cpp/uv/NameToAddr.cpp b/wpiutil/src/main/native/cpp/uv/NameToAddr.cpp
deleted file mode 100644
index 23ec6da..0000000
--- a/wpiutil/src/main/native/cpp/uv/NameToAddr.cpp
+++ /dev/null
@@ -1,59 +0,0 @@
-// Copyright (c) FIRST and other WPILib contributors.
-// Open Source Software; you can modify and/or share it under the terms of
-// the WPILib BSD license file in the root directory of this project.
-
-#include "wpi/uv/util.h"  // NOLINT(build/include_order)
-
-#include <cstring>
-
-#include "wpi/SmallString.h"
-
-namespace wpi::uv {
-
-int NameToAddr(std::string_view ip, unsigned int port, sockaddr_in* addr) {
-  if (ip.empty()) {
-    std::memset(addr, 0, sizeof(sockaddr_in));
-    addr->sin_family = PF_INET;
-    addr->sin_addr.s_addr = INADDR_ANY;
-    addr->sin_port = htons(port);
-    return 0;
-  } else {
-    SmallString<128> ipBuf{ip};
-    return uv_ip4_addr(ipBuf.c_str(), port, addr);
-  }
-}
-
-int NameToAddr(std::string_view ip, unsigned int port, sockaddr_in6* addr) {
-  if (ip.empty()) {
-    std::memset(addr, 0, sizeof(sockaddr_in6));
-    addr->sin6_family = PF_INET6;
-    addr->sin6_addr = in6addr_any;
-    addr->sin6_port = htons(port);
-    return 0;
-  } else {
-    SmallString<128> ipBuf{ip};
-    return uv_ip6_addr(ipBuf.c_str(), port, addr);
-  }
-}
-
-int NameToAddr(std::string_view ip, in_addr* addr) {
-  if (ip.empty()) {
-    addr->s_addr = INADDR_ANY;
-    return 0;
-  } else {
-    SmallString<128> ipBuf{ip};
-    return uv_inet_pton(AF_INET, ipBuf.c_str(), addr);
-  }
-}
-
-int NameToAddr(std::string_view ip, in6_addr* addr) {
-  if (ip.empty()) {
-    *addr = in6addr_any;
-    return 0;
-  } else {
-    SmallString<128> ipBuf{ip};
-    return uv_inet_pton(AF_INET6, ipBuf.c_str(), addr);
-  }
-}
-
-}  // namespace wpi::uv
diff --git a/wpiutil/src/main/native/cpp/uv/NetworkStream.cpp b/wpiutil/src/main/native/cpp/uv/NetworkStream.cpp
deleted file mode 100644
index 0bc3337..0000000
--- a/wpiutil/src/main/native/cpp/uv/NetworkStream.cpp
+++ /dev/null
@@ -1,30 +0,0 @@
-// Copyright (c) FIRST and other WPILib contributors.
-// Open Source Software; you can modify and/or share it under the terms of
-// the WPILib BSD license file in the root directory of this project.
-
-#include "wpi/uv/NetworkStream.h"
-
-namespace wpi::uv {
-
-ConnectReq::ConnectReq() {
-  error = [this](Error err) { GetStream().error(err); };
-}
-
-void NetworkStream::Listen(int backlog) {
-  Invoke(&uv_listen, GetRawStream(), backlog,
-         [](uv_stream_t* handle, int status) {
-           auto& h = *static_cast<NetworkStream*>(handle->data);
-           if (status < 0) {
-             h.ReportError(status);
-           } else {
-             h.connection();
-           }
-         });
-}
-
-void NetworkStream::Listen(std::function<void()> callback, int backlog) {
-  connection.connect(std::move(callback));
-  Listen(backlog);
-}
-
-}  // namespace wpi::uv
diff --git a/wpiutil/src/main/native/cpp/uv/Pipe.cpp b/wpiutil/src/main/native/cpp/uv/Pipe.cpp
deleted file mode 100644
index b5ca673..0000000
--- a/wpiutil/src/main/native/cpp/uv/Pipe.cpp
+++ /dev/null
@@ -1,138 +0,0 @@
-// Copyright (c) FIRST and other WPILib contributors.
-// Open Source Software; you can modify and/or share it under the terms of
-// the WPILib BSD license file in the root directory of this project.
-
-#include "wpi/uv/Pipe.h"
-
-#include <cstdlib>
-
-#include "wpi/SmallString.h"
-
-namespace wpi::uv {
-
-std::shared_ptr<Pipe> Pipe::Create(Loop& loop, bool ipc) {
-  auto h = std::make_shared<Pipe>(private_init{});
-  int err = uv_pipe_init(loop.GetRaw(), h->GetRaw(), ipc ? 1 : 0);
-  if (err < 0) {
-    loop.ReportError(err);
-    return nullptr;
-  }
-  h->Keep();
-  return h;
-}
-
-void Pipe::Reuse(std::function<void()> callback, bool ipc) {
-  if (IsClosing()) {
-    return;
-  }
-  if (!m_reuseData) {
-    m_reuseData = std::make_unique<ReuseData>();
-  }
-  m_reuseData->callback = std::move(callback);
-  m_reuseData->ipc = ipc;
-  uv_close(GetRawHandle(), [](uv_handle_t* handle) {
-    Pipe& h = *static_cast<Pipe*>(handle->data);
-    if (!h.m_reuseData) {
-      return;
-    }
-    auto data = std::move(h.m_reuseData);
-    auto err =
-        uv_pipe_init(h.GetLoopRef().GetRaw(), h.GetRaw(), data->ipc ? 1 : 0);
-    if (err < 0) {
-      h.ReportError(err);
-      return;
-    }
-    data->callback();
-  });
-}
-
-std::shared_ptr<Pipe> Pipe::Accept() {
-  auto client = Create(GetLoopRef(), GetRaw()->ipc);
-  if (!client) {
-    return nullptr;
-  }
-  if (!Accept(client)) {
-    client->Release();
-    return nullptr;
-  }
-  return client;
-}
-
-Pipe* Pipe::DoAccept() {
-  return Accept().get();
-}
-
-void Pipe::Bind(std::string_view name) {
-  SmallString<128> nameBuf{name};
-  Invoke(&uv_pipe_bind, GetRaw(), nameBuf.c_str());
-}
-
-void Pipe::Connect(std::string_view name,
-                   const std::shared_ptr<PipeConnectReq>& req) {
-  SmallString<128> nameBuf{name};
-  uv_pipe_connect(req->GetRaw(), GetRaw(), nameBuf.c_str(),
-                  [](uv_connect_t* req, int status) {
-                    auto& h = *static_cast<PipeConnectReq*>(req->data);
-                    if (status < 0) {
-                      h.ReportError(status);
-                    } else {
-                      h.connected();
-                    }
-                    h.Release();  // this is always a one-shot
-                  });
-  req->Keep();
-}
-
-void Pipe::Connect(std::string_view name, std::function<void()> callback) {
-  auto req = std::make_shared<PipeConnectReq>();
-  req->connected.connect(std::move(callback));
-  Connect(name, req);
-}
-
-std::string Pipe::GetSock() {
-  // Per libuv docs, the returned buffer is NOT null terminated.
-  // common case should be small
-  char buf[128];
-  size_t size = 128;
-  int r = uv_pipe_getsockname(GetRaw(), buf, &size);
-  if (r == 0) {
-    return std::string{buf, size};
-  } else if (r == UV_ENOBUFS) {
-    // need to allocate a big enough buffer
-    char* buf2 = static_cast<char*>(std::malloc(size));
-    r = uv_pipe_getsockname(GetRaw(), buf2, &size);
-    if (r == 0) {
-      std::string out{buf2, size};
-      std::free(buf2);
-      return out;
-    }
-    std::free(buf2);
-  }
-  ReportError(r);
-  return std::string{};
-}
-
-std::string Pipe::GetPeer() {
-  // Per libuv docs, the returned buffer is NOT null terminated.
-  // common case should be small
-  char buf[128];
-  size_t size = 128;
-  int r = uv_pipe_getpeername(GetRaw(), buf, &size);
-  if (r == 0) {
-    return std::string{buf, size};
-  } else if (r == UV_ENOBUFS) {
-    // need to allocate a big enough buffer
-    char* buf2 = static_cast<char*>(std::malloc(size));
-    r = uv_pipe_getpeername(GetRaw(), buf2, &size);
-    if (r == 0) {
-      std::string out{buf2, size};
-      std::free(buf2);
-      return out;
-    }
-    std::free(buf2);
-  }
-  ReportError(r);
-  return std::string{};
-}
-
-}  // namespace wpi::uv
diff --git a/wpiutil/src/main/native/cpp/uv/Poll.cpp b/wpiutil/src/main/native/cpp/uv/Poll.cpp
deleted file mode 100644
index 090a40b..0000000
--- a/wpiutil/src/main/native/cpp/uv/Poll.cpp
+++ /dev/null
@@ -1,95 +0,0 @@
-// Copyright (c) FIRST and other WPILib contributors.
-// Open Source Software; you can modify and/or share it under the terms of
-// the WPILib BSD license file in the root directory of this project.
-
-#include "wpi/uv/Poll.h"
-
-#include "wpi/uv/Loop.h"
-
-namespace wpi::uv {
-
-std::shared_ptr<Poll> Poll::Create(Loop& loop, int fd) {
-  auto h = std::make_shared<Poll>(private_init{});
-  int err = uv_poll_init(loop.GetRaw(), h->GetRaw(), fd);
-  if (err < 0) {
-    loop.ReportError(err);
-    return nullptr;
-  }
-  h->Keep();
-  return h;
-}
-
-std::shared_ptr<Poll> Poll::CreateSocket(Loop& loop, uv_os_sock_t sock) {
-  auto h = std::make_shared<Poll>(private_init{});
-  int err = uv_poll_init_socket(loop.GetRaw(), h->GetRaw(), sock);
-  if (err < 0) {
-    loop.ReportError(err);
-    return nullptr;
-  }
-  h->Keep();
-  return h;
-}
-
-void Poll::Reuse(int fd, std::function<void()> callback) {
-  if (IsClosing()) {
-    return;
-  }
-  if (!m_reuseData) {
-    m_reuseData = std::make_unique<ReuseData>();
-  }
-  m_reuseData->callback = std::move(callback);
-  m_reuseData->isSocket = false;
-  m_reuseData->fd = fd;
-  uv_close(GetRawHandle(), [](uv_handle_t* handle) {
-    Poll& h = *static_cast<Poll*>(handle->data);
-    if (!h.m_reuseData || h.m_reuseData->isSocket) {
-      return;  // just in case
-    }
-    auto data = std::move(h.m_reuseData);
-    int err = uv_poll_init(h.GetLoopRef().GetRaw(), h.GetRaw(), data->fd);
-    if (err < 0) {
-      h.ReportError(err);
-      return;
-    }
-    data->callback();
-  });
-}
-
-void Poll::ReuseSocket(uv_os_sock_t sock, std::function<void()> callback) {
-  if (IsClosing()) {
-    return;
-  }
-  if (!m_reuseData) {
-    m_reuseData = std::make_unique<ReuseData>();
-  }
-  m_reuseData->callback = std::move(callback);
-  m_reuseData->isSocket = true;
-  m_reuseData->sock = sock;
-  uv_close(GetRawHandle(), [](uv_handle_t* handle) {
-    Poll& h = *static_cast<Poll*>(handle->data);
-    if (!h.m_reuseData || !h.m_reuseData->isSocket) {
-      return;  // just in case
-    }
-    auto data = std::move(h.m_reuseData);
-    int err = uv_poll_init(h.GetLoopRef().GetRaw(), h.GetRaw(), data->sock);
-    if (err < 0) {
-      h.ReportError(err);
-      return;
-    }
-    data->callback();
-  });
-}
-
-void Poll::Start(int events) {
-  Invoke(&uv_poll_start, GetRaw(), events,
-         [](uv_poll_t* handle, int status, int events) {
-           Poll& h = *static_cast<Poll*>(handle->data);
-           if (status < 0) {
-             h.ReportError(status);
-           } else {
-             h.pollEvent(events);
-           }
-         });
-}
-
-}  // namespace wpi::uv
diff --git a/wpiutil/src/main/native/cpp/uv/Prepare.cpp b/wpiutil/src/main/native/cpp/uv/Prepare.cpp
deleted file mode 100644
index 048fd08..0000000
--- a/wpiutil/src/main/native/cpp/uv/Prepare.cpp
+++ /dev/null
@@ -1,29 +0,0 @@
-// Copyright (c) FIRST and other WPILib contributors.
-// Open Source Software; you can modify and/or share it under the terms of
-// the WPILib BSD license file in the root directory of this project.
-
-#include "wpi/uv/Prepare.h"
-
-#include "wpi/uv/Loop.h"
-
-namespace wpi::uv {
-
-std::shared_ptr<Prepare> Prepare::Create(Loop& loop) {
-  auto h = std::make_shared<Prepare>(private_init{});
-  int err = uv_prepare_init(loop.GetRaw(), h->GetRaw());
-  if (err < 0) {
-    loop.ReportError(err);
-    return nullptr;
-  }
-  h->Keep();
-  return h;
-}
-
-void Prepare::Start() {
-  Invoke(&uv_prepare_start, GetRaw(), [](uv_prepare_t* handle) {
-    Prepare& h = *static_cast<Prepare*>(handle->data);
-    h.prepare();
-  });
-}
-
-}  // namespace wpi::uv
diff --git a/wpiutil/src/main/native/cpp/uv/Process.cpp b/wpiutil/src/main/native/cpp/uv/Process.cpp
deleted file mode 100644
index c8d5229..0000000
--- a/wpiutil/src/main/native/cpp/uv/Process.cpp
+++ /dev/null
@@ -1,133 +0,0 @@
-// Copyright (c) FIRST and other WPILib contributors.
-// Open Source Software; you can modify and/or share it under the terms of
-// the WPILib BSD license file in the root directory of this project.
-
-#include "wpi/uv/Process.h"
-
-#include "wpi/SmallString.h"
-#include "wpi/uv/Loop.h"
-#include "wpi/uv/Pipe.h"
-
-namespace wpi::uv {
-
-std::shared_ptr<Process> Process::SpawnArray(Loop& loop, std::string_view file,
-                                             span<const Option> options) {
-  // convert Option array to libuv structure
-  uv_process_options_t coptions;
-
-  coptions.exit_cb = [](uv_process_t* handle, int64_t status, int signal) {
-    auto& h = *static_cast<Process*>(handle->data);
-    h.exited(status, signal);
-  };
-
-  SmallString<128> fileBuf{file};
-  coptions.file = fileBuf.c_str();
-  coptions.cwd = nullptr;
-  coptions.flags = 0;
-  coptions.uid = 0;
-  coptions.gid = 0;
-
-  SmallVector<char*, 4> argsBuf;
-  SmallVector<char*, 4> envBuf;
-  struct StdioContainer : public uv_stdio_container_t {
-    StdioContainer() {
-      flags = UV_IGNORE;
-      data.fd = 0;
-    }
-  };
-  SmallVector<StdioContainer, 4> stdioBuf;
-
-  for (auto&& o : options) {
-    switch (o.m_type) {
-      case Option::kArg:
-        argsBuf.push_back(const_cast<char*>(o.m_data.str));
-        break;
-      case Option::kEnv:
-        envBuf.push_back(const_cast<char*>(o.m_data.str));
-        break;
-      case Option::kCwd:
-        coptions.cwd = o.m_data.str[0] == '\0' ? nullptr : o.m_data.str;
-        break;
-      case Option::kUid:
-        coptions.uid = o.m_data.uid;
-        coptions.flags |= UV_PROCESS_SETUID;
-        break;
-      case Option::kGid:
-        coptions.gid = o.m_data.gid;
-        coptions.flags |= UV_PROCESS_SETGID;
-        break;
-      case Option::kSetFlags:
-        coptions.flags |= o.m_data.flags;
-        break;
-      case Option::kClearFlags:
-        coptions.flags &= ~o.m_data.flags;
-        break;
-      case Option::kStdioIgnore: {
-        size_t index = o.m_data.stdio.index;
-        if (index >= stdioBuf.size()) {
-          stdioBuf.resize(index + 1);
-        }
-        stdioBuf[index].flags = UV_IGNORE;
-        stdioBuf[index].data.fd = 0;
-        break;
-      }
-      case Option::kStdioInheritFd: {
-        size_t index = o.m_data.stdio.index;
-        if (index >= stdioBuf.size()) {
-          stdioBuf.resize(index + 1);
-        }
-        stdioBuf[index].flags = UV_INHERIT_FD;
-        stdioBuf[index].data.fd = o.m_data.stdio.fd;
-        break;
-      }
-      case Option::kStdioInheritPipe: {
-        size_t index = o.m_data.stdio.index;
-        if (index >= stdioBuf.size()) {
-          stdioBuf.resize(index + 1);
-        }
-        stdioBuf[index].flags = UV_INHERIT_STREAM;
-        stdioBuf[index].data.stream = o.m_data.stdio.pipe->GetRawStream();
-        break;
-      }
-      case Option::kStdioCreatePipe: {
-        size_t index = o.m_data.stdio.index;
-        if (index >= stdioBuf.size()) {
-          stdioBuf.resize(index + 1);
-        }
-        stdioBuf[index].flags =
-            static_cast<uv_stdio_flags>(UV_CREATE_PIPE | o.m_data.stdio.flags);
-        stdioBuf[index].data.stream = o.m_data.stdio.pipe->GetRawStream();
-        break;
-      }
-      default:
-        break;
-    }
-  }
-
-  if (argsBuf.empty()) {
-    argsBuf.push_back(const_cast<char*>(coptions.file));
-  }
-  argsBuf.push_back(nullptr);
-  coptions.args = argsBuf.data();
-
-  if (envBuf.empty()) {
-    coptions.env = nullptr;
-  } else {
-    envBuf.push_back(nullptr);
-    coptions.env = envBuf.data();
-  }
-
-  coptions.stdio_count = stdioBuf.size();
-  coptions.stdio = static_cast<uv_stdio_container_t*>(stdioBuf.data());
-
-  auto h = std::make_shared<Process>(private_init{});
-  int err = uv_spawn(loop.GetRaw(), h->GetRaw(), &coptions);
-  if (err < 0) {
-    loop.ReportError(err);
-    return nullptr;
-  }
-  h->Keep();
-  return h;
-}
-
-}  // namespace wpi::uv
diff --git a/wpiutil/src/main/native/cpp/uv/Signal.cpp b/wpiutil/src/main/native/cpp/uv/Signal.cpp
deleted file mode 100644
index 81d7c3e..0000000
--- a/wpiutil/src/main/native/cpp/uv/Signal.cpp
+++ /dev/null
@@ -1,32 +0,0 @@
-// Copyright (c) FIRST and other WPILib contributors.
-// Open Source Software; you can modify and/or share it under the terms of
-// the WPILib BSD license file in the root directory of this project.
-
-#include "wpi/uv/Signal.h"
-
-#include "wpi/uv/Loop.h"
-
-namespace wpi::uv {
-
-std::shared_ptr<Signal> Signal::Create(Loop& loop) {
-  auto h = std::make_shared<Signal>(private_init{});
-  int err = uv_signal_init(loop.GetRaw(), h->GetRaw());
-  if (err < 0) {
-    loop.ReportError(err);
-    return nullptr;
-  }
-  h->Keep();
-  return h;
-}
-
-void Signal::Start(int signum) {
-  Invoke(
-      &uv_signal_start, GetRaw(),
-      [](uv_signal_t* handle, int signum) {
-        Signal& h = *static_cast<Signal*>(handle->data);
-        h.signal(signum);
-      },
-      signum);
-}
-
-}  // namespace wpi::uv
diff --git a/wpiutil/src/main/native/cpp/uv/Stream.cpp b/wpiutil/src/main/native/cpp/uv/Stream.cpp
deleted file mode 100644
index a37750b..0000000
--- a/wpiutil/src/main/native/cpp/uv/Stream.cpp
+++ /dev/null
@@ -1,109 +0,0 @@
-// Copyright (c) FIRST and other WPILib contributors.
-// Open Source Software; you can modify and/or share it under the terms of
-// the WPILib BSD license file in the root directory of this project.
-
-#include "wpi/uv/Stream.h"
-
-#include "wpi/SmallVector.h"
-
-using namespace wpi;
-using namespace wpi::uv;
-
-namespace {
-class CallbackWriteReq : public WriteReq {
- public:
-  CallbackWriteReq(span<const Buffer> bufs,
-                   std::function<void(span<Buffer>, Error)> callback)
-      : m_bufs{bufs.begin(), bufs.end()} {
-    finish.connect(
-        [this, f = std::move(callback)](Error err) { f(m_bufs, err); });
-  }
-
- private:
-  SmallVector<Buffer, 4> m_bufs;
-};
-}  // namespace
-
-namespace wpi::uv {
-
-ShutdownReq::ShutdownReq() {
-  error = [this](Error err) { GetStream().error(err); };
-}
-
-WriteReq::WriteReq() {
-  error = [this](Error err) { GetStream().error(err); };
-}
-
-void Stream::Shutdown(const std::shared_ptr<ShutdownReq>& req) {
-  if (Invoke(&uv_shutdown, req->GetRaw(), GetRawStream(),
-             [](uv_shutdown_t* req, int status) {
-               auto& h = *static_cast<ShutdownReq*>(req->data);
-               if (status < 0) {
-                 h.ReportError(status);
-               } else {
-                 h.complete();
-               }
-               h.Release();  // this is always a one-shot
-             })) {
-    req->Keep();
-  }
-}
-
-void Stream::Shutdown(std::function<void()> callback) {
-  auto req = std::make_shared<ShutdownReq>();
-  if (callback) {
-    req->complete.connect(std::move(callback));
-  }
-  Shutdown(req);
-}
-
-void Stream::StartRead() {
-  Invoke(&uv_read_start, GetRawStream(), &Handle::AllocBuf,
-         [](uv_stream_t* stream, ssize_t nread, const uv_buf_t* buf) {
-           auto& h = *static_cast<Stream*>(stream->data);
-           Buffer data = *buf;
-
-           // nread=0 is simply ignored
-           if (nread == UV_EOF) {
-             h.end();
-           } else if (nread > 0) {
-             h.data(data, static_cast<size_t>(nread));
-           } else if (nread < 0) {
-             h.ReportError(nread);
-           }
-
-           // free the buffer
-           h.FreeBuf(data);
-         });
-}
-
-void Stream::Write(span<const Buffer> bufs,
-                   const std::shared_ptr<WriteReq>& req) {
-  if (Invoke(&uv_write, req->GetRaw(), GetRawStream(), bufs.data(), bufs.size(),
-             [](uv_write_t* r, int status) {
-               auto& h = *static_cast<WriteReq*>(r->data);
-               if (status < 0) {
-                 h.ReportError(status);
-               }
-               h.finish(Error(status));
-               h.Release();  // this is always a one-shot
-             })) {
-    req->Keep();
-  }
-}
-
-void Stream::Write(span<const Buffer> bufs,
-                   std::function<void(span<Buffer>, Error)> callback) {
-  Write(bufs, std::make_shared<CallbackWriteReq>(bufs, std::move(callback)));
-}
-
-int Stream::TryWrite(span<const Buffer> bufs) {
-  int val = uv_try_write(GetRawStream(), bufs.data(), bufs.size());
-  if (val < 0) {
-    this->ReportError(val);
-    return 0;
-  }
-  return val;
-}
-
-}  // namespace wpi::uv
diff --git a/wpiutil/src/main/native/cpp/uv/Tcp.cpp b/wpiutil/src/main/native/cpp/uv/Tcp.cpp
deleted file mode 100644
index 6f92557..0000000
--- a/wpiutil/src/main/native/cpp/uv/Tcp.cpp
+++ /dev/null
@@ -1,170 +0,0 @@
-// Copyright (c) FIRST and other WPILib contributors.
-// Open Source Software; you can modify and/or share it under the terms of
-// the WPILib BSD license file in the root directory of this project.
-
-#include "wpi/uv/Tcp.h"
-
-#include <cstring>
-
-#include "wpi/uv/util.h"
-
-namespace wpi::uv {
-
-std::shared_ptr<Tcp> Tcp::Create(Loop& loop, unsigned int flags) {
-  auto h = std::make_shared<Tcp>(private_init{});
-  int err = uv_tcp_init_ex(loop.GetRaw(), h->GetRaw(), flags);
-  if (err < 0) {
-    loop.ReportError(err);
-    return nullptr;
-  }
-  h->Keep();
-  return h;
-}
-
-void Tcp::Reuse(std::function<void()> callback, unsigned int flags) {
-  if (IsClosing()) {
-    return;
-  }
-  if (!m_reuseData) {
-    m_reuseData = std::make_unique<ReuseData>();
-  }
-  m_reuseData->callback = std::move(callback);
-  m_reuseData->flags = flags;
-  uv_close(GetRawHandle(), [](uv_handle_t* handle) {
-    Tcp& h = *static_cast<Tcp*>(handle->data);
-    if (!h.m_reuseData) {
-      return;  // just in case
-    }
-    auto data = std::move(h.m_reuseData);
-    int err = uv_tcp_init_ex(h.GetLoopRef().GetRaw(), h.GetRaw(), data->flags);
-    if (err < 0) {
-      h.ReportError(err);
-      return;
-    }
-    data->callback();
-  });
-}
-
-std::shared_ptr<Tcp> Tcp::Accept() {
-  auto client = Create(GetLoopRef());
-  if (!client) {
-    return nullptr;
-  }
-  if (!Accept(client)) {
-    client->Release();
-    return nullptr;
-  }
-  return client;
-}
-
-Tcp* Tcp::DoAccept() {
-  return Accept().get();
-}
-
-void Tcp::Bind(std::string_view ip, unsigned int port, unsigned int flags) {
-  sockaddr_in addr;
-  int err = NameToAddr(ip, port, &addr);
-  if (err < 0) {
-    ReportError(err);
-  } else {
-    Bind(reinterpret_cast<const sockaddr&>(addr), flags);
-  }
-}
-
-void Tcp::Bind6(std::string_view ip, unsigned int port, unsigned int flags) {
-  sockaddr_in6 addr;
-  int err = NameToAddr(ip, port, &addr);
-  if (err < 0) {
-    ReportError(err);
-  } else {
-    Bind(reinterpret_cast<const sockaddr&>(addr), flags);
-  }
-}
-
-sockaddr_storage Tcp::GetSock() {
-  sockaddr_storage name;
-  int len = sizeof(name);
-  if (!Invoke(&uv_tcp_getsockname, GetRaw(), reinterpret_cast<sockaddr*>(&name),
-              &len)) {
-    std::memset(&name, 0, sizeof(name));
-  }
-  return name;
-}
-
-sockaddr_storage Tcp::GetPeer() {
-  sockaddr_storage name;
-  int len = sizeof(name);
-  if (!Invoke(&uv_tcp_getpeername, GetRaw(), reinterpret_cast<sockaddr*>(&name),
-              &len)) {
-    std::memset(&name, 0, sizeof(name));
-  }
-  return name;
-}
-
-void Tcp::Connect(const sockaddr& addr,
-                  const std::shared_ptr<TcpConnectReq>& req) {
-  if (Invoke(&uv_tcp_connect, req->GetRaw(), GetRaw(), &addr,
-             [](uv_connect_t* req, int status) {
-               auto& h = *static_cast<TcpConnectReq*>(req->data);
-               if (status < 0) {
-                 h.ReportError(status);
-               } else {
-                 h.connected();
-               }
-               h.Release();  // this is always a one-shot
-             })) {
-    req->Keep();
-  }
-}
-
-void Tcp::Connect(const sockaddr& addr, std::function<void()> callback) {
-  auto req = std::make_shared<TcpConnectReq>();
-  req->connected.connect(std::move(callback));
-  Connect(addr, req);
-}
-
-void Tcp::Connect(std::string_view ip, unsigned int port,
-                  const std::shared_ptr<TcpConnectReq>& req) {
-  sockaddr_in addr;
-  int err = NameToAddr(ip, port, &addr);
-  if (err < 0) {
-    ReportError(err);
-  } else {
-    Connect(reinterpret_cast<const sockaddr&>(addr), req);
-  }
-}
-
-void Tcp::Connect(std::string_view ip, unsigned int port,
-                  std::function<void()> callback) {
-  sockaddr_in addr;
-  int err = NameToAddr(ip, port, &addr);
-  if (err < 0) {
-    ReportError(err);
-  } else {
-    Connect(reinterpret_cast<const sockaddr&>(addr), std::move(callback));
-  }
-}
-
-void Tcp::Connect6(std::string_view ip, unsigned int port,
-                   const std::shared_ptr<TcpConnectReq>& req) {
-  sockaddr_in6 addr;
-  int err = NameToAddr(ip, port, &addr);
-  if (err < 0) {
-    ReportError(err);
-  } else {
-    Connect(reinterpret_cast<const sockaddr&>(addr), req);
-  }
-}
-
-void Tcp::Connect6(std::string_view ip, unsigned int port,
-                   std::function<void()> callback) {
-  sockaddr_in6 addr;
-  int err = NameToAddr(ip, port, &addr);
-  if (err < 0) {
-    ReportError(err);
-  } else {
-    Connect(reinterpret_cast<const sockaddr&>(addr), std::move(callback));
-  }
-}
-
-}  // namespace wpi::uv
diff --git a/wpiutil/src/main/native/cpp/uv/Timer.cpp b/wpiutil/src/main/native/cpp/uv/Timer.cpp
deleted file mode 100644
index 33fd851..0000000
--- a/wpiutil/src/main/native/cpp/uv/Timer.cpp
+++ /dev/null
@@ -1,44 +0,0 @@
-// Copyright (c) FIRST and other WPILib contributors.
-// Open Source Software; you can modify and/or share it under the terms of
-// the WPILib BSD license file in the root directory of this project.
-
-#include "wpi/uv/Timer.h"
-
-#include "wpi/uv/Loop.h"
-
-namespace wpi::uv {
-
-std::shared_ptr<Timer> Timer::Create(Loop& loop) {
-  auto h = std::make_shared<Timer>(private_init{});
-  int err = uv_timer_init(loop.GetRaw(), h->GetRaw());
-  if (err < 0) {
-    loop.ReportError(err);
-    return nullptr;
-  }
-  h->Keep();
-  return h;
-}
-
-void Timer::SingleShot(Loop& loop, Time timeout, std::function<void()> func) {
-  auto h = Create(loop);
-  if (!h) {
-    return;
-  }
-  h->timeout.connect([theTimer = h.get(), f = std::move(func)]() {
-    f();
-    theTimer->Close();
-  });
-  h->Start(timeout);
-}
-
-void Timer::Start(Time timeout, Time repeat) {
-  Invoke(
-      &uv_timer_start, GetRaw(),
-      [](uv_timer_t* handle) {
-        Timer& h = *static_cast<Timer*>(handle->data);
-        h.timeout();
-      },
-      timeout.count(), repeat.count());
-}
-
-}  // namespace wpi::uv
diff --git a/wpiutil/src/main/native/cpp/uv/Tty.cpp b/wpiutil/src/main/native/cpp/uv/Tty.cpp
deleted file mode 100644
index 4531ded..0000000
--- a/wpiutil/src/main/native/cpp/uv/Tty.cpp
+++ /dev/null
@@ -1,22 +0,0 @@
-// Copyright (c) FIRST and other WPILib contributors.
-// Open Source Software; you can modify and/or share it under the terms of
-// the WPILib BSD license file in the root directory of this project.
-
-#include "wpi/uv/Tty.h"
-
-#include "wpi/uv/Loop.h"
-
-namespace wpi::uv {
-
-std::shared_ptr<Tty> Tty::Create(Loop& loop, uv_file fd, bool readable) {
-  auto h = std::make_shared<Tty>(private_init{});
-  int err = uv_tty_init(loop.GetRaw(), h->GetRaw(), fd, readable ? 1 : 0);
-  if (err < 0) {
-    loop.ReportError(err);
-    return nullptr;
-  }
-  h->Keep();
-  return h;
-}
-
-}  // namespace wpi::uv
diff --git a/wpiutil/src/main/native/cpp/uv/Udp.cpp b/wpiutil/src/main/native/cpp/uv/Udp.cpp
deleted file mode 100644
index bea2b57..0000000
--- a/wpiutil/src/main/native/cpp/uv/Udp.cpp
+++ /dev/null
@@ -1,184 +0,0 @@
-// Copyright (c) FIRST and other WPILib contributors.
-// Open Source Software; you can modify and/or share it under the terms of
-// the WPILib BSD license file in the root directory of this project.
-
-#include "wpi/uv/Udp.h"
-
-#include <cstring>
-
-#include "wpi/SmallString.h"
-#include "wpi/SmallVector.h"
-#include "wpi/uv/util.h"
-
-namespace {
-
-using namespace wpi;
-using namespace wpi::uv;
-
-class CallbackUdpSendReq : public UdpSendReq {
- public:
-  CallbackUdpSendReq(span<const Buffer> bufs,
-                     std::function<void(span<Buffer>, Error)> callback)
-      : m_bufs{bufs.begin(), bufs.end()} {
-    complete.connect(
-        [this, f = std::move(callback)](Error err) { f(m_bufs, err); });
-  }
-
- private:
-  SmallVector<Buffer, 4> m_bufs;
-};
-
-}  // namespace
-
-namespace wpi::uv {
-
-UdpSendReq::UdpSendReq() {
-  error = [this](Error err) { GetUdp().error(err); };
-}
-
-std::shared_ptr<Udp> Udp::Create(Loop& loop, unsigned int flags) {
-  auto h = std::make_shared<Udp>(private_init{});
-  int err = uv_udp_init_ex(loop.GetRaw(), h->GetRaw(), flags);
-  if (err < 0) {
-    loop.ReportError(err);
-    return nullptr;
-  }
-  h->Keep();
-  return h;
-}
-
-void Udp::Bind(std::string_view ip, unsigned int port, unsigned int flags) {
-  sockaddr_in addr;
-  int err = NameToAddr(ip, port, &addr);
-  if (err < 0) {
-    ReportError(err);
-  } else {
-    Bind(reinterpret_cast<const sockaddr&>(addr), flags);
-  }
-}
-
-void Udp::Bind6(std::string_view ip, unsigned int port, unsigned int flags) {
-  sockaddr_in6 addr;
-  int err = NameToAddr(ip, port, &addr);
-  if (err < 0) {
-    ReportError(err);
-  } else {
-    Bind(reinterpret_cast<const sockaddr&>(addr), flags);
-  }
-}
-
-void Udp::Connect(std::string_view ip, unsigned int port) {
-  sockaddr_in addr;
-  int err = NameToAddr(ip, port, &addr);
-  if (err < 0) {
-    ReportError(err);
-  } else {
-    Connect(reinterpret_cast<const sockaddr&>(addr));
-  }
-}
-
-void Udp::Connect6(std::string_view ip, unsigned int port) {
-  sockaddr_in6 addr;
-  int err = NameToAddr(ip, port, &addr);
-  if (err < 0) {
-    ReportError(err);
-  } else {
-    Connect(reinterpret_cast<const sockaddr&>(addr));
-  }
-}
-
-sockaddr_storage Udp::GetPeer() {
-  sockaddr_storage name;
-  int len = sizeof(name);
-  if (!Invoke(&uv_udp_getpeername, GetRaw(), reinterpret_cast<sockaddr*>(&name),
-              &len)) {
-    std::memset(&name, 0, sizeof(name));
-  }
-  return name;
-}
-
-sockaddr_storage Udp::GetSock() {
-  sockaddr_storage name;
-  int len = sizeof(name);
-  if (!Invoke(&uv_udp_getsockname, GetRaw(), reinterpret_cast<sockaddr*>(&name),
-              &len)) {
-    std::memset(&name, 0, sizeof(name));
-  }
-  return name;
-}
-
-void Udp::SetMembership(std::string_view multicastAddr,
-                        std::string_view interfaceAddr,
-                        uv_membership membership) {
-  SmallString<128> multicastAddrBuf{multicastAddr};
-  SmallString<128> interfaceAddrBuf{interfaceAddr};
-  Invoke(&uv_udp_set_membership, GetRaw(), multicastAddrBuf.c_str(),
-         interfaceAddrBuf.c_str(), membership);
-}
-
-void Udp::SetMulticastInterface(std::string_view interfaceAddr) {
-  SmallString<128> interfaceAddrBuf{interfaceAddr};
-  Invoke(&uv_udp_set_multicast_interface, GetRaw(), interfaceAddrBuf.c_str());
-}
-
-void Udp::Send(const sockaddr& addr, span<const Buffer> bufs,
-               const std::shared_ptr<UdpSendReq>& req) {
-  if (Invoke(&uv_udp_send, req->GetRaw(), GetRaw(), bufs.data(), bufs.size(),
-             &addr, [](uv_udp_send_t* r, int status) {
-               auto& h = *static_cast<UdpSendReq*>(r->data);
-               if (status < 0) {
-                 h.ReportError(status);
-               }
-               h.complete(Error(status));
-               h.Release();  // this is always a one-shot
-             })) {
-    req->Keep();
-  }
-}
-
-void Udp::Send(const sockaddr& addr, span<const Buffer> bufs,
-               std::function<void(span<Buffer>, Error)> callback) {
-  Send(addr, bufs,
-       std::make_shared<CallbackUdpSendReq>(bufs, std::move(callback)));
-}
-
-void Udp::Send(span<const Buffer> bufs,
-               const std::shared_ptr<UdpSendReq>& req) {
-  if (Invoke(&uv_udp_send, req->GetRaw(), GetRaw(), bufs.data(), bufs.size(),
-             nullptr, [](uv_udp_send_t* r, int status) {
-               auto& h = *static_cast<UdpSendReq*>(r->data);
-               if (status < 0) {
-                 h.ReportError(status);
-               }
-               h.complete(Error(status));
-               h.Release();  // this is always a one-shot
-             })) {
-    req->Keep();
-  }
-}
-
-void Udp::Send(span<const Buffer> bufs,
-               std::function<void(span<Buffer>, Error)> callback) {
-  Send(bufs, std::make_shared<CallbackUdpSendReq>(bufs, std::move(callback)));
-}
-
-void Udp::StartRecv() {
-  Invoke(&uv_udp_recv_start, GetRaw(), &AllocBuf,
-         [](uv_udp_t* handle, ssize_t nread, const uv_buf_t* buf,
-            const sockaddr* addr, unsigned flags) {
-           auto& h = *static_cast<Udp*>(handle->data);
-           Buffer data = *buf;
-
-           // nread=0 is simply ignored
-           if (nread > 0) {
-             h.received(data, static_cast<size_t>(nread), *addr, flags);
-           } else if (nread < 0) {
-             h.ReportError(nread);
-           }
-
-           // free the buffer
-           h.FreeBuf(data);
-         });
-}
-
-}  // namespace wpi::uv
diff --git a/wpiutil/src/main/native/cpp/uv/Work.cpp b/wpiutil/src/main/native/cpp/uv/Work.cpp
deleted file mode 100644
index 0fc254e..0000000
--- a/wpiutil/src/main/native/cpp/uv/Work.cpp
+++ /dev/null
@@ -1,50 +0,0 @@
-// Copyright (c) FIRST and other WPILib contributors.
-// Open Source Software; you can modify and/or share it under the terms of
-// the WPILib BSD license file in the root directory of this project.
-
-#include "wpi/uv/Work.h"
-
-#include "wpi/uv/Loop.h"
-
-namespace wpi::uv {
-
-WorkReq::WorkReq() {
-  error = [this](Error err) { GetLoop().error(err); };
-}
-
-void QueueWork(Loop& loop, const std::shared_ptr<WorkReq>& req) {
-  int err = uv_queue_work(
-      loop.GetRaw(), req->GetRaw(),
-      [](uv_work_t* req) {
-        auto& h = *static_cast<WorkReq*>(req->data);
-        h.work();
-      },
-      [](uv_work_t* req, int status) {
-        auto& h = *static_cast<WorkReq*>(req->data);
-        if (status < 0) {
-          h.ReportError(status);
-        } else {
-          h.afterWork();
-        }
-        h.Release();  // this is always a one-shot
-      });
-  if (err < 0) {
-    loop.ReportError(err);
-  } else {
-    req->Keep();
-  }
-}
-
-void QueueWork(Loop& loop, std::function<void()> work,
-               std::function<void()> afterWork) {
-  auto req = std::make_shared<WorkReq>();
-  if (work) {
-    req->work.connect(std::move(work));
-  }
-  if (afterWork) {
-    req->afterWork.connect(std::move(afterWork));
-  }
-  QueueWork(loop, req);
-}
-
-}  // namespace wpi::uv