Class: WifiWand::Platforms::Mac::InterfaceDetector

Inherits:
Object
  • Object
show all
Includes:
StringPredicates, Timing
Defined in:
lib/wifi_wand/platforms/mac/interface_detector.rb

Defined Under Namespace

Classes: DetectionResult

Constant Summary collapse

WIFI_PORT_PATTERNS =
[
  /Wi[-\s]?Fi/i,
  /Air[-\s]?Port/i,
  /Wireless/i,
  /WLAN/i,
].freeze
SYSTEM_PROFILER_NETWORK_ARGS =
%w[system_profiler -json SPNetworkDataType].freeze
SYSTEM_PROFILER_TIMEOUT_SECONDS =
SystemProfilerWifiDataProvider::SYSTEM_PROFILER_TIMEOUT_SECONDS

Instance Method Summary collapse

Methods included from Timing

monotonic_now, status_deadline, status_timeout_for

Methods included from StringPredicates

string_nil_or_blank?, string_nil_or_empty?

Constructor Details

#initialize(command_runner:) ⇒ InterfaceDetector

Returns a new instance of InterfaceDetector.



30
31
32
# File 'lib/wifi_wand/platforms/mac/interface_detector.rb', line 30

def initialize(command_runner:)
  @command_runner = command_runner
end

Instance Method Details

#detect_using_networksetup(timeout_in_secs: nil, known_interface: nil) ⇒ Object

Raises:



103
104
105
106
107
108
109
110
111
# File 'lib/wifi_wand/platforms/mac/interface_detector.rb', line 103

def detect_using_networksetup(timeout_in_secs: nil, known_interface: nil)
  result = wifi_interface_using_networksetup(
    timeout_in_secs: timeout_in_secs,
    known_interface: known_interface
  )
  raise WifiInterfaceError if string_nil_or_empty?(result.interface)

  result
end

#detect_wifi_interface_from_profiler_networks(nets, known_interface: nil, known_service_name: nil) ⇒ Object



171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
# File 'lib/wifi_wand/platforms/mac/interface_detector.rb', line 171

def detect_wifi_interface_from_profiler_networks(nets, known_interface: nil,
  known_service_name: nil)
  wifi = if known_service_name && !known_service_name.empty?
    nets.find { |net| net['_name'] == known_service_name }
  end

  wifi ||= if known_interface && !known_interface.empty?
    nets.find { |net| net['interface'] == known_interface }
  end

  wifi ||= nets.find do |net|
    name = net['_name'].to_s
    WIFI_PORT_PATTERNS.any? { |pattern| pattern.match?(name) }
  end

  DetectionResult.new(
    interface:    present_string(wifi && wifi['interface']),
    service_name: present_string(wifi && wifi['_name'])
  )
end

#detected_wifi_service_name(known_interface: nil, timeout_in_secs: nil) ⇒ Object



95
96
97
98
99
100
101
# File 'lib/wifi_wand/platforms/mac/interface_detector.rb', line 95

def detected_wifi_service_name(known_interface: nil, timeout_in_secs: nil)
  wifi_service_name_from_ports(
    fetch_hardware_ports(timeout_in_secs: timeout_in_secs),
    known_interface:       known_interface,
    fallback_service_name: nil
  )
end

#fetch_hardware_ports(timeout_in_secs: nil) ⇒ Object



34
35
36
37
38
39
40
41
# File 'lib/wifi_wand/platforms/mac/interface_detector.rb', line 34

def fetch_hardware_ports(timeout_in_secs: nil)
  output = command_runner.call(
    %w[networksetup -listallhardwareports],
    timeout_in_secs: timeout_in_secs
  ).stdout

  parse_hardware_ports(output)
end

#is_wifi_interface?(interface) ⇒ Boolean

Returns:

  • (Boolean)


192
193
194
195
196
197
198
199
200
201
# File 'lib/wifi_wand/platforms/mac/interface_detector.rb', line 192

def is_wifi_interface?(interface)
  command_runner.call(['networksetup', '-listpreferredwirelessnetworks', interface])
  true
rescue WifiWand::CommandExecutor::OsCommandError => e
  if e.exitstatus == 10
    false
  else
    raise
  end
end

#parse_hardware_ports(output) ⇒ Object



43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
# File 'lib/wifi_wand/platforms/mac/interface_detector.rb', line 43

def parse_hardware_ports(output)
  ports = []
  current = {}

  output.each_line do |line|
    stripped = line.strip
    next if stripped.empty?

    if (match = stripped.match(/^Hardware Port:\s*(.+)$/))
      ports << current if current[:device]
      current = { name: match[1] }
    elsif (match = stripped.match(/^Device:\s*(.+)$/))
      current[:device] = match[1]
    elsif (match = stripped.match(/^Ethernet Address:\s*(.+)$/))
      current[:ethernet_address] = match[1]
    end
  end

  ports << current if current[:device]
  ports
