//! **Tailscale interop.** Turning a coordination server's answer into a peer
//! set — and being honest that a peer set is not yet a tailnet member.
//!
//! # The finding that shapes this whole crate: DERP is mandatory
//!
//! Measured 2026-09-05. It is tempting to read Tailscale as "WireGuard with a
//! key-distribution service", build the peer set from the netmap's direct
//! endpoints, and treat DERP as a fallback for when NAT traversal fails. **That
//! is wrong, and it fails in the direction that looks like it works.**
//!
//! A real Tailscale peer will not send to an endpoint that **disco** has not
//! validated. Disco is a separate key and a separate probe protocol, and it
//! runs over DERP: a node reaches its home DERP region, its peers reach theirs,
//! and the disco exchange over that relay is what makes a direct path usable at
//! all. So a node with no DERP client is not a node with degraded connectivity
//! — it is a node **no peer will ever send a packet to.**
//!
//! `IsWireGuardOnly` exists and would rescue us: it is the flag Mullvad's exit
//! nodes carry, saying "talk plain WireGuard to me, skip disco". But the
//! **coordination server grants it**; a node cannot claim it by registering.
//!
//! Consequently this crate does **not** offer a function that hands you a
//! tunnel and lets you believe it is a tailnet member. It parses the control
//! plane's answer, builds the peers that answer describes, and returns them
//! together with a [`Reachability`] verdict that says what is still missing.
//! [`TailnetPeerSet::into_tunnel_or_refuse`] refuses while the verdict says
//! unreachable, and that refusal is the honest product of this lane.
//!
//! # Where the code comes from: dependency, not port
//!
//! `tailscale/tailscale-rs` is Tailscale's **official** Rust client,
//! BSD-3-Clause, and **every one of its 44 crates publishes to crates.io**.
//! Verified 2026-09-05 at commit
//! `ce8f3d91c56d8bc7739ced07dd6fbe5d98822a10`. So the question was never
//! port-versus-depend — it was *which* of their crates this workspace may take,
//! and the answer splits the set cleanly in two. Measured with `cargo tree -e
//! normal` over a resolved crates.io closure:
//!
//! **Not takeable**, for two independent reasons, neither of them optional:
//!
//! * `ts_transport` depends unconditionally on **`dyn-eq 0.1.3`, which is
//! MPL-2.0**. No feature gate removes it, and it is reached by `ts_derp` and
//! so by `ts_control`. This workspace's no-copyleft rule is absolute, and
//! `tunnr-licence` enforces it on every build.
//! * `ts_tls_util` hardcodes `tokio-rustls { default-features = false, features
//! = ["aws-lc-rs"] }`, which drags **`aws-lc-sys` (a BoringSSL fork, cmake +
//! cc)** into `ts_control`, `ts_derp` and `ts_http_util`. C, in a distroless
//! static appliance.
//!
//! **Takeable, and taken**: `ts_control_serde` (the whole `tailcfg` wire model)
//! and `ts_keys` (the key types). Their combined normal closure is 124 crates
//! with **zero copyleft, zero `-sys` crates and zero `cc`**, and three of the
//! `ts_*` crates in it are `#![no_std]`. Attribution is in `THIRD-PARTY.md`
//! with the commit the LICENSE was read at, per this repository's rule.
//!
//! **A correction this crate owes its own claim**: "zero C" is true of the
//! `tailscale-rs` subset above and **not** of this workspace's tree as a whole.
//! `boringtun 0.7.1` — reached through `wg-appliance-core`, the seam — depends
//! on **`ring 0.17` unconditionally**, with no feature gate. So the appliance
//! already links C today, and it did before this repository existed. What that
//! changes: the objection to `ts_control`/`ts_derp` is **MPL-2.0**, which is
//! absolute, and the `aws-lc-sys` objection is the lesser one — a cmake-built
//! BoringSSL fork on top of the ring that is already there, not the first C in
//! the tree. Stating it the other way round would have been a tidier story and
//! a false one.
//!
//! What we therefore still owe, and it is the honest cost of Tailscale support:
//! **the transport** — an HTTPS client to the coordination server and a DERP
//! client — because theirs is welded to `aws-lc-rs` and reaches MPL-2.0. That
//! is named as a gap here, with tests, rather than half-built.
//!
//! # What is asserted against what
//!
//! We hold **no auth key for any tailnet**, so nothing in this crate has spoken
//! to a coordination server. What it is tested against is **captured bytes**:
//! real `MapResponse` and `Node` documents recorded from Tailscale's control
//! plane, vendored from their own repository under BSD-3-Clause (see
//! `THIRD-PARTY.md`). Every test that could not reach a live endpoint says so
//! in its name — `captured_*`, never a bare name that reads like a live proof.
//!
//! # Trademark
//!
//! "Tailscale" is Tailscale Inc.'s. This crate is *compatible with* their
//! coordination protocol; it is not a Tailscale client and implies no
//! endorsement.
#![forbid(unsafe_code)]
#![warn(missing_docs)]
pub mod netmap;
pub mod reach;
pub use netmap::{peers_from_map_response, NodeFinding, TailnetPeerSet};
pub use reach::{MissingPiece, Reachability};