pwn 0.5.728 → 0.5.731

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: '0018be9794020a4b996e0d67a076e2741ca124d475f1439a047a77ecc0667d9d'
4
- data.tar.gz: 46eae2746ab67af7ace35e7dc4caa6e09897a187ec7295a6fe5b103a33262722
3
+ metadata.gz: fba7b3d4693ca9b846825957a7bde24e2a1cd6fd18577666cac32f3abfc0fcb2
4
+ data.tar.gz: a1ea687f125fb659f363d7195ac4f32f332918b7707ecf1aa78acafa653eff36
5
5
  SHA512:
6
- metadata.gz: dd8e061914bb0941234e210a8ea346d4f11dbd0babcd5137e93fe529b094826459b469f6cfb5d2e8cabdc306bbb8471df5703275035a9e8d27fc36352b50ec84
7
- data.tar.gz: 4f361334ec04c20998333623be5c2d5b97ab27189dc52f1d4fda470ff40e743c38f58b1ae37b85244557a1f7eaf93954abd3baafdcb96c8bc8d57eb4fc71ff98
6
+ metadata.gz: 50cd5f5f05075760b364ef46dc82ff469d1698bbaf724880563f97793108437fcfbbc441e2283817ce10b44c46f36566b6254f36879be66f67432cdeecc8c57a
7
+ data.tar.gz: 2085d58ae27b634c5d0893ab0c30f428e9442a15695721a4e45d3c923cbbaa67eb95d35e578bb07d1e1b6253be089ac2c833381ba839adf406c2137229063cd3
data/Gemfile CHANGED
@@ -50,9 +50,9 @@ gem 'libusb', '0.8.0'
50
50
  gem 'luhn', '3.0.0'
51
51
  gem 'mail', '2.9.1'
52
52
  gem 'mcp', '1.5.1'
53
- gem 'meshtastic', '0.0.175'
53
+ gem 'meshtastic', '0.0.176'
54
54
  gem 'metasm', '1.0.6'
55
- gem 'mongo', '2.25.0'
55
+ gem 'mongo', '2.26.0'
56
56
  gem 'msfrpc-client', '1.1.2'
57
57
  gem 'netaddr', '2.0.6'
58
58
  gem 'net-ldap', '0.20.0'
@@ -81,7 +81,7 @@ gem 'rmagick', '7.1.4'
81
81
  gem 'rqrcode', '3.2.0'
82
82
  gem 'rspec', '3.13.2'
83
83
  gem 'rtesseract', '3.1.4'
84
- gem 'rubocop', '1.90.0'
84
+ gem 'rubocop', '1.91.0'
85
85
  gem 'rubocop-rake', '0.7.1'
86
86
  gem 'rubocop-rspec', '3.10.2'
87
87
  gem 'ruby-audio', '1.6.1'
@@ -30,6 +30,97 @@ rake # rubocop + rspec - must be zero offenses
30
30
 
31
31
  (`rvmsudo rake` on multi-user RVM installs.)
32
32
 
33
+ ### Optional native decoder integration tests
34
+
35
+ The default `bundle exec rake` does not require `proxmark3` or `rtl_433`.
36
+ Ruby EM4100/Acurite decoding, native input validation, missing-executable errors,
37
+ and cancellation-before-spawn tests always run. Only real native file replays
38
+ are excluded with RSpec metadata (`proxmark3_integration`, `rtl433_integration`),
39
+ not marked pending or silently passed inside examples.
40
+
41
+ After installing the optional clients, explicitly enable their fixture tests:
42
+
43
+ ```bash
44
+ PWN_TEST_PROXMARK3=1 bundle exec rspec spec/lib/pwn/sdr/decoder/rfid_spec.rb
45
+ PWN_TEST_RTL433=1 bundle exec rspec spec/lib/pwn/sdr/decoder/rtl433_spec.rb
46
+ # Enable both in the full gate:
47
+ PWN_TEST_PROXMARK3=1 PWN_TEST_RTL433=1 bundle exec rake
48
+ ```
49
+
50
+ These tests use committed offline amplitude/IQ captures, not RF hardware.
51
+ Opt-in runs fail if their client is unavailable or cannot decode the reference;
52
+ installed versions can differ in supported protocols and output. Fixture provenance
53
+ and tested versions are recorded in `spec/fixtures/sdr/rfid/README.md` and
54
+ `spec/fixtures/sdr/rtl433/README.md`. Neither client is installed by the test suite.
55
+
56
+ At runtime, `RFID.decode(mode: :fdxb_pm3, ...)` and
57
+ `RTL433.decode(mode: :native, ...)` raise an actionable `IOError` when their
58
+ executable is missing, suggesting installation or an explicit `executable:` path.
59
+ An already-true `stop:` callback raises cancellation before spawning either client.
60
+ Invalid inputs still raise `ArgumentError`; native decoding never silently falls
61
+ back to another decoder or a detector.
62
+
63
+ ### Optional Liquid and radare2 integration tests
64
+
65
+ Native Liquid DSP examples and the real radare2 JSON replay are also explicit
66
+ opt-ins, even when the backends happen to be installed. The default gate excludes
67
+ `liquid_integration` and `radare2_integration` metadata rather than marking tests
68
+ pending. Liquid interface checks, missing-library errors, pure-Ruby DSP fallback,
69
+ radare2 argument validation, and binutils fallback coverage still run by default.
70
+ The binary fallback tests require the existing binutils tools and `/bin/true`.
71
+
72
+ ```bash
73
+ PWN_TEST_LIQUID=1 bundle exec rspec spec/lib/pwn/ffi/liquid_spec.rb
74
+ PWN_TEST_RADARE2=1 bundle exec rspec spec/lib/pwn/plugins/binary_analysis_spec.rb
75
+ # Enable both in the full gate:
76
+ PWN_TEST_LIQUID=1 PWN_TEST_RADARE2=1 bundle exec rake
77
+ ```
78
+
79
+ Liquid opt-in requires the liquid-dsp shared library (`libliquid`) visible to the
80
+ dynamic loader. Radare2 opt-in requires `r2` and a C compiler named `cc` on PATH.
81
+ The radare2 example compiles a small unstripped C fixture in a temporary directory,
82
+ checks real JSON disassembly for its known `main` symbol, and removes the fixture
83
+ afterward. It does not assume the host's stripped `/bin/true` has a discoverable
84
+ `main`. Opt-in runs fail with installation guidance when a prerequisite is absent;
85
+ they never skip or accept a degraded backend as native success. The tests install
86
+ nothing and require no radio hardware or network access.
87
+
88
+ ### Optional FFTW, Volk, and SDR shared-library tests
89
+
90
+ The default gate also excludes real FFTW/Volk computations and radio-library
91
+ integration checks, regardless of what is installed. Missing-library behavior,
92
+ mocked device inventory and stream lifecycle tests, and Ruby DSP fallbacks run
93
+ without these shared libraries and produce no pending examples.
94
+
95
+ | Environment opt-in | RSpec metadata | Loader prerequisite / coverage |
96
+ | --- | --- | --- |
97
+ | `PWN_TEST_FFTW=1` | `fftw_integration` | `libfftw3f`; native impulse FFT |
98
+ | `PWN_TEST_VOLK=1` | `volk_integration` | `libvolk`; native conversion, accumulation, DSP dispatch |
99
+ | `PWN_TEST_RTL_SDR=1` | `rtl_sdr_integration` | `librtlsdr`; binding resolution only, no USB scan |
100
+ | `PWN_TEST_ADALM_PLUTO=1` | `adalm_pluto_integration` | Compatible `libiio`; library version only |
101
+ | `PWN_TEST_SOAPY_SDR=1` | `soapy_sdr_integration` | `libSoapySDR`; library/API version only |
102
+ | `PWN_TEST_HACK_RF=1` | `hack_rf_integration` | `libhackrf`; library version only |
103
+
104
+ For example:
105
+
106
+ ```bash
107
+ PWN_TEST_FFTW=1 bundle exec rspec spec/lib/pwn/ffi/fftw_spec.rb
108
+ PWN_TEST_VOLK=1 bundle exec rspec spec/lib/pwn/ffi/volk_spec.rb spec/lib/pwn/sdr/decoder/dsp_spec.rb
109
+ PWN_TEST_ADALM_PLUTO=1 bundle exec rspec spec/lib/pwn/ffi/adalm_pluto_spec.rb
110
+ PWN_TEST_LIQUID=1 bundle exec rspec spec/lib/pwn/ffi/liquid_spec.rb spec/lib/pwn/sdr/decoder/dsp_spec.rb
111
+ ```
112
+
113
+ Each opt-in fails with installation guidance if its library cannot load. Native
114
+ DSP integration tests assert real backend dispatch, not just a matching fallback.
115
+ None of these library opt-ins opens, tunes, receives from, or transmits through a
116
+ radio. Pluto URI discovery is mocked, including scan cleanup.
117
+
118
+ Actual RTL-SDR USB enumeration has a separate `rtl_sdr_hardware` tag, enabled
119
+ only by `PWN_TEST_RTL_SDR_HARDWARE=1`. It requires a connected RTL-SDR and USB
120
+ permissions, and fails on an empty inventory. Do not enable it in unattended
121
+ upgrade gates; library opt-ins do not enable hardware tests. It performs inventory
122
+ only, not tuning or reception. The test suite installs no dependencies.
123
+
33
124
  ## Adding a plugin
