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
|
@@ -0,0 +1,396 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module PWN
|
|
4
|
+
module SDR
|
|
5
|
+
module Decoder
|
|
6
|
+
# Pure-Ruby DSP primitives shared by every PWN::SDR::Decoder::* module.
|
|
7
|
+
#
|
|
8
|
+
# Nothing in here shells out. Everything operates on plain Ruby Arrays
|
|
9
|
+
# of Float samples (normalised -1.0..1.0), so decoders can consume the
|
|
10
|
+
# 48 kHz s16le mono audio that PWN::SDR::GQRX streams over UDP without
|
|
11
|
+
# any external `sox` / `multimon-ng` / `minimodem` / etc dependency.
|
|
12
|
+
#
|
|
13
|
+
# These are intentionally simple, readable, allocation-heavy reference
|
|
14
|
+
# implementations — good enough for ≤48 kHz audio-rate work on a modern
|
|
15
|
+
# CPU. For MHz-rate raw I/Q you would want SIMD/native code, which is
|
|
16
|
+
# out of scope for a pure-Ruby decoder namespace.
|
|
17
|
+
module DSP
|
|
18
|
+
TWO_PI = Math::PI * 2
|
|
19
|
+
|
|
20
|
+
# ITA2 / Baudot 5-bit → ASCII (LTRS + FIGS shift tables). Index = code.
|
|
21
|
+
BAUDOT_LTRS = [
|
|
22
|
+
"\0", 'E', "\n", 'A', ' ', 'S', 'I', 'U',
|
|
23
|
+
"\r", 'D', 'R', 'J', 'N', 'F', 'C', 'K',
|
|
24
|
+
'T', 'Z', 'L', 'W', 'H', 'Y', 'P', 'Q',
|
|
25
|
+
'O', 'B', 'G', nil, 'M', 'X', 'V', nil
|
|
26
|
+
].freeze
|
|
27
|
+
|
|
28
|
+
BAUDOT_FIGS = [
|
|
29
|
+
"\0", '3', "\n", '-', ' ', "'", '8', '7',
|
|
30
|
+
"\r", '$', '4', "\a", ',', '!', ':', '(',
|
|
31
|
+
'5', '+', ')', '2', '#', '6', '0', '1',
|
|
32
|
+
'9', '?', '&', nil, '.', '/', ';', nil
|
|
33
|
+
].freeze
|
|
34
|
+
|
|
35
|
+
# International Morse Code (dit='.', dah='-') → ASCII.
|
|
36
|
+
MORSE_TABLE = {
|
|
37
|
+
'.-' => 'A', '-...' => 'B', '-.-.' => 'C', '-..' => 'D', '.' => 'E',
|
|
38
|
+
'..-.' => 'F', '--.' => 'G', '....' => 'H', '..' => 'I', '.---' => 'J',
|
|
39
|
+
'-.-' => 'K', '.-..' => 'L', '--' => 'M', '-.' => 'N', '---' => 'O',
|
|
40
|
+
'.--.' => 'P', '--.-' => 'Q', '.-.' => 'R', '...' => 'S', '-' => 'T',
|
|
41
|
+
'..-' => 'U', '...-' => 'V', '.--' => 'W', '-..-' => 'X', '-.--' => 'Y',
|
|
42
|
+
'--..' => 'Z', '-----' => '0', '.----' => '1', '..---' => '2',
|
|
43
|
+
'...--' => '3', '....-' => '4', '.....' => '5', '-....' => '6',
|
|
44
|
+
'--...' => '7', '---..' => '8', '----.' => '9', '.-.-.-' => '.',
|
|
45
|
+
'--..--' => ',', '..--..' => '?', '-..-.' => '/', '-....-' => '-',
|
|
46
|
+
'-.--.' => '(', '-.--.-' => ')', '.-...' => '&', '---...' => ':',
|
|
47
|
+
'-...-' => '=', '.-.-.' => '+', '.--.-.' => '@'
|
|
48
|
+
}.freeze
|
|
49
|
+
|
|
50
|
+
# Supported Method Parameters::
|
|
51
|
+
# samples = PWN::SDR::Decoder::DSP.unpack_s16le(
|
|
52
|
+
# data: 'required - raw String of little-endian signed 16-bit PCM'
|
|
53
|
+
# )
|
|
54
|
+
|
|
55
|
+
public_class_method def self.unpack_s16le(opts = {})
|
|
56
|
+
data = opts[:data].to_s
|
|
57
|
+
norm = 1.0 / 32_768.0
|
|
58
|
+
data.unpack('s<*').map { |v| v * norm }
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Supported Method Parameters::
|
|
62
|
+
# out = PWN::SDR::Decoder::DSP.resample(
|
|
63
|
+
# samples: 'required - Array<Float>',
|
|
64
|
+
# src_rate: 'required - input sample rate (Hz)',
|
|
65
|
+
# dst_rate: 'required - output sample rate (Hz)'
|
|
66
|
+
# )
|
|
67
|
+
|
|
68
|
+
public_class_method def self.resample(opts = {})
|
|
69
|
+
samples = opts[:samples]
|
|
70
|
+
src_rate = opts[:src_rate].to_f
|
|
71
|
+
dst_rate = opts[:dst_rate].to_f
|
|
72
|
+
return samples.dup if (src_rate - dst_rate).abs < 1e-6
|
|
73
|
+
|
|
74
|
+
ratio = src_rate / dst_rate
|
|
75
|
+
out_len = (samples.length / ratio).floor
|
|
76
|
+
out = Array.new(out_len)
|
|
77
|
+
i = 0
|
|
78
|
+
while i < out_len
|
|
79
|
+
pos = i * ratio
|
|
80
|
+
idx = pos.floor
|
|
81
|
+
frac = pos - idx
|
|
82
|
+
a = samples[idx] || 0.0
|
|
83
|
+
b = samples[idx + 1] || a
|
|
84
|
+
out[i] = a + ((b - a) * frac)
|
|
85
|
+
i += 1
|
|
86
|
+
end
|
|
87
|
+
out
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# Supported Method Parameters::
|
|
91
|
+
# power = PWN::SDR::Decoder::DSP.goertzel(
|
|
92
|
+
# samples: 'required - Array<Float>',
|
|
93
|
+
# rate: 'required - sample rate (Hz)',
|
|
94
|
+
# freq: 'required - target tone frequency (Hz)'
|
|
95
|
+
# )
|
|
96
|
+
|
|
97
|
+
public_class_method def self.goertzel(opts = {})
|
|
98
|
+
samples = opts[:samples]
|
|
99
|
+
rate = opts[:rate].to_f
|
|
100
|
+
freq = opts[:freq].to_f
|
|
101
|
+
n = samples.length
|
|
102
|
+
return 0.0 if n.zero?
|
|
103
|
+
|
|
104
|
+
k = (0.5 + ((n * freq) / rate)).floor
|
|
105
|
+
w = (TWO_PI * k) / n
|
|
106
|
+
coeff = 2.0 * Math.cos(w)
|
|
107
|
+
s1 = 0.0
|
|
108
|
+
s2 = 0.0
|
|
109
|
+
samples.each do |x|
|
|
110
|
+
s0 = x + (coeff * s1) - s2
|
|
111
|
+
s2 = s1
|
|
112
|
+
s1 = s0
|
|
113
|
+
end
|
|
114
|
+
((s1 * s1) + (s2 * s2) - (coeff * s1 * s2)) / n
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# Supported Method Parameters::
|
|
118
|
+
# env = PWN::SDR::Decoder::DSP.envelope(
|
|
119
|
+
# samples: 'required - Array<Float>',
|
|
120
|
+
# window: 'optional - moving-average window in samples (default 32)'
|
|
121
|
+
# )
|
|
122
|
+
|
|
123
|
+
public_class_method def self.envelope(opts = {})
|
|
124
|
+
samples = opts[:samples]
|
|
125
|
+
window = (opts[:window] || 32).to_i
|
|
126
|
+
window = 1 if window < 1
|
|
127
|
+
acc = 0.0
|
|
128
|
+
buf = Array.new(window, 0.0)
|
|
129
|
+
out = Array.new(samples.length)
|
|
130
|
+
samples.each_with_index do |x, i|
|
|
131
|
+
v = x.abs
|
|
132
|
+
slot = i % window
|
|
133
|
+
acc += v - buf[slot]
|
|
134
|
+
buf[slot] = v
|
|
135
|
+
out[i] = acc / window
|
|
136
|
+
end
|
|
137
|
+
out
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
# Supported Method Parameters::
|
|
141
|
+
# y = PWN::SDR::Decoder::DSP.dc_block(
|
|
142
|
+
# samples: 'required - Array<Float>',
|
|
143
|
+
# alpha: 'optional - pole (default 0.995)'
|
|
144
|
+
# )
|
|
145
|
+
|
|
146
|
+
public_class_method def self.dc_block(opts = {})
|
|
147
|
+
samples = opts[:samples]
|
|
148
|
+
alpha = (opts[:alpha] || 0.995).to_f
|
|
149
|
+
y_prev = 0.0
|
|
150
|
+
x_prev = 0.0
|
|
151
|
+
samples.map do |x|
|
|
152
|
+
y = x - x_prev + (alpha * y_prev)
|
|
153
|
+
x_prev = x
|
|
154
|
+
y_prev = y
|
|
155
|
+
y
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
# Supported Method Parameters::
|
|
160
|
+
# bits = PWN::SDR::Decoder::DSP.nrz_slice(
|
|
161
|
+
# samples: 'required - Array<Float> (post-FM-discriminator baseband)',
|
|
162
|
+
# rate: 'required - sample rate (Hz)',
|
|
163
|
+
# baud: 'required - symbol rate',
|
|
164
|
+
# invert: 'optional - flip bit polarity (default false)'
|
|
165
|
+
# )
|
|
166
|
+
# Simple mid-bit sampler with zero-crossing resync. Returns Array<0|1>.
|
|
167
|
+
|
|
168
|
+
public_class_method def self.nrz_slice(opts = {})
|
|
169
|
+
samples = opts[:samples]
|
|
170
|
+
rate = opts[:rate].to_f
|
|
171
|
+
baud = opts[:baud].to_f
|
|
172
|
+
invert = opts[:invert]
|
|
173
|
+
spb = rate / baud
|
|
174
|
+
return [] if spb < 2.0 || samples.empty?
|
|
175
|
+
|
|
176
|
+
# DC-block then low-pass via short moving average (~1/4 symbol).
|
|
177
|
+
lp_win = [(spb / 4.0).round, 1].max
|
|
178
|
+
filt = envelope_signed(samples: dc_block(samples: samples), window: lp_win)
|
|
179
|
+
|
|
180
|
+
bits = []
|
|
181
|
+
phase = spb / 2.0
|
|
182
|
+
prev = filt.first.to_f
|
|
183
|
+
filt.each do |v|
|
|
184
|
+
# zero-crossing → resync to mid-symbol
|
|
185
|
+
phase = spb / 2.0 if (prev.negative? && v >= 0) || (prev.positive? && v.negative?)
|
|
186
|
+
phase -= 1.0
|
|
187
|
+
if phase <= 0
|
|
188
|
+
b = v.negative? ? 0 : 1
|
|
189
|
+
b ^= 1 if invert
|
|
190
|
+
bits << b
|
|
191
|
+
phase += spb
|
|
192
|
+
end
|
|
193
|
+
prev = v
|
|
194
|
+
end
|
|
195
|
+
bits
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
# Signed moving average (like envelope but keeps sign).
|
|
199
|
+
# Supported Method Parameters::
|
|
200
|
+
# y = PWN::SDR::Decoder::DSP.envelope_signed(samples:, window:)
|
|
201
|
+
|
|
202
|
+
public_class_method def self.envelope_signed(opts = {})
|
|
203
|
+
samples = opts[:samples]
|
|
204
|
+
window = (opts[:window] || 8).to_i
|
|
205
|
+
window = 1 if window < 1
|
|
206
|
+
acc = 0.0
|
|
207
|
+
buf = Array.new(window, 0.0)
|
|
208
|
+
out = Array.new(samples.length)
|
|
209
|
+
samples.each_with_index do |x, i|
|
|
210
|
+
slot = i % window
|
|
211
|
+
acc += x - buf[slot]
|
|
212
|
+
buf[slot] = x
|
|
213
|
+
out[i] = acc / window
|
|
214
|
+
end
|
|
215
|
+
out
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
# Supported Method Parameters::
|
|
219
|
+
# bits = PWN::SDR::Decoder::DSP.fsk_slice(
|
|
220
|
+
# samples: 'required - Array<Float>',
|
|
221
|
+
# rate: 'required - sample rate (Hz)',
|
|
222
|
+
# baud: 'required - symbol rate',
|
|
223
|
+
# mark_hz: 'required - mark tone (bit=1)',
|
|
224
|
+
# space_hz:'required - space tone (bit=0)'
|
|
225
|
+
# )
|
|
226
|
+
# Non-coherent 2-FSK: per-symbol Goertzel on mark/space, pick the winner.
|
|
227
|
+
|
|
228
|
+
public_class_method def self.fsk_slice(opts = {})
|
|
229
|
+
samples = opts[:samples]
|
|
230
|
+
rate = opts[:rate].to_f
|
|
231
|
+
baud = opts[:baud].to_f
|
|
232
|
+
mark_hz = opts[:mark_hz].to_f
|
|
233
|
+
space_hz = opts[:space_hz].to_f
|
|
234
|
+
spb = rate / baud
|
|
235
|
+
nsym = (samples.length / spb).floor
|
|
236
|
+
bits = Array.new(nsym)
|
|
237
|
+
i = 0
|
|
238
|
+
while i < nsym
|
|
239
|
+
a = (i * spb).floor
|
|
240
|
+
b = ((i + 1) * spb).floor
|
|
241
|
+
win = samples[a...b]
|
|
242
|
+
pm = goertzel(samples: win, rate: rate, freq: mark_hz)
|
|
243
|
+
ps = goertzel(samples: win, rate: rate, freq: space_hz)
|
|
244
|
+
bits[i] = pm >= ps ? 1 : 0
|
|
245
|
+
i += 1
|
|
246
|
+
end
|
|
247
|
+
bits
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
# Supported Method Parameters::
|
|
251
|
+
# idx = PWN::SDR::Decoder::DSP.find_sync(
|
|
252
|
+
# bits: 'required - Array<0|1>',
|
|
253
|
+
# pattern: 'required - Array<0|1> or Integer (MSB-first)',
|
|
254
|
+
# width: 'optional - bit-width when pattern is Integer',
|
|
255
|
+
# max_err: 'optional - allowed bit errors (default 0)',
|
|
256
|
+
# from: 'optional - start index (default 0)'
|
|
257
|
+
# )
|
|
258
|
+
|
|
259
|
+
public_class_method def self.find_sync(opts = {})
|
|
260
|
+
bits = opts[:bits]
|
|
261
|
+
pattern = opts[:pattern]
|
|
262
|
+
width = opts[:width]
|
|
263
|
+
max_err = (opts[:max_err] || 0).to_i
|
|
264
|
+
from = (opts[:from] || 0).to_i
|
|
265
|
+
pat = if pattern.is_a?(Integer)
|
|
266
|
+
w = width || pattern.bit_length
|
|
267
|
+
Array.new(w) { |i| (pattern >> (w - 1 - i)) & 1 }
|
|
268
|
+
else
|
|
269
|
+
pattern
|
|
270
|
+
end
|
|
271
|
+
plen = pat.length
|
|
272
|
+
upto = bits.length - plen
|
|
273
|
+
i = from
|
|
274
|
+
while i <= upto
|
|
275
|
+
err = 0
|
|
276
|
+
j = 0
|
|
277
|
+
while j < plen
|
|
278
|
+
err += 1 if bits[i + j] != pat[j]
|
|
279
|
+
break if err > max_err
|
|
280
|
+
|
|
281
|
+
j += 1
|
|
282
|
+
end
|
|
283
|
+
return i if err <= max_err
|
|
284
|
+
|
|
285
|
+
i += 1
|
|
286
|
+
end
|
|
287
|
+
nil
|
|
288
|
+
end
|
|
289
|
+
|
|
290
|
+
# Supported Method Parameters::
|
|
291
|
+
# int = PWN::SDR::Decoder::DSP.bits_to_int(bits: [1,0,1,...])
|
|
292
|
+
|
|
293
|
+
public_class_method def self.bits_to_int(opts = {})
|
|
294
|
+
bits = opts[:bits]
|
|
295
|
+
v = 0
|
|
296
|
+
bits.each { |b| v = (v << 1) | (b & 1) }
|
|
297
|
+
v
|
|
298
|
+
end
|
|
299
|
+
|
|
300
|
+
# Supported Method Parameters::
|
|
301
|
+
# ok = PWN::SDR::Decoder::DSP.even_parity_ok?(word: Integer, width: 32)
|
|
302
|
+
|
|
303
|
+
public_class_method def self.even_parity_ok?(opts = {})
|
|
304
|
+
word = opts[:word].to_i
|
|
305
|
+
width = (opts[:width] || 32).to_i
|
|
306
|
+
p = 0
|
|
307
|
+
width.times { |i| p ^= (word >> i) & 1 }
|
|
308
|
+
p.zero?
|
|
309
|
+
end
|
|
310
|
+
|
|
311
|
+
# BCH(31,21) syndrome — generator poly 0b11101101001 (0x769).
|
|
312
|
+
# Used by POCSAG and FLEX codewords (bits 31..1 are BCH, bit 0 parity).
|
|
313
|
+
# Supported Method Parameters::
|
|
314
|
+
# syn = PWN::SDR::Decoder::DSP.bch_31_21_syndrome(word: Integer)
|
|
315
|
+
|
|
316
|
+
public_class_method def self.bch_31_21_syndrome(opts = {})
|
|
317
|
+
word = (opts[:word].to_i >> 1) & 0x7FFFFFFF
|
|
318
|
+
gen = 0x769
|
|
319
|
+
reg = word
|
|
320
|
+
30.downto(10) do |i|
|
|
321
|
+
reg ^= (gen << (i - 10)) if (reg >> i).odd?
|
|
322
|
+
end
|
|
323
|
+
reg & 0x3FF
|
|
324
|
+
end
|
|
325
|
+
|
|
326
|
+
# Supported Method Parameters::
|
|
327
|
+
# txt = PWN::SDR::Decoder::DSP.baudot_decode(bits: Array<0|1>)
|
|
328
|
+
# 5-bit ITA2 with LTRS(31)/FIGS(27) shift, LSB-first per character.
|
|
329
|
+
|
|
330
|
+
public_class_method def self.baudot_decode(opts = {})
|
|
331
|
+
bits = opts[:bits]
|
|
332
|
+
figs = false
|
|
333
|
+
out = +''
|
|
334
|
+
bits.each_slice(5) do |ch|
|
|
335
|
+
next if ch.length < 5
|
|
336
|
+
|
|
337
|
+
code = ch.each_with_index.sum { |b, i| b << i }
|
|
338
|
+
case code
|
|
339
|
+
when 31 then figs = false
|
|
340
|
+
when 27 then figs = true
|
|
341
|
+
else
|
|
342
|
+
tbl = figs ? BAUDOT_FIGS : BAUDOT_LTRS
|
|
343
|
+
c = tbl[code]
|
|
344
|
+
out << c if c
|
|
345
|
+
end
|
|
346
|
+
end
|
|
347
|
+
out
|
|
348
|
+
end
|
|
349
|
+
|
|
350
|
+
# Supported Method Parameters::
|
|
351
|
+
# dbfs = PWN::SDR::Decoder::DSP.rms_dbfs(samples: Array<Float>)
|
|
352
|
+
|
|
353
|
+
public_class_method def self.rms_dbfs(opts = {})
|
|
354
|
+
samples = opts[:samples]
|
|
355
|
+
return -120.0 if samples.nil? || samples.empty?
|
|
356
|
+
|
|
357
|
+
ms = samples.sum { |v| v * v } / samples.length
|
|
358
|
+
return -120.0 if ms <= 0
|
|
359
|
+
|
|
360
|
+
10.0 * Math.log10(ms)
|
|
361
|
+
end
|
|
362
|
+
|
|
363
|
+
# Author(s):: 0day Inc. <support@0dayinc.com>
|
|
364
|
+
|
|
365
|
+
public_class_method def self.authors
|
|
366
|
+
"AUTHOR(S):\n 0day Inc. <support@0dayinc.com>\n"
|
|
367
|
+
end
|
|
368
|
+
|
|
369
|
+
# Display Usage for this Module
|
|
370
|
+
|
|
371
|
+
public_class_method def self.help
|
|
372
|
+
puts "USAGE (pure-Ruby DSP primitives — no external binaries):
|
|
373
|
+
#{self}.unpack_s16le(data: raw_bytes)
|
|
374
|
+
#{self}.resample(samples:, src_rate:, dst_rate:)
|
|
375
|
+
#{self}.goertzel(samples:, rate:, freq:)
|
|
376
|
+
#{self}.envelope(samples:, window: 32)
|
|
377
|
+
#{self}.envelope_signed(samples:, window: 8)
|
|
378
|
+
#{self}.dc_block(samples:, alpha: 0.995)
|
|
379
|
+
#{self}.nrz_slice(samples:, rate:, baud:, invert: false)
|
|
380
|
+
#{self}.fsk_slice(samples:, rate:, baud:, mark_hz:, space_hz:)
|
|
381
|
+
#{self}.find_sync(bits:, pattern:, width:, max_err: 0, from: 0)
|
|
382
|
+
#{self}.bits_to_int(bits:)
|
|
383
|
+
#{self}.even_parity_ok?(word:, width: 32)
|
|
384
|
+
#{self}.bch_31_21_syndrome(word:)
|
|
385
|
+
#{self}.baudot_decode(bits:)
|
|
386
|
+
#{self}.rms_dbfs(samples:)
|
|
387
|
+
|
|
388
|
+
Constants: MORSE_TABLE, BAUDOT_LTRS, BAUDOT_FIGS
|
|
389
|
+
|
|
390
|
+
#{self}.authors
|
|
391
|
+
"
|
|
392
|
+
end
|
|
393
|
+
end
|
|
394
|
+
end
|
|
395
|
+
end
|
|
396
|
+
end
|