pwn 0.5.728 → 0.5.729
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 +4 -4
- data/documentation/Contributing.md +30 -0
- data/lib/pwn/sdr/decoder/rfid.rb +10 -2
- data/lib/pwn/sdr/decoder/rtl433.rb +9 -0
- data/lib/pwn/version.rb +1 -1
- data/spec/lib/pwn/sdr/decoder/rfid_spec.rb +27 -2
- data/spec/lib/pwn/sdr/decoder/rtl433_spec.rb +27 -2
- data/spec/spec_helper.rb +2 -0
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz: '
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: '04697e334aa79768d74f64b19c7e90c8a8b0372899abdf68d83bcfd4f2dcf96b'
|
|
4
|
+
data.tar.gz: 8e8934c327d039dbfaad46d62837d1e6241af718b915deba0ed3aac240ded64b
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 68965dcb416fa10c340a8aaa4345409313f8949de2ea17529fbe570b35022e0eaa782639941f5d1ed873542dc6d1432a20372c0c16c1365ff32c199635829921
|
|
7
|
+
data.tar.gz: cb5176d386d0d26308ad52ac292714ddb11134fe384cf994ce275326a6d6dffbfa628f5616af99b7f6d44c9cf7df0add18a4f4d9669805e85278851da1b32a17
|
|
@@ -30,6 +30,36 @@ 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
|
+
|
|
33
63
|
## Adding a plugin
|
|
34
64
|
|
|
35
65
|
1. `lib/pwn/plugins/my_thing.rb` following the conventions above.
|
data/lib/pwn/sdr/decoder/rfid.rb
CHANGED
|
@@ -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
|
-
|
|
199
|
-
|
|
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
|
@@ -3,7 +3,28 @@
|
|
|
3
3
|
require 'spec_helper'
|
|
4
4
|
|
|
5
5
|
describe PWN::SDR::Decoder::RFID do
|
|
6
|
-
it '
|
|
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
|
|
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
|
|
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,8 @@ 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'
|
|
22
24
|
|
|
23
25
|
next if ENV['PWN_SPEC_VERBOSE']
|
|
24
26
|
|