34
125
 
35
126
  1. `lib/pwn/plugins/my_thing.rb` following the conventions above.
@@ -191,12 +191,19 @@ module PWN
191
191
  data = File.read(path)
192
192
  raise ArgumentError, 'PM3 file must contain signed integer amplitude samples' unless data.lines.all? { |line| line.strip.match?(/\A-?\d+\z/) }
193
193
 
194
+ raise IOError, 'proxmark3 replay cancelled' if opts[:stop]&.call
195
+
194
196
  Tempfile.create(['pwn-fdxb-', '.pm3']) do |trace|
195
197
  Tempfile.create('pwn-fdxb-output-') do |output|
196
198
  trace.write(data)
197
199
  trace.flush
198
- pid = Process.spawn(opts.fetch(:executable, 'proxmark3').to_s, '--incognito', '-c',
199
- "data load -f #{trace.path}; lf fdxb demod", in: File::NULL, out: output, err: output)
200
+ executable = opts.fetch(:executable, 'proxmark3').to_s
201
+ begin
202
+ pid = Process.spawn(executable, '--incognito', '-c',
203
+ "data load -f #{trace.path}; lf fdxb demod", in: File::NULL, out: output, err: output)
204
+ rescue Errno::ENOENT
205
+ raise IOError, "proxmark3 executable #{executable.inspect} not found; install the optional proxmark3 client or set executable: to its path"
206
+ end
200
207
  deadline = Process.clock_gettime(Process::CLOCK_MONOTONIC) + duration
201
208
  begin
202
209
  loop do
@@ -303,6 +310,7 @@ module PWN
303
310
  # file: explicit .pm3 signed integer AMPLITUDE trace, not SDR IQ; no hardware access.
304
311
  # Valid CRC16 required. Returns national/country ID, animal and data-block flags.
305
312
  # executable: 'proxmark3'; duration: 30s deadline; stop: callable; output/on_frame/log_file.
313
+ # Missing executable raises IOError with install/path guidance; already-stopped replay never spawns.
306
314
  # PM3 mode is finite offline replay; no ISO14443/ISO15693/EPC Gen2 coverage.
307
315
  # carrier_hz: 125000 default; clocks_per_bit: 64 default, or 32/16.
308
316
  # threshold: 0.5 default, normalized envelope amplitude; tune to the received levels.
@@ -206,10 +206,14 @@ module PWN
206
206
  command.push('-R', '0')
207
207
  ids.each { |id| command.push('-R', id.to_s) }
208
208
  end
209
+ raise IOError, 'rtl_433 replay cancelled' if opts[:stop]&.call
210
+
209
211
  count = 0
210
212
  output = opts.fetch(:output, $stdout)
211
213
  deadline = Process.clock_gettime(Process::CLOCK_MONOTONIC) + duration
214
+ started = false
212
215
  Open3.popen3(*command) do |input, stdout, stderr, worker|
216
+ started = true
213
217
  input.close
214
218
  buffers = { stdout => +'', stderr => +'' }
215
219
  errors = +''
@@ -269,6 +273,10 @@ module PWN
269
273
  end
270
274
  end
271
275
  { protocol: 'RTL433', mode: :native, backend: 'rtl_433', frames: count, source: path }
276
+ rescue Errno::ENOENT
277
+ raise unless started == false
278
+
279
+ raise IOError, "rtl_433 executable #{command.first.inspect} not found; install the optional rtl_433 package or set executable: to its path"
272
280
  end
273
281
 
274
282
  public_class_method def self.detect(opts = {})
@@ -333,6 +341,7 @@ module PWN
333
341
  # Native mode is finite FILE replay only: file required, no auto RF/config loading.
334
342
  # Native options: format: :cu8/:cs16/:cf32, protocols: [positive native IDs],
335
343
  # executable: 'rtl_433', duration: 30 seconds (deadline), stop: callable.
