Each implementation has its own contribution guide covering language-specific requirements, build tooling, and code style. This file covers the general guidelines that apply across all implementations.
- Keep PRs focused: one logical change per PR
- All tests must pass
- Update CHANGELOG.md under
[Unreleased] - Document all new public API surface in whatever format is idiomatic for that implementation
Open an issue at github.com/Quiet-Terminal-Interactive/QTINeon/issues.
Include:
- The implementation and version you are using
- A minimal reproducible test or description of the packet trace
- The config you are using (or "defaults")
Any new packet type must be added to all implementations to maintain interoperability. The wire format is defined in PROTOCOL.md — that is the source of truth.
Java
- Add a constant to
PacketTypewith its byte value - Add a
recordimplementingPacketPayloadwithtoBytes()andfromBytes(byte[]) - Register the deserializer in
PayloadDeserializer/NeonPacket.fromBytes() - Handle it in
NeonRelay.handlePacket(),NeonHost.handlePacket(), orNeonClient.handlePacket()as appropriate - Add tests
Python
- Add a member to
PacketTypein_protocol.pywith its byte value - Add a
@dataclass(frozen=True)withto_bytes()andfrom_bytes(data)class methods - Add a
casebranch toNeonPacket.from_bytes()for the new type - Handle it in
NeonRelay._handle_packet(),NeonHost._handle_packet(), orNeonClient._handle_packet()as appropriate - Add tests
TypeScript
- Add a member to the
PacketTypeenum insrc/_protocol.ts - Add a payload interface (e.g.
MyPacketPayload) and a type guard (isMyPacket) - Add serialisation in
serializePacketand deserialisation inparsePacket - Handle it in
NeonRelay._handlePacket(),NeonHost._handlePacket(), orNeonClient._handlePacket()as appropriate - Export the new interface and type guard from
src/index.ts - Add tests
Godot
- Add a constant to
_protocol.gd(e.g.const PT_MY_PACKET := 0x07) - Add
parse_my_packet(p)andbuild_my_packet(...)static functions to_protocol.gd - Add a
matchbranch to_handle_raw()inNeonRelay.gd,_handle_packet()inNeonHost.gd, or_handle_packet()inNeonClient.gdas appropriate - Add tests
New language implementations are always welcome. To be accepted, the implementation must be integrated into test_compliance.py and all compliance tests must pass.
Concretely, for a new implementation called <lang>:
- Add host and client script templates (analogous to
_PY_HOST/_PY_CLIENTor the Java runners) that print the same sentinel markers:HOST_READY,HOST_FAILED,CLIENT_CONNECTED:<id>:<name>,PACKET_RECEIVED:<type>:<sender>,CONNECTED:<id>,PACKET_SENT,CONNECT_FAILED. - Add a setup step (build, install, create an isolated environment) following the pattern of the Java
mvn installand Pythonvenvsteps. - Add two test functions:
_test_java_host_<lang>_client()— Java host, new client (use a new session ID constant)_test_<lang>_host_java_client()— new host, Java client (use another new session ID constant)
- Call both functions from
main(), appending failures to thefailureslist. - Add cleanup of any build artifacts or temporary environments in the cleanup step.
- Update
CONTRIBUTING.mdwith language-specific Requirements, Build, Tests, and Code Style sections.
The Java implementation is the wire-format reference. If the new implementation disagrees with Java on packet framing or session handshake, fix the new implementation — not Java.
Godot
- Godot 4.2+ (headless build required for the compliance test runner)
- No additional dependencies — all DTLS uses Godot's built-in
DTLSServer/PacketPeerDTLS/TLSOptions
Java
- Java 25 (OpenJDK 25.0.2+)
- Maven 3.9+
Python
- Python 3.11+
- pip / a virtual environment
Optional for DTLS:
pyopenssl>=23.0(pip install qti-neon[dtls])
TypeScript
- Node.js 18+
- npm 9+
Optional for DTLS:
koffi(npm install koffi) — requires OpenSSL 3 (libssl.so.3) on the system
Godot
No build step, the implementation is pure GDScript. Copy (or symlink) godot/addons/qti_neon/ into your project's addons/ directory and enable the plugin in Project -> Project Settings -> Plugins.
To verify the compliance scripts parse correctly:
cd godot
godot --headless --check-only --script compliance/neon_host_runner.gdJava
mvn verifyThis compiles, runs all tests, generates Javadoc, and enforces code coverage.
Python
cd python
pip install -e ".[dev]"Generate docs:
pdoc src/qti_neon --output-dir ../docs/python
# output: ../docs/python/qti_neon.htmlTypeScript
cd js-ts
npm install
npm run build # tsc → dist/Generate docs:
npm run docs
# output: ../docs/ts/index.htmlTo install DTLS support:
npm install koffiGodot
The GDScript implementation is covered by the cross-language compliance test in test_compliance.py (tests E and F). There is no standalone unit test suite; protocol correctness is validated by interoperating with the Java reference implementation.
To run the Godot compliance tests:
python3 test_compliance.pyCompliance scripts are in godot/compliance/ and are run as:
godot --headless --path godot/ --script compliance/neon_host_runner.gd -- <session_id> <relay>
godot --headless --path godot/ --script compliance/neon_client_runner.gd -- <session_id> <relay>Java
Most tests open real UDP sockets on loopback. Run them in a terminal — not in a sandboxed IDE runner:
mvn testRun a specific test class:
mvn test -Dtest="NeonHostTest"Run only the integration tests:
mvn test -Dtest="*Integration*"Tests are split by concern:
| Package | What it tests |
|---|---|
core |
Protocol parsing, config, buffer pool |
relay |
NeonRelay with raw socket counterparts |
host |
NeonHost with a mock relay |
client |
NeonClient with a mock relay |
reliability |
ReliablePacketManager in isolation |
integration |
Full stack: relay + host + client over loopback |
Python
Most tests open real UDP sockets on loopback. Run them in a terminal — not in a sandboxed IDE runner:
cd python
pytestRun a specific test file:
pytest tests/test_host.pyRun only the integration tests:
pytest tests/test_integration.pyTests are split by concern:
| File | What it tests |
|---|---|
test_protocol.py |
Packet parsing, serialisation, config |
test_config.py |
NeonConfig validation |
test_relay.py |
NeonRelay with raw socket counterparts |
test_host.py |
NeonHost with a mock relay |
test_client.py |
NeonClient with a mock relay |
test_reliable.py |
ReliablePacketManager in isolation |
test_integration.py |
Full stack: relay + host + client over loopback |
TypeScript
Tests open real UDP sockets on loopback. Run them in a terminal — not in a sandboxed IDE runner:
cd js-ts
npm testRun a specific test file:
npx vitest run tests/host.test.tsTests are split by concern:
| File | What it tests |
|---|---|
protocol.test.ts |
Packet parsing, serialisation, wire bytes |
relay.test.ts |
NeonRelay with raw socket counterparts |
host.test.ts |
NeonHost with a mock relay |
client.test.ts |
NeonClient with a mock relay |
integration.test.ts |
Full stack: relay + host + client over loopback |
Godot
- Godot 4.2+ GDScript with static typing where practical
- No
print()inaddons/qti_neon/(usepush_error()/push_warning()for runtime diagnostics) - No comments that describe what the code does — only why, when non-obvious
- No speculative abstractions — solve the problem in front of you
- Prefix internal scripts with
_(e.g._protocol.gd,_socket.gd);class_nameonly for public API scripts - Use
Mutexfor all state shared between the processing thread and the application thread - All 64-bit wire fields (
token,host_token,timestamp) use GDScript's nativeint(64-bit signed); no special handling needed
Java
- Java 25 — use records, sealed interfaces, pattern matching, and virtual threads where natural
- No
System.out.printlninsrc/main/ - No comments that describe what the code does — only why, when non-obvious
- No speculative abstractions — solve the problem in front of you
- Package-private for implementation classes;
publiconly for the API surface - One
Loggerper class viaLogger.getLogger(Foo.class.getName())
Python
- Python 3.11+ — use
match/case,dataclass(frozen=True), and|union types where natural - No
print()insrc/ - No comments that describe what the code does — only why, when non-obvious
- No speculative abstractions — solve the problem in front of you
- Prefix internal classes and functions with
_; public API only in__init__.py - One
logger = logging.getLogger(__name__)per module
TypeScript
- TypeScript strict mode;
ES2022target, CommonJS output - No
console.loginsrc/(useemit('error', ...)for runtime errors) - No comments that describe what the code does — only why, when non-obvious
- No speculative abstractions — solve the problem in front of you
- Prefix internal modules with
_(e.g._protocol.ts,_socket.ts); public API only inindex.ts - Use
bigintfor all wire-format 64-bit integer fields (token, timestamp); nevernumber - Async entry points (
start(),connect()) returnPromise; the run loop is driven bysetIntervalwith.unref()so the process does not stay alive indefinitely