Class: PWN::SDR::Decoder::Bluetooth::DemodIQ

Inherits:
Object
  • Object
show all
Defined in:
lib/pwn/sdr/decoder/bluetooth.rb

Overview

Streaming BLE GFSK demod for Base.run_iq — I/Q → advertising PDUs.

Constant Summary collapse

BAUD =
1_000_000

Instance Method Summary collapse

Constructor Details

#initialize(rate:, channel: 37, ble: true, extended: false, **connection) ⇒ DemodIQ

Returns a new instance of DemodIQ.

Raises:

  • (ArgumentError)


86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
# File 'lib/pwn/sdr/decoder/bluetooth.rb', line 86

def initialize(rate:, channel: 37, ble: true, extended: false, **connection)
  access_address = connection.fetch(:access_address, BLE_ADV_AA)
  crc_init = connection[:crc_init]
  encrypted = connection[:encrypted]
  raise ArgumentError, 'BR/EDR is unsupported; only legacy BLE advertising is decoded' unless ble

  @connected = access_address != BLE_ADV_AA
  @extended = extended
  raise ArgumentError, 'advertising channel must be 37, 38 or 39' unless @connected || @extended || BLE_ADV_CHANNELS.key?(channel)
  raise ArgumentError, 'channel must be an integer 0..39' unless channel.is_a?(Integer) && (0..39).cover?(channel)
  raise ArgumentError, 'connected channel must be 0..36' if @connected && channel >= 37
  raise ArgumentError, 'connected decoding requires crc_init and explicit encrypted: true/false' if @connected && (crc_init.nil? || ![true, false].include?(encrypted))

  @access_address = Integer(access_address)
  @crc_init = crc_init || BLE_CRC_INIT
  raise ArgumentError, 'invalid access address or CRCInit width' unless (0..0xFFFFFFFF).cover?(@access_address) && @crc_init.is_a?(Integer) && (0..0xFFFFFF).cover?(@crc_init)

  @encrypted = encrypted || false
  @aa_bits = Array.new(32) { |i| (@access_address >> i) & 1 }
  @aa_inv = @aa_bits.map { |b| b ^ 1 }

  @rate    = rate.to_f
  @channel = channel
  @ble     = ble
  @bits    = []
end

Instance Method Details

#feed_bits(bits) ⇒ Object

Raises:

  • (ArgumentError)


121
122
123
124
125
126
# File 'lib/pwn/sdr/decoder/bluetooth.rb', line 121

def feed_bits(bits, &)
  raise ArgumentError, 'bits must contain only 0 or 1' unless bits.all? { |bit| [0, 1].include?(bit) }

  @bits.concat(bits)
  scan(&) if block_given?
end

#feed_iq(samples, rate: nil) ⇒ Object



113
114
115
116
117
118
119
# File 'lib/pwn/sdr/decoder/bluetooth.rb', line 113

def feed_iq(samples, rate: nil, &)
  @rate = rate.to_f if rate
  # Try both polarities — GFSK sign depends on tuner spectral inversion.
  @slicer ||= Bluetooth::SymbolStream.new(rate: @rate, baud: BAUD)
  new_bits = @slicer.feed_iq(samples)
  feed_bits(new_bits, &)
end