344
+ # Missing executable raises IOError with install/path guidance; already-stopped replay never spawns.
336
345
  # Output is per-device native JSON, device_protocol preserves native numeric ID.
337
346
  # Individual sensors/integrity depend on installed version; mic absent means not-reported.
338
347
  # sample_rate: 250000 default, >=20000; threshold: 0.5 normalized envelope amplitude.
data/lib/pwn/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module PWN
4
- VERSION = '0.5.728'
4
+ VERSION = '0.5.731'
5
5
  end
@@ -101,15 +101,57 @@ RSpec.describe 'PWN::AI::Agent::Verification' do
101
101
  expect(report[:status]).to eq(:fail)
102
102
  end
103
103
 
104
- it 'executes opt-in test commands with bounded duration and treats timeouts as unknown' do
105
- opts = { request: 'Run tests', requirements: ['Run tests'], root: @root, allow_commands: true, timeout: 0.2 }
106
- check = { requirement: 'Run tests', kind: :command, argv: [RbConfig.ruby, '-e', 'puts "verified"'], expected: "verified\n" }
107
- expect(PWN::AI::Agent::Verification.run(opts.merge(checks: [check]))[:status]).to eq(:pass)
108
- check[:argv] = [RbConfig.ruby, '-e', 'exit 1']
109
- expect(PWN::AI::Agent::Verification.run(opts.merge(checks: [check]))[:status]).to eq(:fail)
110
- check[:argv] = [RbConfig.ruby, '-e', 'sleep 10']
111
- expect(PWN::AI::Agent::Verification.run(opts.merge(checks: [check]))[:status]).to eq(:unknown)
112
- expect(PWN::AI::Agent::Verification.run(opts.merge(allow_commands: false, checks: [check]))[:status]).to eq(:unknown)
104
+ it 'executes opt-in test commands with a bounded budget that allows interpreter startup' do
105
+ report = PWN::AI::Agent::Verification.run(
106
+ request: 'Run tests', requirements: ['Run tests'], root: @root, allow_commands: true, timeout: 10,
107
+ checks: [{ requirement: 'Run tests', kind: :command, argv: [RbConfig.ruby, '-e', 'puts "verified"'], expected: "verified\n" }]
108
+ )
109
+ expect(report[:status]).to eq(:pass)
110
+ expect(report[:checks].first).to include(passed: true, exit_code: 0, evidence: Digest::SHA256.hexdigest("verified\n"))
111
+ end
112
+
113
+ it 'allows simulated slow interpreter startup within the successful command budget' do
114
+ # Loading a startup file before -e models a slower host without mocking the runner.
115
+ startup = File.join(@root, 'startup.rb')
116
+ File.write(startup, 'sleep 0.3')
117
+ report = PWN::AI::Agent::Verification.run(
118
+ request: 'Run tests', requirements: ['Run tests'], root: @root, allow_commands: true, timeout: 10,
119
+ checks: [{ requirement: 'Run tests', kind: :command, argv: [RbConfig.ruby, '-r', startup, '-e', 'puts "verified"'], expected: "verified\n" }]
120
+ )
121
+ expect(report[:status]).to eq(:pass)
122
+ expect(report[:checks].first).to include(passed: true, exit_code: 0, evidence: Digest::SHA256.hexdigest("verified\n"))
123
+ end
124
+
125
+ it 'reports an actual unsuccessful command exit as failure' do
126
+ report = PWN::AI::Agent::Verification.run(
127
+ request: 'Run tests', requirements: ['Run tests'], root: @root, allow_commands: true, timeout: 10,
128
+ checks: [{ requirement: 'Run tests', kind: :command, argv: [RbConfig.ruby, '-e', 'exit 1'] }]
129
+ )
130
+ expect(report[:status]).to eq(:fail)
131
+ expect(report[:checks].first).to include(passed: false, exit_code: 1)
132
+ end
133
+
134
+ it 'bounds an unfinished command and reports a timeout as unknown, not failure or success' do
135
+ # This command cannot finish within the deadline, regardless of startup speed.
136
+ report = Timeout.timeout(5) do
137
+ PWN::AI::Agent::Verification.run(
138
+ request: 'Run tests', requirements: ['Run tests'], root: @root, allow_commands: true, timeout: 0.2,
139
+ checks: [{ requirement: 'Run tests', kind: :command, argv: [RbConfig.ruby, '-e', 'sleep 60'] }]
140
+ )
141
+ end
142
+ expect(report[:status]).to eq(:unknown)
143
+ expect(report[:checks].first).to include(passed: nil, evidence: 'unavailable', error: 'Timeout::Error')
144
+ expect(report[:checks].first).not_to have_key(:exit_code)
145
+ end
146
+
147
+ it 'does not spawn test commands without explicit opt-in' do
148
+ expect(Open3).not_to receive(:popen3)
149
+ report = PWN::AI::Agent::Verification.run(
150
+ request: 'Run tests', requirements: ['Run tests'], root: @root, allow_commands: false,
151
+ checks: [{ requirement: 'Run tests', kind: :command, argv: [RbConfig.ruby, '-e', 'puts "verified"'] }]
152
+ )
153
+ expect(report[:status]).to eq(:unknown)
154
+ expect(report[:checks].first).to include(passed: nil, error: 'ArgumentError')
113
155
  end
114
156
 
115
157
  it 'attributes a checked artifact only to its last observed writer rather than a later noop' do
@@ -3,6 +3,12 @@
3
3
  require 'spec_helper'
4
4
 
5
5
  describe PWN::FFI::AdalmPluto do
6
+ it 'handles a missing shared library without native calls' do
7
+ allow(described_class).to receive(:available?).and_return(false)
8
+ expect(described_class.info).to include(available: false)
9
+ expect(described_class.list_uris).to eq([])
10
+ end
11
+
6
12
  it 'waits for a cancelled refill to return before destroying its buffer' do
7
13
  handle = { buffer: FFI::MemoryPointer.new(:char, 4) }
8
14
  entered = Queue.new
@@ -62,8 +68,8 @@ describe PWN::FFI::AdalmPluto do
62
68
  expect(PWN::FFI::AdalmPluto).to respond_to :available?
63
69
  end
64
70
 
65
- it 'should report library info when libiio is present' do
66
- skip 'libiio not installed' unless PWN::FFI::AdalmPluto.available?
71
+ it 'should report library info', :adalm_pluto_integration do
72
+ expect(described_class.available?).to be(true), 'Install compatible libiio and make it visible to the dynamic loader (PWN_TEST_ADALM_PLUTO=1).'
67
73
 
68
74
  info = PWN::FFI::AdalmPluto.info
69
75
  expect(info[:available]).to eq(true)
