PTP NIC hardware-timestamping
For Vision Block customers running tight sensor-fusion pipelines on dedicated deployments, the api-server's PTP source can deliver a sub-microsecond envelope when paired with a privileged sidecar that has NIC access. This page is the deployment recipe for that bridge.
/dev/ptp*, no CAP_NET_RAW. The api-server runs the shipped software path (~100 µs envelope) and surfaces an anchorWarnings entry honestly. The sub-µs path requires a dedicated deployment with NIC access.What you provision
- An IEEE 1588v2 grandmaster on the same LAN/VPC. Bring your own — bare-metal
linuxptp ptp4l, AWS Time Sync PTP, or Google Cloud Hyperdisk PTP all work. - The
tblk-ptp-sidecarbinary. Audited single-file Rust source forlinux/amd64+linux/arm64, built from source on your own infrastructure against the pinned toolchain (no public release channel — you own the binary you run). HoldsCAP_NET_RAW, reads LinuxSO_TIMESTAMPINGcmsg control messages from its slave socket, and publishes per-packet NIC stamps on a local Unix-domain socket. The bring-your-own Rust/C/Go path is still supported — the wire protocol is documented below — but the in-tree binary is the recommended starting point. - The
@workspace/ptp-hwstampaddon ships in the api-server. It auto-loads at boot whenSPIRAL_PTP_HWSTAMP_SIDECARis set, connects to your sidecar over the Unix socket, and registers itself with the temporalspiral hardware-stamping seam.
Environment variables
SPIRAL_PTP_INTERFACE=eth0 SPIRAL_PTP_DOMAIN=0 SPIRAL_PTP_MODE=multicast # or unicast SPIRAL_PTP_HARDWARE_TIMESTAMPING=required # refuse to wire on missing addon SPIRAL_PTP_HWSTAMP_SIDECAR=/var/run/temporalblock-ptp.sock
With =required and the sidecar reachable, the meta.spiral.anchorWarnings field reports "PTP adapter is running with NIC hardware-timestamping (hw:linuxptp-uds)" and the per-sample sourceLabel becomes ptp:eth0:0+hw:linuxptp-uds. With =preferred (the default) the api-server boots either way: present sidecar → hardware path; absent → the shipped software path with its honest envelope warning.
Per-cloud caveats
- AWS Time Sync PTP (
169.254.169.123on c6i / c7g / m6i / m7g and newer): PHC exposed at/dev/ptp0. Envelope ≤100 ns on healthy nitro hosts. Older instance families do not expose the PHC — the addon will be unavailable there. - Google Cloud Hyperdisk PTP (C3 / C3D / N4): PHC exposed at
/dev/ptp_hyperdiskwith--enable-confidential-storage. Envelope ~250 ns end-to-end. - Bare-metal grandmaster (in-tenant rack with
ptp4l+phc2syson a dedicated NIC): the most accurate option (≤50 ns). The sidecar attachesSO_TIMESTAMPINGdirectly to its slave socket, or readsphc2sys's SHM segment. - Azure App Service / Replit / shared tenants: not supported. Use the shipped software path; the envelope warning is honest. If you need sub-µs, migrate to a dedicated deployment.
Sidecar wire protocol
Newline-delimited JSON over SOCK_STREAMUnix-domain socket. One frame per RX/TX stamp; one frame per line; malformed frames are dropped silently so a single bad line can't kill the bridge.
{"v":1,
"type":"rx", // or "tx"
"msgType":0, // PTPv2: 0=SYNC, 1=DELAY_REQ,
// 8=FOLLOW_UP, 9=DELAY_RESP
"seqId":1234, // PTPv2 sequenceId, 0..65535
"unixNs":"1748073600123456789", // string: exceeds JS safe int
"mechanism":"so_timestamping" // operator-readable tag
}The production binary tblk-ptp-sidecar implements this protocol in ~700 lines of audited Rust (source under lib/ptp-hwstamp-sidecar/) and is the recommended deployment path. The legacy in-process reference at lib/ptp-hwstamp/src/sidecar.ts is now used only by CI smoke tests and as a copy-paste starting point for operators writing their own implementation in C/Go.
Building and installing the sidecar
Build tblk-ptp-sidecar from source on your own infrastructure against the pinned toolchain in lib/ptp-hwstamp-sidecar/rust-toolchain.toml, then record the resulting binary's SHA256 in your internal change-management and install. The binary links statically against musl, so the same build works across glibc + musl hosts. temporalBLOCK does not publish prebuilt binaries through GitHub Releases or any other public channel.
ARCH=$(uname -m) # x86_64 or aarch64
TARGET=${ARCH}-unknown-linux-musl
cd lib/ptp-hwstamp-sidecar
cargo build --release --target ${TARGET}
ASSET=target/${TARGET}/release/tblk-ptp-sidecar
sha256sum ${ASSET} # pin this digest in your change-management
install -m 0755 ${ASSET} /usr/local/bin/tblk-ptp-sidecar
useradd --system --no-create-home --shell /usr/sbin/nologin tblk-ptp
install -m 0644 systemd/tblk-ptp-sidecar.service /etc/systemd/system/
systemctl daemon-reload
systemctl enable --now tblk-ptp-sidecar.serviceFor supply-chain auditing, generate a CycloneDX SBOM locally with cargo cyclonedx and store it alongside the pinned digest. The disabled stub at .github/workflows/ptp-sidecar-release.yml has no triggers and exists only to make the no-public-release posture explicit in the tree.
Sidecar systemd unit (reference)
Pair the addon's aggressive ~10 ms reconnect with a tight Restart=always systemd policy and the bounded hold-over window described below, and a sidecar crash or redeploy lands back inside the cap before the api-server has to fail-closed. Reference unit (adapt the binary path and user to your build):
[Unit] Description=temporalBLOCK PTP hardware-stamping sidecar After=network-online.target Wants=network-online.target [Service] Type=notify ExecStart=/usr/local/bin/tblk-ptp-sidecar \ --interface eth0 \ --socket /var/run/temporalblock-ptp.sock User=tblk-ptp AmbientCapabilities=CAP_NET_RAW CAP_NET_ADMIN Restart=always RestartSec=100ms WatchdogSec=5s [Install] WantedBy=multi-user.target
Hold-over window. Set SPIRAL_PTP_HW_HOLDOVER_MS (default 0, fail-closed) to a small bounded value (e.g. 250) and the api-server will publish the last-known offset for up to that many milliseconds when the sidecar briefly drops, with the rtt envelope widened by SPIRAL_PTP_HW_HOLDOVER_DRIFT_PPM (default 1 ppm, typical commercial TCXO) times the elapsed time. The sourceLabel grows a +holdover suffix during the window so consumers see the cached posture honestly, and a distinct "holding over" anchorWarnings entry is pushed the first time the cache engages. After the cap elapses the source returns to fail-closed and the existing "became unavailable mid-flight" warning fires.
Verifying the bridge
- Boot the api-server with the env knobs above. Watch the startup log for "registered NIC hardware-stamping addon".
- Call
GET /api/v1/spiralwith any valid API key and inspectmeta.spiral.anchorWarnings. You should see thehw:linuxptp-udsactivation message and no "not wired" or"software-timestamping" entries. - If the sidecar restarts mid-flight under
=required, the provider'savailable()flips to false and subsequent exchanges fail-closed withPtpHardwareStampUnavailableErrorrather than silently widening the envelope by 1000× to the userland ~100 µs reading — this is the contract the=requiredposture promises. A distinct "became unavailable mid-flight"anchorWarningsentry is pushed the first time the fail-closed path engages, and stays sticky for the rest of the process so the SLA-breach window stays visible to operators even after the sidecar recovers. The breaker re-probes on the next sync cycle. - Under
=preferred, the same mid-flight loss falls back to the userland envelope (honest ~100 µs warning surfaced). Choose=requiredwhen your downstream consumers genuinely cannot tolerate the SW envelope; choose=preferredwhen best-effort sub-µs is good enough.
Implementation reference: lib/ptp-hwstamp/README.md in the customer dedicated-deployment repo bundle.