end

#probe(timeout_in_secs: nil, known_interface: nil, known_service_name: nil) ⇒ Object



113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
# File 'lib/wifi_wand/platforms/mac/interface_detector.rb', line 113

def probe(timeout_in_secs: nil, known_interface: nil, known_service_name: nil)
  deadline = status_deadline(timeout_in_secs)
  networksetup_result = nil

  begin
    networksetup_result = wifi_interface_using_networksetup(
      timeout_in_secs: status_timeout_for(deadline),
      known_interface: known_interface
    )
    if networksetup_result.interface && !networksetup_result.interface.empty?
      return networksetup_result
    end
  rescue WifiWand::Error
    # Fall through to system_profiler fallback.
  end

  service_name = networksetup_result&.service_name || known_service_name
  interface = networksetup_result&.interface || known_interface
  wifi_interface_using_system_profiler(
    timeout_in_secs:    status_timeout_for(deadline),
    known_interface:    interface,
    known_service_name: service_name
  )
end

#wifi_interface_using_networksetup(timeout_in_secs: nil, known_interface: nil) ⇒ Object



138
139
140
141
142
143
144
145
146
147
148
149
# File 'lib/wifi_wand/platforms/mac/interface_detector.rb', line 138

def wifi_interface_using_networksetup(timeout_in_secs: nil, known_interface: nil)
  ports = fetch_hardware_ports(timeout_in_secs: timeout_in_secs)
  service_name = wifi_service_name_from_ports(ports, known_interface: known_interface)

  wifi_port = ports.find do |port|
    port[:name] == service_name && port[:device] && !port[:device].empty?
  end
  wifi_port ||= wifi_port_from_ports(ports)

  iface = wifi_port && wifi_port[:device]
  DetectionResult.new(interface: present_string(iface), service_name: present_string(service_name))
end

#wifi_interface_using_system_profiler(timeout_in_secs: nil, known_interface: nil, known_service_name: nil) ⇒ Object



151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
# File 'lib/wifi_wand/platforms/mac/interface_detector.rb', line 151

def wifi_interface_using_system_profiler(timeout_in_secs: nil, known_interface: nil,
  known_service_name: nil)
  json_text = command_runner.call(
    SYSTEM_PROFILER_NETWORK_ARGS,
    raise_on_error:  true,
    timeout_in_secs: timeout_in_secs || SYSTEM_PROFILER_TIMEOUT_SECONDS
  ).stdout
  return DetectionResult.new if string_nil_or_blank?(json_text)

  net_data = JSON.parse(json_text)
  nets = net_data['SPNetworkDataType']
  return DetectionResult.new if nets.nil? || nets.empty?

  detect_wifi_interface_from_profiler_networks(
    nets,
    known_interface:    known_interface,
    known_service_name: known_service_name
  )
end

#wifi_port_from_ports(ports) ⇒ Object



65
66
67
68
69
70
71
72
# File 'lib/wifi_wand/platforms/mac/interface_detector.rb', line 65

def wifi_port_from_ports(ports)
  ports.find do |port|
    name = port[:name].to_s
    next false if name.empty?

    WIFI_PORT_PATTERNS.any? { |pattern| pattern.match?(name) }
  end
end

#wifi_service_name(known_interface: nil, timeout_in_secs: nil) ⇒ Object



88
89
90
91
92
93
# File 'lib/wifi_wand/platforms/mac/interface_detector.rb', line 88

def wifi_service_name(known_interface: nil, timeout_in_secs: nil)
  wifi_service_name_from_ports(
    fetch_hardware_ports(timeout_in_secs: timeout_in_secs),
    known_interface: known_interface
  )
end

#wifi_service_name_from_ports(ports, known_interface: nil, fallback_service_name: 'Wi-Fi') ⇒ Object



74
75
76
77
78
79
80
81
82
83
84
85
86
# File 'lib/wifi_wand/platforms/mac/interface_detector.rb', line 74

def wifi_service_name_from_ports(ports, known_interface: nil, fallback_service_name: 'Wi-Fi')
  wifi_port = wifi_port_from_ports(ports)
  return wifi_port[:name] if wifi_port && wifi_port[:name] && !wifi_port[:name].empty?

  if known_interface && !known_interface.empty?
    match = ports.find do |port|
      port[:device] == known_interface && port[:name] && !port[:name].empty?
    end
    return match[:name] if match
  end

  fallback_service_name
end