@@ -71,15 +77,23 @@ describe PWN::FFI::AdalmPluto do
71
77
  expect(info[:minor]).to be_a(Integer)
72
78
  end
73
79
 
74
- it 'should list URIs (possibly empty) when libiio is present' do
75
- skip 'libiio not installed' unless PWN::FFI::AdalmPluto.available?
76
-
77
- # Restrict to usb,local so libiio does not attempt mDNS/DNS-SD (avahi)
78
- # discovery during the test suite — avoids the noisy
79
- # "ERROR: Unable to create Avahi DNS-SD client :Daemon not running"
80
- # C-level stderr write on hosts where avahi-daemon is not running.
81
- list = PWN::FFI::AdalmPluto.list_uris(backends: 'usb,local')
82
- expect(list).to be_a(Array)
80
+ it 'maps URIs and releases the mocked scan without hardware discovery' do
81
+ allow(described_class).to receive(:available?).and_return(true)
82
+ scan = FFI::MemoryPointer.new(:char)
83
+ entry = FFI::MemoryPointer.new(:char)
84
+ entries = FFI::MemoryPointer.new(:pointer)
85
+ entries.write_pointer(entry)
86
+ expect(described_class).to receive(:iio_create_scan_context).with('usb,local', 0).and_return(scan)
87
+ expect(described_class).to receive(:iio_scan_context_get_info_list) do |context, output|
88
+ expect(context).to eq(scan)
89
+ output.write_pointer(entries)
90
+ 1
91
+ end
92
+ allow(described_class).to receive(:iio_context_info_get_uri).with(entry).and_return('usb:mock')
93
+ allow(described_class).to receive(:iio_context_info_get_description).with(entry).and_return('Mock Pluto')
94
+ expect(described_class).to receive(:iio_context_info_list_free).with(entries)
95
+ expect(described_class).to receive(:iio_scan_context_destroy).with(scan)
96
+ expect(described_class.list_uris(backends: 'usb,local')).to eq([{ uri: 'usb:mock', description: 'Mock Pluto' }])
83
97
  end
84
98
 
85
99
  it 'should appear in PWN::FFI.backends' do
@@ -3,6 +3,11 @@
3
3
  require 'spec_helper'
4
4
 
5
5
  describe PWN::FFI::FFTW do
6
+ it 'handles a missing shared library without native calls' do
7
+ allow(described_class).to receive(:available?).and_return(false)
8
+ expect { described_class.rfft(samples: [1.0]) }.to raise_error(RuntimeError, /libfftw3f not available/)
9
+ end
10
+
6
11
  it 'should display information for authors' do
7
12
  expect(PWN::FFI::FFTW).to respond_to :authors
8
13
  end
@@ -15,8 +20,8 @@ describe PWN::FFI::FFTW do
15
20
  expect(PWN::FFI::FFTW).to respond_to :available?
16
21
  end
17
22
 
18
- it 'should compute an rfft impulse response when libfftw3f is present' do
19
- skip 'libfftw3f not installed' unless PWN::FFI::FFTW.available?
23
+ it 'should compute an rfft impulse response', :fftw_integration do
24
+ expect(described_class.available?).to be(true), 'Install libfftw3f and make it visible to the dynamic loader (PWN_TEST_FFTW=1).'
20
25
 
21
26
  spec = PWN::FFI::FFTW.rfft(samples: [1.0, 0, 0, 0, 0, 0, 0, 0])
22
27
  expect(spec.length).to eq(5) # n/2+1
@@ -3,6 +3,12 @@
3
3
  require 'spec_helper'
4
4
 
5
5
  describe PWN::FFI::HackRF do
6
+ it 'handles a missing shared library without native calls' do
7
+ allow(described_class).to receive(:available?).and_return(false)
8
+ expect(described_class.info).to include(available: false)
9
+ expect { described_class.open }.to raise_error(RuntimeError, /libhackrf not available/)
10
+ end
11
+
6
12
  it 'releases the GVL while native stop waits for callbacks' do
7
13
  source = File.read(File.expand_path('../../../../lib/pwn/ffi/hack_rf.rb', __dir__))
8
14
  expect(source).to match(/attach_function :hackrf_stop_rx, \[:pointer\], :int, blocking: true/)
@@ -51,8 +57,8 @@ describe PWN::FFI::HackRF do
51
57
  expect(PWN::FFI::HackRF).to respond_to :available?
52
58
  end
53
59
 
54
- it 'should report library info when libhackrf is present' do
55
- skip 'libhackrf not installed' unless PWN::FFI::HackRF.available?
60
+ it 'should report library info', :hack_rf_integration do
61
+ expect(described_class.available?).to be(true), 'Install libhackrf and make it visible to the dynamic loader (PWN_TEST_HACK_RF=1).'
56
62
 
57
63
  info = PWN::FFI::HackRF.info
58
64
  expect(info[:available]).to eq(true)
@@ -15,17 +15,33 @@ describe PWN::FFI::Liquid do
15
15
  expect(PWN::FFI::Liquid).to respond_to :available?
16
16
  end
17
17
 
18
- it 'should resample at half rate when libliquid is present' do
19
- skip 'libliquid not installed' unless PWN::FFI::Liquid.available?
18
+ it 'rejects native DSP calls when libliquid is unavailable' do
19
+ allow(described_class).to receive(:available?).and_return(false)
20
+ expect { described_class.resample(samples: [0.0], rate: 0.5) }.to raise_error(RuntimeError, /libliquid not available/)
21
+ expect { described_class.freq_demod(iq: [1.0, 0.0]) }.to raise_error(RuntimeError, /libliquid not available/)
22
+ end
23
+
24
+ it 'keeps DSP resampling functional when libliquid is unavailable' do
25
+ allow(described_class).to receive(:available?).and_return(false)
26
+ allow(PWN::SDR::Decoder::DSP).to receive(:native).and_return(true)
27
+ expect(described_class).not_to receive(:resample)
28
+ result = PWN::SDR::Decoder::DSP.resample(samples: [0.0, 1.0, 2.0, 3.0], src_rate: 4, dst_rate: 2)
29
+ expect(result).to eq([0.0, 2.0])
30
+ end
20
31
 
32
+ before(:each, :liquid_integration) do
33
+ expect(described_class.available?).to be(true),
34
+ 'PWN_TEST_LIQUID=1 requires liquid-dsp: install libliquid and make it visible to the dynamic loader. ' \
35
+ "Load error: #{described_class.load_error}"
36
+ end
37
+
38
+ it 'should resample at half rate with libliquid', :liquid_integration do
21
39
  samples = Array.new(64) { |i| Math.sin(2 * Math::PI * i / 16.0) }
