1#  Copyright (C) 2024 The Android Open Source Project
2#
3#  Licensed under the Apache License, Version 2.0 (the "License");
4#  you may not use this file except in compliance with the License.
5#  You may obtain a copy of the License at
6#
7#       http://www.apache.org/licenses/LICENSE-2.0
8#
9#  Unless required by applicable law or agreed to in writing, software
10#  distributed under the License is distributed on an "AS IS" BASIS,
11#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12#  See the License for the specific language governing permissions and
13#  limitations under the License.
14
15"""iperf test util."""
16import time
17from mobly import utils
18from mobly.controllers import android_device
19
20from betocq import nc_constants
21
22# IPv4, 10 sec, 1 stream
23DEFAULT_IPV4_CLIENT_ARGS = '-t 10 -P1'
24DEFAULT_IPV4_SERVER_ARGS = '-J'
25GROUP_OWNER_IPV4_ADDR_LEN_MAX = 15
26IPERF_SERVER_START_DELAY_SEC = 1
27IPERF_DEBUG_TIME_SEC = 300
28
29
30class IPerfServerOnDevice:
31  """Class that handles iperf3 server operations on device."""
32
33  def __init__(self, serial, arg=DEFAULT_IPV4_SERVER_ARGS):
34    self.iperf_str = 'adb -s {serial} shell iperf3 -s {arg}'.format(
35        serial=serial, arg=arg
36    )
37    self.iperf_process = None
38    self.started = False
39
40  def start(self):
41    """Starts iperf server on specified port."""
42    if self.started:
43      return
44
45    cmd = self.iperf_str
46    self.iperf_process = utils.start_standing_subprocess(cmd, shell=True)
47    self.started = True
48
49  def stop(self):
50    if self.started:
51      utils.stop_standing_subprocess(self.iperf_process)
52      self.started = False
53
54
55def run_iperf_test(
56    ad_network_client: android_device.AndroidDevice,
57    ad_network_owner: android_device.AndroidDevice,
58    medium: nc_constants.NearbyConnectionMedium,
59) -> int:
60  """Run iperf test from ad_network_client to ad_network_owner.
61
62  Args:
63    ad_network_client: android device that is the client in the iperf test.
64    ad_network_owner: android device that is the server in the iperf test.
65    medium: wifi medium used in the transfer
66
67  Returns:
68    speed in KB/s if there is a valid result or nc_constants.INVALID_INT.
69  """
70  speed_kbyte_sec = nc_constants.INVALID_INT
71  owner_addr = get_owner_ip_addr(ad_network_client, ad_network_owner, medium)
72  if not owner_addr:
73    return nc_constants.INVALID_INT
74
75  client_arg = DEFAULT_IPV4_CLIENT_ARGS
76  server_arg = DEFAULT_IPV4_SERVER_ARGS
77  # Add IPv6 option if the owner address is an IPv6 address
78  if len(owner_addr) > GROUP_OWNER_IPV4_ADDR_LEN_MAX:
79    client_arg = DEFAULT_IPV4_CLIENT_ARGS + ' -6'
80    server_arg = DEFAULT_IPV4_SERVER_ARGS + ' -6'
81
82  server = IPerfServerOnDevice(ad_network_owner.serial, server_arg)
83  try:
84    ad_network_owner.log.info('Start iperf server')
85    server.start()
86    time.sleep(IPERF_SERVER_START_DELAY_SEC)
87    ad_network_client.log.info(f'Start iperf client {owner_addr}')
88    success, result_list = ad_network_client.run_iperf_client(
89        owner_addr, client_arg
90    )
91    result = ''.join(result_list)
92    last_mbits_sec_index = result.rfind('Mbits/sec')
93    if success and last_mbits_sec_index > 0:
94      speed_mbps = int(
95          float(result[:last_mbits_sec_index].strip().split(' ')[-1])
96      )
97      speed_kbyte_sec = int(speed_mbps * 1024 / 8)
98    else:
99      ad_network_client.log.info('Can not find valid iperf test result')
100  except android_device.adb.AdbError:
101    ad_network_client.log.info('run_iperf_client() failed')
102    owner_ifconfig = get_ifconfig(ad_network_owner)
103    client_ifconfig = get_ifconfig(ad_network_client)
104    ad_network_client.log.info(client_ifconfig)
105    ad_network_client.log.info(owner_addr)
106    ad_network_client.log.info(client_arg)
107    ad_network_owner.log.info(owner_ifconfig)
108    # time.sleep(IPERF_DEBUG_TIME_SEC)
109  else:
110    server.stop()
111  return speed_kbyte_sec
112
113
114def get_owner_ip_addr(
115    ad_network_client: android_device.AndroidDevice,
116    ad_network_owner: android_device.AndroidDevice,
117    medium: nc_constants.NearbyConnectionMedium,
118) -> str:
119  """Get owner ip address which for ipv6 is postfix-ed by the client interface name."""
120  ip_addr = ''
121  if medium == nc_constants.NearbyConnectionMedium.WIFI_DIRECT:
122    ip_addr = get_group_owner_addr(ad_network_client)
123  elif medium == nc_constants.NearbyConnectionMedium.WIFI_LAN:
124    ip_addr = get_wlan_ip_addr(ad_network_owner)
125    if len(ip_addr) > GROUP_OWNER_IPV4_ADDR_LEN_MAX:
126      ip_addr += '%' + get_wlan_ifname(ad_network_client)
127  elif medium == nc_constants.NearbyConnectionMedium.WIFI_HOTSPOT:
128    ip_addr = get_p2p_ip_addr(ad_network_owner)
129    if len(ip_addr) > GROUP_OWNER_IPV4_ADDR_LEN_MAX:
130      ip_addr = ip_addr + '%' + get_wlan_ifname(ad_network_client)
131  elif medium == nc_constants.NearbyConnectionMedium.WIFI_AWARE:
132    ip_addr = get_aware_ip_addr(ad_network_owner)
133    if len(ip_addr) > GROUP_OWNER_IPV4_ADDR_LEN_MAX:
134      ip_addr = ip_addr + '%' + get_aware_ifname(ad_network_client)
135  return ip_addr
136
137
138def get_aware_ip_addr(ad: android_device.AndroidDevice) -> str:
139  """Get wlan ip address from ifconfig."""
140  ifconfig = get_ifconfig_aware(ad)
141  return extract_ip_addr_from_ifconfig(ifconfig)
142
143
144def get_wlan_ip_addr(ad: android_device.AndroidDevice) -> str:
145  """Get wlan ip address from ifconfig."""
146  ifconfig = get_ifconfig_wlan(ad)
147  return extract_ip_addr_from_ifconfig(ifconfig)
148
149
150def get_p2p_ip_addr(ad: android_device.AndroidDevice) -> str:
151  """Get p2p ip address from ifconfig."""
152  ifconfig = get_ifconfig_p2p(ad)
153  return extract_ip_addr_from_ifconfig(ifconfig)
154
155
156def get_aware_ifname(ad: android_device.AndroidDevice) -> str:
157  """Get Aware interface name from ifconfig."""
158  return get_ifconfig_aware(ad).split()[0].strip()
159
160
161def get_wlan_ifname(ad: android_device.AndroidDevice) -> str:
162  """Get WLAN interface name from ifconfig."""
163  ifconfig = get_ifconfig_wlan(ad)
164  found_wlan1 = ifconfig.rfind('wlan1')
165  if found_wlan1 >= 0:
166    return 'wlan1'
167  found_wlan0 = ifconfig.rfind('wlan0')
168  if found_wlan0 >= 0:
169    return 'wlan0'
170  return ''
171
172
173
174def get_p2p_ifname(ad: android_device.AndroidDevice) -> str:
175  """Get P2P interface name from ifconfig."""
176  return  get_ifconfig_p2p(ad).split()[0].strip()
177
178
179def extract_ip_addr_from_ifconfig(ifconfig: str) -> str:
180  """Extract ip address from ifconfig with IPv6 preferred."""
181  ipv6 = get_substr_between_prefix_postfix(
182      ifconfig, 'inet6 addr:', '/64 Scope: Link'
183  )
184  if ipv6:
185    return ipv6
186  ipv4 = get_substr_between_prefix_postfix(ifconfig, 'inet addr:', 'Bcast')
187  if ipv4:
188    return ipv4
189  return ''
190
191
192def get_substr_between_prefix_postfix(
193    string: str, prefix: str, postfix: str
194) -> str:
195  """Get substring between prefix and postfix by searching postfix and then prefix."""
196  right_index = string.rfind(postfix)
197  if right_index == -1:
198    return ''
199  left_index = string[:right_index].rfind(prefix)
200  if left_index > 0:
201    try:
202      return string[left_index + len(prefix): right_index].strip()
203    except IndexError:
204      return ''
205  return ''
206
207
208def get_ifconfig(
209    ad: android_device.AndroidDevice,
210) -> str:
211  """Get network info from adb shell ifconfig."""
212  return ad.adb.shell('ifconfig').decode('utf-8').strip()
213
214
215def get_ifconfig_aware(
216    ad: android_device.AndroidDevice,
217) -> str:
218  """Get aware network info from adb shell ifconfig."""
219  ifconfig = ad.adb.shell('ifconfig | grep -A5 aware').decode('utf-8').strip()
220  # Use the last one if there are multiple aware interfaces.
221  index = ifconfig.rfind('aware')
222  return ifconfig[index:]
223
224
225def get_ifconfig_wlan(
226    ad: android_device.AndroidDevice,
227) -> str:
228  """Get wlan network info from adb shell ifconfig."""
229  return ad.adb.shell('ifconfig | grep -A5 wlan').decode('utf-8').strip()
230
231
232def get_ifconfig_p2p(
233    ad: android_device.AndroidDevice,
234) -> str:
235  """Get p2p network info from adb shell ifconfig."""
236  return ad.adb.shell('ifconfig | grep -A5 p2p').decode('utf-8').strip()
237
238
239def get_group_owner_addr(
240    ad: android_device.AndroidDevice,
241) -> str:
242  """Get the group owner address from adb shell dumpsys wifip2p.
243
244  This works only if group owner address is the last substring of the line.
245
246  Args:
247    ad: android device that is the group client
248  Returns:
249    ipv4 address or ipv6 address with the link interface
250  """
251
252  try:
253    return (
254        ad.adb.shell(
255            'dumpsys wifip2p | egrep "groupOwnerAddress|groupOwnerIpAddress"'
256        )
257        .decode('utf-8')
258        .strip()
259        .split()[-1]
260        .replace('/', '')
261    )
262  except android_device.adb.Error:
263    ad.log.info('Failed to get group owner address.')
264    return ''
265