Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -92,3 +92,10 @@ jobs:
exit 1
fi
echo "STANDARD.md matches upstream"

roadmap:
name: ROADMAP.md matches docs/roadmap.sexp
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- run: make roadmap-check
26 changes: 26 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# ROADMAP.md is generated from docs/roadmap.sexp by nova-roadmap, the one roadmap
# generator (a nova-tools release binary): `make roadmap` renders the page and
# `make roadmap-check` exits 1 when the committed page is not what the data
# renders (CI runs it). Edit the data, never the page; `nova-roadmap help form`
# documents the form. The pinned binary is downloaded once into bin/ and checked
# against the release's SHA256SUMS; NOVA_ROADMAP=<path> runs another one.
NOVA_ROADMAP_VERSION ?= v1.6.0
NOVA_ROADMAP ?= bin/nova-roadmap-$(NOVA_ROADMAP_VERSION)

.PHONY: roadmap roadmap-check
roadmap: $(NOVA_ROADMAP)
$(NOVA_ROADMAP) render

roadmap-check: $(NOVA_ROADMAP)
$(NOVA_ROADMAP) check

bin/nova-roadmap-%:
@mkdir -p bin
@os=$$(uname -s | tr A-Z a-z); arch=$$(uname -m); \
case $$arch in x86_64) arch=amd64;; aarch64) arch=arm64;; esac; \
asset=nova-roadmap_$*_$${os}_$${arch}; \
base=https://github.com/mas-bandwidth/nova-tools/releases/download/$*; \
curl -fsSL -o $@.tmp "$$base/$$asset" && \
sum=$$(curl -fsSL "$$base/SHA256SUMS" | awk -v a="$$asset" '$$2 == a {print $$1}') && \
test -n "$$sum" && { echo "$$sum $@.tmp" | sha256sum -c - >/dev/null 2>&1 || echo "$$sum $@.tmp" | shasum -a 256 -c - >/dev/null; } && \
chmod +x $@.tmp && mv $@.tmp $@ || { rm -f $@.tmp; echo "nova-roadmap $* for $$os/$$arch: download or checksum failed" >&2; exit 2; }
140 changes: 105 additions & 35 deletions ROADMAP.md
Original file line number Diff line number Diff line change
@@ -1,70 +1,140 @@
# netcode.cs roadmap

<!-- Generated from docs/roadmap.sexp by tools/roadmap. Do not edit this file: edit docs/roadmap.sexp and run `make roadmap`. -->
<!-- Generated from docs/roadmap.sexp by nova-roadmap. Do not edit this file: edit the data and run `nova-roadmap render`. -->

## Connection

Connect tokens, client slots, the handshake, keep-alive, disconnects and timeouts between a client and a server.

Planned work for netcode.cs. Each item below was an open sprint card, moved out of the stopped sprint on 2026-10-14; it names its card, its tier, its base branch and any pushed work to resume from. Landed work is in git.
<!-- nova-work:connection:start -->

