Module: PWN::SDR::Decoder::P25
- Defined in:
- lib/pwn/sdr/decoder/p25.rb
Overview
APCO Project 25 Phase-1 (C4FM) true-air decoder.
I/Q → PWN::FFI::Liquid.freq_demod (or DSP.fm_demod_iq) → resample to 48 kHz → 4-level slice at 4800 sym/s → dibits → hunt the 24-symbol Frame Sync (0x5575F5FF77FF) → recover the 64-bit NID (12-bit NAC + 4-bit DUID + BCH(63,16,23) parity). Emits duid:, duid_name: per frame — the same intel OP25 / DSD show — with no external binary.
Defined Under Namespace
Classes: DemodIQ
Constant Summary collapse
- FS_DIBITS =
24-symbol / 48-bit Frame Sync (dibit MSB-first)
[ 1, 1, 1, 1, 3, 1, 1, 3, 3, 3, 3, 1, 1, 3, 3, 3, 3, 3, 3, 3, 1, 3, 3, 3, 3, 3, 3, 3 ].freeze
- FS_DIBITS_24 =
→ 0x5575F5FF77FF (see TIA-102.BAAA)
[ 1, 1, 1, 1, 3, 1, 1, 3, 3, 3, 3, 1, 3, 3, 1, 1, 3, 3, 3, 3, 3, 3, 3, 3 ].freeze
- FRAME_SYNC =
Correct 24-dibit Frame Sync per TIA-102 (+3 +3 +3 +3 −3 +3 …). Derived from bit pattern 5575F5FF77FF, MSB-first, 2 bits/sym, C4FM map: 01→+3, 00→+1, 10→−1, 11→−3 → dibits 1,0,2,3.
0x5575F5FF77FF- FS_BITS =
Array.new(48) { |i| (FRAME_SYNC >> (47 - i)) & 1 }.freeze
- FS_SYMS =
FS_BITS.each_slice(2).map { |a, b| (a << 1) | b }.freeze
- DUID_NAME =
{ 0x0 => 'HDU', 0x3 => 'TDU', 0x5 => 'LDU1', 0x7 => 'TSBK', 0xA => 'LDU2', 0xC => 'PDU', 0xF => 'TDULC' }.freeze
Class Method Summary collapse
-
.authors ⇒ Object
- Author(s)
0day Inc.
-
.decode(opts = {}) ⇒ Object
- Supported Method Parameters
PWN::SDR::Decoder::P25.decode( freq_obj: 'required - freq_obj returned from PWN::SDR::GQRX.init_freq' ).
- .help ⇒ Object
- .parse_line(opts = {}) ⇒ Object
Class Method Details
.authors ⇒ Object
- Author(s)
0day Inc. support@0dayinc.com
131 132 133 |
# File 'lib/pwn/sdr/decoder/p25.rb', line 131 public_class_method def self. "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' )
103 104 105 106 107 108 109 110 111 112 113 114 115 116 |
# File 'lib/pwn/sdr/decoder/p25.rb', line 103 public_class_method def self.decode(opts = {}) freq_obj = opts[:freq_obj] rate = (opts[:sample_rate] || freq_obj[:iq_rate] || (48_000 * 20)).to_i PWN::SDR::Decoder::Base.run_iq( freq_obj: freq_obj, protocol: 'P25', sample_rate: rate, source: opts[:source], file: opts[:file], demod: DemodIQ.new(rate: rate), note: 'C4FM 4800 sym/s — I/Q→FM→4-FSK→FS 0x5575F5FF77FF→NAC/DUID.', describe: proc { |b| { modulation: 'C4FM', classification: b[:duration_ms] > 180 ? 'voice-LDU' : 'TSBK/control' } } ) end |
.help ⇒ Object
135 136 137 138 139 140 141 142 143 144 145 |
# File 'lib/pwn/sdr/decoder/p25.rb', line 135 public_class_method def self.help puts "USAGE (true-air I/Q via PWN::FFI + detector fallback): #{self}.decode( freq_obj: 'required - freq_obj returned from PWN::SDR::GQRX.init_freq', source: 'optional - :auto|:rtlsdr|:adalm_pluto|:file', file: 'optional - .cu8/.cs16 capture' ) #{self}.authors " end |
.parse_line(opts = {}) ⇒ Object
118 119 120 121 122 123 124 125 126 127 |
# File 'lib/pwn/sdr/decoder/p25.rb', line 118 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[:= ]+([0-9A-Fa-f]+)/ out[:tg] = ::Regexp.last_match(1) if line =~ /(?:TG|talkgroup)[:= ]+(\d+)/i out[:rid] = ::Regexp.last_match(1) if line =~ /(?:RID|src|source)[:= ]+(\d+)/i out[:duid] = ::Regexp.last_match(1) if line =~ /DUID[:= ]+(\w+)/i out[:summary] = "P25 NAC=#{out[:nac]} TG=#{out[:tg]} RID=#{out[:rid]}" out.compact end |