diff --git a/packaging/common/fips.yaml b/packaging/common/fips.yaml index c8ea17ce..e23b4bf1 100644 --- a/packaging/common/fips.yaml +++ b/packaging/common/fips.yaml @@ -103,7 +103,8 @@ transports: # auto_connect: true # accept_connections: true - # Bluetooth Low Energy transport — requires BlueZ and the 'ble' feature. + # Bluetooth Low Energy transport: Linux (glibc) builds only, with BlueZ + # (bluetoothd) running. Not available on macOS, FreeBSD, Windows or OpenWrt. # ble: # adapter: "hci0" # mtu: 2048 diff --git a/packaging/openwrt-ipk/files/etc/fips/fips.yaml b/packaging/openwrt-ipk/files/etc/fips/fips.yaml index a12db9b8..2f15160d 100644 --- a/packaging/openwrt-ipk/files/etc/fips/fips.yaml +++ b/packaging/openwrt-ipk/files/etc/fips/fips.yaml @@ -162,14 +162,7 @@ transports: # auto_connect: true # accept_connections: true - # Bluetooth Low Energy transport — requires BlueZ and the 'ble' feature. - # ble: - # adapter: "hci0" - # mtu: 2048 - # advertise: true - # scan: true - # auto_connect: true - # accept_connections: true + # No BLE transport: OpenWrt builds target musl, which has no BlueZ backend. # Outbound LAN gateway. dnsmasq forwards .fips queries to listen=[::1]:5353 # while it runs (configured by the fips-gateway init script). Requires IPv6 diff --git a/src/config/transport.rs b/src/config/transport.rs index 6bc3d380..b035b302 100644 --- a/src/config/transport.rs +++ b/src/config/transport.rs @@ -702,7 +702,7 @@ const DEFAULT_BLE_PROBE_COOLDOWN_SECS: u64 = 30; /// BLE transport instance configuration. /// /// BleConfig is always compiled (for config parsing on any platform), -/// but the transport runtime requires Linux and the `ble` feature. +/// but the transport runtime is compiled only for glibc Linux and Android. #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub struct BleConfig { diff --git a/src/packaging_tests.rs b/src/packaging_tests.rs index 6716cac1..7812c7d1 100644 --- a/src/packaging_tests.rs +++ b/src/packaging_tests.rs @@ -188,6 +188,53 @@ fn case_branch(sh: &str, label: &str) -> Vec { lines[start..start + len].to_vec() } +/// Returns the feature names declared in the `[features]` table of a +/// Cargo.toml. +fn cargo_features(cargo_toml: &str) -> Vec { + toml_section(cargo_toml, "[features]") + .into_iter() + .map(str::trim) + .filter(|l| !l.is_empty() && !l.starts_with('#')) + .filter_map(|l| l.split_once('=').map(|(key, _)| key.trim().to_string())) + .collect() +} + +/// Returns the cargo feature names a config file's comments mention: on each +/// `#` comment line, the token before the word `feature` or `features` when +/// that token is wrapped in `'`, `"` or `` ` ``. +fn feature_mentions(text: &str) -> Vec { + let mut found = Vec::new(); + for line in text.lines().map(str::trim) { + if !line.starts_with('#') { + continue; + } + let words: Vec<&str> = line.split_whitespace().collect(); + for pair in words.windows(2) { + let word = pair[1].trim_end_matches(|c: char| c.is_ascii_punctuation()); + if word != "feature" && word != "features" { + continue; + } + let quoted = ['\'', '"', '`'].iter().find_map(|q| { + pair[0] + .strip_prefix(*q) + .and_then(|rest| rest.strip_suffix(*q)) + }); + if let Some(name) = quoted { + found.push(name.to_string()); + } + } + } + found +} + +/// Whether a config line, commented out or not, starts a `ble:` block. +fn is_ble_key(line: &str) -> bool { + line.trim() + .trim_start_matches('#') + .trim_start() + .starts_with("ble:") +} + #[test] fn deb_and_aur_packages_declare_nftables_for_the_firewall_units_nft() { let unit = repo_file("packaging/debian/fips-firewall.service"); @@ -758,3 +805,57 @@ fn windows_installer_icacls_calls_act_on_links_and_check_exit_codes() { ); } } + +const COMMON_CONFIG: &str = "packaging/common/fips.yaml"; +const OPENWRT_CONFIG: &str = "packaging/openwrt-ipk/files/etc/fips/fips.yaml"; + +#[test] +fn shipped_configs_name_only_cargo_features_that_exist() { + assert_eq!( + feature_mentions( + " # Bluetooth Low Energy transport — requires BlueZ and the 'ble' feature." + ), + ["ble"], + "control: the feature-mention scanner no longer finds a quoted feature name" + ); + let features = cargo_features(&repo_file("Cargo.toml")); + assert!( + features.iter().any(|f| f == "profiling"), + "control: expected the profiling feature in Cargo.toml [features], read {features:?}" + ); + + let mut unknown = Vec::new(); + for rel in [COMMON_CONFIG, OPENWRT_CONFIG] { + for name in feature_mentions(&repo_file(rel)) { + if !features.contains(&name) { + unknown.push(format!("{rel}: '{name}'")); + } + } + } + assert!( + unknown.is_empty(), + "shipped configs name cargo features that Cargo.toml does not define \ + (it defines {features:?}):\n {}", + unknown.join("\n ") + ); +} + +#[test] +fn openwrt_config_offers_no_ble_block_because_musl_builds_have_no_ble() { + assert!( + repo_file(COMMON_CONFIG).lines().any(is_ble_key), + "control: expected the ble: example in {COMMON_CONFIG}" + ); + let text = repo_file(OPENWRT_CONFIG); + let found: Vec<(usize, &str)> = text + .lines() + .enumerate() + .filter(|(_, l)| is_ble_key(l)) + .map(|(i, l)| (i + 1, l.trim_end())) + .collect(); + assert!( + found.is_empty(), + "{OPENWRT_CONFIG} offers a ble: block, but OpenWrt builds target musl, \ + where the BLE transport is not compiled: {found:?}" + ); +}