Module: PWN::FFI::HackRF
- Extended by:
- Library
- Defined in:
- lib/pwn/ffi/hack_rf.rb
Overview
Thin libhackrf binding for inventory / RX of raw I/Q.
Intentionally control-plane first: init/open/tune/rate/gains + one-shot
sync-style helpers used by Extrospection probe_rf and by wideband
PWN::SDR::Decoder::* modules that need real I/Q (not GQRX audio).
Streaming callbacks stay opt-in — Ruby GC must not run on the
libusb transfer thread, so call sites that need continuous RX should
buffer into a Queue from a dedicated thread.
Defined Under Namespace
Classes: Transfer
Constant Summary collapse
- HACKRF_SUCCESS =
0
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::HackRF.available?.
-
.capture(opts = {}) ⇒ Object
- Supported Method Parameters
data = PWN::FFI::HackRF.capture( freq_hz: 'required - center frequency Hz', rate_hz: 'optional - sample rate (default 10e6)', samples: 'optional - I/Q sample pairs to capture (default 262_144)', lna_gain: 'optional', vga_gain: 'optional', amp: 'optional', serial: 'optional' ) One-shot open/configure/start_rx/read/stop_rx/close.
-
.close(opts = {}) ⇒ Object
- Supported Method Parameters
PWN::FFI::HackRF.close(device: pointer).
-
.configure(opts = {}) ⇒ Object
rubocop:disable Naming/PredicateMethod.
-
.device_info(opts = {}) ⇒ Object
- Supported Method Parameters
meta = PWN::FFI::HackRF.device_info(device: pointer) Returns { board_id:, board_name:, version: }.
-
.help ⇒ Object
Display Usage for this Module.
-
.info ⇒ Object
- Supported Method Parameters
info = PWN::FFI::HackRF.info Returns Hash with library_version / library_release / available.
-
.open(opts = {}) ⇒ Object
- Supported Method Parameters
dev = PWN::FFI::HackRF.open(serial: nil) Returns FFI::Pointer (opaque hackrf_device*) or raises.
-
.read_sync(opts = {}) ⇒ Object
- Supported Method Parameters
data = PWN::FFI::HackRF.read_sync( handle: 'required - handle from .start_rx', timeout: 'optional - seconds to wait for a chunk (default 1.0)' ) Returns String of interleaved cs8 I/Q, or nil on timeout.
-
.start_rx(opts = {}) ⇒ Object
- Supported Method Parameters
rx = PWN::FFI::HackRF.start_rx( device: 'required - pointer from .open', max_queue: 'optional - max buffered chunks (default 64)' ) Returns { device:, queue:, callback: } handle for read_sync/stop_rx.
-
.stop_rx(opts = {}) ⇒ Object
- Supported Method Parameters
PWN::FFI::HackRF.stop_rx(handle: rx).
Class Attribute Details
.load_error ⇒ Object (readonly)
Returns the value of attribute load_error.
30 31 32 |
# File 'lib/pwn/ffi/hack_rf.rb', line 30 def load_error @load_error end |
Class Method Details
.authors ⇒ Object
- Author(s)
0day Inc. support@0dayinc.com
277 278 279 |
# File 'lib/pwn/ffi/hack_rf.rb', line 277 public_class_method def self. "AUTHOR(S):\n 0day Inc. <support@0dayinc.com>\n" end |
.available? ⇒ Boolean
- Supported Method Parameters
PWN::FFI::HackRF.available?
63 64 65 66 67 |
# File 'lib/pwn/ffi/hack_rf.rb', line 63 public_class_method def self.available? !@load_error && respond_to?(:hackrf_init, true) rescue StandardError false end |
.capture(opts = {}) ⇒ Object
- Supported Method Parameters
data = PWN::FFI::HackRF.capture( freq_hz: 'required - center frequency Hz', rate_hz: 'optional - sample rate (default 10e6)', samples: 'optional - I/Q sample pairs to capture (default 262_144)', lna_gain: 'optional', vga_gain: 'optional', amp: 'optional', serial: 'optional' ) One-shot open/configure/start_rx/read/stop_rx/close. Returns String of interleaved cs8 I/Q.
252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 |
# File 'lib/pwn/ffi/hack_rf.rb', line 252 public_class_method def self.capture(opts = {}) want = (opts[:samples] || 262_144).to_i * 2 dev = self.open(serial: opts[:serial]) configure( device: dev, freq_hz: opts[:freq_hz], rate_hz: opts[:rate_hz] || 10_000_000, lna_gain: opts[:lna_gain], vga_gain: opts[:vga_gain], amp: opts[:amp] ) rx = start_rx(device: dev) buf = +'' while buf.bytesize < want chunk = read_sync(handle: rx, timeout: 2.0) break unless chunk buf << chunk end buf[0, want] ensure stop_rx(handle: rx) if defined?(rx) && rx close(device: dev) if defined?(dev) && dev end |
.close(opts = {}) ⇒ Object
- Supported Method Parameters
PWN::FFI::HackRF.close(device: pointer)
105 106 107 108 109 110 111 112 |
# File 'lib/pwn/ffi/hack_rf.rb', line 105 public_class_method def self.close(opts = {}) dev = opts[:device] return unless dev && !dev.null? check!(hackrf_close(dev)) hackrf_exit nil end |
.configure(opts = {}) ⇒ Object
rubocop:disable Naming/PredicateMethod
125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 |
# File 'lib/pwn/ffi/hack_rf.rb', line 125 public_class_method def self.configure(opts = {}) # rubocop:disable Naming/PredicateMethod dev = opts[:device] raise 'ERROR: :device required' if dev.nil? || dev.null? freq = opts[:freq_hz].to_i rate = (opts[:rate_hz] || 10_000_000).to_f lna = (opts[:lna_gain] || 16).to_i vga = (opts[:vga_gain] || 20).to_i amp = opts[:amp] ? 1 : 0 check!(hackrf_set_freq(dev, freq)) check!(hackrf_set_sample_rate(dev, rate)) check!(hackrf_set_lna_gain(dev, lna)) check!(hackrf_set_vga_gain(dev, vga)) check!(hackrf_set_amp_enable(dev, amp)) check!(hackrf_set_baseband_filter_bandwidth(dev, opts[:bb_bw_hz].to_i)) if opts[:bb_bw_hz] true end |
.device_info(opts = {}) ⇒ Object
- Supported Method Parameters
meta = PWN::FFI::HackRF.device_info(device: pointer) Returns { board_id:, board_name:, version: }
148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 |
# File 'lib/pwn/ffi/hack_rf.rb', line 148 public_class_method def self.device_info(opts = {}) dev = opts[:device] raise 'ERROR: :device required' if dev.nil? || dev.null? bid_ptr = PubFFI::MemoryPointer.new(:uint8) check!(hackrf_board_id_read(dev, bid_ptr)) bid = bid_ptr.read_uint8 ver_buf = PubFFI::MemoryPointer.new(:char, 255) check!(hackrf_version_string_read(dev, ver_buf, 255)) { board_id: bid, board_name: hackrf_board_id_name(bid).to_s, version: ver_buf.read_string } end |
.help ⇒ Object
Display Usage for this Module
283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 |
# File 'lib/pwn/ffi/hack_rf.rb', line 283 public_class_method def self.help puts "USAGE: #{self}.available? #{self}.info dev = #{self}.open(serial: nil) #{self}.configure(device: dev, freq_hz:, rate_hz: 10e6, lna_gain: 16, vga_gain: 20, amp: false) rx = #{self}.start_rx(device: dev) data = #{self}.read_sync(handle: rx) # cs8 interleaved I/Q #{self}.stop_rx(handle: rx) data = #{self}.capture(freq_hz:, rate_hz: 10e6, samples: 262_144) #{self}.device_info(device: dev) #{self}.close(device: dev) #{self}.authors " end |
.info ⇒ Object
- Supported Method Parameters
info = PWN::FFI::HackRF.info Returns Hash with library_version / library_release / available.
73 74 75 76 77 78 79 80 81 |
# File 'lib/pwn/ffi/hack_rf.rb', line 73 public_class_method def self.info return { available: false, error: @load_error&. } unless available? { available: true, library_version: hackrf_library_version.to_s, library_release: hackrf_library_release.to_s } end |
.open(opts = {}) ⇒ Object
- Supported Method Parameters
dev = PWN::FFI::HackRF.open(serial: nil) Returns FFI::Pointer (opaque hackrf_device*) or raises.
87 88 89 90 91 92 93 94 95 96 97 98 99 100 |
# File 'lib/pwn/ffi/hack_rf.rb', line 87 public_class_method def self.open(opts = {}) raise 'ERROR: libhackrf not available' unless available? check!(hackrf_init) dev_ptr = PubFFI::MemoryPointer.new(:pointer) serial = opts[:serial] rc = if serial hackrf_open_by_serial(serial.to_s, dev_ptr) else hackrf_open(dev_ptr) end check!(rc) dev_ptr.read_pointer end |
.read_sync(opts = {}) ⇒ Object
- Supported Method Parameters
data = PWN::FFI::HackRF.read_sync( handle: 'required - handle from .start_rx', timeout: 'optional - seconds to wait for a chunk (default 1.0)' ) Returns String of interleaved cs8 I/Q, or nil on timeout.
211 212 213 214 215 216 217 218 219 220 221 222 223 224 |
# File 'lib/pwn/ffi/hack_rf.rb', line 211 public_class_method def self.read_sync(opts = {}) h = opts[:handle] raise 'ERROR: :handle required (from start_rx)' unless h.is_a?(Hash) && h[:queue] q = h[:queue] to = (opts[:timeout] || 1.0).to_f deadline = Time.now + to while q.empty? return nil if Time.now > deadline sleep 0.005 end q.pop end |
.start_rx(opts = {}) ⇒ Object
- Supported Method Parameters
rx = PWN::FFI::HackRF.start_rx( device: 'required - pointer from .open', max_queue: 'optional - max buffered chunks (default 64)' ) Returns { device:, queue:, callback: } handle for read_sync/stop_rx. libhackrf runs the callback on its own libusb thread; FFI acquires the GVL for us, so keep the callback body to a bare byte copy.
182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 |
# File 'lib/pwn/ffi/hack_rf.rb', line 182 public_class_method def self.start_rx(opts = {}) raise 'ERROR: libhackrf not available' unless available? dev = opts[:device] raise 'ERROR: :device required' if dev.nil? || dev.null? max_q = (opts[:max_queue] || 64).to_i queue = Queue.new cb = PubFFI::Function.new(:int, [:pointer]) do |xfer_ptr| begin xfer = Transfer.new(xfer_ptr) len = xfer[:valid_length].to_i queue.push(xfer[:buffer].read_bytes(len)) if len.positive? && queue.size < max_q rescue StandardError nil end 0 end check!(hackrf_start_rx(dev, cb, nil)) { device: dev, queue: queue, callback: cb } end |
.stop_rx(opts = {}) ⇒ Object
- Supported Method Parameters
PWN::FFI::HackRF.stop_rx(handle: rx)
229 230 231 232 233 234 235 236 237 238 239 |
# File 'lib/pwn/ffi/hack_rf.rb', line 229 public_class_method def self.stop_rx(opts = {}) h = opts[:handle] return unless h.is_a?(Hash) && h[:device] hackrf_stop_rx(h[:device]) h[:queue]&.clear h[:callback] = nil nil rescue StandardError nil end |