1 /*
2  * Copyright (C) 2016 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License
15  */
16 
17 package com.android.server.wifi;
18 
19 import static android.net.wifi.WifiScanner.WIFI_BAND_24_GHZ;
20 import static android.net.wifi.WifiScanner.WIFI_BAND_5_GHZ;
21 
22 import static org.junit.Assert.assertArrayEquals;
23 import static org.junit.Assert.assertEquals;
24 import static org.junit.Assert.assertFalse;
25 import static org.junit.Assert.assertNotNull;
26 import static org.junit.Assert.assertNull;
27 import static org.junit.Assert.assertTrue;
28 import static org.junit.Assume.assumeFalse;
29 import static org.junit.Assume.assumeTrue;
30 import static org.mockito.ArgumentMatchers.any;
31 import static org.mockito.ArgumentMatchers.anyString;
32 import static org.mockito.ArgumentMatchers.same;
33 import static org.mockito.Mockito.anyBoolean;
34 import static org.mockito.Mockito.anyInt;
35 import static org.mockito.Mockito.eq;
36 import static org.mockito.Mockito.mock;
37 import static org.mockito.Mockito.never;
38 import static org.mockito.Mockito.times;
39 import static org.mockito.Mockito.verify;
40 import static org.mockito.Mockito.when;
41 
42 import android.net.MacAddress;
43 import android.net.wifi.CoexUnsafeChannel;
44 import android.net.wifi.ScanResult;
45 import android.net.wifi.WifiConfiguration;
46 import android.net.wifi.WifiScanner;
47 import android.net.wifi.nl80211.NativeScanResult;
48 import android.net.wifi.nl80211.RadioChainInfo;
49 import android.net.wifi.nl80211.WifiNl80211Manager;
50 import android.net.wifi.nl80211.WifiNl80211Manager.SendMgmtFrameCallback;
51 import android.os.Bundle;
52 import android.os.Handler;
53 import android.os.WorkSource;
54 import android.text.TextUtils;
55 
56 import androidx.test.filters.SmallTest;
57 
58 import com.android.modules.utils.build.SdkLevel;
59 import com.android.server.wifi.coex.CoexManager;
60 import com.android.server.wifi.util.NativeUtil;
61 import com.android.server.wifi.util.NetdWrapper;
62 
63 import org.junit.Before;
64 import org.junit.Test;
65 import org.mockito.AdditionalMatchers;
66 import org.mockito.ArgumentCaptor;
67 import org.mockito.Mock;
68 import org.mockito.MockitoAnnotations;
69 
70 import java.util.ArrayList;
71 import java.util.Arrays;
72 import java.util.Collections;
73 import java.util.HashSet;
74 import java.util.List;
75 import java.util.Random;
76 import java.util.Set;
77 import java.util.regex.Pattern;
78 
79 /**
80  * Unit tests for {@link com.android.server.wifi.WifiNative}.
81  */
82 @SmallTest
83 public class WifiNativeTest extends WifiBaseTest {
84     private static final String WIFI_IFACE_NAME = "mockWlan";
85     private static final long FATE_REPORT_DRIVER_TIMESTAMP_USEC = 12345;
86     private static final byte[] FATE_REPORT_FRAME_BYTES = new byte[] {
87             'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 0, 1, 2, 3, 4, 5, 6, 7};
88     private static final WifiNative.TxFateReport TX_FATE_REPORT = new WifiNative.TxFateReport(
89             WifiLoggerHal.TX_PKT_FATE_SENT,
90             FATE_REPORT_DRIVER_TIMESTAMP_USEC,
91             WifiLoggerHal.FRAME_TYPE_ETHERNET_II,
92             FATE_REPORT_FRAME_BYTES
93     );
94     private static final WifiNative.RxFateReport RX_FATE_REPORT = new WifiNative.RxFateReport(
95             WifiLoggerHal.RX_PKT_FATE_FW_DROP_INVALID,
96             FATE_REPORT_DRIVER_TIMESTAMP_USEC,
97             WifiLoggerHal.FRAME_TYPE_ETHERNET_II,
98             FATE_REPORT_FRAME_BYTES
99     );
100     private static final FrameTypeMapping[] FRAME_TYPE_MAPPINGS = new FrameTypeMapping[] {
101             new FrameTypeMapping(WifiLoggerHal.FRAME_TYPE_UNKNOWN, "unknown", "N/A"),
102             new FrameTypeMapping(WifiLoggerHal.FRAME_TYPE_ETHERNET_II, "data", "Ethernet"),
103             new FrameTypeMapping(WifiLoggerHal.FRAME_TYPE_80211_MGMT, "802.11 management",
104                     "802.11 Mgmt"),
105             new FrameTypeMapping((byte) 42, "42", "N/A")
106     };
107     private static final FateMapping[] TX_FATE_MAPPINGS = new FateMapping[] {
108             new FateMapping(WifiLoggerHal.TX_PKT_FATE_ACKED, "acked"),
109             new FateMapping(WifiLoggerHal.TX_PKT_FATE_SENT, "sent"),
110             new FateMapping(WifiLoggerHal.TX_PKT_FATE_FW_QUEUED, "firmware queued"),
111             new FateMapping(WifiLoggerHal.TX_PKT_FATE_FW_DROP_INVALID,
112                     "firmware dropped (invalid frame)"),
113             new FateMapping(
114                     WifiLoggerHal.TX_PKT_FATE_FW_DROP_NOBUFS,  "firmware dropped (no bufs)"),
115             new FateMapping(
116                     WifiLoggerHal.TX_PKT_FATE_FW_DROP_OTHER, "firmware dropped (other)"),
117             new FateMapping(WifiLoggerHal.TX_PKT_FATE_DRV_QUEUED, "driver queued"),
118             new FateMapping(WifiLoggerHal.TX_PKT_FATE_DRV_DROP_INVALID,
119                     "driver dropped (invalid frame)"),
120             new FateMapping(WifiLoggerHal.TX_PKT_FATE_DRV_DROP_NOBUFS,
121                     "driver dropped (no bufs)"),
122             new FateMapping(WifiLoggerHal.TX_PKT_FATE_DRV_DROP_OTHER, "driver dropped (other)"),
123             new FateMapping((byte) 42, "42")
124     };
125     private static final FateMapping[] RX_FATE_MAPPINGS = new FateMapping[] {
126             new FateMapping(WifiLoggerHal.RX_PKT_FATE_SUCCESS, "success"),
127             new FateMapping(WifiLoggerHal.RX_PKT_FATE_FW_QUEUED, "firmware queued"),
128             new FateMapping(
129                     WifiLoggerHal.RX_PKT_FATE_FW_DROP_FILTER, "firmware dropped (filter)"),
130             new FateMapping(WifiLoggerHal.RX_PKT_FATE_FW_DROP_INVALID,
131                     "firmware dropped (invalid frame)"),
132             new FateMapping(
133                     WifiLoggerHal.RX_PKT_FATE_FW_DROP_NOBUFS, "firmware dropped (no bufs)"),
134             new FateMapping(
135                     WifiLoggerHal.RX_PKT_FATE_FW_DROP_OTHER, "firmware dropped (other)"),
136             new FateMapping(WifiLoggerHal.RX_PKT_FATE_DRV_QUEUED, "driver queued"),
137             new FateMapping(
138                     WifiLoggerHal.RX_PKT_FATE_DRV_DROP_FILTER, "driver dropped (filter)"),
139             new FateMapping(WifiLoggerHal.RX_PKT_FATE_DRV_DROP_INVALID,
140                     "driver dropped (invalid frame)"),
141             new FateMapping(
142                     WifiLoggerHal.RX_PKT_FATE_DRV_DROP_NOBUFS, "driver dropped (no bufs)"),
143             new FateMapping(WifiLoggerHal.RX_PKT_FATE_DRV_DROP_OTHER, "driver dropped (other)"),
144             new FateMapping((byte) 42, "42")
145     };
146     private static final WifiNl80211Manager.SignalPollResult SIGNAL_POLL_RESULT =
147             new WifiNl80211Manager.SignalPollResult(-60, 12, 6, 5240);
148 
149     private static final Set<Integer> SCAN_FREQ_SET =
150             new HashSet<Integer>() {{
151                 add(2410);
152                 add(2450);
153                 add(5050);
154                 add(5200);
155             }};
156     private static final String TEST_QUOTED_SSID_1 = "\"testSsid1\"";
157     private static final String TEST_QUOTED_SSID_2 = "\"testSsid2\"";
158     private static final int[] TEST_FREQUENCIES_1 = {};
159     private static final int[] TEST_FREQUENCIES_2 = {2500, 5124};
160     private static final List<String> SCAN_HIDDEN_NETWORK_SSID_SET =
161             new ArrayList<String>() {{
162                 add(TEST_QUOTED_SSID_1);
163                 add(TEST_QUOTED_SSID_2);
164             }};
165     private static final List<byte[]> SCAN_HIDDEN_NETWORK_BYTE_SSID_SET =
166             new ArrayList<byte[]>() {{
167                 add(NativeUtil.byteArrayFromArrayList(NativeUtil.decodeSsid(TEST_QUOTED_SSID_1)));
168                 add(NativeUtil.byteArrayFromArrayList(NativeUtil.decodeSsid(TEST_QUOTED_SSID_2)));
169             }};
170 
171     private static final WifiNative.PnoSettings TEST_PNO_SETTINGS =
172             new WifiNative.PnoSettings() {{
173                 isConnected = false;
174                 periodInMs = 6000;
175                 networkList = new WifiNative.PnoNetwork[2];
176                 networkList[0] = new WifiNative.PnoNetwork();
177                 networkList[1] = new WifiNative.PnoNetwork();
178                 networkList[0].ssid = TEST_QUOTED_SSID_1;
179                 networkList[1].ssid = TEST_QUOTED_SSID_2;
180                 networkList[0].frequencies = TEST_FREQUENCIES_1;
181                 networkList[1].frequencies = TEST_FREQUENCIES_2;
182             }};
183     private static final MacAddress TEST_MAC_ADDRESS = MacAddress.fromString("ee:33:a2:94:10:92");
184 
185     private static final String TEST_MAC_ADDRESS_STR = "f4:f5:e8:51:9e:09";
186     private static final String TEST_BSSID_STR = "a8:bd:27:5b:33:72";
187     private static final int TEST_MCS_RATE = 5;
188     private static final int TEST_SEQUENCE_NUM = 0x66b0;
189 
190     private static final byte[] TEST_SSID =
191             new byte[] {'G', 'o', 'o', 'g', 'l', 'e', 'G', 'u', 'e', 's', 't'};
192     private static final byte[] TEST_BSSID =
193             new byte[] {(byte) 0x12, (byte) 0xef, (byte) 0xa1,
194                     (byte) 0x2c, (byte) 0x97, (byte) 0x8b};
195     // This the IE buffer which is consistent with TEST_SSID.
196     private static final byte[] TEST_INFO_ELEMENT_SSID =
197             new byte[] {
198                     // Element ID for SSID.
199                     (byte) 0x00,
200                     // Length of the SSID: 0x0b or 11.
201                     (byte) 0x0b,
202                     // This is string "GoogleGuest"
203                     'G', 'o', 'o', 'g', 'l', 'e', 'G', 'u', 'e', 's', 't'};
204     // RSN IE data indicating EAP key management.
205     private static final byte[] TEST_INFO_ELEMENT_RSN =
206             new byte[] {
207                     // Element ID for RSN.
208                     (byte) 0x30,
209                     // Length of the element data.
210                     (byte) 0x18,
211                     (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x0F, (byte) 0xAC, (byte) 0x02,
212                     (byte) 0x02, (byte) 0x00, (byte) 0x00, (byte) 0x0F, (byte) 0xAC, (byte) 0x04,
213                     (byte) 0x00, (byte) 0x0F, (byte) 0xAC, (byte) 0x02, (byte) 0x01, (byte) 0x00,
214                     (byte) 0x00, (byte) 0x0F, (byte) 0xAC, (byte) 0x01, (byte) 0x00, (byte) 0x00 };
215 
216     private static final int TEST_FREQUENCY = 2456;
217     private static final int TEST_SIGNAL_MBM = -4500;
218     private static final long TEST_TSF = 34455441;
219     private static final int TEST_CAPABILITY = 0b0000_0000_0010_0100;
220     private static final boolean TEST_ASSOCIATED = true;
221     private static final NativeScanResult MOCK_NATIVE_SCAN_RESULT = createMockNativeScanResult();
createMockNativeScanResult()222     private static NativeScanResult createMockNativeScanResult() {
223         NativeScanResult result = new NativeScanResult();
224         result.ssid = TEST_SSID;
225         result.bssid = TEST_BSSID;
226         result.infoElement = TEST_INFO_ELEMENT_SSID;
227         result.frequency = TEST_FREQUENCY;
228         result.signalMbm = TEST_SIGNAL_MBM;
229         result.tsf = TEST_TSF;
230         result.capability = TEST_CAPABILITY;
231         result.associated = TEST_ASSOCIATED;
232         result.radioChainInfos = new ArrayList<>();
233         return result;
234     }
235 
236     private static final RadioChainInfo MOCK_NATIVE_RADIO_CHAIN_INFO_1 = new RadioChainInfo(1, -89);
237     private static final RadioChainInfo MOCK_NATIVE_RADIO_CHAIN_INFO_2 = new RadioChainInfo(0, -78);
238     private static final WorkSource TEST_WORKSOURCE = new WorkSource();
239     private static final WorkSource TEST_WORKSOURCE2 = new WorkSource();
240 
241     @Mock private WifiVendorHal mWifiVendorHal;
242     @Mock private WifiNl80211Manager mWificondControl;
243     @Mock private SupplicantStaIfaceHal mStaIfaceHal;
244     @Mock private HostapdHal mHostapdHal;
245     @Mock private WifiMonitor mWifiMonitor;
246     @Mock private PropertyService mPropertyService;
247     @Mock private WifiMetrics mWifiMetrics;
248     @Mock private Handler mHandler;
249     @Mock private SendMgmtFrameCallback mSendMgmtFrameCallback;
250     @Mock private Random mRandom;
251     @Mock private WifiInjector mWifiInjector;
252     @Mock private NetdWrapper mNetdWrapper;
253     @Mock private CoexManager mCoexManager;
254     @Mock BuildProperties mBuildProperties;
255     @Mock private WifiNative.InterfaceCallback mInterfaceCallback;
256     @Mock private WifiCountryCode.ChangeListener mWifiCountryCodeChangeListener;
257 
258     ArgumentCaptor<WifiNl80211Manager.ScanEventCallback> mScanCallbackCaptor =
259             ArgumentCaptor.forClass(WifiNl80211Manager.ScanEventCallback.class);
260 
261     private WifiNative mWifiNative;
262 
263     @Before
setUp()264     public void setUp() throws Exception {
265         MockitoAnnotations.initMocks(this);
266 
267         when(mWifiVendorHal.initialize(any())).thenReturn(true);
268         when(mWifiVendorHal.isVendorHalSupported()).thenReturn(true);
269         when(mWifiVendorHal.startVendorHal()).thenReturn(true);
270         when(mWifiVendorHal.startVendorHalSta()).thenReturn(true);
271         when(mWifiVendorHal.startVendorHalAp()).thenReturn(true);
272         when(mWifiVendorHal.createStaIface(any(), any())).thenReturn(WIFI_IFACE_NAME);
273 
274         when(mBuildProperties.isEngBuild()).thenReturn(false);
275         when(mBuildProperties.isUserdebugBuild()).thenReturn(false);
276         when(mBuildProperties.isUserBuild()).thenReturn(true);
277 
278         when(mWificondControl.setupInterfaceForClientMode(any(), any(), any(), any())).thenReturn(
279                 true);
280 
281         when(mStaIfaceHal.registerDeathHandler(any())).thenReturn(true);
282         when(mStaIfaceHal.isInitializationComplete()).thenReturn(true);
283         when(mStaIfaceHal.initialize()).thenReturn(true);
284         when(mStaIfaceHal.startDaemon()).thenReturn(true);
285         when(mStaIfaceHal.setupIface(any())).thenReturn(true);
286 
287         when(mWifiInjector.makeNetdWrapper()).thenReturn(mNetdWrapper);
288         when(mWifiInjector.getCoexManager()).thenReturn(mCoexManager);
289 
290         mWifiNative = new WifiNative(
291                 mWifiVendorHal, mStaIfaceHal, mHostapdHal, mWificondControl,
292                 mWifiMonitor, mPropertyService, mWifiMetrics,
293                 mHandler, mRandom, mBuildProperties, mWifiInjector);
294         mWifiNative.initialize();
295     }
296 
297     /**
298      * Verifies that TxFateReport's constructor sets all of the TxFateReport fields.
299      */
300     @Test
testTxFateReportCtorSetsFields()301     public void testTxFateReportCtorSetsFields() {
302         WifiNative.TxFateReport fateReport = new WifiNative.TxFateReport(
303                 WifiLoggerHal.TX_PKT_FATE_SENT,  // non-zero value
304                 FATE_REPORT_DRIVER_TIMESTAMP_USEC,
305                 WifiLoggerHal.FRAME_TYPE_ETHERNET_II,  // non-zero value
306                 FATE_REPORT_FRAME_BYTES
307         );
308         assertEquals(WifiLoggerHal.TX_PKT_FATE_SENT, fateReport.mFate);
309         assertEquals(FATE_REPORT_DRIVER_TIMESTAMP_USEC, fateReport.mDriverTimestampUSec);
310         assertEquals(WifiLoggerHal.FRAME_TYPE_ETHERNET_II, fateReport.mFrameType);
311         assertArrayEquals(FATE_REPORT_FRAME_BYTES, fateReport.mFrameBytes);
312     }
313 
314     /**
315      * Verifies that RxFateReport's constructor sets all of the RxFateReport fields.
316      */
317     @Test
testRxFateReportCtorSetsFields()318     public void testRxFateReportCtorSetsFields() {
319         WifiNative.RxFateReport fateReport = new WifiNative.RxFateReport(
320                 WifiLoggerHal.RX_PKT_FATE_FW_DROP_INVALID,  // non-zero value
321                 FATE_REPORT_DRIVER_TIMESTAMP_USEC,
322                 WifiLoggerHal.FRAME_TYPE_ETHERNET_II,  // non-zero value
323                 FATE_REPORT_FRAME_BYTES
324         );
325         assertEquals(WifiLoggerHal.RX_PKT_FATE_FW_DROP_INVALID, fateReport.mFate);
326         assertEquals(FATE_REPORT_DRIVER_TIMESTAMP_USEC, fateReport.mDriverTimestampUSec);
327         assertEquals(WifiLoggerHal.FRAME_TYPE_ETHERNET_II, fateReport.mFrameType);
328         assertArrayEquals(FATE_REPORT_FRAME_BYTES, fateReport.mFrameBytes);
329     }
330 
331     /**
332      * Verifies the hashCode methods for HiddenNetwork and PnoNetwork classes
333      */
334     @Test
testHashCode()335     public void testHashCode() {
336         WifiNative.HiddenNetwork hiddenNet1 = new WifiNative.HiddenNetwork();
337         hiddenNet1.ssid = new String("sametext");
338 
339         WifiNative.HiddenNetwork hiddenNet2 = new WifiNative.HiddenNetwork();
340         hiddenNet2.ssid = new String("sametext");
341 
342         assertTrue(hiddenNet1.equals(hiddenNet2));
343         assertEquals(hiddenNet1.hashCode(), hiddenNet2.hashCode());
344 
345         WifiNative.PnoNetwork pnoNet1 = new WifiNative.PnoNetwork();
346         pnoNet1.ssid = new String("sametext");
347         pnoNet1.flags = 2;
348         pnoNet1.auth_bit_field = 4;
349         pnoNet1.frequencies = TEST_FREQUENCIES_2;
350 
351         WifiNative.PnoNetwork pnoNet2 = new WifiNative.PnoNetwork();
352         pnoNet2.ssid = new String("sametext");
353         pnoNet2.flags = 2;
354         pnoNet2.auth_bit_field = 4;
355         pnoNet2.frequencies = TEST_FREQUENCIES_2;
356 
357         assertTrue(pnoNet1.equals(pnoNet2));
358         assertEquals(pnoNet1.hashCode(), pnoNet2.hashCode());
359     }
360 
361     // Support classes for test{Tx,Rx}FateReportToString.
362     private static class FrameTypeMapping {
363         byte mTypeNumber;
364         String mExpectedTypeText;
365         String mExpectedProtocolText;
FrameTypeMapping(byte typeNumber, String expectedTypeText, String expectedProtocolText)366         FrameTypeMapping(byte typeNumber, String expectedTypeText, String expectedProtocolText) {
367             this.mTypeNumber = typeNumber;
368             this.mExpectedTypeText = expectedTypeText;
369             this.mExpectedProtocolText = expectedProtocolText;
370         }
371     }
372     private static class FateMapping {
373         byte mFateNumber;
374         String mExpectedText;
FateMapping(byte fateNumber, String expectedText)375         FateMapping(byte fateNumber, String expectedText) {
376             this.mFateNumber = fateNumber;
377             this.mExpectedText = expectedText;
378         }
379     }
380 
381     /**
382      * Verifies that FateReport.getTableHeader() prints the right header.
383      */
384     @Test
testFateReportTableHeader()385     public void testFateReportTableHeader() {
386         final String header = WifiNative.FateReport.getTableHeader();
387         assertEquals(
388                 "\nTime usec        Walltime      Direction  Fate                              "
389                 + "Protocol      Type                     Result\n"
390                 + "---------        --------      ---------  ----                              "
391                 + "--------      ----                     ------\n", header);
392     }
393 
394     /**
395      * Verifies that TxFateReport.toTableRowString() includes the information we care about.
396      */
397     @Test
testTxFateReportToTableRowString()398     public void testTxFateReportToTableRowString() {
399         WifiNative.TxFateReport fateReport = TX_FATE_REPORT;
400         assertTrue(
401                 fateReport.toTableRowString().replaceAll("\\s+", " ").trim().matches(
402                     FATE_REPORT_DRIVER_TIMESTAMP_USEC + " "  // timestamp
403                             + "\\d{2}:\\d{2}:\\d{2}\\.\\d{3} "  // walltime
404                             + "TX "  // direction
405                             + "sent "  // fate
406                             + "Ethernet "  // type
407                             + "N/A "  // protocol
408                             + "N/A"  // result
409                 )
410         );
411 
412         for (FrameTypeMapping frameTypeMapping : FRAME_TYPE_MAPPINGS) {
413             fateReport = new WifiNative.TxFateReport(
414                     WifiLoggerHal.TX_PKT_FATE_SENT,
415                     FATE_REPORT_DRIVER_TIMESTAMP_USEC,
416                     frameTypeMapping.mTypeNumber,
417                     FATE_REPORT_FRAME_BYTES
418             );
419             assertTrue(
420                     fateReport.toTableRowString().replaceAll("\\s+", " ").trim().matches(
421                             FATE_REPORT_DRIVER_TIMESTAMP_USEC + " "  // timestamp
422                                     + "\\d{2}:\\d{2}:\\d{2}\\.\\d{3} "  // walltime
423                                     + "TX "  // direction
424                                     + "sent "  // fate
425                                     + frameTypeMapping.mExpectedProtocolText + " "  // type
426                                     + "N/A "  // protocol
427                                     + "N/A"  // result
428                     )
429             );
430         }
431 
432         for (FateMapping fateMapping : TX_FATE_MAPPINGS) {
433             fateReport = new WifiNative.TxFateReport(
434                     fateMapping.mFateNumber,
435                     FATE_REPORT_DRIVER_TIMESTAMP_USEC,
436                     WifiLoggerHal.FRAME_TYPE_80211_MGMT,
437                     FATE_REPORT_FRAME_BYTES
438             );
439             assertTrue(
440                     fateReport.toTableRowString().replaceAll("\\s+", " ").trim().matches(
441                             FATE_REPORT_DRIVER_TIMESTAMP_USEC + " "  // timestamp
442                                     + "\\d{2}:\\d{2}:\\d{2}\\.\\d{3} "  // walltime
443                                     + "TX "  // direction
444                                     + Pattern.quote(fateMapping.mExpectedText) + " "  // fate
445                                     + "802.11 Mgmt "  // type
446                                     + "N/A "  // protocol
447                                     + "N/A"  // result
448                     )
449             );
450         }
451     }
452 
453     /**
454      * Verifies that TxFateReport.toVerboseStringWithPiiAllowed() includes the information we care
455      * about.
456      */
457     @Test
testTxFateReportToVerboseStringWithPiiAllowed()458     public void testTxFateReportToVerboseStringWithPiiAllowed() {
459         WifiNative.TxFateReport fateReport = TX_FATE_REPORT;
460 
461         String verboseFateString = fateReport.toVerboseStringWithPiiAllowed();
462         assertTrue(verboseFateString.contains("Frame direction: TX"));
463         assertTrue(verboseFateString.contains("Frame timestamp: 12345"));
464         assertTrue(verboseFateString.contains("Frame fate: sent"));
465         assertTrue(verboseFateString.contains("Frame type: data"));
466         assertTrue(verboseFateString.contains("Frame protocol: Ethernet"));
467         assertTrue(verboseFateString.contains("Frame protocol type: N/A"));
468         assertTrue(verboseFateString.contains("Frame length: 16"));
469         assertTrue(verboseFateString.contains(
470                 "61 62 63 64 65 66 67 68 00 01 02 03 04 05 06 07")); // hex dump
471         // TODO(quiche): uncomment this, once b/27975149 is fixed.
472         // assertTrue(verboseFateString.contains("abcdefgh........"));  // hex dump
473 
474         for (FrameTypeMapping frameTypeMapping : FRAME_TYPE_MAPPINGS) {
475             fateReport = new WifiNative.TxFateReport(
476                     WifiLoggerHal.TX_PKT_FATE_SENT,
477                     FATE_REPORT_DRIVER_TIMESTAMP_USEC,
478                     frameTypeMapping.mTypeNumber,
479                     FATE_REPORT_FRAME_BYTES
480             );
481             verboseFateString = fateReport.toVerboseStringWithPiiAllowed();
482             assertTrue(verboseFateString.contains("Frame type: "
483                     + frameTypeMapping.mExpectedTypeText));
484         }
485 
486         for (FateMapping fateMapping : TX_FATE_MAPPINGS) {
487             fateReport = new WifiNative.TxFateReport(
488                     fateMapping.mFateNumber,
489                     FATE_REPORT_DRIVER_TIMESTAMP_USEC,
490                     WifiLoggerHal.FRAME_TYPE_80211_MGMT,
491                     FATE_REPORT_FRAME_BYTES
492             );
493             verboseFateString = fateReport.toVerboseStringWithPiiAllowed();
494             assertTrue(verboseFateString.contains("Frame fate: " + fateMapping.mExpectedText));
495         }
496     }
497 
498     /**
499      * Verifies that RxFateReport.toTableRowString() includes the information we care about.
500      */
501     @Test
testRxFateReportToTableRowString()502     public void testRxFateReportToTableRowString() {
503         WifiNative.RxFateReport fateReport = RX_FATE_REPORT;
504         assertTrue(
505                 fateReport.toTableRowString().replaceAll("\\s+", " ").trim().matches(
506                         FATE_REPORT_DRIVER_TIMESTAMP_USEC + " "  // timestamp
507                                 + "\\d{2}:\\d{2}:\\d{2}\\.\\d{3} "  // walltime
508                                 + "RX "  // direction
509                                 + Pattern.quote("firmware dropped (invalid frame) ")  // fate
510                                 + "Ethernet "  // type
511                                 + "N/A "  // protocol
512                                 + "N/A"  // result
513                 )
514         );
515 
516         // FrameTypeMappings omitted, as they're the same as for TX.
517 
518         for (FateMapping fateMapping : RX_FATE_MAPPINGS) {
519             fateReport = new WifiNative.RxFateReport(
520                     fateMapping.mFateNumber,
521                     FATE_REPORT_DRIVER_TIMESTAMP_USEC,
522                     WifiLoggerHal.FRAME_TYPE_80211_MGMT,
523                     FATE_REPORT_FRAME_BYTES
524             );
525             assertTrue(
526                     fateReport.toTableRowString().replaceAll("\\s+", " ").trim().matches(
527                             FATE_REPORT_DRIVER_TIMESTAMP_USEC + " "  // timestamp
528                                     + "\\d{2}:\\d{2}:\\d{2}\\.\\d{3} "  // walltime
529                                     + "RX "  // direction
530                                     + Pattern.quote(fateMapping.mExpectedText) + " " // fate
531                                     + "802.11 Mgmt "  // type
532                                     + "N/A " // protocol
533                                     + "N/A"  // result
534                     )
535             );
536         }
537     }
538 
539     /**
540      * Verifies that RxFateReport.toVerboseStringWithPiiAllowed() includes the information we care
541      * about.
542      */
543     @Test
testRxFateReportToVerboseStringWithPiiAllowed()544     public void testRxFateReportToVerboseStringWithPiiAllowed() {
545         WifiNative.RxFateReport fateReport = RX_FATE_REPORT;
546 
547         String verboseFateString = fateReport.toVerboseStringWithPiiAllowed();
548         assertTrue(verboseFateString.contains("Frame direction: RX"));
549         assertTrue(verboseFateString.contains("Frame timestamp: 12345"));
550         assertTrue(verboseFateString.contains("Frame fate: firmware dropped (invalid frame)"));
551         assertTrue(verboseFateString.contains("Frame type: data"));
552         assertTrue(verboseFateString.contains("Frame protocol: Ethernet"));
553         assertTrue(verboseFateString.contains("Frame protocol type: N/A"));
554         assertTrue(verboseFateString.contains("Frame length: 16"));
555         assertTrue(verboseFateString.contains(
556                 "61 62 63 64 65 66 67 68 00 01 02 03 04 05 06 07")); // hex dump
557         // TODO(quiche): uncomment this, once b/27975149 is fixed.
558         // assertTrue(verboseFateString.contains("abcdefgh........"));  // hex dump
559 
560         // FrameTypeMappings omitted, as they're the same as for TX.
561 
562         for (FateMapping fateMapping : RX_FATE_MAPPINGS) {
563             fateReport = new WifiNative.RxFateReport(
564                     fateMapping.mFateNumber,
565                     FATE_REPORT_DRIVER_TIMESTAMP_USEC,
566                     WifiLoggerHal.FRAME_TYPE_80211_MGMT,
567                     FATE_REPORT_FRAME_BYTES
568             );
569             verboseFateString = fateReport.toVerboseStringWithPiiAllowed();
570             assertTrue(verboseFateString.contains("Frame fate: " + fateMapping.mExpectedText));
571         }
572     }
573 
574     /**
575      * Verifies that startPktFateMonitoring returns false when HAL is not started.
576      */
577     @Test
testStartPktFateMonitoringReturnsFalseWhenHalIsNotStarted()578     public void testStartPktFateMonitoringReturnsFalseWhenHalIsNotStarted() {
579         assertFalse(mWifiNative.isHalStarted());
580         assertFalse(mWifiNative.startPktFateMonitoring(WIFI_IFACE_NAME));
581     }
582 
583     /**
584      * Verifies that getTxPktFates returns error when HAL is not started.
585      */
586     @Test
testGetTxPktFatesReturnsErrorWhenHalIsNotStarted()587     public void testGetTxPktFatesReturnsErrorWhenHalIsNotStarted() {
588         assertFalse(mWifiNative.isHalStarted());
589         assertEquals(0, mWifiNative.getTxPktFates(WIFI_IFACE_NAME).size());
590     }
591 
592     /**
593      * Verifies that getRxPktFates returns error when HAL is not started.
594      */
595     @Test
testGetRxPktFatesReturnsErrorWhenHalIsNotStarted()596     public void testGetRxPktFatesReturnsErrorWhenHalIsNotStarted() {
597         assertFalse(mWifiNative.isHalStarted());
598         assertEquals(0, mWifiNative.getRxPktFates(WIFI_IFACE_NAME).size());
599     }
600 
601     // TODO(quiche): Add tests for the success cases (when HAL has been started). Specifically:
602     // - testStartPktFateMonitoringCallsHalIfHalIsStarted()
603     // - testGetTxPktFatesCallsHalIfHalIsStarted()
604     // - testGetRxPktFatesCallsHalIfHalIsStarted()
605     //
606     // Adding these tests is difficult to do at the moment, because we can't mock out the HAL
607     // itself. Also, we can't mock out the native methods, because those methods are private.
608     // b/28005116.
609 
610     /** Verifies that getDriverStateDumpNative returns null when HAL is not started. */
611     @Test
testGetDriverStateDumpReturnsNullWhenHalIsNotStarted()612     public void testGetDriverStateDumpReturnsNullWhenHalIsNotStarted() {
613         assertEquals(null, mWifiNative.getDriverStateDump());
614     }
615 
616     // TODO(b/28005116): Add test for the success case of getDriverStateDump().
617 
618     /**
619      * Verifies client mode + scan success.
620      */
621     @Test
testClientModeScanSuccess()622     public void testClientModeScanSuccess() {
623         mWifiNative.setupInterfaceForClientInConnectivityMode(null, TEST_WORKSOURCE);
624         verify(mWificondControl).setupInterfaceForClientMode(eq(WIFI_IFACE_NAME), any(),
625                 mScanCallbackCaptor.capture(), any());
626 
627         mScanCallbackCaptor.getValue().onScanResultReady();
628         verify(mWifiMonitor).broadcastScanResultEvent(WIFI_IFACE_NAME);
629     }
630 
631     /**
632      * Verifies client mode + scan failure.
633      */
634     @Test
testClientModeScanFailure()635     public void testClientModeScanFailure() {
636         mWifiNative.setupInterfaceForClientInConnectivityMode(null, TEST_WORKSOURCE);
637         verify(mWificondControl).setupInterfaceForClientMode(eq(WIFI_IFACE_NAME), any(),
638                 mScanCallbackCaptor.capture(), any());
639 
640         mScanCallbackCaptor.getValue().onScanFailed();
641         verify(mWifiMonitor).broadcastScanFailedEvent(WIFI_IFACE_NAME);
642     }
643 
644     /**
645      * Verifies client mode + PNO scan success.
646      */
647     @Test
testClientModePnoScanSuccess()648     public void testClientModePnoScanSuccess() {
649         mWifiNative.setupInterfaceForClientInConnectivityMode(null, TEST_WORKSOURCE);
650         verify(mWificondControl).setupInterfaceForClientMode(eq(WIFI_IFACE_NAME), any(),
651                 any(), mScanCallbackCaptor.capture());
652 
653         mScanCallbackCaptor.getValue().onScanResultReady();
654         verify(mWifiMonitor).broadcastPnoScanResultEvent(WIFI_IFACE_NAME);
655         verify(mWifiMetrics).incrementPnoFoundNetworkEventCount();
656     }
657 
658     /**
659      * Verifies client mode + PNO scan failure.
660      */
661     @Test
testClientModePnoScanFailure()662     public void testClientModePnoScanFailure() {
663         mWifiNative.setupInterfaceForClientInConnectivityMode(null, TEST_WORKSOURCE);
664         verify(mWificondControl).setupInterfaceForClientMode(eq(WIFI_IFACE_NAME), any(),
665                 any(), mScanCallbackCaptor.capture());
666 
667         mScanCallbackCaptor.getValue().onScanFailed();
668         verify(mWifiMetrics).incrementPnoScanFailedCount();
669     }
670 
671     /**
672      * Verifies scan mode + scan success.
673      */
674     @Test
testScanModeScanSuccess()675     public void testScanModeScanSuccess() {
676         mWifiNative.setupInterfaceForClientInScanMode(null, TEST_WORKSOURCE);
677         verify(mWificondControl).setupInterfaceForClientMode(eq(WIFI_IFACE_NAME), any(),
678                 mScanCallbackCaptor.capture(), any());
679 
680         mScanCallbackCaptor.getValue().onScanResultReady();
681         verify(mWifiMonitor).broadcastScanResultEvent(WIFI_IFACE_NAME);
682     }
683 
684     /**
685      * Verifies scan mode + scan failure.
686      */
687     @Test
testScanModeScanFailure()688     public void testScanModeScanFailure() {
689         mWifiNative.setupInterfaceForClientInScanMode(null, TEST_WORKSOURCE);
690         verify(mWificondControl).setupInterfaceForClientMode(eq(WIFI_IFACE_NAME), any(),
691                 mScanCallbackCaptor.capture(), any());
692 
693         mScanCallbackCaptor.getValue().onScanFailed();
694         verify(mWifiMonitor).broadcastScanFailedEvent(WIFI_IFACE_NAME);
695     }
696 
697     /**
698      * Verifies scan mode + PNO scan success.
699      */
700     @Test
testScanModePnoScanSuccess()701     public void testScanModePnoScanSuccess() {
702         mWifiNative.setupInterfaceForClientInScanMode(null, TEST_WORKSOURCE);
703         verify(mWificondControl).setupInterfaceForClientMode(eq(WIFI_IFACE_NAME), any(),
704                 any(), mScanCallbackCaptor.capture());
705 
706         mScanCallbackCaptor.getValue().onScanResultReady();
707         verify(mWifiMonitor).broadcastPnoScanResultEvent(WIFI_IFACE_NAME);
708         verify(mWifiMetrics).incrementPnoFoundNetworkEventCount();
709     }
710 
711     /**
712      * Verifies scan mode + PNO scan failure.
713      */
714     @Test
testScanModePnoScanFailure()715     public void testScanModePnoScanFailure() {
716         mWifiNative.setupInterfaceForClientInScanMode(null, TEST_WORKSOURCE);
717         verify(mWificondControl).setupInterfaceForClientMode(eq(WIFI_IFACE_NAME), any(),
718                 any(), mScanCallbackCaptor.capture());
719 
720         mScanCallbackCaptor.getValue().onScanFailed();
721         verify(mWifiMetrics).incrementPnoScanFailedCount();
722     }
723 
724     /**
725      * Verifies starting the hal results in coex unsafe channels being updated with cached values.
726      */
727     @Test
testStartHalUpdatesCoexUnsafeChannels()728     public void testStartHalUpdatesCoexUnsafeChannels() {
729         assumeTrue(SdkLevel.isAtLeastS());
730         final List<CoexUnsafeChannel> unsafeChannels = new ArrayList<>();
731         unsafeChannels.add(new CoexUnsafeChannel(WIFI_BAND_24_GHZ, 6));
732         unsafeChannels.add(new CoexUnsafeChannel(WIFI_BAND_5_GHZ, 36));
733         final int restrictions = 0;
734         when(mCoexManager.getCoexUnsafeChannels()).thenReturn(unsafeChannels);
735         when(mCoexManager.getCoexRestrictions()).thenReturn(restrictions);
736         mWifiNative.setCoexUnsafeChannels(unsafeChannels, restrictions);
737 
738         mWifiNative.setupInterfaceForClientInConnectivityMode(null, TEST_WORKSOURCE);
739         verify(mWifiVendorHal, times(2)).setCoexUnsafeChannels(unsafeChannels, restrictions);
740 
741         mWifiNative.teardownAllInterfaces();
742         mWifiNative.setupInterfaceForClientInScanMode(null, TEST_WORKSOURCE);
743         verify(mWifiVendorHal, times(3)).setCoexUnsafeChannels(unsafeChannels, restrictions);
744 
745         mWifiNative.teardownAllInterfaces();
746         mWifiNative.setupInterfaceForSoftApMode(null, TEST_WORKSOURCE, WIFI_BAND_24_GHZ, false);
747         verify(mWifiVendorHal, times(4)).setCoexUnsafeChannels(unsafeChannels, restrictions);
748     }
749 
750     /**
751      * Verifies that signalPoll() calls underlying WificondControl.
752      */
753     @Test
testSignalPoll()754     public void testSignalPoll() throws Exception {
755         when(mWificondControl.signalPoll(WIFI_IFACE_NAME))
756                 .thenReturn(SIGNAL_POLL_RESULT);
757 
758         WifiNl80211Manager.SignalPollResult pollResult = mWifiNative.signalPoll(WIFI_IFACE_NAME);
759         assertEquals(SIGNAL_POLL_RESULT.currentRssiDbm, pollResult.currentRssiDbm);
760         assertEquals(SIGNAL_POLL_RESULT.txBitrateMbps, pollResult.txBitrateMbps);
761         assertEquals(SIGNAL_POLL_RESULT.associationFrequencyMHz,
762                 pollResult.associationFrequencyMHz);
763         assertEquals(SIGNAL_POLL_RESULT.rxBitrateMbps, pollResult.rxBitrateMbps);
764 
765         verify(mWificondControl).signalPoll(WIFI_IFACE_NAME);
766     }
767 
768     /**
769      * Verifies that scan() calls underlying WificondControl.
770      */
771     @Test
testScan()772     public void testScan() throws Exception {
773         // This test will not run if the device has SDK level S or later
774         assumeFalse(SdkLevel.isAtLeastS());
775         mWifiNative.scan(WIFI_IFACE_NAME, WifiScanner.SCAN_TYPE_HIGH_ACCURACY, SCAN_FREQ_SET,
776                 SCAN_HIDDEN_NETWORK_SSID_SET, false);
777         ArgumentCaptor<List<byte[]>> ssidSetCaptor = ArgumentCaptor.forClass(List.class);
778         verify(mWificondControl).startScan(
779                 eq(WIFI_IFACE_NAME), eq(WifiScanner.SCAN_TYPE_HIGH_ACCURACY),
780                 eq(SCAN_FREQ_SET), ssidSetCaptor.capture());
781         List<byte[]> ssidSet = ssidSetCaptor.getValue();
782         assertArrayEquals(ssidSet.toArray(), SCAN_HIDDEN_NETWORK_BYTE_SSID_SET.toArray());
783     }
784 
785     /**
786      * Verifies that scan() calls the new startScan API with a Bundle when the Sdk level
787      * is S or above.
788      */
789     @Test
testScanWithBundle()790     public void testScanWithBundle() throws Exception {
791         // This test will only run if the device has SDK level S and later.
792         assumeTrue(SdkLevel.isAtLeastS());
793         mWifiNative.scan(WIFI_IFACE_NAME, WifiScanner.SCAN_TYPE_HIGH_ACCURACY, SCAN_FREQ_SET,
794                 SCAN_HIDDEN_NETWORK_SSID_SET, true);
795         ArgumentCaptor<List<byte[]>> ssidSetCaptor = ArgumentCaptor.forClass(List.class);
796         ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
797         verify(mWificondControl).startScan(
798                 eq(WIFI_IFACE_NAME), eq(WifiScanner.SCAN_TYPE_HIGH_ACCURACY),
799                 eq(SCAN_FREQ_SET), ssidSetCaptor.capture(), bundleCaptor.capture());
800         List<byte[]> ssidSet = ssidSetCaptor.getValue();
801         assertArrayEquals(ssidSet.toArray(), SCAN_HIDDEN_NETWORK_BYTE_SSID_SET.toArray());
802         Bundle bundle = bundleCaptor.getValue();
803         assertTrue(bundle.getBoolean(WifiNl80211Manager.SCANNING_PARAM_ENABLE_6GHZ_RNR));
804     }
805 
806     /**
807      * Verifies that startPnoscan() calls underlying WificondControl.
808      */
809     @Test
testStartPnoScanOnRequestProcessed()810     public void testStartPnoScanOnRequestProcessed() throws Exception {
811         mWifiNative.startPnoScan(WIFI_IFACE_NAME, TEST_PNO_SETTINGS);
812 
813         ArgumentCaptor<WifiNl80211Manager.PnoScanRequestCallback> captor =
814                 ArgumentCaptor.forClass(WifiNl80211Manager.PnoScanRequestCallback.class);
815         verify(mWificondControl).startPnoScan(eq(WIFI_IFACE_NAME),
816                 eq(TEST_PNO_SETTINGS.toNativePnoSettings()), any(), captor.capture());
817         captor.getValue().onPnoRequestSucceeded();
818         verify(mWifiMetrics).incrementPnoScanStartAttemptCount();
819     }
820 
821     /**
822      * Verifies that startPnoscan() calls underlying WificondControl.
823      */
824     @Test
testStartPnoScanOnRequestFailed()825     public void testStartPnoScanOnRequestFailed() throws Exception {
826         mWifiNative.startPnoScan(WIFI_IFACE_NAME, TEST_PNO_SETTINGS);
827 
828         ArgumentCaptor<WifiNl80211Manager.PnoScanRequestCallback> captor =
829                 ArgumentCaptor.forClass(WifiNl80211Manager.PnoScanRequestCallback.class);
830         verify(mWificondControl).startPnoScan(eq(WIFI_IFACE_NAME),
831                 eq(TEST_PNO_SETTINGS.toNativePnoSettings()), any(), captor.capture());
832         captor.getValue().onPnoRequestFailed();
833         verify(mWifiMetrics).incrementPnoScanStartAttemptCount();
834         verify(mWifiMetrics).incrementPnoScanFailedCount();
835     }
836 
837     /**
838      * Verifies that stopPnoscan() calls underlying WificondControl.
839      */
840     @Test
testStopPnoScan()841     public void testStopPnoScan() throws Exception {
842         mWifiNative.stopPnoScan(WIFI_IFACE_NAME);
843         verify(mWificondControl).stopPnoScan(WIFI_IFACE_NAME);
844     }
845 
846     /**
847      * Verifies that getScanResults() can parse NativeScanResult from wificond correctly,
848      */
849     @Test
testGetScanResults()850     public void testGetScanResults() {
851         // Mock the returned array of NativeScanResult.
852         List<NativeScanResult> mockScanResults = Arrays.asList(MOCK_NATIVE_SCAN_RESULT);
853         when(mWificondControl.getScanResults(anyString(), anyInt())).thenReturn(mockScanResults);
854 
855         ArrayList<ScanDetail> returnedScanResults = mWifiNative.getScanResults(WIFI_IFACE_NAME);
856         assertEquals(mockScanResults.size(), returnedScanResults.size());
857         // Since NativeScanResult is organized differently from ScanResult, this only checks
858         // a few fields.
859         for (int i = 0; i < mockScanResults.size(); i++) {
860             assertArrayEquals(mockScanResults.get(i).getSsid(),
861                     returnedScanResults.get(i).getScanResult().SSID.getBytes());
862             assertEquals(mockScanResults.get(i).getFrequencyMhz(),
863                     returnedScanResults.get(i).getScanResult().frequency);
864             assertEquals(mockScanResults.get(i).getTsf(),
865                     returnedScanResults.get(i).getScanResult().timestamp);
866         }
867     }
868 
869     /**
870      * Verifies that getScanResults() can parse NativeScanResult from wificond correctly,
871      * when there is radio chain info.
872      */
873     @Test
testGetScanResultsWithRadioChainInfo()874     public void testGetScanResultsWithRadioChainInfo() throws Exception {
875         // Mock the returned array of NativeScanResult.
876         NativeScanResult nativeScanResult = createMockNativeScanResult();
877         // Add radio chain info
878         List<RadioChainInfo> nativeRadioChainInfos = Arrays.asList(
879                 MOCK_NATIVE_RADIO_CHAIN_INFO_1, MOCK_NATIVE_RADIO_CHAIN_INFO_2);
880         nativeScanResult.radioChainInfos = nativeRadioChainInfos;
881         List<NativeScanResult> mockScanResults = Arrays.asList(nativeScanResult);
882 
883         when(mWificondControl.getScanResults(anyString(), anyInt())).thenReturn(mockScanResults);
884 
885         ArrayList<ScanDetail> returnedScanResults = mWifiNative.getScanResults(WIFI_IFACE_NAME);
886         assertEquals(mockScanResults.size(), returnedScanResults.size());
887         // Since NativeScanResult is organized differently from ScanResult, this only checks
888         // a few fields.
889         for (int i = 0; i < mockScanResults.size(); i++) {
890             assertArrayEquals(mockScanResults.get(i).getSsid(),
891                     returnedScanResults.get(i).getScanResult().SSID.getBytes());
892             assertEquals(mockScanResults.get(i).getFrequencyMhz(),
893                     returnedScanResults.get(i).getScanResult().frequency);
894             assertEquals(mockScanResults.get(i).getTsf(),
895                     returnedScanResults.get(i).getScanResult().timestamp);
896             ScanResult.RadioChainInfo[] scanRcis = returnedScanResults.get(
897                     i).getScanResult().radioChainInfos;
898             assertEquals(nativeRadioChainInfos.size(), scanRcis.length);
899             for (int j = 0; j < scanRcis.length; ++j) {
900                 assertEquals(nativeRadioChainInfos.get(j).getChainId(), scanRcis[j].id);
901                 assertEquals(nativeRadioChainInfos.get(j).getLevelDbm(), scanRcis[j].level);
902             }
903         }
904     }
905 
906     /**
907      * Verifies that connectToNetwork() calls underlying WificondControl and SupplicantStaIfaceHal.
908      */
909     @Test
testConnectToNetwork()910     public void testConnectToNetwork() throws Exception {
911         WifiConfiguration config = mock(WifiConfiguration.class);
912         mWifiNative.connectToNetwork(WIFI_IFACE_NAME, config);
913         // connectToNetwork() should abort ongoing scan before connection.
914         verify(mWificondControl).abortScan(WIFI_IFACE_NAME);
915         verify(mStaIfaceHal).connectToNetwork(WIFI_IFACE_NAME, config);
916     }
917 
918     /**
919      * Verifies that roamToNetwork() calls underlying WificondControl and SupplicantStaIfaceHal.
920      */
921     @Test
testRoamToNetwork()922     public void testRoamToNetwork() throws Exception {
923         WifiConfiguration config = mock(WifiConfiguration.class);
924         mWifiNative.roamToNetwork(WIFI_IFACE_NAME, config);
925         // roamToNetwork() should abort ongoing scan before connection.
926         verify(mWificondControl).abortScan(WIFI_IFACE_NAME);
927         verify(mStaIfaceHal).roamToNetwork(WIFI_IFACE_NAME, config);
928     }
929 
930     /**
931      * Verifies that removeIfaceInstanceFromBridgedApIface() calls underlying WifiVendorHal.
932      */
933     @Test
testRemoveIfaceInstanceFromBridgedApIface()934     public void testRemoveIfaceInstanceFromBridgedApIface() throws Exception {
935         mWifiNative.removeIfaceInstanceFromBridgedApIface(
936                 "br_" + WIFI_IFACE_NAME, WIFI_IFACE_NAME);
937         verify(mWifiVendorHal).removeIfaceInstanceFromBridgedApIface(
938                 "br_" + WIFI_IFACE_NAME, WIFI_IFACE_NAME);
939     }
940 
941     /**
942      * Verifies that setMacAddress() calls underlying WifiVendorHal.
943      */
944     @Test
testStaSetMacAddress()945     public void testStaSetMacAddress() throws Exception {
946         mWifiNative.setStaMacAddress(WIFI_IFACE_NAME, TEST_MAC_ADDRESS);
947         verify(mStaIfaceHal).disconnect(WIFI_IFACE_NAME);
948         verify(mWifiVendorHal).setStaMacAddress(WIFI_IFACE_NAME, TEST_MAC_ADDRESS);
949     }
950 
951     /**
952      * Verifies that setMacAddress() calls underlying WifiVendorHal.
953      */
954     @Test
testApSetMacAddress()955     public void testApSetMacAddress() throws Exception {
956         mWifiNative.setApMacAddress(WIFI_IFACE_NAME, TEST_MAC_ADDRESS);
957         verify(mWifiVendorHal).setApMacAddress(WIFI_IFACE_NAME, TEST_MAC_ADDRESS);
958     }
959 
960     /**
961      * Verifies that resetApMacToFactoryMacAddress() calls underlying WifiVendorHal.
962      */
963     @Test
testResetApMacToFactoryMacAddress()964     public void testResetApMacToFactoryMacAddress() throws Exception {
965         mWifiNative.resetApMacToFactoryMacAddress(WIFI_IFACE_NAME);
966         verify(mWifiVendorHal).resetApMacToFactoryMacAddress(WIFI_IFACE_NAME);
967     }
968 
969     /**
970      * Verifies that setCoexUnsafeChannels() calls underlying WifiVendorHal.
971      */
972     @Test
testSetCoexUnsafeChannels()973     public void testSetCoexUnsafeChannels() throws Exception {
974         assumeTrue(SdkLevel.isAtLeastS());
975         mWifiNative.setCoexUnsafeChannels(Collections.emptyList(), 0);
976         verify(mWifiVendorHal).setCoexUnsafeChannels(Collections.emptyList(), 0);
977     }
978 
979     /**
980      * Verifies that isSetMacAddressSupported() calls underlying WifiVendorHal.
981      */
982     @Test
testIsStaSetMacAddressSupported()983     public void testIsStaSetMacAddressSupported() throws Exception {
984         mWifiNative.isStaSetMacAddressSupported(WIFI_IFACE_NAME);
985         verify(mWifiVendorHal).isStaSetMacAddressSupported(WIFI_IFACE_NAME);
986     }
987 
988     /**
989      * Verifies that isSetMacAddressSupported() calls underlying WifiVendorHal.
990      */
991     @Test
testIsApSetMacAddressSupported()992     public void testIsApSetMacAddressSupported() throws Exception {
993         mWifiNative.isApSetMacAddressSupported(WIFI_IFACE_NAME);
994         verify(mWifiVendorHal).isApSetMacAddressSupported(WIFI_IFACE_NAME);
995     }
996 
997     /**
998      * Test that selectTxPowerScenario() calls into WifiVendorHal (success case)
999      */
1000     @Test
testSelectTxPowerScenario_success()1001     public void testSelectTxPowerScenario_success() throws Exception {
1002         when(mWifiVendorHal.selectTxPowerScenario(any(SarInfo.class))).thenReturn(true);
1003         SarInfo sarInfo = new SarInfo();
1004         assertTrue(mWifiNative.selectTxPowerScenario(sarInfo));
1005         verify(mWifiVendorHal).selectTxPowerScenario(sarInfo);
1006     }
1007 
1008     /**
1009      * Test that selectTxPowerScenario() calls into WifiVendorHal (failure case)
1010      */
1011     @Test
testSelectTxPowerScenario_failure()1012     public void testSelectTxPowerScenario_failure() throws Exception {
1013         when(mWifiVendorHal.selectTxPowerScenario(any(SarInfo.class))).thenReturn(false);
1014         SarInfo sarInfo = new SarInfo();
1015         assertFalse(mWifiNative.selectTxPowerScenario(sarInfo));
1016         verify(mWifiVendorHal).selectTxPowerScenario(sarInfo);
1017     }
1018 
1019     /**
1020      * Test that setPowerSave() with true, results in calling into SupplicantStaIfaceHal
1021      */
1022     @Test
testSetPowerSaveTrue()1023     public void testSetPowerSaveTrue() throws Exception {
1024         mWifiNative.setPowerSave(WIFI_IFACE_NAME, true);
1025         verify(mStaIfaceHal).setPowerSave(WIFI_IFACE_NAME, true);
1026     }
1027 
1028     /**
1029      * Test that setPowerSave() with false, results in calling into SupplicantStaIfaceHal
1030      */
1031     @Test
testSetPowerSaveFalse()1032     public void testSetPowerSaveFalse() throws Exception {
1033         mWifiNative.setPowerSave(WIFI_IFACE_NAME, false);
1034         verify(mStaIfaceHal).setPowerSave(WIFI_IFACE_NAME, false);
1035     }
1036 
1037     /**
1038      * Test that setLowLatencyMode() with true, results in calling into WifiVendorHal
1039      */
1040     @Test
testLowLatencyModeTrue()1041     public void testLowLatencyModeTrue() throws Exception {
1042         when(mWifiVendorHal.setLowLatencyMode(anyBoolean())).thenReturn(true);
1043         assertTrue(mWifiNative.setLowLatencyMode(true));
1044         verify(mWifiVendorHal).setLowLatencyMode(true);
1045     }
1046 
1047     /**
1048      * Test that setLowLatencyMode() with false, results in calling into WifiVendorHal
1049      */
1050     @Test
testLowLatencyModeFalse()1051     public void testLowLatencyModeFalse() throws Exception {
1052         when(mWifiVendorHal.setLowLatencyMode(anyBoolean())).thenReturn(true);
1053         assertTrue(mWifiNative.setLowLatencyMode(false));
1054         verify(mWifiVendorHal).setLowLatencyMode(false);
1055     }
1056 
1057     /**
1058      * Test that setLowLatencyMode() returns with failure when WifiVendorHal fails.
1059      */
1060     @Test
testSetLowLatencyModeFail()1061     public void testSetLowLatencyModeFail() throws Exception {
1062         final boolean lowLatencyMode = true;
1063         when(mWifiVendorHal.setLowLatencyMode(anyBoolean())).thenReturn(false);
1064         assertFalse(mWifiNative.setLowLatencyMode(lowLatencyMode));
1065         verify(mWifiVendorHal).setLowLatencyMode(lowLatencyMode);
1066     }
1067 
1068     @Test
testStaGetFactoryMacAddress()1069     public void testStaGetFactoryMacAddress() throws Exception {
1070         when(mWifiVendorHal.getStaFactoryMacAddress(any()))
1071                 .thenReturn(MacAddress.BROADCAST_ADDRESS);
1072         assertNotNull(mWifiNative.getStaFactoryMacAddress(WIFI_IFACE_NAME));
1073         verify(mWifiVendorHal).getStaFactoryMacAddress(any());
1074     }
1075 
1076 
1077     @Test
testGetApFactoryMacAddress()1078     public void testGetApFactoryMacAddress() throws Exception {
1079         when(mWifiVendorHal.getApFactoryMacAddress(any())).thenReturn(MacAddress.BROADCAST_ADDRESS);
1080         assertNotNull(mWifiNative.getApFactoryMacAddress(WIFI_IFACE_NAME));
1081         verify(mWifiVendorHal).getApFactoryMacAddress(any());
1082     }
1083 
1084     /**
1085      * Test that flushRingBufferData(), results in calling into WifiVendorHal
1086      */
1087     @Test
testFlushRingBufferDataTrue()1088     public void testFlushRingBufferDataTrue() throws Exception {
1089         when(mWifiVendorHal.flushRingBufferData()).thenReturn(true);
1090         assertTrue(mWifiNative.flushRingBufferData());
1091         verify(mWifiVendorHal).flushRingBufferData();
1092     }
1093 
1094     /**
1095      * Tests that WifiNative#sendMgmtFrame() calls WificondControl#sendMgmtFrame()
1096      */
1097     @Test
testSendMgmtFrame()1098     public void testSendMgmtFrame() {
1099         mWifiNative.sendMgmtFrame(WIFI_IFACE_NAME, FATE_REPORT_FRAME_BYTES,
1100                 mSendMgmtFrameCallback, TEST_MCS_RATE);
1101 
1102         verify(mWificondControl).sendMgmtFrame(eq(WIFI_IFACE_NAME),
1103                 AdditionalMatchers.aryEq(FATE_REPORT_FRAME_BYTES), eq(TEST_MCS_RATE),
1104                 any(), eq(mSendMgmtFrameCallback));
1105     }
1106 
1107     /**
1108      * Tests that probeLink() generates the correct frame and calls WificondControl#sendMgmtFrame().
1109      */
1110     @Test
testProbeLinkSuccess()1111     public void testProbeLinkSuccess() {
1112         byte[] expectedFrame = {
1113                 0x40, 0x00, 0x3c, 0x00, (byte) 0xa8, (byte) 0xbd, 0x27, 0x5b,
1114                 0x33, 0x72, (byte) 0xf4, (byte) 0xf5, (byte) 0xe8, 0x51, (byte) 0x9e, 0x09,
1115                 (byte) 0xa8, (byte) 0xbd, 0x27, 0x5b, 0x33, 0x72, (byte) 0xb0, 0x66,
1116                 0x00, 0x00
1117         };
1118 
1119         when(mStaIfaceHal.getMacAddress(WIFI_IFACE_NAME)).thenReturn(TEST_MAC_ADDRESS_STR);
1120 
1121         when(mRandom.nextInt()).thenReturn(TEST_SEQUENCE_NUM);
1122 
1123         mWifiNative.probeLink(WIFI_IFACE_NAME, MacAddress.fromString(TEST_BSSID_STR),
1124                 mSendMgmtFrameCallback, TEST_MCS_RATE);
1125 
1126         verify(mSendMgmtFrameCallback, never()).onFailure(anyInt());
1127         verify(mWificondControl).sendMgmtFrame(eq(WIFI_IFACE_NAME),
1128                 AdditionalMatchers.aryEq(expectedFrame), eq(TEST_MCS_RATE),
1129                 any(), eq(mSendMgmtFrameCallback));
1130     }
1131 
1132     /**
1133      * Tests that probeLink() triggers the failure callback when it cannot get the sender MAC
1134      * address.
1135      */
1136     @Test
testProbeLinkFailureCannotGetSenderMac()1137     public void testProbeLinkFailureCannotGetSenderMac() {
1138         when(mStaIfaceHal.getMacAddress(WIFI_IFACE_NAME)).thenReturn(null);
1139 
1140         mWifiNative.probeLink(WIFI_IFACE_NAME, MacAddress.fromString(TEST_BSSID_STR),
1141                 mSendMgmtFrameCallback, TEST_MCS_RATE);
1142 
1143         verify(mSendMgmtFrameCallback).onFailure(
1144                 WifiNl80211Manager.SEND_MGMT_FRAME_ERROR_UNKNOWN);
1145         verify(mWificondControl, never()).sendMgmtFrame(any(), any(), anyInt(), any(), any());
1146     }
1147 
1148     /**
1149      * Tests that probeLink() triggers the failure callback when it cannot get the BSSID.
1150      */
1151     @Test
testProbeLinkFailureCannotGetBssid()1152     public void testProbeLinkFailureCannotGetBssid() {
1153         when(mStaIfaceHal.getMacAddress(WIFI_IFACE_NAME)).thenReturn(TEST_MAC_ADDRESS_STR);
1154 
1155         mWifiNative.probeLink(WIFI_IFACE_NAME, null, mSendMgmtFrameCallback, TEST_MCS_RATE);
1156 
1157         verify(mSendMgmtFrameCallback).onFailure(
1158                 WifiNl80211Manager.SEND_MGMT_FRAME_ERROR_UNKNOWN);
1159         verify(mWificondControl, never()).sendMgmtFrame(any(), any(), anyInt(), any(), any());
1160     }
1161 
1162     /**
1163      * Tests that WifiNative#addHlpReq() calls
1164      * SupplicantStaIfaceHal#addHlpReq()
1165      */
1166     @Test
testaddHlpReq()1167     public void testaddHlpReq() {
1168         byte[] hlpPacket = {
1169                 0x40, 0x00, 0x3c, 0x00, (byte) 0xa8, (byte) 0xbd, 0x27, 0x5b,
1170                 0x33, 0x72, (byte) 0xf4, (byte) 0xf5, (byte) 0xe8, 0x51, (byte) 0x9e, 0x09,
1171                 (byte) 0xa8, (byte) 0xbd, 0x27, 0x5b, 0x33, 0x72, (byte) 0xb0, 0x66,
1172                 0x00, 0x00
1173         };
1174         mWifiNative.addHlpReq(WIFI_IFACE_NAME, TEST_MAC_ADDRESS, hlpPacket);
1175 
1176         verify(mStaIfaceHal).addHlpReq(eq(WIFI_IFACE_NAME),
1177                 eq(TEST_MAC_ADDRESS.toByteArray()), eq(hlpPacket));
1178     }
1179 
1180     /**
1181      * Tests that WifiNative#flushAllHlp() calls
1182      * SupplicantStaIfaceHal#flushAllHlp()
1183      */
1184     @Test
testflushAllHlp()1185     public void testflushAllHlp() {
1186         mWifiNative.flushAllHlp(WIFI_IFACE_NAME);
1187 
1188         verify(mStaIfaceHal).flushAllHlp(eq(WIFI_IFACE_NAME));
1189     }
1190 
1191     @Test
testIsItPossibleToCreateIface()1192     public void testIsItPossibleToCreateIface() {
1193         when(mWifiVendorHal.isItPossibleToCreateApIface(any())).thenReturn(true);
1194         assertTrue(mWifiNative.isItPossibleToCreateApIface(new WorkSource()));
1195 
1196         when(mWifiVendorHal.isItPossibleToCreateStaIface(any())).thenReturn(true);
1197         assertTrue(mWifiNative.isItPossibleToCreateStaIface(new WorkSource()));
1198     }
1199 
1200     @Test
testReplaceStaIfaceRequestorWs()1201     public void testReplaceStaIfaceRequestorWs() {
1202         assertEquals(WIFI_IFACE_NAME,
1203                 mWifiNative.setupInterfaceForClientInConnectivityMode(
1204                         mInterfaceCallback, TEST_WORKSOURCE));
1205         when(mWifiVendorHal.replaceStaIfaceRequestorWs(WIFI_IFACE_NAME, TEST_WORKSOURCE2))
1206                 .thenReturn(true);
1207 
1208         assertTrue(mWifiNative.replaceStaIfaceRequestorWs(WIFI_IFACE_NAME, TEST_WORKSOURCE2));
1209         verify(mWifiVendorHal).replaceStaIfaceRequestorWs(
1210                 eq(WIFI_IFACE_NAME), same(TEST_WORKSOURCE2));
1211     }
1212 
1213     /**
1214      * Verifies that updateLinkedNetworks() calls underlying SupplicantStaIfaceHal.
1215      */
1216     @Test
testUpdateLinkedNetworks()1217     public void testUpdateLinkedNetworks() {
1218         when(mStaIfaceHal.updateLinkedNetworks(any(), anyInt(), any())).thenReturn(true);
1219 
1220         assertTrue(mWifiNative.updateLinkedNetworks(WIFI_IFACE_NAME, 0, null));
1221         verify(mStaIfaceHal).updateLinkedNetworks(WIFI_IFACE_NAME, 0, null);
1222     }
1223 
1224     /**
1225      * Verifies that getEapAnonymousIdentity() works as expected.
1226      */
1227     @Test
testGetEapAnonymousIdentity()1228     public void testGetEapAnonymousIdentity() {
1229         // Verify the empty use case
1230         when(mStaIfaceHal.getCurrentNetworkEapAnonymousIdentity(WIFI_IFACE_NAME))
1231                 .thenReturn("");
1232         assertTrue(TextUtils.isEmpty(mWifiNative.getEapAnonymousIdentity(WIFI_IFACE_NAME)));
1233 
1234         // Verify with an anonymous identity
1235         final String anonymousId = "anonymous@homerealm.example.org";
1236         when(mStaIfaceHal.getCurrentNetworkEapAnonymousIdentity(WIFI_IFACE_NAME))
1237                 .thenReturn(anonymousId);
1238         assertEquals(anonymousId, mWifiNative.getEapAnonymousIdentity(WIFI_IFACE_NAME));
1239 
1240         // Verify with a pseudonym identity
1241         final String pseudonymId = "a4624bc22490da3@homerealm.example.org";
1242         when(mStaIfaceHal.getCurrentNetworkEapAnonymousIdentity(WIFI_IFACE_NAME))
1243                 .thenReturn(pseudonymId);
1244         assertEquals(pseudonymId, mWifiNative.getEapAnonymousIdentity(WIFI_IFACE_NAME));
1245 
1246         // Verify that decorated anonymous identity is truncated
1247         when(mStaIfaceHal.getCurrentNetworkEapAnonymousIdentity(WIFI_IFACE_NAME))
1248                 .thenReturn("otherrealm.example.net!" + anonymousId);
1249         assertEquals(anonymousId, mWifiNative.getEapAnonymousIdentity(WIFI_IFACE_NAME));
1250 
1251         // Verify that recursive decorated anonymous identity is truncated
1252         when(mStaIfaceHal.getCurrentNetworkEapAnonymousIdentity(WIFI_IFACE_NAME))
1253                 .thenReturn("proxyrealm.example.com!otherrealm.example.net!" + anonymousId);
1254         assertEquals(anonymousId, mWifiNative.getEapAnonymousIdentity(WIFI_IFACE_NAME));
1255 
1256         // Verify an invalid decoration with no identity use cases
1257         when(mStaIfaceHal.getCurrentNetworkEapAnonymousIdentity(WIFI_IFACE_NAME))
1258                 .thenReturn("otherrealm.example.net!");
1259         assertNull(mWifiNative.getEapAnonymousIdentity(WIFI_IFACE_NAME));
1260     }
1261 
1262 
1263     @Test
testCountryCodeChangedListener()1264     public void testCountryCodeChangedListener() {
1265         assumeTrue(SdkLevel.isAtLeastS());
1266         final String testCountryCode = "US";
1267         ArgumentCaptor<WifiNl80211Manager.CountryCodeChangedListener>
1268                 mCountryCodeChangedListenerCaptor = ArgumentCaptor.forClass(
1269                 WifiNl80211Manager.CountryCodeChangedListener.class);
1270         mWifiNative.registerCountryCodeEventListener(mWifiCountryCodeChangeListener);
1271         verify(mWificondControl).registerCountryCodeChangedListener(any(),
1272                 mCountryCodeChangedListenerCaptor.capture());
1273         mCountryCodeChangedListenerCaptor.getValue().onCountryCodeChanged(testCountryCode);
1274         verify(mWifiCountryCodeChangeListener).onDriverCountryCodeChanged(testCountryCode);
1275     }
1276 
1277     @Test
testSetStaCountryCodeSuccessful()1278     public void testSetStaCountryCodeSuccessful() {
1279         when(mStaIfaceHal.setCountryCode(any(), any())).thenReturn(true);
1280         final String testCountryCode = "US";
1281         mWifiNative.registerCountryCodeEventListener(mWifiCountryCodeChangeListener);
1282         mWifiNative.setStaCountryCode(WIFI_IFACE_NAME, testCountryCode);
1283         verify(mStaIfaceHal).setCountryCode(WIFI_IFACE_NAME, testCountryCode);
1284         if (SdkLevel.isAtLeastS()) {
1285             verify(mWifiCountryCodeChangeListener).onSetCountryCodeSucceeded(testCountryCode);
1286         }
1287     }
1288 
1289     @Test
testSetStaCountryCodeFailure()1290     public void testSetStaCountryCodeFailure() {
1291         when(mStaIfaceHal.setCountryCode(any(), any())).thenReturn(false);
1292         final String testCountryCode = "US";
1293         mWifiNative.registerCountryCodeEventListener(mWifiCountryCodeChangeListener);
1294         mWifiNative.setStaCountryCode(WIFI_IFACE_NAME, testCountryCode);
1295         verify(mStaIfaceHal).setCountryCode(WIFI_IFACE_NAME, testCountryCode);
1296         if (SdkLevel.isAtLeastS()) {
1297             verify(mWifiCountryCodeChangeListener, never())
1298                     .onSetCountryCodeSucceeded(testCountryCode);
1299         }
1300     }
1301 
1302     @Test
testSetApCountryCodeSuccessful()1303     public void testSetApCountryCodeSuccessful() {
1304         when(mWifiVendorHal.setApCountryCode(any(), any())).thenReturn(true);
1305         final String testCountryCode = "US";
1306         mWifiNative.registerCountryCodeEventListener(mWifiCountryCodeChangeListener);
1307         mWifiNative.setApCountryCode(WIFI_IFACE_NAME, testCountryCode);
1308         verify(mWifiVendorHal).setApCountryCode(WIFI_IFACE_NAME, testCountryCode);
1309         if (SdkLevel.isAtLeastS()) {
1310             verify(mWifiCountryCodeChangeListener).onSetCountryCodeSucceeded(testCountryCode);
1311         }
1312     }
1313 
1314     @Test
testSetApCountryCodeFailure()1315     public void testSetApCountryCodeFailure() {
1316         when(mWifiVendorHal.setApCountryCode(any(), any())).thenReturn(false);
1317         final String testCountryCode = "US";
1318         mWifiNative.registerCountryCodeEventListener(mWifiCountryCodeChangeListener);
1319         mWifiNative.setApCountryCode(WIFI_IFACE_NAME, testCountryCode);
1320         verify(mWifiVendorHal).setApCountryCode(WIFI_IFACE_NAME, testCountryCode);
1321         if (SdkLevel.isAtLeastS()) {
1322             verify(mWifiCountryCodeChangeListener, never())
1323                     .onSetCountryCodeSucceeded(testCountryCode);
1324         }
1325     }
1326 
1327     @Test
testSetChipCountryCodeSuccessful()1328     public void testSetChipCountryCodeSuccessful() {
1329         when(mWifiVendorHal.setChipCountryCode(any())).thenReturn(true);
1330         final String testCountryCode = "US";
1331         mWifiNative.registerCountryCodeEventListener(mWifiCountryCodeChangeListener);
1332         mWifiNative.setChipCountryCode(testCountryCode);
1333         verify(mWifiVendorHal).setChipCountryCode(testCountryCode);
1334         if (SdkLevel.isAtLeastS()) {
1335             verify(mWifiCountryCodeChangeListener).onSetCountryCodeSucceeded(testCountryCode);
1336         }
1337     }
1338 
1339     @Test
testSetChipCountryCodeFailure()1340     public void testSetChipCountryCodeFailure() {
1341         when(mWifiVendorHal.setChipCountryCode(any())).thenReturn(false);
1342         final String testCountryCode = "US";
1343         mWifiNative.registerCountryCodeEventListener(mWifiCountryCodeChangeListener);
1344         mWifiNative.setChipCountryCode(testCountryCode);
1345         verify(mWifiVendorHal).setChipCountryCode(testCountryCode);
1346         if (SdkLevel.isAtLeastS()) {
1347             verify(mWifiCountryCodeChangeListener, never())
1348                 .onSetCountryCodeSucceeded(testCountryCode);
1349         }
1350     }
1351 }
1352