Module: PWN::SDR::Decoder::P25

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

Overview

APCO Project 25 (P25) Phase-1 C4FM decoder for the 700/800 MHz public-safety allocations. GQRX supplies NBFM-discriminator audio via the UDP tap; dsd (Digital Speech Decoder) recovers the C4FM symbol stream and prints trunking-control frames (NAC, TGID, RID, DUID, ...).

Class Method Summary collapse

Class Method Details

.authorsObject

Author(s)

0day Inc. support@0dayinc.com



49
50
51
# File 'lib/pwn/sdr/decoder/p25.rb', line 49

public_class_method def self.authors
  "AUTHOR(S):\n  0day Inc. <support@0dayinc.com>\n"
end

.decode(opts = {}) ⇒ Object

Supported Method Parameters

PWN::SDR::Decoder::P25.decode( freq_obj: 'required - freq_obj returned from PWN::SDR::GQRX.init_freq' )



16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# File 'lib/pwn/sdr/decoder/p25.rb', line 16

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

  # dsd expects 48 kHz s16le on stdin — bypass Base's 22 050 Hz resample
  # by asking for 48 000 (sox becomes a passthrough / format guard).
  PWN::SDR::Decoder::Base.run_pipeline(
    freq_obj: freq_obj,
    protocol: 'P25',
    required_bins: %w[sox dsd],
    resample_hz: 48_000,
    decode_cmd: 'dsd -q -i - -o /dev/null -f1',
    line_match: /(NAC|TGID|TG:|RID|src:|P25|Sync:)/i,
    parser: proc { |line| parse_line(line: line) }
  )
end

.helpObject

Display Usage for this Module



55
56
57
58
59
60
61
62
63
64
65
66
67
# File 'lib/pwn/sdr/decoder/p25.rb', line 55

public_class_method def self.help
  puts "USAGE:
    #{self}.decode(
      freq_obj: 'required - freq_obj returned from PWN::SDR::GQRX.init_freq'
    )

    NOTE: Requires `dsd`. Set GQRX demod to Narrow FM, ~12.5 kHz.

    #{self}.parse_line(line: 'Sync: +P25p1 NAC: 293 src: 1234 tg: 5678')

    #{self}.authors
  "
end

.parse_line(opts = {}) ⇒ Object

Supported Method Parameters

PWN::SDR::Decoder::P25.parse_line(line: 'Sync: +P25p1 NAC: 293 src: 1234 tg: 5678')



35
36
37
38
39
40
41
42
43
44
45
# File 'lib/pwn/sdr/decoder/p25.rb', line 35

public_class_method def self.parse_line(opts = {})
  line = opts[:line].to_s
  out  = { protocol: 'P25' }
  out[:nac]       = ::Regexp.last_match(1) if line =~ /NAC:\s*([0-9A-Fa-f]+)/
  out[:talkgroup] = ::Regexp.last_match(1) if line =~ /(?:TGID|tg[: ])\s*(\d+)/i
  out[:radio_id]  = ::Regexp.last_match(1) if line =~ /(?:RID|src[: ])\s*(\d+)/i
  out[:duid]      = ::Regexp.last_match(1) if line =~ /DUID:\s*(\S+)/
  out[:sync]      = ::Regexp.last_match(1) if line =~ /Sync:\s*(\S+)/
  out[:summary]   = "P25 NAC=#{out[:nac]} TG=#{out[:talkgroup]} RID=#{out[:radio_id]}".squeeze(' ')
  out.compact
end