Module: PWN::FFI::SoapySDR
- Extended by:
- Library
- Defined in:
- lib/pwn/ffi/soapy_sdr.rb
Overview
Thin SoapySDR C-API binding (libSoapySDR). Inventory-focused first —
enumerate devices and report API version so Extrospection probe_rf
can list every Soapy-backed front-end (RTL-SDR, HackRF, Airspy,
Pluto, UHD, …) without shelling out to SoapySDRUtil.
Streaming: open/configure/start_rx/read_sync/stop_rx/close for CS16 I/Q so PWN::SDR::Decoder::Base.run_iq can true-air decode from ANY Soapy-backed front-end. Missing lib -> .available? false, callers fall back to pure Ruby / other PWN::FFI front-ends.
Defined Under Namespace
Classes: Kwargs
Constant Summary collapse
- SOAPY_SDR_RX =
1- SOAPY_SDR_CS16 =
'CS16'
Class Attribute Summary collapse
-
.load_error ⇒ Object
readonly
Returns the value of attribute load_error.
Class Method Summary collapse
-
.authors ⇒ Object
- Author(s)
0day Inc.
-
.available? ⇒ Boolean
- Supported Method Parameters
PWN::FFI::SoapySDR.available?.
-
.close(opts = {}) ⇒ Object
- Supported Method Parameters
PWN::FFI::SoapySDR.close(handle: h).
-
.configure(opts = {}) ⇒ Object
rubocop:disable Naming/PredicateMethod.
-
.device_keys(opts = {}) ⇒ Object
- Supported Method Parameters
meta = PWN::FFI::SoapySDR.device_keys(device: pointer) Returns { driver:, hardware: }.
-
.help ⇒ Object
Display Usage for this Module.
-
.info ⇒ Object
- Supported Method Parameters
info = PWN::FFI::SoapySDR.info Returns { available:, api:, abi:, lib: }.
-
.list_devices ⇒ Object
- Supported Method Parameters
devices = PWN::FFI::SoapySDR.list_devices Returns Array
of keyword→value maps for every enumerated device.
-
.make(opts = {}) ⇒ Object
- Supported Method Parameters
dev = PWN::FFI::SoapySDR.make(args: 'driver=rtlsdr') Returns opaque pointer (SoapySDRDevice*) or raises.
-
.open(opts = {}) ⇒ Object
- Supported Method Parameters
h = PWN::FFI::SoapySDR.open( args: 'optional - Soapy args string, e.g. "driver=rtlsdr"', channel: 'optional - RX channel (default 0)' ) Convenience wrapper around .make; returns { device:, channel: }.
-
.read_sync(opts = {}) ⇒ Object
- Supported Method Parameters
data = PWN::FFI::SoapySDR.read_sync( handle: 'required - from .start_rx', timeout_us: 'optional - microseconds (default 1_000_000)' ) Returns String of interleaved cs16le I/Q (4 bytes/sample), or nil.
-
.start_rx(opts = {}) ⇒ Object
- Supported Method Parameters
PWN::FFI::SoapySDR.start_rx( handle: 'required - from .open', samples: 'optional - MTU / read block size (default 65536)' ) Sets up + activates a CS16 RX stream.
-
.stop_rx(opts = {}) ⇒ Object
- Supported Method Parameters
PWN::FFI::SoapySDR.stop_rx(handle: h).
-
.unmake(opts = {}) ⇒ Object
- Supported Method Parameters
PWN::FFI::SoapySDR.unmake(device: pointer).
Class Attribute Details
.load_error ⇒ Object (readonly)
Returns the value of attribute load_error.
32 33 34 |
# File 'lib/pwn/ffi/soapy_sdr.rb', line 32 def load_error @load_error end |
Class Method Details
.authors ⇒ Object
- Author(s)
0day Inc. support@0dayinc.com
312 313 314 |
# File 'lib/pwn/ffi/soapy_sdr.rb', line 312 public_class_method def self. "AUTHOR(S):\n 0day Inc. <support@0dayinc.com>\n" end |
.available? ⇒ Boolean
- Supported Method Parameters
PWN::FFI::SoapySDR.available?
91 92 93 94 95 |
# File 'lib/pwn/ffi/soapy_sdr.rb', line 91 public_class_method def self.available? !@load_error && respond_to?(:SoapySDR_getAPIVersion, true) rescue StandardError false end |
.close(opts = {}) ⇒ Object
- Supported Method Parameters
PWN::FFI::SoapySDR.close(handle: h)
299 300 301 302 303 304 305 306 307 308 |
# File 'lib/pwn/ffi/soapy_sdr.rb', line 299 public_class_method def self.close(opts = {}) h = opts[:handle] return unmake(device: opts[:device]) if opts[:device] return unless h.is_a?(Hash) && h[:device] stop_rx(handle: h) if h[:stream] unmake(device: h[:device]) h[:device] = nil nil end |
.configure(opts = {}) ⇒ Object
rubocop:disable Naming/PredicateMethod
203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 |
# File 'lib/pwn/ffi/soapy_sdr.rb', line 203 public_class_method def self.configure(opts = {}) # rubocop:disable Naming/PredicateMethod h = opts[:handle] raise 'ERROR: :handle required (from .open)' unless h.is_a?(Hash) && h[:device] dev = h[:device] ch = h[:channel] rate = (opts[:rate_hz] || 2_048_000).to_f freq = opts[:freq_hz].to_f SoapySDRDevice_setSampleRate(dev, SOAPY_SDR_RX, ch, rate) SoapySDRDevice_setFrequency(dev, SOAPY_SDR_RX, ch, freq, nil) if opts[:gain_db] SoapySDRDevice_setGainMode(dev, SOAPY_SDR_RX, ch, false) SoapySDRDevice_setGain(dev, SOAPY_SDR_RX, ch, opts[:gain_db].to_f) else SoapySDRDevice_setGainMode(dev, SOAPY_SDR_RX, ch, true) end SoapySDRDevice_setBandwidth(dev, SOAPY_SDR_RX, ch, opts[:bw_hz].to_f) if opts[:bw_hz] h[:rate_hz] = rate.to_i h[:freq_hz] = freq.to_i true end |
.device_keys(opts = {}) ⇒ Object
- Supported Method Parameters
meta = PWN::FFI::SoapySDR.device_keys(device: pointer) Returns { driver:, hardware: }.
170 171 172 173 174 175 176 177 178 |
# File 'lib/pwn/ffi/soapy_sdr.rb', line 170 public_class_method def self.device_keys(opts = {}) dev = opts[:device] raise 'ERROR: :device required' if dev.nil? || dev.null? { driver: SoapySDRDevice_getDriverKey(dev).to_s, hardware: SoapySDRDevice_getHardwareKey(dev).to_s } end |
.help ⇒ Object
Display Usage for this Module
318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 |
# File 'lib/pwn/ffi/soapy_sdr.rb', line 318 public_class_method def self.help puts "USAGE: #{self}.available? #{self}.info #{self}.list_devices dev = #{self}.make(args: 'driver=rtlsdr') #{self}.device_keys(device: dev) #{self}.unmake(device: dev) h = #{self}.open(args: 'driver=rtlsdr') #{self}.configure(handle: h, freq_hz:, rate_hz: 2_048_000, gain_db: nil) #{self}.start_rx(handle: h) data = #{self}.read_sync(handle: h) # cs16le interleaved I/Q #{self}.stop_rx(handle: h) #{self}.close(handle: h) #{self}.authors " end |
.info ⇒ Object
- Supported Method Parameters
info = PWN::FFI::SoapySDR.info Returns { available:, api:, abi:, lib: }.
101 102 103 104 105 106 107 108 109 110 |
# File 'lib/pwn/ffi/soapy_sdr.rb', line 101 public_class_method def self.info return { available: false, error: @load_error&. } unless available? { available: true, api: SoapySDR_getAPIVersion().to_s, abi: SoapySDR_getABIVersion().to_s, lib: SoapySDR_getLibVersion().to_s } end |
.list_devices ⇒ Object
- Supported Method Parameters
devices = PWN::FFI::SoapySDR.list_devices
Returns Array
116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 |
# File 'lib/pwn/ffi/soapy_sdr.rb', line 116 public_class_method def self.list_devices raise 'ERROR: libSoapySDR not available' unless available? len_ptr = PubFFI::MemoryPointer.new(:size_t) # pass NULL args → enumerate everything list_ptr = SoapySDRDevice_enumerate(nil, len_ptr) length = len_ptr.read_ulong return [] if list_ptr.null? || length.zero? results = [] length.times do |i| kw = Kwargs.new(list_ptr + (i * Kwargs.size)) h = {} kw[:size].times do |j| key = kw[:keys].get_pointer(j * PubFFI::Pointer.size) val = kw[:vals].get_pointer(j * PubFFI::Pointer.size) h[key.read_string.to_sym] = val.read_string unless key.null? end results << h end SoapySDRKwargsList_clear(list_ptr, length) results end |
.make(opts = {}) ⇒ Object
- Supported Method Parameters
dev = PWN::FFI::SoapySDR.make(args: 'driver=rtlsdr') Returns opaque pointer (SoapySDRDevice*) or raises. ALWAYS pair with .unmake.
145 146 147 148 149 150 151 152 153 |
# File 'lib/pwn/ffi/soapy_sdr.rb', line 145 public_class_method def self.make(opts = {}) raise 'ERROR: libSoapySDR not available' unless available? args = opts[:args].to_s dev = SoapySDRDevice_makeStrArgs(args) raise "ERROR: SoapySDRDevice_makeStrArgs(#{args.inspect}) returned NULL" if dev.null? dev end |
.open(opts = {}) ⇒ Object
- Supported Method Parameters
h = PWN::FFI::SoapySDR.open( args: 'optional - Soapy args string, e.g. "driver=rtlsdr"', channel: 'optional - RX channel (default 0)' ) Convenience wrapper around .make; returns { device:, channel: }.
186 187 188 189 190 191 192 |
# File 'lib/pwn/ffi/soapy_sdr.rb', line 186 public_class_method def self.open(opts = {}) args = opts[:args].to_s args = list_devices.first&.map { |k, v| "#{k}=#{v}" }&.join(',').to_s if args.empty? raise 'ERROR: no SoapySDR devices found' if args.empty? { device: make(args: args), args: args, channel: (opts[:channel] || 0).to_i } end |
.read_sync(opts = {}) ⇒ Object
- Supported Method Parameters
data = PWN::FFI::SoapySDR.read_sync( handle: 'required - from .start_rx', timeout_us: 'optional - microseconds (default 1_000_000)' ) Returns String of interleaved cs16le I/Q (4 bytes/sample), or nil.
268 269 270 271 272 273 274 275 276 277 278 279 |
# File 'lib/pwn/ffi/soapy_sdr.rb', line 268 public_class_method def self.read_sync(opts = {}) h = opts[:handle] raise 'ERROR: :handle required (from .start_rx)' unless h.is_a?(Hash) && h[:stream] to = (opts[:timeout_us] || 1_000_000).to_i n = SoapySDRDevice_readStream( h[:device], h[:stream], h[:buffs], h[:elems], h[:flags_p], h[:time_p], to ) return nil if n <= 0 h[:buf].read_bytes(n * 4) end |
.start_rx(opts = {}) ⇒ Object
- Supported Method Parameters
PWN::FFI::SoapySDR.start_rx( handle: 'required - from .open', samples: 'optional - MTU / read block size (default 65536)' ) Sets up + activates a CS16 RX stream. Extends handle in place.
232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 |
# File 'lib/pwn/ffi/soapy_sdr.rb', line 232 public_class_method def self.start_rx(opts = {}) h = opts[:handle] raise 'ERROR: :handle required (from .open)' unless h.is_a?(Hash) && h[:device] dev = h[:device] ch = h[:channel] chan_ptr = PubFFI::MemoryPointer.new(:size_t, 1) chan_ptr.write_ulong(ch) stream = SoapySDRDevice_setupStream(dev, SOAPY_SDR_RX, SOAPY_SDR_CS16, chan_ptr, 1, nil) raise "ERROR: setupStream failed: #{SoapySDRDevice_lastError()}" if stream.nil? || stream.null? rc = SoapySDRDevice_activateStream(dev, stream, 0, 0, 0) raise "ERROR: activateStream rc=#{rc}: #{SoapySDRDevice_lastError()}" if rc.nonzero? mtu = SoapySDRDevice_getStreamMTU(dev, stream) elems = (opts[:samples] || (mtu.positive? ? mtu : 65_536)).to_i # CS16 = 2x int16 per sample = 4 bytes per element buf = PubFFI::MemoryPointer.new(:int8, elems * 4) buffs = PubFFI::MemoryPointer.new(:pointer, 1) buffs.write_pointer(buf) h.merge!( stream: stream, buf: buf, buffs: buffs, elems: elems, flags_p: PubFFI::MemoryPointer.new(:int), time_p: PubFFI::MemoryPointer.new(:long_long), format: :cs16 ) h end |
.stop_rx(opts = {}) ⇒ Object
- Supported Method Parameters
PWN::FFI::SoapySDR.stop_rx(handle: h)
284 285 286 287 288 289 290 291 292 293 294 |
# File 'lib/pwn/ffi/soapy_sdr.rb', line 284 public_class_method def self.stop_rx(opts = {}) h = opts[:handle] return unless h.is_a?(Hash) && h[:stream] SoapySDRDevice_deactivateStream(h[:device], h[:stream], 0, 0) SoapySDRDevice_closeStream(h[:device], h[:stream]) h[:stream] = nil nil rescue StandardError nil end |
.unmake(opts = {}) ⇒ Object
- Supported Method Parameters
PWN::FFI::SoapySDR.unmake(device: pointer)
158 159 160 161 162 163 164 |
# File 'lib/pwn/ffi/soapy_sdr.rb', line 158 public_class_method def self.unmake(opts = {}) dev = opts[:device] return unless dev && !dev.null? SoapySDRDevice_unmake(dev) nil end |