22
40
  out = PWN::FFI::Liquid.resample(samples: samples, rate: 0.5)
23
41
  expect(out.length).to be_between(24, 40)
24
42
  end
25
43
 
26
- it 'should FM-demod a complex tone when libliquid is present' do
27
- skip 'libliquid not installed' unless PWN::FFI::Liquid.available?
28
-
44
+ it 'should FM-demod a complex tone with libliquid', :liquid_integration do
29
45
  iq = []
30
46
  phase = 0.0
31
47
  dphi = 2 * Math::PI * 0.05
@@ -3,6 +3,12 @@
3
3
  require 'spec_helper'
4
4
 
5
5
  describe PWN::FFI::RTLSdr do
6
+ it 'handles a missing shared library without native calls' do
7
+ allow(described_class).to receive(:available?).and_return(false)
8
+ expect { described_class.list_devices }.to raise_error(RuntimeError, /librtlsdr not available/)
9
+ expect { described_class.open }.to raise_error(RuntimeError, /librtlsdr not available/)
10
+ end
11
+
6
12
  it 'releases the GVL during native reads so DSP workers can run' do
7
13
  source = File.read(File.expand_path('../../../../lib/pwn/ffi/rtl_sdr.rb', __dir__))
8
14
  declaration = source[/attach_function :rtlsdr_read_sync,.*?(?=attach_function)/m]
@@ -31,10 +37,27 @@ describe PWN::FFI::RTLSdr do
31
37
  expect(PWN::FFI::RTLSdr).to respond_to :available?
32
38
  end
33
39
 
34
- it 'should list devices (possibly empty) when librtlsdr is present' do
35
- skip 'librtlsdr not installed' unless PWN::FFI::RTLSdr.available?
40
+ it 'maps device inventory using mocked USB calls' do
41
+ allow(described_class).to receive(:available?).and_return(true)
42
+ allow(described_class).to receive(:rtlsdr_get_device_count).and_return(1)
43
+ allow(described_class).to receive(:rtlsdr_get_device_name).with(0).and_return('Mock tuner')
44
+ expect(described_class).to receive(:rtlsdr_get_device_usb_strings) do |index, manufacturer, product, serial|
45
+ expect(index).to eq(0)
46
+ manufacturer.write_string('Test')
47
+ product.write_string('SDR')
48
+ serial.write_string('123')
49
+ 0
50
+ end
51
+ expect(described_class.list_devices).to eq([{ index: 0, name: 'Mock tuner', manufacturer: 'Test', product: 'SDR', serial: '123' }])
52
+ end
53
+
54
+ it 'loads the native inventory and read bindings without scanning USB', :rtl_sdr_integration do
55
+ expect(described_class.available?).to be(true), 'Install librtlsdr and make it visible to the dynamic loader (PWN_TEST_RTL_SDR=1).'
56
+ expect(described_class).to respond_to(:rtlsdr_get_device_count, :rtlsdr_read_sync)
57
+ end
36
58
 
37
- list = PWN::FFI::RTLSdr.list_devices
38
- expect(list).to be_a(Array)
59
+ it 'lists a connected RTL-SDR', :rtl_sdr_hardware do
60
+ expect(described_class.available?).to be(true), 'Install librtlsdr before enabling PWN_TEST_RTL_SDR_HARDWARE=1.'
61
+ expect(described_class.list_devices).not_to be_empty, 'Connect an RTL-SDR with USB permissions before enabling PWN_TEST_RTL_SDR_HARDWARE=1.'
39
62
  end
40
63
  end
@@ -3,6 +3,12 @@
3
3
  require 'spec_helper'
4
4
 
5
5
  describe PWN::FFI::SoapySDR do
6
+ it 'handles a missing shared library without native calls' do
7
+ allow(described_class).to receive(:available?).and_return(false)
8
+ expect(described_class.info).to include(available: false)
9
+ expect { described_class.make(args: 'driver=mock') }.to raise_error(RuntimeError, /libSoapySDR not available/)
10
+ end
11
+
6
12
  it 'reports close failure and retains the stream for cleanup retry' do
7
13
  handle = { device: :device, stream: :stream }
8
14
  allow(described_class).to receive(:SoapySDRDevice_deactivateStream).and_return(0)
@@ -39,8 +45,8 @@ describe PWN::FFI::SoapySDR do
39
45
  expect(PWN::FFI::SoapySDR).to respond_to :available?
40
46
  end
41
47
 
42
- it 'should report API info when libSoapySDR is present' do
43
- skip 'libSoapySDR not installed' unless PWN::FFI::SoapySDR.available?
48
+ it 'should report API info', :soapy_sdr_integration do
49
+ expect(described_class.available?).to be(true), 'Install libSoapySDR and make it visible to the dynamic loader (PWN_TEST_SOAPY_SDR=1).'
44
50
 
45
51
  info = PWN::FFI::SoapySDR.info
46
52
  expect(info[:available]).to eq(true)
@@ -3,6 +3,12 @@
3
3
  require 'spec_helper'
4
4
 
5
5
  describe PWN::FFI::Volk do
6
+ it 'handles a missing shared library without native calls' do
7
+ allow(described_class).to receive(:available?).and_return(false)
8
+ expect { described_class.unpack_s16le(data: '') }.to raise_error(RuntimeError, /libvolk not available/)
9
+ expect { described_class.accumulate(samples: []) }.to raise_error(RuntimeError, /libvolk not available/)
10
+ end
11
+
6
12
  it 'should display information for authors' do
7
13
  expect(PWN::FFI::Volk).to respond_to :authors
8
14
  end
@@ -15,8 +21,8 @@ describe PWN::FFI::Volk do
15
21
  expect(PWN::FFI::Volk).to respond_to :available?
16
22
  end
17
23
 
18
- it 'should unpack s16le to unit-range floats when libvolk is present' do
19
- skip 'libvolk not installed' unless PWN::FFI::Volk.available?
24
+ it 'should unpack s16le to unit-range floats', :volk_integration do
25
+ expect(described_class.available?).to be(true), 'Install libvolk and make it visible to the dynamic loader (PWN_TEST_VOLK=1).'
20
26
 
21
27
  raw = [0, 16_384, -16_384, 32_767].pack('s<*')
22
28
  out = PWN::FFI::Volk.unpack_s16le(data: raw)
@@ -27,8 +33,8 @@ describe PWN::FFI::Volk do
27
33
  expect(out[3]).to be_within(1e-3).of(1.0)
28
34
  end
29
35
 
30
- it 'should accumulate floats when libvolk is present' do
31
- skip 'libvolk not installed' unless PWN::FFI::Volk.available?
36
+ it 'should accumulate floats', :volk_integration do
37
+ expect(described_class.available?).to be(true), 'Install libvolk and make it visible to the dynamic loader (PWN_TEST_VOLK=1).'
32
38
 
