Module: PWN::SDR::Decoder::Iridium

Defined in:
lib/pwn/sdr/decoder/iridium.rb

Overview

IRA protocol decoding from pre-synchronized symbol IQ, plus separate wideband energy observations (.detect). General raw-IQ acquisition and non-IRA message families are not implemented. See the independent RF vectors and BSD protocol-source notice in spec/fixtures/sdr/iridium/.

Defined Under Namespace

Classes: DemodIQ, RingAlert, RingAlertSymbolsIQ

Class Method Summary collapse

Class Method Details

.authorsObject

Author(s)

0day Inc. [email protected]



281
282
283
# File 'lib/pwn/sdr/decoder/iridium.rb', line 281

public_class_method def self.authors
  "AUTHOR(S):\n  0day Inc. <[email protected]>\n"
end

.decode(opts = {}) ⇒ Object

Realtime options forwarded to Base: on_frame (Hash callback), output (writable IO), interactive (default true), duration (seconds), stop (callable), queue_size (bounded chunks), log_file (path or false).

Raises:



228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
# File 'lib/pwn/sdr/decoder/iridium.rb', line 228

public_class_method def self.decode(opts = {})
  raise NotImplementedError, 'Wideband IQ protocol decoding is not implemented; only mode: :ira_symbols_iq (synchronized IRA symbols) is supported. Use .detect for energy observations.' unless opts[:mode] == :ira_symbols_iq

  rate = opts[:sample_rate] || 25_000
  raise ArgumentError, 'IRA symbol IQ requires 25000 samples/s' unless rate == 25_000

  explicit_source = opts[:source].respond_to?(:read) || (opts[:file] && [nil, :file].include?(opts[:source]))
  raise ArgumentError, 'Explicit file or readable symbol IQ source required; RF acquisition is not supported' unless explicit_source

  PWN::SDR::Decoder::Base.run_iq(
    **opts, freq_obj: opts[:freq_obj] || {}, protocol: 'IRIDIUM',
            sample_rate: rate, demod: RingAlertSymbolsIQ.new,
            note: 'IRA only: pre-synchronized complex symbols; no wideband acquisition or RS message families.'
  )
end

.decode_ring_alert(opts = {}) ⇒ Object

IRA layout reference: muccc/iridium-toolkit bitsparser.py, BSD-2-Clause. See spec/fixtures/sdr/iridium/README.md for source and RF provenance. Accepts canonical differential/Gray bits INCLUDING the downlink UW; extractor RAW records swap each dibit and must be normalized first.



93
94
95
# File 'lib/pwn/sdr/decoder/iridium.rb', line 93

public_class_method def self.decode_ring_alert(opts = {})
  RingAlert.new.decode(opts[:bits])
end

.detect(opts = {}) ⇒ Object



244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
# File 'lib/pwn/sdr/decoder/iridium.rb', line 244

public_class_method def self.detect(opts = {})
  freq_obj = opts[:freq_obj] || {}

  rate  = (opts[:sample_rate] || freq_obj[:iq_rate] || 2_000_000).to_i
  proto = 'IRIDIUM'
  demod = DemodIQ.new(
    rate: rate, protocol: proto, modulation: 'DE-QPSK',
    extra: { threshold: 7.0 }
  )
  PWN::SDR::Decoder::Base.run_iq(
    **opts,
    freq_obj: freq_obj,
    protocol: proto,
    sample_rate: rate,
    source: opts[:source],
    file: opts[:file],
    demod: demod,
    threshold: 7.0,
    note: '25 kbit/s DE-QPSK — true-air I/Q path reports burst timing/energy.',
    describe: proc { |_b| { modulation: 'DE-QPSK', symbol_rate: 25_000 } }
  )
end

.helpObject



285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
# File 'lib/pwn/sdr/decoder/iridium.rb', line 285

public_class_method def self.help
  puts "USAGE:
    # Decode IRA only from already channelized, carrier/timing-synchronized
    # symbol IQ. Not raw wideband IQ; no LCW, RS, voice or messaging support.
    #{self}.decode(
      mode: 'required - :ira_symbols_iq; default :iq raises NotImplementedError',
      source: 'required - :file or readable IO; never automatically acquires RF',
      file: 'optional - path required for file source',
      sample_rate: 'optional - exactly 25000 complex symbol samples/s',
      freq_obj: 'optional - metadata Hash; no radio control',
      iq_format: 'optional - :cs16 recommended for the supplied fixtures',
      on_frame: 'optional - decoded IRA Hash callback',
      interactive: 'optional - false for file replay',
      log_file: 'optional - path or false'
    )
    # Decode a canonical downlink IRA bit frame, with BCH/parity checks.
    #{self}.decode_ring_alert(
      bits: 'required - binary String including access word; canonical RWA order'
    )
    # Run detection only (not protocol decoding).
    #{self}.detect(
      freq_obj: 'required - freq_obj returned from PWN::SDR::GQRX.init_freq',
      on_frame: 'optional - callback receiving each emitted Hash',
      output: 'optional - writable IO (default stdout)',
      interactive: 'optional - false disables ENTER input',
      duration: 'optional - finite seconds to run',
      stop: 'optional - callable returning true to stop',
      queue_size: 'optional - bounded pending chunks (default 8)',
      log_file: 'optional - JSONL path or false to disable logging',
      sample_rate: 'optional - sample rate value consumed by #decode',
      source: 'optional - source value consumed by #decode',
      file: 'optional - filesystem path'
    )

    # Run parse line and return its result
    #{self}.parse_line(
      line: 'optional - line value consumed by #parse_line'
    )

    # Print the AUTHOR(S) string for this module.
    #{self}.authors
  "
  constants.sort
end

.parse_line(opts = {}) ⇒ Object



267
268
269
270
271
272
273
274
275
276
277
# File 'lib/pwn/sdr/decoder/iridium.rb', line 267

public_class_method def self.parse_line(opts = {})
  line = opts[:line].to_s
  out  = { protocol: 'IRIDIUM' }
  out[:frame_type] = ::Regexp.last_match(1) if line =~ /^([A-Z]{3}):/
  out[:sat]        = ::Regexp.last_match(1) if line =~ /sat:(\d+)/
  out[:beam]       = ::Regexp.last_match(1) if line =~ /beam:(\d+)/
  out[:pos]        = ::Regexp.last_match(1) if line =~ /pos=\(([^)]+)\)/
  out[:ra_id]      = ::Regexp.last_match(1) if line =~ /ric:(\d+)/
  out[:summary]    = "IRIDIUM #{out[:frame_type]} sat=#{out[:sat]} beam=#{out[:beam]}"
  out.compact
end