Module: PWN::SDR::Decoder::Flex
- Defined in:
- lib/pwn/sdr/decoder/flex.rb
Overview
Pure-Ruby FLEX™ pager decoder.
FLEX is Motorola's synchronous paging protocol running at 1600,
3200 or 6400 bps in 2- or 4-level FSK. GQRX's NBFM discriminator
audio (48 kHz UDP tap) is NRZ-sliced at 1600 baud (2-FSK mode),
locked onto the Sync-1 A-word (bit-sync 0xAAAA + A1 = 0x78F3), then
each 1.875 s frame's 88 × 32-bit BCH(31,21) codewords are walked to
recover Frame-Info, Address and Vector/Message words. Alphanumeric
(7-bit ASCII, 3 chars per 21-bit payload) and numeric (BCD) message
bodies are extracted. No multimon-ng, no sox — 100 % Ruby.
Limitation: 3200/6400 bps 4-FSK modes require raw discriminator amplitude quantisation into four levels; only frame/cycle/capcode metadata (not message body) is emitted for those speeds.
Defined Under Namespace
Classes: Demod
Constant Summary collapse
- BS1 =
0xAAAA- A1 =
1600 / 2-FSK Sync-1 A-word
0x78F3- A_TABLE =
{ 0x870C => [1600, 2], 0x78F3 => [1600, 2], 0xB068 => [3200, 2], 0x4F97 => [3200, 2], 0x7B18 => [3200, 4], 0x84E7 => [3200, 4], 0xDEA0 => [6400, 4], 0x215F => [6400, 4] }.freeze
- NUM_TABLE =
['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '/', 'U', ' ', '-', ']', '['].freeze
Class Method Summary collapse
-
.alpha_decode(opts = {}) ⇒ Object
- Supported Method Parameters
str = PWN::SDR::Decoder::Flex.alpha_decode(words: [Integer, ...]).
-
.authors ⇒ Object
- Author(s)
0day Inc.
-
.decode(opts = {}) ⇒ Object
- Supported Method Parameters
PWN::SDR::Decoder::Flex.decode( freq_obj: 'required - freq_obj returned from PWN::SDR::GQRX.init_freq' ).
-
.decode_bits(opts = {}) ⇒ Object
- Supported Method Parameters
carry = PWN::SDR::Decoder::Flex.decode_bits(bits: [...]) { |msg| ... }.
-
.emit_frame(opts = {}) ⇒ Object
- Supported Method Parameters
PWN::SDR::Decoder::Flex.emit_frame(words:, cycle:, frame:, speed:) { |msg| ... }.
-
.help ⇒ Object
Display Usage for this Module.
-
.hex_decode(opts = {}) ⇒ Object
- Supported Method Parameters
str = PWN::SDR::Decoder::Flex.hex_decode(words: [Integer, ...]).
-
.numeric_decode(opts = {}) ⇒ Object
- Supported Method Parameters
str = PWN::SDR::Decoder::Flex.numeric_decode(words: [Integer, ...]).
Class Method Details
.alpha_decode(opts = {}) ⇒ Object
- Supported Method Parameters
str = PWN::SDR::Decoder::Flex.alpha_decode(words: [Integer, ...])
147 148 149 150 151 152 153 154 155 156 157 158 159 160 |
# File 'lib/pwn/sdr/decoder/flex.rb', line 147 public_class_method def self.alpha_decode(opts = {}) words = opts[:words] || [] out = +'' words.each do |w| payload = (w >> 11) & 0x1FFFFF 3.times do |c| ch = (payload >> (c * 7)) & 0x7F next if ch < 0x20 || ch == 0x7F out << ch.chr end end out.strip end |
.authors ⇒ Object
- Author(s)
0day Inc. support@0dayinc.com
199 200 201 |
# File 'lib/pwn/sdr/decoder/flex.rb', line 199 public_class_method def self. "AUTHOR(S):\n 0day Inc. <support@0dayinc.com>\n" end |
.decode(opts = {}) ⇒ Object
- Supported Method Parameters
PWN::SDR::Decoder::Flex.decode( freq_obj: 'required - freq_obj returned from PWN::SDR::GQRX.init_freq' )
188 189 190 191 192 193 194 195 |
# File 'lib/pwn/sdr/decoder/flex.rb', line 188 public_class_method def self.decode(opts = {}) freq_obj = opts[:freq_obj] PWN::SDR::Decoder::Base.run_native( freq_obj: freq_obj, protocol: 'FLEX', demod: Demod.new ) end |
.decode_bits(opts = {}) ⇒ Object
- Supported Method Parameters
carry = PWN::SDR::Decoder::Flex.decode_bits(bits: [...]) { |msg| ... }
68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 |
# File 'lib/pwn/sdr/decoder/flex.rb', line 68 public_class_method def self.decode_bits(opts = {}) bits = opts[:bits] || [] i = 0 loop do # Sync-1 = 16-bit BS1 + 16-bit A + 16-bit B (=~A) + 16-bit ~A idx = PWN::SDR::Decoder::DSP.find_sync(bits: bits, pattern: BS1, width: 16, max_err: 1, from: i) break unless idx break if idx + 144 > bits.length a_word = PWN::SDR::Decoder::DSP.bits_to_int(bits: bits[idx + 16, 16]) speed = A_TABLE[a_word] unless speed i = idx + 1 next end # Frame Info Word follows Sync-1 (32-bit BCH codeword) fiw = PWN::SDR::Decoder::DSP.bits_to_int(bits: bits[idx + 64, 32]) cycle = (fiw >> 4) & 0x0F frame = (fiw >> 8) & 0x7F block0 = idx + 64 + 32 + 16 # after FIW + Sync-2 marker frame_bits = 1600 * 2 # ≈ one FLEX frame @ 1600 bps (upper bound) avail = [bits.length - block0, frame_bits].min words = [] (avail / 32).times do |w| words << PWN::SDR::Decoder::DSP.bits_to_int(bits: bits[block0 + (w * 32), 32]) end emit_frame(words: words, cycle: cycle, frame: frame, speed: speed) { |m| yield m if block_given? } i = block0 + avail end tail_from = [bits.length - 3200, 0].max bits[tail_from..] || [] end |
.emit_frame(opts = {}) ⇒ Object
- Supported Method Parameters
PWN::SDR::Decoder::Flex.emit_frame(words:, cycle:, frame:, speed:) { |msg| ... }
104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 |
# File 'lib/pwn/sdr/decoder/flex.rb', line 104 public_class_method def self.emit_frame(opts = {}) words = opts[:words] || [] cycle = opts[:cycle] frame = opts[:frame] speed = opts[:speed] || [1600, 2] return if words.empty? # Block Info Word (word 0): a = address-start, v = vector-start biw = words[0] a_start = (biw >> 8) & 0x3F v_start = (biw >> 2) & 0x3F v_start = a_start + 1 if v_start <= a_start addr_words = words[a_start...v_start] || [] addr_words.each_with_index do |aw, k| next if aw.nil? || aw.zero? capcode = (aw >> 11) & 0x1FFFFF vec = words[v_start + k] type = vec ? ((vec >> 4) & 0x7) : nil mstart = vec ? ((vec >> 7) & 0x7F) : nil mlen = vec ? ((vec >> 14) & 0x7F) : 0 mwords = mstart && mlen.positive? ? (words[mstart, mlen] || []) : [] body, tname = case type when 5 then [alpha_decode(words: mwords), 'ALN'] when 3 then [numeric_decode(words: mwords), 'NUM'] when 0, nil then [nil, 'TON'] else [hex_decode(words: mwords), 'BIN'] end out = { protocol: 'FLEX', speed: "#{speed[0]}/#{speed[1]}", cycle: cycle, frame: frame, capcode: capcode.to_s.rjust(9, '0'), type: tname, type_payload: body }.compact out[:summary] = "FLEX RIC=#{out[:capcode]} #{tname}: #{body}"[0, 160] yield out if block_given? end end |
.help ⇒ Object
Display Usage for this Module
205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 |
# File 'lib/pwn/sdr/decoder/flex.rb', line 205 public_class_method def self.help puts "USAGE (ruby-native, no external binaries): #{self}.decode( freq_obj: 'required - freq_obj returned from PWN::SDR::GQRX.init_freq' ) #{self}.decode_bits(bits: [0,1,...]) { |msg| ... } #{self}.alpha_decode(words: [Integer, ...]) #{self}.numeric_decode(words: [Integer, ...]) NOTE: Set GQRX to Narrow FM (~15 kHz). 1600 bps 2-FSK is fully decoded; 3200/6400 4-FSK emit frame/capcode metadata only. #{self}.authors " end |
.hex_decode(opts = {}) ⇒ Object
- Supported Method Parameters
str = PWN::SDR::Decoder::Flex.hex_decode(words: [Integer, ...])
178 179 180 181 |
# File 'lib/pwn/sdr/decoder/flex.rb', line 178 public_class_method def self.hex_decode(opts = {}) words = opts[:words] || [] words.map { |w| format('%08X', w & 0xFFFFFFFF) }.join(' ') end |
.numeric_decode(opts = {}) ⇒ Object
- Supported Method Parameters
str = PWN::SDR::Decoder::Flex.numeric_decode(words: [Integer, ...])
165 166 167 168 169 170 171 172 173 |
# File 'lib/pwn/sdr/decoder/flex.rb', line 165 public_class_method def self.numeric_decode(opts = {}) words = opts[:words] || [] out = +'' words.each do |w| payload = (w >> 11) & 0x1FFFFF 5.times { |d| out << NUM_TABLE[(payload >> (d * 4)) & 0xF] } end out.strip end |