33
39
  sum = PWN::FFI::Volk.accumulate(samples: [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0])
34
40
  expect(sum).to be_within(1e-4).of(36.0)
@@ -5,20 +5,50 @@ require 'tmpdir'
5
5
  require 'digest'
6
6
 
7
7
  describe 'normalized binary analysis' do
8
- it 'executes the actual radare2 read-only JSON interfaces when installed' do
9
- require 'pwn/plugins/radare2'
10
- skip 'r2 unavailable' unless PWN::Plugins::BinaryAnalysis.available?(name: 'r2')
11
- result = PWN::Plugins::Radare2.analyze_all(path: '/bin/true')
12
- expect(result[:backend]).to eq('radare2')
13
- expect(result[:functions]).not_to be_empty
14
- expect(PWN::Plugins::Radare2.list_functions(path: '/bin/true')[:data]).not_to be_empty
15
- expect(PWN::Plugins::Radare2.disasm_function(path: '/bin/true', function: 'main')[:data]).to include('ops')
16
- expect(PWN::Plugins::Radare2.xrefs(path: '/bin/true', addr: 'main')[:data]).to be_a(Array)
17
- expect(PWN::Plugins::Radare2.strings(path: '/bin/true')[:data]).not_to be_empty
18
- expect(PWN::Plugins::Radare2.imports(path: '/bin/true')[:data]).not_to be_empty
8
+ it 'executes the actual radare2 read-only JSON interfaces on a controlled binary', :radare2_integration do
9
+ expect(PWN::Plugins::BinaryAnalysis.available?(name: 'r2')).to be(true),
10
+ 'PWN_TEST_RADARE2=1 requires radare2: install r2 and add it to PATH.'
11
+ expect(PWN::Plugins::BinaryAnalysis.available?(name: 'cc')).to be(true),
12
+ 'PWN_TEST_RADARE2=1 requires a C compiler: install cc and add it to PATH.'
13
+
14
+ Dir.mktmpdir('pwn-radare2-spec-') do |dir|
15
+ path = File.join(dir, 'fixture')
16
+ source = File.join(dir, 'fixture.c')
17
+ File.write(source, "#include <stdio.h>\nint main(void) { return puts(\"pwn radare2 fixture\") < 0; }\n")
18
+ output, status = Open3.capture2e('cc', '-O0', '-g', '-o', path, source, chdir: dir)
19
+ expect(status.success?).to be(true), "Unable to compile radare2 fixture with cc: #{output}"
20
+ sha = Digest::SHA256.file(path).hexdigest
21
+
22
+ result = PWN::Plugins::Radare2.analyze_all(path: path)
23
+ expect(result).to include(backend: 'radare2', status: 'ok', sha256: sha)
24
+ expect(result[:functions]).not_to be_empty
25
+ expect(PWN::Plugins::Radare2.list_functions(path: path)[:data]).not_to be_empty
26
+ disassembly = PWN::Plugins::Radare2.disasm_function(path: path, function: 'main')
27
+ expect(disassembly).to include(backend: 'radare2', status: 'ok')
28
+ expect(disassembly[:data]).to include('ops')
29
+ expect(disassembly[:data]['ops']).not_to be_empty
30
+ xrefs = PWN::Plugins::Radare2.xrefs(path: path, addr: 'main')
31
+ expect(xrefs).to include(backend: 'radare2', status: 'ok')
32
+ expect(xrefs[:data]).to be_a(Array)
33
+ expect(PWN::Plugins::Radare2.strings(path: path)[:data]).not_to be_empty
34
+ expect(PWN::Plugins::Radare2.imports(path: path)[:data]).not_to be_empty
35
+ expect(Digest::SHA256.file(path).hexdigest).to eq(sha)
36
+ end
37
+ end
38
+
39
+ it 'rejects command injection without requiring a native backend' do
40
+ expect(PWN::Plugins::BinaryAnalysis).not_to receive(:run)
19
41
  expect { PWN::Plugins::Radare2.disasm_function(path: '/bin/true', function: 'main;!id') }.to raise_error(ArgumentError)
20
42
  end
21
43
 
44
+ it 'falls back deterministically when radare2 is unavailable' do
45
+ allow(PWN::Plugins::BinaryAnalysis).to receive(:available?).with(name: 'r2').and_return(false)
46
+ result = PWN::Plugins::Radare2.analyze_all(path: '/bin/true')
47
+ expect(result).to include(backend: 'binutils', status: 'degraded')
48
+ expect(result[:strings]).not_to be_empty
49
+ expect(result[:warnings]).not_to be_empty
50
+ end
51
+
22
52
  it 'reports an honest Ghidra fallback and uses the requested hash cache directory' do
23
53
  Dir.mktmpdir do |dir|
24
54
  result = PWN::Plugins::GhidraHeadless.analyze(path: '/bin/true', backend: 'binutils', cache_dir: dir)
@@ -29,33 +29,58 @@ describe PWN::SDR::Decoder::DSP do
29
29
  PWN::SDR::Decoder::DSP.native = was
30
30
  end
31
31
 
32
- it 'should unpack s16le via Volk when available' do
33
- skip 'libvolk not installed' unless PWN::FFI.available?(mod: :Volk)
34
-
32
+ it 'should unpack s16le via Volk', :volk_integration do
35
33
  was = PWN::SDR::Decoder::DSP.native
34
+ expect(PWN::FFI.available?(mod: :Volk)).to be(true), 'Install libvolk and make it visible to the dynamic loader (PWN_TEST_VOLK=1).'
36
35
  PWN::SDR::Decoder::DSP.native = true
37
36
  raw = [0, 16_384, -16_384, 32_767].pack('s<*')
37
+ native_out = PWN::FFI::Volk.unpack_s16le(data: raw)
38
+ expect(PWN::FFI::Volk).to receive(:unpack_s16le).with(data: raw).and_call_original
38
39
  out = PWN::SDR::Decoder::DSP.unpack_s16le(data: raw)
40
+ expect(out).to eq(native_out)
39
41
  expect(out[1]).to be_within(1e-3).of(0.5)
40
42
  ensure
41
43
  PWN::SDR::Decoder::DSP.native = was
42
44
  end
43
45
 
