pwn 0.5.620 → 0.5.621
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/README.md +1 -1
- data/documentation/Agent-Tool-Registry.md +2 -2
- data/documentation/Cron.md +13 -0
- data/documentation/Diagrams.md +1 -1
- data/documentation/Extrospection.md +71 -5
- data/documentation/Home.md +2 -2
- data/documentation/How-PWN-Works.md +1 -1
- data/documentation/Mistakes.md +2 -1
- data/documentation/Persistence.md +2 -1
- data/documentation/Skills-Memory-Learning.md +1 -0
- data/documentation/Transparent-Browser.md +18 -1
- data/documentation/diagrams/agent-tool-registry.svg +55 -54
- data/documentation/diagrams/ai-integration-tool-calling.svg +24 -24
- data/documentation/diagrams/aws-cloud-security.svg +25 -25
- data/documentation/diagrams/burp-vs-zap-preference.svg +12 -12
- data/documentation/diagrams/code-scanning-sast.svg +25 -25
- data/documentation/diagrams/cron-scheduling.svg +23 -23
- data/documentation/diagrams/dot/agent-tool-registry.dot +2 -2
- data/documentation/diagrams/dot/extrospection-world-awareness.dot +29 -8
- data/documentation/diagrams/dot/memory-skills-detailed.dot +4 -0
- data/documentation/diagrams/dot/overall-pwn-architecture.dot +2 -2
- data/documentation/diagrams/dot/persistence-filesystem.dot +2 -1
- data/documentation/diagrams/dot/pwn-ai-feedback-learning-loop.dot +10 -4
- data/documentation/diagrams/driver-framework.svg +13 -13
- data/documentation/diagrams/extrospection-world-awareness.svg +229 -99
- data/documentation/diagrams/fuzzing-workflow.svg +24 -24
- data/documentation/diagrams/hardware-hacking.svg +18 -18
- data/documentation/diagrams/history-to-drivers.svg +18 -18
- data/documentation/diagrams/memory-skills-detailed.svg +124 -101
- data/documentation/diagrams/mistakes-negative-feedback.svg +55 -55
- data/documentation/diagrams/network-infra-testing.svg +27 -27
- data/documentation/diagrams/overall-pwn-architecture.svg +85 -85
- data/documentation/diagrams/penetration-testing-workflow.svg +30 -30
- data/documentation/diagrams/persistence-filesystem.svg +94 -85
- data/documentation/diagrams/plugin-ecosystem.svg +35 -35
- data/documentation/diagrams/pwn-ai-feedback-learning-loop.svg +226 -180
- data/documentation/diagrams/pwn-repl-prototyping.svg +20 -20
- data/documentation/diagrams/reporting-pipeline.svg +18 -18
- data/documentation/diagrams/reverse-engineering-flow.svg +21 -21
- data/documentation/diagrams/sdr-radio-flow.svg +31 -31
- data/documentation/diagrams/sessions-cron-automation.svg +18 -18
- data/documentation/diagrams/swarm-multi-agent.svg +39 -39
- data/documentation/diagrams/web-application-testing.svg +26 -26
- data/documentation/diagrams/zero-day-research-flow.svg +25 -25
- data/documentation/pwn-ai-Agent.md +1 -1
- data/lib/pwn/ai/agent/extrospection.rb +433 -4
- data/lib/pwn/ai/agent/tools/extrospection.rb +88 -3
- data/lib/pwn/sdr/decoder/adsb.rb +14 -33
- data/lib/pwn/sdr/decoder/apt.rb +97 -36
- data/lib/pwn/sdr/decoder/base.rb +258 -171
- data/lib/pwn/sdr/decoder/bluetooth.rb +13 -20
- data/lib/pwn/sdr/decoder/dect.rb +13 -27
- data/lib/pwn/sdr/decoder/dsp.rb +396 -0
- data/lib/pwn/sdr/decoder/flex.rb +177 -210
- data/lib/pwn/sdr/decoder/gps.rb +12 -21
- data/lib/pwn/sdr/decoder/gsm.rb +21 -73
- data/lib/pwn/sdr/decoder/iridium.rb +11 -26
- data/lib/pwn/sdr/decoder/lora.rb +16 -23
- data/lib/pwn/sdr/decoder/lte.rb +11 -32
- data/lib/pwn/sdr/decoder/morse.rb +95 -21
- data/lib/pwn/sdr/decoder/p25.rb +19 -16
- data/lib/pwn/sdr/decoder/pager.rb +22 -38
- data/lib/pwn/sdr/decoder/pocsag.rb +175 -68
- data/lib/pwn/sdr/decoder/rfid.rb +24 -33
- data/lib/pwn/sdr/decoder/rtl433.rb +21 -30
- data/lib/pwn/sdr/decoder/rtty.rb +100 -27
- data/lib/pwn/sdr/decoder/wifi.rb +21 -42
- data/lib/pwn/sdr/decoder/zigbee.rb +18 -40
- data/lib/pwn/sdr/decoder.rb +8 -0
- data/lib/pwn/version.rb +1 -1
- data/spec/lib/pwn/sdr/decoder/dsp_spec.rb +15 -0
- data/third_party/pwn_rdoc.jsonl +24 -3
- metadata +3 -1
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require 'shellwords'
|
|
4
|
-
|
|
5
3
|
module PWN
|
|
6
4
|
module SDR
|
|
7
5
|
module Decoder
|
|
8
|
-
# Iridium L-band
|
|
9
|
-
#
|
|
10
|
-
# simplex/duplex bursts
|
|
11
|
-
#
|
|
6
|
+
# Pure-Ruby Iridium L-band burst detector.
|
|
7
|
+
#
|
|
8
|
+
# Iridium simplex/duplex bursts are 25 kbit/s DE-QPSK across
|
|
9
|
+
# 1616–1626.5 MHz. Native mode reports burst count/duration/energy
|
|
10
|
+
# per channel. `parse_line` retained for offline iridium-toolkit
|
|
11
|
+
# text analysis.
|
|
12
12
|
module Iridium
|
|
13
13
|
# Supported Method Parameters::
|
|
14
14
|
# PWN::SDR::Decoder::Iridium.decode(
|
|
@@ -17,24 +17,12 @@ module PWN
|
|
|
17
17
|
|
|
18
18
|
public_class_method def self.decode(opts = {})
|
|
19
19
|
freq_obj = opts[:freq_obj]
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
hz = PWN::SDR.hz_to_i(freq: freq_obj[:freq])
|
|
23
|
-
gain = (freq_obj[:rf_gain] || 40).to_s.to_f
|
|
24
|
-
sdr_args = (freq_obj[:sdr_args] || 'rtl=0').to_s
|
|
25
|
-
|
|
26
|
-
inner = "iridium-extractor -D 4 --multi-frame -c #{hz} -r 2000000 " \
|
|
27
|
-
"-g #{gain} -o - #{Shellwords.escape(sdr_args)} 2>/dev/null " \
|
|
28
|
-
'| iridium-parser.py --harder /dev/stdin'
|
|
29
|
-
direct_cmd = "bash -c #{Shellwords.escape(inner)}"
|
|
30
|
-
|
|
31
|
-
PWN::SDR::Decoder::Base.run_pipeline(
|
|
20
|
+
PWN::SDR::Decoder::Base.run_detector(
|
|
32
21
|
freq_obj: freq_obj,
|
|
33
22
|
protocol: 'IRIDIUM',
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
parser: proc { |line| parse_line(line: line) }
|
|
23
|
+
note: '25 kbit/s DE-QPSK — native mode reports burst timing/energy only.',
|
|
24
|
+
threshold: 7.0,
|
|
25
|
+
describe: proc { |b| { modulation: 'DE-QPSK', symbol_rate: 25_000, classification: b[:duration_ms] < 10 ? 'simplex-burst' : 'duplex-frame' } }
|
|
38
26
|
)
|
|
39
27
|
end
|
|
40
28
|
|
|
@@ -62,14 +50,11 @@ module PWN
|
|
|
62
50
|
# Display Usage for this Module
|
|
63
51
|
|
|
64
52
|
public_class_method def self.help
|
|
65
|
-
puts "USAGE:
|
|
53
|
+
puts "USAGE (ruby-native detector, no external binaries):
|
|
66
54
|
#{self}.decode(
|
|
67
55
|
freq_obj: 'required - freq_obj returned from PWN::SDR::GQRX.init_freq'
|
|
68
56
|
)
|
|
69
57
|
|
|
70
|
-
NOTE: Requires `iridium-extractor` (gr-iridium) and
|
|
71
|
-
`iridium-parser.py` (iridium-toolkit). Owns the SDR.
|
|
72
|
-
|
|
73
58
|
#{self}.parse_line(line: 'IRA: sat:23 beam:14 pos=(+32.1/-097.0) ...')
|
|
74
59
|
|
|
75
60
|
#{self}.authors
|
data/lib/pwn/sdr/decoder/lora.rb
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require 'json'
|
|
4
|
-
require 'shellwords'
|
|
5
4
|
|
|
6
5
|
module PWN
|
|
7
6
|
module SDR
|
|
8
7
|
module Decoder
|
|
9
|
-
# LoRa / LoRaWAN CSS
|
|
10
|
-
#
|
|
11
|
-
#
|
|
12
|
-
#
|
|
13
|
-
#
|
|
8
|
+
# Pure-Ruby LoRa / LoRaWAN CSS activity detector.
|
|
9
|
+
#
|
|
10
|
+
# LoRa is chirp-spread-spectrum over 125/250/500 kHz — recoverable
|
|
11
|
+
# only from raw I/Q at ≥250 ksps, not from a 48 kHz audio tap.
|
|
12
|
+
# Native mode reports chirp-burst duration/energy (from which SF
|
|
13
|
+
# can be estimated). `parse_line` retained for offline JSON analysis.
|
|
14
14
|
module LoRa
|
|
15
15
|
# Supported Method Parameters::
|
|
16
16
|
# PWN::SDR::Decoder::LoRa.decode(
|
|
@@ -19,22 +19,17 @@ module PWN
|
|
|
19
19
|
|
|
20
20
|
public_class_method def self.decode(opts = {})
|
|
21
21
|
freq_obj = opts[:freq_obj]
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
hz = PWN::SDR.hz_to_i(freq: freq_obj[:freq])
|
|
25
|
-
sdr_args = freq_obj[:sdr_args].to_s
|
|
26
|
-
|
|
27
|
-
cmd = ['rtl_433', '-f', hz.to_s, '-s', '1024k',
|
|
28
|
-
'-R', '264', '-F', 'json', '-M', 'level']
|
|
29
|
-
cmd.push('-d', sdr_args) unless sdr_args.empty?
|
|
30
|
-
|
|
31
|
-
PWN::SDR::Decoder::Base.run_pipeline(
|
|
22
|
+
PWN::SDR::Decoder::Base.run_detector(
|
|
32
23
|
freq_obj: freq_obj,
|
|
33
24
|
protocol: 'LoRa',
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
25
|
+
note: 'CSS over 125–500 kHz — native mode reports chirp bursts and estimates SF from duration.',
|
|
26
|
+
threshold: 8.0,
|
|
27
|
+
describe: proc { |b|
|
|
28
|
+
# Preamble ≈ 8 symbols; T_sym = 2^SF / BW. Assume BW=125k.
|
|
29
|
+
t_sym_ms = b[:duration_ms] / 20.0
|
|
30
|
+
sf_est = t_sym_ms.positive? ? Math.log2(t_sym_ms * 125).round.clamp(6, 12) : nil
|
|
31
|
+
{ modulation: 'CSS', bw_khz_assumed: 125, sf_estimate: sf_est }.compact
|
|
32
|
+
}
|
|
38
33
|
)
|
|
39
34
|
end
|
|
40
35
|
|
|
@@ -69,13 +64,11 @@ module PWN
|
|
|
69
64
|
# Display Usage for this Module
|
|
70
65
|
|
|
71
66
|
public_class_method def self.help
|
|
72
|
-
puts "USAGE:
|
|
67
|
+
puts "USAGE (ruby-native detector, no external binaries):
|
|
73
68
|
#{self}.decode(
|
|
74
69
|
freq_obj: 'required - freq_obj returned from PWN::SDR::GQRX.init_freq'
|
|
75
70
|
)
|
|
76
71
|
|
|
77
|
-
NOTE: Requires `rtl_433` >= 23.x (native LoRa demod). Owns SDR.
|
|
78
|
-
|
|
79
72
|
#{self}.parse_line(line: '{\"model\":\"LoRa\",\"sf\":7,\"bw\":125,...}')
|
|
80
73
|
|
|
81
74
|
#{self}.authors
|
data/lib/pwn/sdr/decoder/lte.rb
CHANGED
|
@@ -1,16 +1,14 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require 'shellwords'
|
|
4
|
-
|
|
5
3
|
module PWN
|
|
6
4
|
module SDR
|
|
7
5
|
module Decoder
|
|
8
|
-
# LTE / UMTS / CDMA
|
|
6
|
+
# Pure-Ruby LTE / UMTS / CDMA cellular activity detector.
|
|
9
7
|
#
|
|
10
|
-
# 1.4–20 MHz OFDMA/WCDMA
|
|
11
|
-
#
|
|
12
|
-
#
|
|
13
|
-
#
|
|
8
|
+
# 1.4–20 MHz OFDMA/WCDMA is far beyond a 48 kHz audio tap and beyond
|
|
9
|
+
# interpreted Ruby's real-time I/Q throughput. Native mode reports
|
|
10
|
+
# channel occupancy / power only. `parse_line` retained for offline
|
|
11
|
+
# cell_search / CellSearch text analysis.
|
|
14
12
|
module LTE
|
|
15
13
|
# Supported Method Parameters::
|
|
16
14
|
# PWN::SDR::Decoder::LTE.decode(
|
|
@@ -19,33 +17,17 @@ module PWN
|
|
|
19
17
|
|
|
20
18
|
public_class_method def self.decode(opts = {})
|
|
21
19
|
freq_obj = opts[:freq_obj]
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
hz = PWN::SDR.hz_to_i(freq: freq_obj[:freq])
|
|
25
|
-
gain = (freq_obj[:rf_gain] || 40).to_s.to_f
|
|
26
|
-
sdr_args = (freq_obj[:sdr_args] || 'driver=rtlsdr').to_s
|
|
27
|
-
|
|
28
|
-
direct_cmd =
|
|
29
|
-
if PWN::SDR::Decoder::Base.bin_available?(bin: 'cell_search')
|
|
30
|
-
# srsRAN cell_search takes a band number; fall back to explicit
|
|
31
|
-
# DL frequency via -s/-e range when unavailable.
|
|
32
|
-
"cell_search -a #{Shellwords.escape(sdr_args)} -g #{gain} -s #{hz} -e #{hz}"
|
|
33
|
-
else
|
|
34
|
-
"CellSearch -s #{hz / 1_000_000.0} -e #{hz / 1_000_000.0} -g #{gain}"
|
|
35
|
-
end
|
|
36
|
-
|
|
37
|
-
PWN::SDR::Decoder::Base.run_pipeline(
|
|
20
|
+
PWN::SDR::Decoder::Base.run_detector(
|
|
38
21
|
freq_obj: freq_obj,
|
|
39
22
|
protocol: 'LTE',
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
parser: proc { |line| parse_line(line: line) }
|
|
23
|
+
note: 'OFDMA (1.4–20 MHz, 15 kHz subcarriers) — native mode reports channel occupancy only.',
|
|
24
|
+
threshold: 4.0,
|
|
25
|
+
describe: proc { |_b| { modulation: 'OFDMA', subcarrier_khz: 15 } }
|
|
44
26
|
)
|
|
45
27
|
end
|
|
46
28
|
|
|
47
29
|
# Supported Method Parameters::
|
|
48
|
-
# PWN::SDR::Decoder::LTE.parse_line(line: 'Found CELL ... PCI: 123 ...
|
|
30
|
+
# PWN::SDR::Decoder::LTE.parse_line(line: 'Found CELL ... PCI: 123 ...')
|
|
49
31
|
|
|
50
32
|
public_class_method def self.parse_line(opts = {})
|
|
51
33
|
line = opts[:line].to_s
|
|
@@ -69,14 +51,11 @@ module PWN
|
|
|
69
51
|
# Display Usage for this Module
|
|
70
52
|
|
|
71
53
|
public_class_method def self.help
|
|
72
|
-
puts "USAGE:
|
|
54
|
+
puts "USAGE (ruby-native detector, no external binaries):
|
|
73
55
|
#{self}.decode(
|
|
74
56
|
freq_obj: 'required - freq_obj returned from PWN::SDR::GQRX.init_freq'
|
|
75
57
|
)
|
|
76
58
|
|
|
77
|
-
NOTE: Requires srsRAN `cell_search` (or LTE-Cell-Scanner
|
|
78
|
-
`CellSearch`). Owns the SDR — set freq_obj[:sdr_args].
|
|
79
|
-
|
|
80
59
|
#{self}.parse_line(line: 'Found CELL 739.0 MHz, EARFCN=5110, PCI=123, 50 PRB, -85.2 dBm')
|
|
81
60
|
|
|
82
61
|
#{self}.authors
|
|
@@ -3,11 +3,93 @@
|
|
|
3
3
|
module PWN
|
|
4
4
|
module SDR
|
|
5
5
|
module Decoder
|
|
6
|
-
# CW / Morse decoder for the amateur CW sub-bands
|
|
7
|
-
#
|
|
8
|
-
#
|
|
9
|
-
#
|
|
6
|
+
# Pure-Ruby CW / Morse decoder for the amateur CW sub-bands.
|
|
7
|
+
#
|
|
8
|
+
# GQRX's CW/USB demodulator produces a ~600–800 Hz sidetone in the
|
|
9
|
+
# 48 kHz UDP audio stream. This module envelope-detects the tone
|
|
10
|
+
# with a state-preserving single-pole low-pass, adaptively
|
|
11
|
+
# thresholds it into on/off runs, classifies each run as dit / dah
|
|
12
|
+
# / char-gap / word-gap by timing, and looks the resulting `.-`
|
|
13
|
+
# sequences up in DSP::MORSE_TABLE. No `multimon-ng`, no `sox`.
|
|
10
14
|
module Morse
|
|
15
|
+
# Stateful, streaming CW demodulator fed by Base.run_native.
|
|
16
|
+
class Demod
|
|
17
|
+
def initialize(rate: 48_000)
|
|
18
|
+
@rate = rate
|
|
19
|
+
# single-pole low-pass on |x|; τ ≈ 5 ms
|
|
20
|
+
@env_a = Math.exp(-1.0 / (rate * 0.005))
|
|
21
|
+
@env = 0.0
|
|
22
|
+
@floor = 0.0
|
|
23
|
+
@peak = 0.0
|
|
24
|
+
@state = :off
|
|
25
|
+
@run = 0
|
|
26
|
+
@dit = rate * 60 / 1000 # seed 60 ms (≈ 20 WPM)
|
|
27
|
+
@sym = +''
|
|
28
|
+
@word = +''
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def feed(samples, &)
|
|
32
|
+
samples.each do |x|
|
|
33
|
+
@env = (@env_a * @env) + ((1.0 - @env_a) * x.abs)
|
|
34
|
+
# slow trackers for adaptive threshold (attack fast, decay slow)
|
|
35
|
+
@peak = @env > @peak ? ((@peak * 0.7) + (@env * 0.3)) : (@peak * 0.9999)
|
|
36
|
+
@floor = @env < @floor ? @env : ((@floor * 0.9999) + (@env * 0.0001))
|
|
37
|
+
thresh = @floor + ((@peak - @floor) * 0.4)
|
|
38
|
+
on = @env > thresh && (@peak - @floor) > 0.01
|
|
39
|
+
if on == (@state == :on)
|
|
40
|
+
@run += 1
|
|
41
|
+
else
|
|
42
|
+
classify_run(&)
|
|
43
|
+
@state = on ? :on : :off
|
|
44
|
+
@run = 1
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
return unless @state == :off && @run > 5 * @dit && !(@sym.empty? && @word.empty?)
|
|
48
|
+
|
|
49
|
+
flush_char
|
|
50
|
+
flush_word(&)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
private
|
|
54
|
+
|
|
55
|
+
def classify_run(&)
|
|
56
|
+
return if @run < @rate / 500 # <2 ms glitch
|
|
57
|
+
|
|
58
|
+
if @state == :on
|
|
59
|
+
@dit = ((@dit * 3) + @run) / 4 if @run < @dit * 1.5 && @run > @rate / 200
|
|
60
|
+
@sym << (@run > 2 * @dit ? '-' : '.')
|
|
61
|
+
else
|
|
62
|
+
return if @sym.empty? && @word.empty?
|
|
63
|
+
|
|
64
|
+
if @run > 5 * @dit
|
|
65
|
+
flush_char
|
|
66
|
+
flush_word(&)
|
|
67
|
+
elsif @run > 2 * @dit
|
|
68
|
+
flush_char
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def flush_char
|
|
74
|
+
return if @sym.empty?
|
|
75
|
+
|
|
76
|
+
@word << (PWN::SDR::Decoder::DSP::MORSE_TABLE[@sym] || '_')
|
|
77
|
+
@sym = +''
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def flush_word
|
|
81
|
+
return if @word.empty?
|
|
82
|
+
|
|
83
|
+
out = { protocol: 'MORSE-CW', text: @word.dup }
|
|
84
|
+
if (m = @word.match(/\b([A-Z0-9]{1,3}\d[A-Z]{1,4})\b/))
|
|
85
|
+
out[:callsign] = m[1]
|
|
86
|
+
end
|
|
87
|
+
out[:summary] = "CW #{@word}"[0, 120]
|
|
88
|
+
yield out
|
|
89
|
+
@word = +''
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
11
93
|
# Supported Method Parameters::
|
|
12
94
|
# PWN::SDR::Decoder::Morse.decode(
|
|
13
95
|
# freq_obj: 'required - freq_obj returned from PWN::SDR::GQRX.init_freq'
|
|
@@ -15,28 +97,20 @@ module PWN
|
|
|
15
97
|
|
|
16
98
|
public_class_method def self.decode(opts = {})
|
|
17
99
|
freq_obj = opts[:freq_obj]
|
|
18
|
-
|
|
19
|
-
PWN::SDR::Decoder::Base.run_pipeline(
|
|
100
|
+
PWN::SDR::Decoder::Base.run_native(
|
|
20
101
|
freq_obj: freq_obj,
|
|
21
102
|
protocol: 'MORSE-CW',
|
|
22
|
-
|
|
23
|
-
decode_cmd: 'multimon-ng -q -t raw -a MORSE_CW -',
|
|
24
|
-
line_match: /\S/,
|
|
25
|
-
parser: proc { |line| parse_line(line: line) }
|
|
103
|
+
demod: Demod.new
|
|
26
104
|
)
|
|
27
105
|
end
|
|
28
106
|
|
|
29
107
|
# Supported Method Parameters::
|
|
30
|
-
# PWN::SDR::Decoder::Morse.
|
|
108
|
+
# h = PWN::SDR::Decoder::Morse.decode_string(pattern: '.- -...')
|
|
31
109
|
|
|
32
|
-
public_class_method def self.
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
out[:callsign] = m[1]
|
|
37
|
-
end
|
|
38
|
-
out[:summary] = "CW #{line}"[0, 120]
|
|
39
|
-
out
|
|
110
|
+
public_class_method def self.decode_string(opts = {})
|
|
111
|
+
pattern = opts[:pattern].to_s
|
|
112
|
+
txt = pattern.strip.split(/\s+/).map { |s| PWN::SDR::Decoder::DSP::MORSE_TABLE[s] || '_' }.join
|
|
113
|
+
{ protocol: 'MORSE-CW', text: txt, summary: "CW #{txt}" }
|
|
40
114
|
end
|
|
41
115
|
|
|
42
116
|
# Author(s):: 0day Inc. <support@0dayinc.com>
|
|
@@ -48,12 +122,12 @@ module PWN
|
|
|
48
122
|
# Display Usage for this Module
|
|
49
123
|
|
|
50
124
|
public_class_method def self.help
|
|
51
|
-
puts "USAGE:
|
|
125
|
+
puts "USAGE (ruby-native, no external binaries):
|
|
52
126
|
#{self}.decode(
|
|
53
127
|
freq_obj: 'required - freq_obj returned from PWN::SDR::GQRX.init_freq'
|
|
54
128
|
)
|
|
55
129
|
|
|
56
|
-
#{self}.
|
|
130
|
+
#{self}.decode_string(pattern: '.- -...')
|
|
57
131
|
|
|
58
132
|
#{self}.authors
|
|
59
133
|
"
|
data/lib/pwn/sdr/decoder/p25.rb
CHANGED
|
@@ -3,10 +3,14 @@
|
|
|
3
3
|
module PWN
|
|
4
4
|
module SDR
|
|
5
5
|
module Decoder
|
|
6
|
-
# APCO Project 25
|
|
7
|
-
#
|
|
8
|
-
#
|
|
9
|
-
#
|
|
6
|
+
# Pure-Ruby APCO Project 25 Phase-1 C4FM activity detector.
|
|
7
|
+
#
|
|
8
|
+
# P25 is 4800 sym/s 4-level FSK with 1/2-rate trellis + RS + IMBE
|
|
9
|
+
# vocoder. Full symbol recovery, deinterleave, FEC and voice decode
|
|
10
|
+
# is out of scope for a portable pure-Ruby implementation; this
|
|
11
|
+
# module instead characterises C4FM keying activity (talkgroup key-
|
|
12
|
+
# ups, control-channel duty cycle) natively via Base.run_detector.
|
|
13
|
+
# `parse_line` is retained for offline dsd-format log analysis.
|
|
10
14
|
module P25
|
|
11
15
|
# Supported Method Parameters::
|
|
12
16
|
# PWN::SDR::Decoder::P25.decode(
|
|
@@ -15,17 +19,18 @@ module PWN
|
|
|
15
19
|
|
|
16
20
|
public_class_method def self.decode(opts = {})
|
|
17
21
|
freq_obj = opts[:freq_obj]
|
|
18
|
-
|
|
19
|
-
# dsd expects 48 kHz s16le on stdin — bypass Base's 22 050 Hz resample
|
|
20
|
-
# by asking for 48 000 (sox becomes a passthrough / format guard).
|
|
21
|
-
PWN::SDR::Decoder::Base.run_pipeline(
|
|
22
|
+
PWN::SDR::Decoder::Base.run_detector(
|
|
22
23
|
freq_obj: freq_obj,
|
|
23
24
|
protocol: 'P25',
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
25
|
+
note: 'C4FM 4-FSK + trellis/RS/IMBE — native mode reports key-up bursts (duration/peak/duty) only. Feed captured dsd text to .parse_line for NAC/TG/RID.',
|
|
26
|
+
threshold: 6.0,
|
|
27
|
+
describe: proc { |b|
|
|
28
|
+
kind = if b[:duration_ms] > 1500 then 'voice-superframe'
|
|
29
|
+
elsif b[:duration_ms] > 150 then 'LDU/HDU'
|
|
30
|
+
else 'TSBK/control'
|
|
31
|
+
end
|
|
32
|
+
{ modulation: 'C4FM', symbol_rate: 4800, classification: kind }
|
|
33
|
+
}
|
|
29
34
|
)
|
|
30
35
|
end
|
|
31
36
|
|
|
@@ -53,13 +58,11 @@ module PWN
|
|
|
53
58
|
# Display Usage for this Module
|
|
54
59
|
|
|
55
60
|
public_class_method def self.help
|
|
56
|
-
puts "USAGE:
|
|
61
|
+
puts "USAGE (ruby-native detector, no external binaries):
|
|
57
62
|
#{self}.decode(
|
|
58
63
|
freq_obj: 'required - freq_obj returned from PWN::SDR::GQRX.init_freq'
|
|
59
64
|
)
|
|
60
65
|
|
|
61
|
-
NOTE: Requires `dsd`. Set GQRX demod to Narrow FM, ~12.5 kHz.
|
|
62
|
-
|
|
63
66
|
#{self}.parse_line(line: 'Sync: +P25p1 NAC: 293 src: 1234 tg: 5678')
|
|
64
67
|
|
|
65
68
|
#{self}.authors
|
|
@@ -3,12 +3,24 @@
|
|
|
3
3
|
module PWN
|
|
4
4
|
module SDR
|
|
5
5
|
module Decoder
|
|
6
|
-
#
|
|
7
|
-
#
|
|
8
|
-
#
|
|
9
|
-
#
|
|
10
|
-
# per-protocol parser (::Flex-style or ::POCSAG.parse_line).
|
|
6
|
+
# Pure-Ruby combined pager decoder for the mixed-protocol `pager_all`
|
|
7
|
+
# band plan. Feeds every incoming 48 kHz audio chunk to BOTH the
|
|
8
|
+
# native POCSAG and FLEX demodulators concurrently; whichever locks
|
|
9
|
+
# emits messages. No `multimon-ng`, no `sox`.
|
|
11
10
|
module Pager
|
|
11
|
+
# Composite demodulator wrapping POCSAG::Demod + Flex::Demod.
|
|
12
|
+
class Demod
|
|
13
|
+
def initialize(rate: 48_000)
|
|
14
|
+
@pocsag = PWN::SDR::Decoder::POCSAG::Demod.new(rate: rate)
|
|
15
|
+
@flex = PWN::SDR::Decoder::Flex::Demod.new(rate: rate)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def feed(samples, &)
|
|
19
|
+
@pocsag.feed(samples.dup, &)
|
|
20
|
+
@flex.feed(samples, &)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
12
24
|
# Supported Method Parameters::
|
|
13
25
|
# PWN::SDR::Decoder::Pager.decode(
|
|
14
26
|
# freq_obj: 'required - freq_obj returned from PWN::SDR::GQRX.init_freq'
|
|
@@ -16,42 +28,13 @@ module PWN
|
|
|
16
28
|
|
|
17
29
|
public_class_method def self.decode(opts = {})
|
|
18
30
|
freq_obj = opts[:freq_obj]
|
|
19
|
-
|
|
20
|
-
decode_cmd = 'multimon-ng -q -t raw -e -u -p ' \
|
|
21
|
-
'-a FLEX -a FLEX_NEXT ' \
|
|
22
|
-
'-a POCSAG512 -a POCSAG1200 -a POCSAG2400 -'
|
|
23
|
-
|
|
24
|
-
PWN::SDR::Decoder::Base.run_pipeline(
|
|
31
|
+
PWN::SDR::Decoder::Base.run_native(
|
|
25
32
|
freq_obj: freq_obj,
|
|
26
33
|
protocol: 'PAGER',
|
|
27
|
-
|
|
28
|
-
decode_cmd: decode_cmd,
|
|
29
|
-
line_match: /^(FLEX|POCSAG)/,
|
|
30
|
-
parser: proc { |line| parse_line(line: line) }
|
|
34
|
+
demod: Demod.new
|
|
31
35
|
)
|
|
32
36
|
end
|
|
33
37
|
|
|
34
|
-
# Supported Method Parameters::
|
|
35
|
-
# PWN::SDR::Decoder::Pager.parse_line(line: 'FLEX|... or POCSAG1200: ...')
|
|
36
|
-
|
|
37
|
-
public_class_method def self.parse_line(opts = {})
|
|
38
|
-
line = opts[:line].to_s
|
|
39
|
-
return PWN::SDR::Decoder::POCSAG.parse_line(line: line) if line.start_with?('POCSAG')
|
|
40
|
-
|
|
41
|
-
# FLEX / FLEX_NEXT — pipe- or space-delimited (see ::Flex for format).
|
|
42
|
-
delim = line.start_with?('FLEX: ') ? ' ' : '|'
|
|
43
|
-
parts = line.split(delim)
|
|
44
|
-
proto = line.start_with?('FLEX_NEXT') ? 'FLEX_NEXT' : 'FLEX'
|
|
45
|
-
types = %w[ALN BIN HEX NUM TON TONE UNK]
|
|
46
|
-
t_idx = parts.index { |p| types.include?(p) }
|
|
47
|
-
out = { protocol: proto, raw_inspected: line.inspect }
|
|
48
|
-
out[:capcode] = parts.find { |p| p.match?(/^\[?\d{7,10}\]?$/) }
|
|
49
|
-
out[:type] = t_idx ? parts[t_idx] : nil
|
|
50
|
-
out[:type_payload] = t_idx ? parts[(t_idx + 1)..].join(delim) : nil
|
|
51
|
-
out[:summary] = "#{proto} RIC=#{out[:capcode]} #{out[:type]}: #{out[:type_payload]}"
|
|
52
|
-
out.compact
|
|
53
|
-
end
|
|
54
|
-
|
|
55
38
|
# Author(s):: 0day Inc. <support@0dayinc.com>
|
|
56
39
|
|
|
57
40
|
public_class_method def self.authors
|
|
@@ -61,12 +44,13 @@ module PWN
|
|
|
61
44
|
# Display Usage for this Module
|
|
62
45
|
|
|
63
46
|
public_class_method def self.help
|
|
64
|
-
puts "USAGE:
|
|
47
|
+
puts "USAGE (ruby-native, no external binaries):
|
|
65
48
|
#{self}.decode(
|
|
66
49
|
freq_obj: 'required - freq_obj returned from PWN::SDR::GQRX.init_freq'
|
|
67
50
|
)
|
|
68
51
|
|
|
69
|
-
|
|
52
|
+
NOTE: Runs the native POCSAG (512/1200/2400) and FLEX (1600)
|
|
53
|
+
demodulators in parallel on the same GQRX NBFM audio tap.
|
|
70
54
|
|
|
71
55
|
#{self}.authors
|
|
72
56
|
"
|