diff --git a/.github/workflows/rust-gate.yml b/.github/workflows/rust-gate.yml new file mode 100644 index 00000000..59959756 --- /dev/null +++ b/.github/workflows/rust-gate.yml @@ -0,0 +1,86 @@ +# Rust Gate — a required, always-reporting status check for Rust quality. +# +# Why this exists: +# The comprehensive Rust workflows (rust-crypto.yml, rust-security-suite.yml) +# are path-filtered to rust_crypto/** and crypto_core/**. A path-filtered +# workflow that is made a *required* status check deadlocks every PR that does +# NOT touch those paths — the check never runs, so the PR waits forever. +# +# This workflow has NO path filter, so it always runs and always reports a +# conclusion, making it safe to require in branch protection. It does the +# expensive work (clippy + crypto_core tests) only when Rust sources actually +# changed; otherwise it passes trivially and fast. +# +# Detection is fail-safe: if the diff can't be computed for any reason, the +# checks RUN (a slightly slower pass) rather than being skipped, so real Rust +# changes can never slip through un-linted. + +name: Rust Gate + +on: + pull_request: + branches: [main] + push: + branches: [main] + +permissions: + contents: read + +concurrency: + group: rust-gate-${{ github.ref }} + cancel-in-progress: true + +jobs: + rust-gate: + name: Rust Gate (clippy + tests) + runs-on: ubuntu-latest + timeout-minutes: 30 + steps: + - name: Checkout + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + fetch-depth: 0 + + - name: Detect Rust source changes + id: changes + run: | + run_rust=true + if [ "${{ github.event_name }}" = "pull_request" ]; then + if git fetch --no-tags origin "${{ github.base_ref }}" 2>/dev/null; then + diff=$(git diff --name-only "origin/${{ github.base_ref }}...HEAD" 2>/dev/null || echo "__ERR__") + if [ "$diff" != "__ERR__" ]; then + if echo "$diff" | grep -qE '^(rust_crypto|crypto_core)/'; then + run_rust=true + else + run_rust=false + fi + fi + fi + fi + echo "rust=$run_rust" >> "$GITHUB_OUTPUT" + if [ "$run_rust" = "true" ]; then + echo "::notice::Rust sources changed (or detection inconclusive) — running clippy + crypto_core tests." + else + echo "::notice::No Rust source changes — gate passes without building." + fi + + - name: Install Rust toolchain + if: steps.changes.outputs.rust == 'true' + uses: dtolnay/rust-toolchain@d0592fe69e35bc8f12e3dbaf9ad2694d976cb8e3 # stable + with: + toolchain: stable + components: clippy + + - name: Install system dependencies + if: steps.changes.outputs.rust == 'true' + run: | + sudo apt-get update + sudo apt-get install -y build-essential libssl-dev pkg-config libpcsclite-dev libudev-dev + + - name: Clippy (deny warnings) + if: steps.changes.outputs.rust == 'true' + run: cargo clippy --workspace --all-targets -- -D warnings + + - name: crypto_core tests + if: steps.changes.outputs.rust == 'true' + run: cargo test -p crypto_core diff --git a/crypto_core/src/meow_fountain/wire.rs b/crypto_core/src/meow_fountain/wire.rs index 3903b355..c2d779f0 100644 --- a/crypto_core/src/meow_fountain/wire.rs +++ b/crypto_core/src/meow_fountain/wire.rs @@ -221,7 +221,7 @@ mod tests { buf.extend_from_slice(&0u32.to_be_bytes()); buf.extend_from_slice(&1u16.to_be_bytes()); buf.extend_from_slice(&0u16.to_be_bytes()); - buf.extend(std::iter::repeat(0xFFu8).take(99)); + buf.extend(std::iter::repeat_n(0xFFu8, 99)); assert!(matches!( Droplet::from_wire(&buf, 100), Err(WireError::DataLengthMismatch { @@ -240,7 +240,7 @@ mod tests { buf.extend_from_slice(&3u16.to_be_bytes()); buf.extend_from_slice(&1u16.to_be_bytes()); buf.extend_from_slice(&2u16.to_be_bytes()); - buf.extend(std::iter::repeat(0u8).take(32)); + buf.extend(std::iter::repeat_n(0u8, 32)); assert!(matches!( Droplet::from_wire(&buf, 32), Err(WireError::UnsortedOrDuplicateIndices) @@ -254,7 +254,7 @@ mod tests { buf.extend_from_slice(&2u16.to_be_bytes()); buf.extend_from_slice(&5u16.to_be_bytes()); buf.extend_from_slice(&5u16.to_be_bytes()); - buf.extend(std::iter::repeat(0u8).take(32)); + buf.extend(std::iter::repeat_n(0u8, 32)); assert!(matches!( Droplet::from_wire(&buf, 32), Err(WireError::UnsortedOrDuplicateIndices) diff --git a/crypto_core/src/secure_alloc.rs b/crypto_core/src/secure_alloc.rs index ef7c6266..ed22d79d 100644 --- a/crypto_core/src/secure_alloc.rs +++ b/crypto_core/src/secure_alloc.rs @@ -238,7 +238,7 @@ impl SecureBox { }; // Round data up to page boundary - let data_pages = (data_size + page_size - 1) / page_size; + let data_pages = data_size.div_ceil(page_size); let data_region_size = data_pages * page_size; // Total: guard_before + data + guard_after let total_size = data_region_size + 2 * page_size; diff --git a/crypto_core/tests/coverage_boost_tests.rs b/crypto_core/tests/coverage_boost_tests.rs index 80b6cb2b..42c050a6 100644 --- a/crypto_core/tests/coverage_boost_tests.rs +++ b/crypto_core/tests/coverage_boost_tests.rs @@ -72,7 +72,7 @@ mod secure_alloc_coverage { fn test_secure_box_deref_mut() { let mut sbox = SecureBox::new([0u8; 32]).expect("alloc failed"); // Exercise DerefMut - let data: &mut [u8; 32] = &mut *sbox; + let data: &mut [u8; 32] = &mut sbox; data[0] = 0xFF; data[31] = 0xAA; assert_eq!(sbox[0], 0xFF); diff --git a/rust_crypto/tests/coverage_boost_tests.rs b/rust_crypto/tests/coverage_boost_tests.rs index b69f411d..d08b751e 100644 --- a/rust_crypto/tests/coverage_boost_tests.rs +++ b/rust_crypto/tests/coverage_boost_tests.rs @@ -405,7 +405,7 @@ mod stego_coverage { let mut costs = vec![1.0f64; 100]; stego::compute_adaptive_costs(&seed, &cover_bits, &pixels, &mut costs); // With medium texture, some costs get texture_factor=1.0 (unchanged) plus jitter - assert!(costs.iter().any(|&c| c >= 0.9 && c <= 1.1)); + assert!(costs.iter().any(|&c| (0.9..=1.1).contains(&c))); } }