44
- it 'should resample pure-ruby and liquid paths to comparable length' do
45
- samples = Array.new(200) { |i| Math.sin(2 * Math::PI * i / 40.0) }
46
+ it 'falls back to Ruby unpacking with native enabled but Volk absent' do
47
+ was = described_class.native
48
+ described_class.native = true
49
+ allow(PWN::FFI).to receive(:available?).with(mod: :Volk).and_return(false)
50
+ expect(PWN::FFI::Volk).not_to receive(:unpack_s16le)
51
+ raw = [0, 16_384, -16_384, 32_767].pack('s<*')
52
+ expect(described_class.unpack_s16le(data: raw)).to eq([0.0, 0.5, -0.5, 32_767 / 32_768.0])
53
+ ensure
54
+ described_class.native = was
55
+ end
46
56
 
47
- was = PWN::SDR::Decoder::DSP.native
48
- PWN::SDR::Decoder::DSP.native = false
49
- ruby_out = PWN::SDR::Decoder::DSP.resample(samples: samples, src_rate: 48_000, dst_rate: 24_000)
57
+ it 'falls back to Ruby resampling with native enabled but Liquid absent' do
58
+ was = described_class.native
59
+ samples = Array.new(200) { |i| Math.sin(2 * Math::PI * i / 40.0) }
60
+ described_class.native = false
61
+ ruby_out = described_class.resample(samples: samples, src_rate: 48_000, dst_rate: 24_000)
50
62
  expect(ruby_out.length).to eq(100)
51
63
 
52
- if PWN::FFI.available?(mod: :Liquid)
53
- PWN::SDR::Decoder::DSP.native = true
54
- liq_out = PWN::SDR::Decoder::DSP.resample(samples: samples, src_rate: 48_000, dst_rate: 24_000)
55
- # multi-stage resampler length can differ by a few samples due to delay
56
- expect(liq_out.length).to be_between(90, 120)
57
- end
64
+ allow(PWN::FFI).to receive(:available?).with(mod: :Liquid).and_return(false)
65
+ expect(PWN::FFI::Liquid).not_to receive(:resample)
66
+ described_class.native = true
67
+ expect(described_class.resample(samples: samples, src_rate: 48_000, dst_rate: 24_000)).to eq(ruby_out)
58
68
  ensure
59
- PWN::SDR::Decoder::DSP.native = was
69
+ described_class.native = was
70
+ end
71
+
72
+ it 'resamples using Liquid to a comparable length', :liquid_integration do
73
+ was = described_class.native
74
+ expect(PWN::FFI.available?(mod: :Liquid)).to be(true), 'Install libliquid and make it visible to the dynamic loader (PWN_TEST_LIQUID=1).'
75
+ samples = Array.new(200) { |i| Math.sin(2 * Math::PI * i / 40.0) }
76
+ described_class.native = true
77
+ native_out = PWN::FFI::Liquid.resample(samples: samples, rate: 0.5)
78
+ expect(PWN::FFI::Liquid).to receive(:resample).with(samples: samples, rate: 0.5).and_call_original
79
+ liq_out = described_class.resample(samples: samples, src_rate: 48_000, dst_rate: 24_000)
80
+ expect(liq_out).to eq(native_out)
81
+ # Multi-stage resampler length can differ due to delay.
82
+ expect(liq_out.length).to be_between(90, 120)
83
+ ensure
84
+ described_class.native = was
60
85
  end
61
86
  end
@@ -3,7 +3,28 @@
3
3
  require 'spec_helper'
4
4
 
5
5
  describe PWN::SDR::Decoder::RFID do
6
- it 'decodes independent ISO11784/11785 FDX-B amplitude captures with the native offline client' do
6
+ it 'reports an actionable missing native executable without emitting frames' do
7
+ Dir.mktmpdir('pwn-missing-backend-') do |dir|
8
+ executable = File.join(dir, 'proxmark3')
9
+ frames = []
10
+ output = StringIO.new
11
+ expect do
12
+ described_class.decode(mode: :fdxb_pm3, file: 'spec/fixtures/sdr/rfid/fdxb_animal.pm3',
13
+ executable: executable, output: output, on_frame: ->(frame) { frames << frame })
14
+ end.to raise_error(IOError, /proxmark3.*not found.*install.*executable:/i)
15
+ expect(frames).to be_empty
16
+ expect(output.string).to be_empty
17
+ end
18
+ end
19
+
20
+ it 'cancels an already stopped native replay before spawning' do
21
+ expect(Process).not_to receive(:spawn)
22
+ expect do
23
+ described_class.decode(mode: :fdxb_pm3, file: 'spec/fixtures/sdr/rfid/fdxb_animal.pm3', stop: -> { true })
24
+ end.to raise_error(IOError, /cancelled/)
25
+ end
26
+
27
+ it 'decodes independent ISO11784/11785 FDX-B amplitude captures with the native offline client', :proxmark3_integration do
7
28
  %w[animal extended].each do |variant|
8
29
  frames = []
