chore(lints): enforce per-crate lint policy and add build profiles

Give every member crate its own `[lints.clippy]` (`pedantic`, `todo`,
`unwrap_used`) and `[lints.rust]` block, add explicit `release` and
`production` profiles to the workspace manifest, and fix the code so that
`just clippy` -- `cargo clippy --workspace --all-targets --all-features
-- -D warnings` -- passes with zero warnings.

Declaring the lints is the easy half and on its own it was actively
harmful: `unsafe_code = "forbid"` cannot be overridden from source, so it
broke `lanparty-gateway`, `lanparty-client-tap`, and
`lanparty-client-route`, which all need OS FFI, and the workspace no
longer compiled on Linux at all. The rest of the workspace produced
roughly 120 denied lints. A lint policy nobody can satisfy is worse than
no policy, so this commit makes the tree actually clean.

Unsafe policy: the three FFI crates use `unsafe_code = "deny"`, and the
one module in each that holds the FFI (`gateway::packet`,
`client_tap::windows`, `client_route::windows`) opts back in with a
documented `#![allow(unsafe_code)]`. Every other crate keeps `"forbid"`.
`deny` was chosen over dropping the lint so that `unsafe` outside those
modules is still a hard error, and over per-block allows because the FFI
is dense enough that per-block attributes would drown the code.

Build profiles: `release` stays optimized but debuggable (debug info,
debug assertions, overflow checks, no LTO, incremental) so that a bug
reproduced at close-to-real speed still panics loudly and gives a usable
backtrace. `production` inherits from it and turns all of that off, adding
fat LTO and a single codegen unit, and is what ships. Note the
consequence: `cargo build --release` binaries are now slower than before
this commit and must not be shipped; use `just build-production`.

Code changes made to satisfy the lints, grouped by kind:

- `# Errors` sections on every public fallible function, and `# Panics`
  on `RoomRegistry::new`. The Windows-only implementations and their
  non-Windows `bail!` stubs are documented in parallel so the crates stay
  clean when built for Windows too, which CI on Linux cannot check.
- Panicking paths removed rather than documented where the panic was only
  an unreachable invariant: `OverlayHeader::decode` and
  `declared_payload_len` take a fixed-size prefix via `first_chunk`
  instead of `try_into().expect(...)`, and `RoomRegistry::join` returns an
  `InternalError` reject instead of `expect`ing the room it just inserted.
- Tests keep using `unwrap`/`unwrap_err`: each crate root carries
  `#![cfg_attr(test, allow(clippy::unwrap_used))]`. The lint is about
  production code; an `expect` message per assertion buys nothing.
- Lossy casts replaced with `try_from` where a real conversion was
  happening (datagram-size negotiation, control-frame length prefix,
  MTU clamping). In `gateway::packet` the cast lints are allowed
  module-wide instead: those casts move values between libc's C types
  and ours after the value is already known to fit, so fallible
  conversions would only add unreachable branches.
- Signature changes, all on private or crate-internal items except one:
  `RoomRegistry::join` now takes `&EndpointHello` (public, it never
  consumed the hello), `Room::join` and `reject_control_error` likewise.
  `Room`'s `room_id` field is now `id`.
- `#[allow(clippy::too_many_lines)]` with a reason on three end-to-end
  test scenarios and on `bridge_until_shutdown`, whose `select!` loop
  mutates state shared by every arm; splitting it would hide that.
- Mechanical fixes from `cargo clippy --fix`: `map_or_else`, `let...else`,
  inline format args, backticks in doc comments, `Duration::from_mins`.

No runtime behavior changes: the only observable differences are the
error text when a relay room lookup fails immediately after insertion
(previously a panic) and the profile rename described above.

Also documents the lint policy and the profile table in README.md, whose
build section now points at the `just` recipes it should have used all
along.

Test Plan:
- `just clippy` -- passes, zero warnings
- `just test` -- 186 tests pass, 0 failed
- `cargo check --profile production --workspace --all-features` -- passes
- `just fmt` -- clean
- Not verified: the Windows-only code paths and their doc comments, which
  need a Windows target to compile.

