DAP model¶
How the Debug Adapter Protocol is represented and driven. Two crates:
dap-protocol for the types, dap-client for the conversation.
Bugsaur targets DAP 1.71.
dap-protocol: types are generated¶
The protocol types are generated from the DAP schema by
crates/dap-protocol/generator, not written by hand.
That is a deliberate trade. The protocol is large and changes upstream; a hand-maintained copy drifts, and the drift shows up as a field that silently never arrives. Generated types make a schema change a compile error instead.
Round-trip tests (crates/dap-protocol/tests/roundtrip.rs) check that
serialising and deserialising a message returns the same value — the property
that matters when the other side is a program you did not write.
dap-client: two transports¶
| Transport | Used by | How it works |
|---|---|---|
| stdio | debugpy, php |
Bugsaur starts the adapter and speaks over its stdin / stdout |
| TCP | dlv, codelldb |
Bugsaur picks a free port, passes it via tcp_argument, and connects |
Both carry the same framing: Content-Length headers followed by a JSON body,
implemented in codec.rs and tested against malformed and split input.
Splitting matters more than it sounds: a TCP read boundary can land anywhere, including in the middle of a header, and a codec that assumes one read equals one message fails only under load.
Request management¶
request.rs and the request manager pair responses with the requests that
produced them, using the protocol's sequence numbers.
Three things it has to get right:
Late responses. A response can arrive after the state it belongs to is gone. The stop generation decides whether it still matters, and it is dropped if not.
Long requests. The startup handshake — launch / attach and
configurationDone — is waited on for 120 seconds by default. Some adapters
compile inside launch; others answer configurationDone only after the
debuggee starts. Every 30 seconds the wait is reported to the log with the pid
holding the answer — a silent debugger is indistinguishable from a hung one.
Adapter death. An adapter that exits mid-conversation must fail the session cleanly rather than leave requests pending forever.
What Bugsaur does not do¶
It does not interpret request bodies. launch_arguments from a profile goes
to the adapter as-is, overriding whatever was generated, key by key. Neither
project-config nor the launcher knows what those keys mean — that knowledge
belongs to the adapter.
It does not special-case adapters in shared code. Where an adapter needs different treatment, that lives in the adapter catalog as data. This is a deliberate contrast with implementations that put, say, Python string unescaping in the common variable path.
It does not log request bodies. Values of env never reach the log. This is
an invariant: request tracing cannot be extended without stripping them first,
which is why the long-request message carries only mode and program.
Testing without an adapter¶
fake-dap-adapter is a scriptable adapter used by tests: it replies with
prepared responses, so protocol handling, state reduction and the UI can be
tested with no language toolchain installed and no network.
Real adapters are covered by ignored end-to-end tests, run explicitly:
Related decisions¶
| ADR | Subject |
|---|---|
| ADR-001 | the protocol layer and generated types |
| ADR-002 | transports |
| ADR-003 | the state model and stale responses |
| ADR-006 | the variable tree: laziness, pagination, caching |
| ADR-008 | the PHP DBGp transport |