9
30
  result = described_class.decode(mode: :fdxb_pm3, file: "spec/fixtures/sdr/rfid/fdxb_#{variant}.pm3",
@@ -17,12 +38,16 @@ describe PWN::SDR::Decoder::RFID do
17
38
  end
18
39
  end
19
40
 
20
- it 'rejects automatic RF and emits no identity for an incomplete native amplitude trace' do
41
+ it 'rejects automatic RF and supports cancellation without the native client' do
21
42
  allow(PWN::SDR::Decoder::Base).to receive(:run_iq).and_raise('unexpected automatic RF')
22
43
  expect { described_class.decode(mode: :fdxb_pm3) }.to raise_error(ArgumentError, /explicit/)
23
44
  path = 'spec/fixtures/sdr/rfid/fdxb_animal.pm3'
24
45
  expect { described_class.decode(mode: :fdxb_pm3, file: path, source: :rtl_sdr) }.to raise_error(ArgumentError, /live/)
25
46
  expect { described_class.decode(mode: :fdxb_pm3, file: path, stop: -> { true }) }.to raise_error(IOError, /cancelled/)
47
+ end
48
+
49
+ it 'emits no identity for an incomplete native amplitude trace', :proxmark3_integration do
50
+ path = 'spec/fixtures/sdr/rfid/fdxb_animal.pm3'
26
51
  Tempfile.create(['fdxb-short-', '.pm3']) do |file|
27
52
  file.write(File.readlines(path).first(100).join)
28
53
  file.flush
@@ -3,6 +3,27 @@
3
3
  require 'spec_helper'
4
4
 
5
5
  describe PWN::SDR::Decoder::RTL433 do
6
+ it 'reports an actionable missing native executable without emitting frames' do
7
+ Dir.mktmpdir('pwn-missing-backend-') do |dir|
8
+ executable = File.join(dir, 'rtl_433')
9
+ frames = []
10
+ output = StringIO.new
11
+ expect do
12
+ described_class.decode(mode: :native, file: 'spec/fixtures/sdr/rtl433/toyota.cu8',
13
+ executable: executable, output: output, on_frame: ->(frame) { frames << frame })
14
+ end.to raise_error(IOError, /rtl_433.*not found.*install.*executable:/i)
15
+ expect(frames).to be_empty
16
+ expect(output.string).to be_empty
17
+ end
18
+ end
19
+
20
+ it 'cancels an already stopped native replay before spawning' do
21
+ expect(Open3).not_to receive(:popen3)
22
+ expect do
23
+ described_class.decode(mode: :native, file: 'spec/fixtures/sdr/rtl433/toyota.cu8', stop: -> { true })
24
+ end.to raise_error(IOError, /cancelled/)
25
+ end
26
+
6
27
  it 'decodes six real over-air Acurite packets with payloads matching the upstream reference' do
7
28
  demod = described_class::AcuriteIQ.new(rate: 250_000)
8
29
  frames = []
@@ -30,7 +51,7 @@ describe PWN::SDR::Decoder::RTL433 do
30
51
  expect { described_class.decode(mode: :all) }.to raise_error(ArgumentError, /mode/)
31
52
  end
32
53
 
33
- it 'replays OOK and FSK native catalogue decoders against published captures' do
54
+ it 'replays OOK and FSK native catalogue decoders against published captures', :rtl433_integration do
34
55
  %w[acurite_th_609_001 wh31 toyota].each do |name|
35
56
  frames = []
36
57
  result = described_class.decode(mode: :native, file: "spec/fixtures/sdr/rtl433/#{name}.cu8",
@@ -44,7 +65,7 @@ describe PWN::SDR::Decoder::RTL433 do
44
65
  end
45
66
  end
46
67
 
47
- it 'requires explicit native files, validates formats and supports native protocol selection' do
68
+ it 'requires explicit native files and validates formats and protocol selection without the native client' do
48
69
  allow(PWN::SDR::Decoder::Base).to receive(:run_iq).and_raise('unexpected automatic RF')
49
70
  expect { described_class.decode(mode: :native) }.to raise_error(ArgumentError, /explicit file/)
50
71
  path = 'spec/fixtures/sdr/rtl433/toyota.cu8'
@@ -52,6 +73,10 @@ describe PWN::SDR::Decoder::RTL433 do
52
73
  expect { described_class.decode(mode: :native, file: path, source: :rtl_sdr) }.to raise_error(ArgumentError, /live source/)
53
74
  expect { described_class.decode(mode: :native, file: path, protocols: [0]) }.to raise_error(ArgumentError, /protocol/)
54
75
  expect { described_class.decode(mode: :native, file: path, stop: -> { true }) }.to raise_error(IOError, /cancelled/)
76
+ end
77
+
78
+ it 'supports native protocol selection', :rtl433_integration do
79
+ path = 'spec/fixtures/sdr/rtl433/toyota.cu8'
55
80
  expect(described_class.decode(mode: :native, file: path, protocols: [11], output: StringIO.new)[:frames]).to eq(0)
56
81
  expect(described_class.decode(mode: :native, file: path, protocols: [88], output: StringIO.new)[:frames]).to eq(1)
57
82
  end
data/spec/spec_helper.rb CHANGED
@@ -19,6 +19,14 @@ Dir[File.join(__dir__, 'support', '**', '*.rb')].each { |f| require f }
19
19
  # ─────────────────────────────────────────────────────────────────────────────
20
20
  RSpec.configure do |config|
21
21
  config.filter_run_excluding :local_embeddings unless ENV['PWN_TEST_EMBED_ENDPOINT'] && ENV['PWN_SQLITE_VEC_EXTENSION']
22
+ config.filter_run_excluding :proxmark3_integration unless ENV['PWN_TEST_PROXMARK3'] == '1'
23
+ config.filter_run_excluding :rtl433_integration unless ENV['PWN_TEST_RTL433'] == '1'
24
+ config.filter_run_excluding :liquid_integration unless ENV['PWN_TEST_LIQUID'] == '1'
25
+ config.filter_run_excluding :radare2_integration unless ENV['PWN_TEST_RADARE2'] == '1'
26
+ %w[fftw volk rtl_sdr adalm_pluto soapy_sdr hack_rf].each do |backend|
27
+ config.filter_run_excluding :"#{backend}_integration" unless ENV["PWN_TEST_#{backend.upcase}"] == '1'
28
+ end
29
+ config.filter_run_excluding :rtl_sdr_hardware unless ENV['PWN_TEST_RTL_SDR_HARDWARE'] == '1'
22
30
 
23
31
  next if ENV['PWN_SPEC_VERBOSE']
24
32
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pwn
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.728
4
+ version: 0.5.731
5
5
  platform: ruby
6
6
  authors:
7
7
  - 0day Inc.
@@ -519,14 +519,14 @@ dependencies:
519
519
  requirements:
520
520
  - - '='
521
521
  - !ruby/object:Gem::Version
522
- version: 0.0.175
522
+ version: 0.0.176
523
523
  type: :development
524
524
  prerelease: false
525
525
  version_requirements: !ruby/object:Gem::Requirement
526
526
  requirements:
527
527
  - - '='
528
528
  - !ruby/object:Gem::Version
529
- version: 0.0.175
529
+ version: 0.0.176
530
530
  - !ruby/object:Gem::Dependency
531
531
  name: metasm
532
532
  requirement: !ruby/object:Gem::Requirement
@@ -547,14 +547,14 @@ dependencies:
547
547
  requirements:
548
548
  - - '='
549
549
  - !ruby/object:Gem::Version
550
- version: 2.25.0
550
+ version: 2.26.0
551
551
  type: :runtime
552
552
  prerelease: false
553
553
  version_requirements: !ruby/object:Gem::Requirement
554
554
  requirements:
555
555
  - - '='
556
556
  - !ruby/object:Gem::Version
557
- version: 2.25.0
557
+ version: 2.26.0
558
558
  - !ruby/object:Gem::Dependency
559
559
  name: msfrpc-client
560
560
  requirement: !ruby/object:Gem::Requirement
@@ -953,14 +953,14 @@ dependencies:
953
953
  requirements:
954
954
  - - '='
955
955
  - !ruby/object:Gem::Version
956
- version: 1.90.0
956
+ version: 1.91.0
957
957
  type: :runtime
958
958
  prerelease: false
959
959
  version_requirements: !ruby/object:Gem::Requirement
960
960
  requirements:
961
961
  - - '='
962
962
  - !ruby/object:Gem::Version
963
- version: 1.90.0
963
+ version: 1.91.0
964
964
  - !ruby/object:Gem::Dependency
965
965
  name: rubocop-rake
966
966
  requirement: !ruby/object:Gem::Requirement