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 all samples read as cs16le I/Q, or nil on timeout/no samples.
-
.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. [email protected]
328 329 330 |
# File 'lib/pwn/ffi/soapy_sdr.rb', line 328 public_class_method def self. "AUTHOR(S):\n 0day Inc. <[email protected]>\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)
315 316 317 318 319 320 321 322 323 324 |
# File 'lib/pwn/ffi/soapy_sdr.rb', line 315 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
334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 |
# File 'lib/pwn/ffi/soapy_sdr.rb', line 334 public_class_method def self.help puts "USAGE: # Run available and return its result #{self}.available? # Run info and return its result #{self}.info # Run list devices and return its result #{self}.list_devices # Run make and return its result #{self}.make( args: 'required - args value consumed by #make' ) # Run unmake and return its result #{self}.unmake( device: 'optional - device value consumed by #unmake' ) # Run device keys and return its result #{self}.device_keys( device: 'required - device value consumed by #device_keys' ) # Open a session or connection and return a handle. #{self}.open( args: 'optional - Soapy args string, e.g. driver=rtlsdr', channel: 'optional - RX channel (default 0)' ) # Run configure and return its result #{self}.configure( handle: 'required - from .open', freq_hz: 'required - center frequency Hz', rate_hz: 'optional - sample rate Hz (default 2_048_000)', gain_db: 'optional - overall gain dB (default AGC on)', bw_hz: 'optional - baseband filter BW Hz' ) # Sets up + activates a CS16 RX stream. Extends handle in place #{self}.start_rx( handle: 'required - from .open', samples: 'optional - MTU / read block size (default 65536)' ) # Run read sync and return its result #{self}.read_sync( handle: 'required - from .start_rx', timeout_us: 'optional - microseconds (default 1_000_000)' ) # Run stop rx and return its result #{self}.stop_rx( handle: 'optional - handle value consumed by #stop_rx' ) # Close a session previously returned by #open. #{self}.close( handle: 'optional - handle value consumed by #close', device: 'optional - device value consumed by #close' ) # Print the AUTHOR(S) string for this module. #{self}.authors " constants.sort 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 all samples read as cs16le I/Q, or nil on timeout/no samples. Other native errors raise. Overflow increments handle; the hardware lost-sample count is unknown. Callers must not bridge the gap. Serialize read/stop/close for this stream (driver thread safety varies).
275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 |
# File 'lib/pwn/ffi/soapy_sdr.rb', line 275 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.zero? || n == -1 # SOAPY_SDR_TIMEOUT if n == -4 # SOAPY_SDR_OVERFLOW; lost sample count is unknown h[:overruns] = h.fetch(:overruns, 0) + 1 raise 'ERROR: SoapySDR RX overrun rc=-4: IQ continuity lost' end raise "ERROR: SoapySDR readStream rc=#{n}" if n.negative? raise "ERROR: SoapySDR invalid read length #{n}" if n > h[:elems] 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 260 261 262 263 |
# 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) if rc.nonzero? error = "ERROR: activateStream rc=#{rc}: #{SoapySDRDevice_lastError()}" SoapySDRDevice_closeStream(dev, stream) raise error end 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)
298 299 300 301 302 303 304 305 306 307 308 309 310 |
# File 'lib/pwn/ffi/soapy_sdr.rb', line 298 public_class_method def self.stop_rx(opts = {}) h = opts[:handle] return unless h.is_a?(Hash) && h[:stream] deactivate_rc = SoapySDRDevice_deactivateStream(h[:device], h[:stream], 0, 0) close_rc = SoapySDRDevice_closeStream(h[:device], h[:stream]) raise "ERROR: closeStream rc=#{close_rc}" unless close_rc.zero? h[:stream] = nil raise "ERROR: deactivateStream rc=#{deactivate_rc}" unless deactivate_rc.zero? 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 |