Refs: https://doc.rust-lang.org/cargo/reference/profiles.html
Refs: https://rust-lang.github.io/rust-clippy/master/index.html#missing_errors_doc
Refs: https://doc.rust-lang.org/reference/attributes/diagnostics.html#lint-check-attributes
This commit is contained in:
2026-08-16 19:00:54 +02:00
parent 2bed62e9ec
commit e62f584377
35 changed files with 1024 additions and 227 deletions
+10
View File
@@ -18,3 +18,13 @@ windows-sys = {
"Win32_System_Registry",
]
}
[lints.clippy]
pedantic = { level = "warn", priority = -1 }
todo = "warn"
unwrap_used = "warn"
[lints.rust]
# `deny` rather than `forbid`: this crate needs OS FFI, so single
# modules opt back in with `#![allow(unsafe_code)]`.
unsafe_code = "deny"
+28 -2
View File
@@ -3,6 +3,7 @@
//! This crate deliberately stays below the relay session layer. It only knows
//! how to find and open an installed TAP-Windows6 Ethernet adapter; the Windows
//! client binary owns when to connect it to QUIC and how to protect routes.
#![cfg_attr(test, allow(clippy::unwrap_used))]
use anyhow::{Context, Result, bail};
use lanparty_proto::{EthernetFrame, MAX_STANDARD_ETHERNET_FRAME_LEN, MacAddr};
@@ -31,6 +32,12 @@ pub struct TapAdapterInfo {
}
impl TapAdapterInfo {
/// Describes an adapter that was identified outside the registry scan.
///
/// # Errors
///
/// Returns an error if `instance_id` is blank or `component_id` is not a
/// supported TAP-Windows6 component id.
pub fn new(instance_id: impl Into<String>, component_id: impl Into<String>) -> Result<Self> {
Self::from_parts(instance_id, component_id, None)
}
@@ -104,6 +111,13 @@ pub fn tap_device_path(instance_id: &str) -> String {
format!("{TAP_DEVICE_PREFIX}{instance_id}{TAP_DEVICE_SUFFIX}")
}
/// Checks that a buffer read from the TAP device is a well-formed,
/// non-oversized Ethernet frame.
///
/// # Errors
///
/// Returns an error if the buffer is too short to be an Ethernet frame or is
/// longer than [`MAX_STANDARD_ETHERNET_FRAME_LEN`].
pub fn validate_tap_ethernet_frame(frame: &[u8]) -> Result<()> {
let frame = EthernetFrame::parse(frame).context("TAP Ethernet frame is malformed")?;
if frame.is_jumbo() {
@@ -117,13 +131,20 @@ pub fn validate_tap_ethernet_frame(frame: &[u8]) -> Result<()> {
Ok(())
}
/// Renders a MAC address the way the TAP-Windows6 driver's `NetworkAddress`
/// registry value expects it: twelve upper-case hex digits, no separators.
///
/// # Errors
///
/// Returns an error if `mac` is not a locally administered unicast address, as
/// the driver rejects anything else.
pub fn tap_network_address_value(mac: MacAddr) -> Result<String> {
if !mac.is_valid_client_identity() {
bail!("TAP MAC {mac} is not a locally administered unicast address");
}
let [a, b, c, d, e, f] = mac.octets();
Ok(format!("{a:02X}{b:02X}{c:02X}{d:02X}{e:02X}{f:02X}"))
let [o0, o1, o2, o3, o4, o5] = mac.octets();
Ok(format!("{o0:02X}{o1:02X}{o2:02X}{o3:02X}{o4:02X}{o5:02X}"))
}
#[must_use]
@@ -152,6 +173,11 @@ mod windows;
#[cfg(windows)]
pub use windows::{TapAdapter, available_adapters, configure_adapter_mac, open_first_adapter};
/// Enumerates the installed TAP-Windows6 adapters.
///
/// # Errors
///
/// Always fails on non-Windows targets; TAP-Windows6 is a Windows driver.
#[cfg(not(windows))]
pub fn available_adapters() -> Result<Vec<TapAdapterInfo>> {
bail!("TAP-Windows6 adapter discovery is only available on Windows");
+71
View File
@@ -1,3 +1,9 @@
//! TAP-Windows6 device and registry access.
//!
//! All Win32 FFI is confined to this module, which is why it opts back into
//! `unsafe`.
#![allow(unsafe_code)]
use std::{
ffi::c_void,
io::{self, ErrorKind},
@@ -62,6 +68,12 @@ pub struct TapAdapter {
}
impl TapAdapter {
/// Opens the adapter's device file for synchronous frame I/O.
///
/// # Errors
///
/// Returns an error if the device cannot be opened, e.g. because the
/// adapter is gone or the process lacks the required privileges.
pub fn open(info: TapAdapterInfo) -> Result<Self> {
let path = info.device_path();
let wide_path = wide_null(&path);
@@ -89,6 +101,12 @@ impl TapAdapter {
&self.info
}
/// Reports the adapter's media state to Windows, which decides whether the
/// interface counts as connected.
///
/// # Errors
///
/// Returns an error if the driver rejects the IOCTL.
pub fn set_media_connected(&self, connected: bool) -> Result<()> {
let mut status = u32::from(connected);
self.device_io_control(
@@ -103,6 +121,11 @@ impl TapAdapter {
Ok(())
}
/// Reads the MAC address the driver currently presents.
///
/// # Errors
///
/// Returns an error if the driver rejects the IOCTL.
pub fn driver_mac(&self) -> Result<MacAddr> {
let mut bytes = [0_u8; 6];
self.device_io_control(
@@ -117,6 +140,11 @@ impl TapAdapter {
Ok(MacAddr::new(bytes))
}
/// Reads the MTU the driver currently presents.
///
/// # Errors
///
/// Returns an error if the driver rejects the IOCTL.
pub fn driver_mtu(&self) -> Result<u32> {
let mut mtu = 0_u32;
self.device_io_control(
@@ -131,6 +159,12 @@ impl TapAdapter {
Ok(mtu)
}
/// Reads one raw frame into `buffer`, returning its length.
///
/// # Errors
///
/// Returns an error if `buffer` is larger than a Win32 read can express or
/// the device read fails.
pub fn read_frame(&self, buffer: &mut [u8]) -> Result<usize> {
let mut bytes_read = 0_u32;
let ok = unsafe {
@@ -154,6 +188,12 @@ impl TapAdapter {
Ok(bytes_read as usize)
}
/// Reads one frame and validates it as a standard-sized Ethernet frame.
///
/// # Errors
///
/// Returns an error if the read fails or the frame fails
/// [`validate_tap_ethernet_frame`].
pub fn read_ethernet_frame(&self, buffer: &mut [u8]) -> Result<usize> {
let len = self.read_frame(buffer)?;
validate_tap_ethernet_frame(&buffer[..len])?;
@@ -161,6 +201,12 @@ impl TapAdapter {
Ok(len)
}
/// Writes one raw frame, returning how many bytes the driver accepted.
///
/// # Errors
///
/// Returns an error if `frame` is larger than a Win32 write can express or
/// the device write fails.
pub fn write_frame(&self, frame: &[u8]) -> Result<usize> {
let mut bytes_written = 0_u32;
let ok = unsafe {
@@ -181,6 +227,12 @@ impl TapAdapter {
Ok(bytes_written as usize)
}
/// Validates `frame` and writes it in full.
///
/// # Errors
///
/// Returns an error if the frame fails [`validate_tap_ethernet_frame`], the
/// write fails, or the driver accepts only part of the frame.
pub fn write_ethernet_frame(&self, frame: &[u8]) -> Result<()> {
validate_tap_ethernet_frame(frame)?;
let written = self.write_frame(frame)?;
@@ -222,6 +274,12 @@ impl TapAdapter {
}
}
/// Opens the first installed TAP-Windows6 adapter.
///
/// # Errors
///
/// Returns an error if adapter enumeration fails, no adapter is installed, or
/// the adapter cannot be opened.
pub fn open_first_adapter() -> Result<TapAdapter> {
let mut adapters = available_adapters()?;
let info = adapters
@@ -232,6 +290,13 @@ pub fn open_first_adapter() -> Result<TapAdapter> {
TapAdapter::open(info)
}
/// Persists `mac` as the adapter's `NetworkAddress`, which the driver picks up
/// on its next restart.
///
/// # Errors
///
/// Returns an error if the adapter was not discovered from the registry, `mac`
/// is not a valid client identity, or the registry write fails.
pub fn configure_adapter_mac(info: &TapAdapterInfo, mac: MacAddr) -> Result<()> {
let driver_key_name = info
.driver_key_name()
@@ -245,6 +310,12 @@ pub fn configure_adapter_mac(info: &TapAdapterInfo, mac: MacAddr) -> Result<()>
Ok(())
}
/// Enumerates the installed TAP-Windows6 adapters by scanning the network class
/// registry key.
///
/// # Errors
///
/// Returns an error if the registry cannot be read.
pub fn available_adapters() -> Result<Vec<TapAdapterInfo>> {
let adapters_key = RegKey::open(HKEY_LOCAL_MACHINE, TAP_ADAPTER_KEY)
.context("failed to open TAP adapter registry key")?;