Module: PWN::FFI::Liquid

Extended by:
Library
Defined in:
lib/pwn/ffi/liquid.rb

Overview

Thin liquid-dsp binding.

Provides the high-level DSP building blocks that PWN::SDR::Decoder::* needs beyond pure-Ruby primitives: FM / frequency demodulation, multi-stage arbitrary resampling, and Kaiser FIR filters. Opaque liquid handles are held as FFI::Pointer and always freed in an ensure — no compile step, no shells. If libliquid is absent .available? is false and DSP falls back to pure Ruby.

Complex samples use the GCC float _Complex ABI: interleaved float (re, im) per sample, which is how liquid_float_complex is laid out under LIQUID_DEFINE_COMPLEX.

Class Attribute Summary collapse

Class Method Summary collapse

Class Attribute Details

.load_errorObject (readonly)

Returns the value of attribute load_error.



32
33
34
# File 'lib/pwn/ffi/liquid.rb', line 32

def load_error
  @load_error
end

Class Method Details

.authorsObject

Author(s)

0day Inc. support@0dayinc.com



215
216
217
# File 'lib/pwn/ffi/liquid.rb', line 215

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

.available?Boolean

Supported Method Parameters

PWN::FFI::Liquid.available?

Returns:

  • (Boolean)


73
74
75
76
77
# File 'lib/pwn/ffi/liquid.rb', line 73

public_class_method def self.available?
  !@load_error && respond_to?(:freqdem_create, true)
rescue StandardError
  false
end

.dc_block(opts = {}) ⇒ Object

Supported Method Parameters

out = PWN::FFI::Liquid.dc_block( samples: 'required - Array', m: 'optional - prototype semi-length (default 7 → len 15)', as_db: 'optional - stop-band attenuation dB (default 60.0)' )



189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
# File 'lib/pwn/ffi/liquid.rb', line 189

public_class_method def self.dc_block(opts = {})
  raise 'ERROR: libliquid not available' unless available?

  samples = opts[:samples]
  m       = (opts[:m] || 7).to_i
  as_db   = (opts[:as_db] || 60.0).to_f
  return [] if samples.empty?

  q = firfilt_rrrf_create_dc_blocker(m, as_db)
  raise 'ERROR: firfilt_rrrf_create_dc_blocker failed' if q.null?

  begin
    n = samples.length
    in_ptr = float_ptr(samples)
    out_ptr = PubFFI::MemoryPointer.new(:float, n)
    rc = firfilt_rrrf_execute_block(q, in_ptr, n, out_ptr)
    raise "ERROR: firfilt_rrrf_execute_block rc=#{rc}" unless rc.zero?

    out_ptr.read_array_of_float(n)
  ensure
    firfilt_rrrf_destroy(q)
  end
end

.fir_kaiser(opts = {}) ⇒ Object

Supported Method Parameters

out = PWN::FFI::Liquid.fir_kaiser( samples: 'required - Array', length: 'optional - filter length (default 51, odd preferred)', fc: 'optional - normalised cutoff 0..0.5 (default 0.1)', as_db: 'optional - stop-band attenuation dB (default 60.0)', mu: 'optional - fractional sample offset (default 0.0)' ) Returns Array same length as input.



156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
# File 'lib/pwn/ffi/liquid.rb', line 156

public_class_method def self.fir_kaiser(opts = {})
  raise 'ERROR: libliquid not available' unless available?

  samples = opts[:samples]
  length  = (opts[:length] || 51).to_i
  fc      = (opts[:fc] || 0.1).to_f
  as_db   = (opts[:as_db] || 60.0).to_f
  mu      = (opts[:mu] || 0.0).to_f
  return [] if samples.empty?

  q = firfilt_rrrf_create_kaiser(length, fc, as_db, mu)
  raise 'ERROR: firfilt_rrrf_create_kaiser failed' if q.null?

  begin
    n = samples.length
    in_ptr = float_ptr(samples)
    out_ptr = PubFFI::MemoryPointer.new(:float, n)
    rc = firfilt_rrrf_execute_block(q, in_ptr, n, out_ptr)
    raise "ERROR: firfilt_rrrf_execute_block rc=#{rc}" unless rc.zero?

    out_ptr.read_array_of_float(n)
  ensure
    firfilt_rrrf_destroy(q)
  end
end

.freq_demod(opts = {}) ⇒ Object

Supported Method Parameters

audio = PWN::FFI::Liquid.freq_demod( iq: 'required - interleaved I/Q Array (even length)', kf: 'optional - modulation factor (default 0.5)' ) Returns Array of demodulated real samples (length = iq.length/2).



86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
# File 'lib/pwn/ffi/liquid.rb', line 86

public_class_method def self.freq_demod(opts = {})
  raise 'ERROR: libliquid not available' unless available?

  iq = opts[:iq]
  kf = (opts[:kf] || 0.5).to_f
  n  = iq.length / 2
  return [] if n.zero?

  fd = freqdem_create(kf)
  raise 'ERROR: freqdem_create failed' if fd.null?

  begin
    in_ptr = float_ptr(iq.first(n * 2))
    out_ptr = PubFFI::MemoryPointer.new(:float, n)
    rc = freqdem_demodulate_block(fd, in_ptr, n, out_ptr)
    raise "ERROR: freqdem_demodulate_block rc=#{rc}" unless rc.zero?

    out_ptr.read_array_of_float(n)
  ensure
    freqdem_destroy(fd)
  end
end

.helpObject

Display Usage for this Module



221
222
223
224
225
226
227
228
229
230
231
# File 'lib/pwn/ffi/liquid.rb', line 221

public_class_method def self.help
  puts "USAGE:
    #{self}.available?                              # => true/false
    #{self}.freq_demod(iq:, kf: 0.5)                # FM I/Q → audio
    #{self}.resample(samples:, rate:, as_db: 60.0)  # arbitrary rate
    #{self}.fir_kaiser(samples:, length: 51, fc: 0.1, as_db: 60.0)
    #{self}.dc_block(samples:, m: 7, as_db: 60.0)

    #{self}.authors
  "
end

.resample(opts = {}) ⇒ Object

Supported Method Parameters

out = PWN::FFI::Liquid.resample( samples: 'required - Array', rate: 'required - output/input ratio (>0)', as_db: 'optional - stop-band attenuation dB (default 60.0)' ) Returns Array of resampled samples.



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
143
144
# File 'lib/pwn/ffi/liquid.rb', line 117

public_class_method def self.resample(opts = {})
  raise 'ERROR: libliquid not available' unless available?

  samples = opts[:samples]
  rate    = opts[:rate].to_f
  as_db   = (opts[:as_db] || 60.0).to_f
  raise 'ERROR: rate must be > 0' unless rate.positive?
  return samples.dup if samples.empty?

  q = msresamp_rrrf_create(rate, as_db)
  raise 'ERROR: msresamp_rrrf_create failed' if q.null?

  begin
    nx = samples.length
    # allocate generously: ceil(1 + 2*r*nx)
    ny_max = [1, (1 + (2 * rate * nx)).ceil].max
    in_ptr = float_ptr(samples)
    out_ptr = PubFFI::MemoryPointer.new(:float, ny_max)
    ny_ptr = PubFFI::MemoryPointer.new(:uint, 1)
    rc = msresamp_rrrf_execute(q, in_ptr, nx, out_ptr, ny_ptr)
    raise "ERROR: msresamp_rrrf_execute rc=#{rc}" unless rc.zero?

    ny = ny_ptr.read_uint
    out_ptr.read_array_of_float(ny)
  ensure
    msresamp_rrrf_destroy(q)
  end
end