## Contents
| feature | done |
|---|:---:|
| connect tokens issued by a web backend | ✅ |
| request, challenge and response handshake | ✅ |
| client slots; a full server denies at once | ✅ |
| many clients on one server | ✅ |
| fall through to the next server in the token | ✅ |
| keep-alive | ✅ |
| clean disconnect from the client | ✅ |
| clean disconnect from the server, with a reason | ✅ |
| request, response and connection timeouts | ✅ |
| expired and invalid connect tokens refused | ✅ |
| reconnect with a new token | ✅ |
| timeouts can be disabled for debugging | ✅ |
| loopback clients inside the server process | ✅ |
| override and loopback modes refuse to start without their callbacks (netcode#191) | ✅ |
| the disconnect callback cannot re-enter a slot being cleared | ❌ |
| the client skips a server address it has no socket for | ❌ |
| complete | 87% |

- [Moved from GitHub issues on 2026-10-14](#moved-from-github-issues-on-2026-10-14) (2)
- [Moved out of the sprint on 2026-10-14: netcode-cs-port](#moved-out-of-the-sprint-on-2026-10-14-netcode-cs-port) (5)
[Source data](docs/roadmap.sexp)

## Moved from GitHub issues on 2026-10-14
<!-- nova-work:connection:end -->

Open GitHub issues of mas-bandwidth/netcode.cs, recorded here on 2026-10-14 when roadmaps became the standard for future work (Glenn, 2026-10-14). Each item names its issue; the issue is closed with a pointer here.
## Security

### Port still has the pre-1.4.5 connect-token reuse (GHSA-v29p-3vj4-vg4f)
Encrypted, signed packets and the protections against forged, replayed and malformed traffic.

C netcode 1.4.5 (advisory GHSA-v29p-3vj4-vg4f) spends a connect token when the client is installed. A consumed token admits nothing, from any address. History-full refuses instead of evicting. Tokens that could have been issued before this server process started are refused.
<!-- nova-work:security:start -->

From: mas-bandwidth/netcode.cs issue #6, opened 2026-09-07 by gafferongames; moved here 2026-10-14, added 2026-10-16
| feature | done |
|---|:---:|
| encrypted and signed packets (ChaCha20-Poly1305) | ✅ |
| encrypted connect token private data (XChaCha20-Poly1305) | ✅ |
| fully managed crypto, checked against the .NET built-in AEAD | ✅ |
| replay protection | ✅ |
| a used connect token is refused (GHSA-v29p-3vj4-vg4f) | ✅ |
| a full token history refuses rather than forgets | ✅ |
| a token created before the server started is refused | ✅ |
| no nonce reuse across a server restart | ✅ |
| malformed packets, tokens and addresses ignored, never thrown, fuzzed | ✅ |
| amplification protection | ❌ |
| each connection response is bound to its own challenge | ❌ |
| replay protection at the top of the sequence space | ❌ |
| pre-connect mappings expire even for tokens without a timeout | ❌ |
| servers sharing a connect token use distinct global packet nonces | ❌ |
| key material zeroized on disconnect and stop | ❌ |
| complete | 60% |

### Mirror #191: two new create-time error codes and the unchecked callback class
[Source data](docs/roadmap.sexp)

The C reference landed a robustness batch in mas-bandwidth/netcode#191 (from netcode#190). Two pieces of it are mirrored surface in this port and should land here too. No wire format change in either. netcode.h gained:
<!-- nova-work:security:end -->

From: mas-bandwidth/netcode.cs issue #7, opened 2026-09-13 by rowan-claude; moved here 2026-10-14
## Networking

## Moved out of the sprint on 2026-10-14: netcode-cs-port
Sockets, address families, platforms and the tools for testing under bad networks.

The open cards of sprint stream netcode-cs-port, moved out of the stopped sprint on 2026-10-14, one item per card. Landed cards of the stream are in git and are not listed. Each item names its card, its tier, its base branch and any pushed work to resume from.
<!-- nova-work:network:start -->

### Cs netcode disconnect callback cannot reenter a clearing slot
| feature | done |
|---|:---:|
| IPv4 | ✅ |
| IPv6 | ✅ |
| dual-stack server (IPv4 and IPv6 at once) | ✅ |
| address parsing and formatting | ✅ |
| packet tagging (DSCP) on unix and macOS | ❌ |
| network simulator: latency, jitter, loss, duplicates | ✅ |
| receive drains skip empty datagrams and stop at a cap | ❌ |
| Linux and macOS | ✅ |
| Windows | ❌ |
| socket layer reviewed line by line against the C | ❌ |
| complete | 60% |

Server.DisconnectClientInternal calls the callback at src/Server.cs:563 and clears the slot at 580-584; a re-entrant disconnect reaches ClientIndex[-1] at :580 and throws IndexOutOfRangeException, and a callback with no self-guard recurses to an uncatchable StackOverflow.
[Source data](docs/roadmap.sexp)

Why it waits: Moved out of the stopped sprint on 2026-10-14: neither rocketnet nor v1.3 work.
<!-- nova-work:network:end -->

From: sprint card cs-netcode-disconnect-callback-cannot-reenter-a-clearing-slot (stream netcode-cs-port, column working, tier pro, base sprint/netcode-cs-port), moved out of the sprint 2026-10-14
## .NET

### Cs netcode replay top sequence and restart floor edges
The runtimes the library targets and the properties it keeps on them.

N4: `if (sequence == ulong.MaxValue) return true;` first in AlreadyReceived.
<!-- nova-work:dotnet:start -->

Why it waits: Moved out of the stopped sprint on 2026-10-14: neither rocketnet nor v1.3 work.
| feature | done |
|---|:---:|
| .NET 10 | ✅ |
| .NET 8 (LTS) | ✅ |
| zero allocation on the steady-state packet paths | ✅ |
| no fused multiply-add in the network arithmetic | ✅ |
| Unity (netstandard2.1) (later) | ❌ |
| complete | 80% |

From: sprint card cs-netcode-replay-top-sequence-and-restart-floor-edges (stream netcode-cs-port, column waiting, tier pro, base sprint/netcode-cs-port), moved out of the sprint 2026-10-14
[Source data](docs/roadmap.sexp)

### Cs netcode token lifetimes are bounded
<!-- nova-work:dotnet:end -->

N7: a pure PreconnectExpireTime( time, timeoutSeconds ) returning time + (timeoutSeconds > 0 ? timeoutSeconds : Defines.PreconnectMappingSeconds /* 10 */), used at src/Server.cs:714.
## Compatibility

Why it waits: Moved out of the stopped sprint on 2026-10-14: neither rocketnet nor v1.3 work.
Wire compatibility with the C reference implementation and the netcode 1.02 standard.

From: sprint card cs-netcode-token-lifetimes-are-bounded (stream netcode-cs-port, column waiting, tier pro, base sprint/netcode-cs-port), moved out of the sprint 2026-10-14
<!-- nova-work:compat:start -->

### Cs netcode client refuses a server address it has no socket for
| feature | done |
|---|:---:|
| netcode 1.02 standard, kept in sync with upstream | ✅ |
| every token and packet type byte-identical to C, both directions | ✅ |
| live sessions with the C client and server, both directions | ✅ |
| every packet type reads and writes as the C suite expects | ✅ |
| tracks the C reference release (1.4.8) | ✅ |
| complete | 100% |

The port never touches a closed descriptor (`socketIpv4?.` at src/Netcode.cs:1990, 1994; null checks at src/Client.cs:508-517) but silently drops sends, and Connect and ConnectToNextServer (src/Client.cs:348-349, 630-631) never skip the address or set InvalidConnectToken.
[Source data](docs/roadmap.sexp)

Why it waits: Moved out of the stopped sprint on 2026-10-14: neither rocketnet nor v1.3 work.
<!-- nova-work:compat:end -->

From: sprint card cs-netcode-client-refuses-a-server-address-it-has-no-socket-for (stream netcode-cs-port, column waiting, tier pro, base sprint/netcode-cs-port), moved out of the sprint 2026-10-14
## Testing

### Cs netcode global packet nonces differ between servers sharing a token
How the implementation is checked on every change.

Cs netcode global packet nonces differ between servers sharing a token. Done when: the new tests are red before the fix and green after it;
<!-- nova-work:testing:start -->

Why it waits: Moved out of the stopped sprint on 2026-10-14: neither rocketnet nor v1.3 work.
| feature | done |
|---|:---:|
| test-for-test port of the C test suite | ✅ |
| soak test | ✅ |
| complete | 100% |

From: sprint card cs-netcode-global-packet-nonces-differ-between-servers-sharing-a-token (stream netcode-cs-port, column waiting, tier pro, base sprint/netcode-cs-port), moved out of the sprint 2026-10-14
[Source data](docs/roadmap.sexp)

<!-- nova-work:testing:end -->
Loading
Loading