1 /*
2  * Copyright (C) 2015 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 android.net.wifi.ScanResult;
20 import android.net.wifi.WifiScanner.ScanData;
21 import android.net.wifi.WifiSsid;
22 
23 import com.android.server.wifi.hotspot2.NetworkDetail;
24 import com.android.server.wifi.util.NativeUtil;
25 
26 import java.math.BigInteger;
27 import java.nio.charset.Charset;
28 import java.util.ArrayList;
29 import java.util.Arrays;
30 import java.util.Collections;
31 import java.util.Comparator;
32 import java.util.List;
33 import java.util.Random;
34 
35 /**
36  * Utility for creating scan results from a scan
37  */
38 public class ScanResults {
39     private final ArrayList<ScanDetail> mScanDetails = new ArrayList<>();
40     private final ScanData mScanData;
41     private final ScanData mRawScanData;
42     private final ScanResult[] mScanResults;
43 
ScanResults(ArrayList<ScanDetail> scanDetails, ScanData scanData, ScanResult[] scanResults)44     private ScanResults(ArrayList<ScanDetail> scanDetails, ScanData scanData,
45             ScanResult[] scanResults) {
46         mScanDetails.addAll(scanDetails);
47         mScanData = scanData;
48         mRawScanData = scanData;
49         mScanResults = scanResults;
50     }
51 
52     /**
53      * Merge the results contained in a number of ScanResults into a single ScanResults
54      */
merge(int bandScanned, ScanResults... others)55     public static ScanResults merge(int bandScanned, ScanResults... others) {
56         ArrayList<ScanDetail> scanDetails = new ArrayList<>();
57         ArrayList<ScanResult> scanDataResults = new ArrayList<>();
58         ArrayList<ScanResult> rawScanResults = new ArrayList<>();
59         for (ScanResults other : others) {
60             scanDetails.addAll(other.getScanDetailArrayList());
61             scanDataResults.addAll(Arrays.asList(other.getScanData().getResults()));
62             rawScanResults.addAll(Arrays.asList(other.getRawScanResults()));
63         }
64         Collections.sort(scanDataResults, SCAN_RESULT_RSSI_COMPARATOR);
65         int id = others[0].getScanData().getId();
66         return new ScanResults(scanDetails, new ScanData(id, 0, 0, bandScanned, scanDataResults
67                         .toArray(new ScanResult[scanDataResults.size()])),
68                 rawScanResults.toArray(new ScanResult[rawScanResults.size()]));
69     }
70 
generateBssid(Random r)71     private static String generateBssid(Random r) {
72         return String.format("%02X:%02X:%02X:%02X:%02X:%02X",
73                 r.nextInt(256), r.nextInt(256), r.nextInt(256),
74                 r.nextInt(256), r.nextInt(256), r.nextInt(256));
75     }
76 
77     public static final Comparator<ScanResult> SCAN_RESULT_RSSI_COMPARATOR =
78             new Comparator<ScanResult>() {
79         public int compare(ScanResult r1, ScanResult r2) {
80             return r2.level - r1.level;
81         }
82     };
83 
generateSsidIe(String ssid)84     public static ScanResult.InformationElement generateSsidIe(String ssid) {
85         ScanResult.InformationElement ie = new ScanResult.InformationElement();
86         ie.id = ScanResult.InformationElement.EID_SSID;
87         ie.bytes = ssid.getBytes(Charset.forName("UTF-8"));
88         return ie;
89     }
90 
generateIERawDatafromScanResultIE(ScanResult.InformationElement[] ies)91     public static byte[] generateIERawDatafromScanResultIE(ScanResult.InformationElement[] ies) {
92         ArrayList<Byte> ieRawData = new ArrayList<>();
93         for (int i = 0; i < ies.length; i++) {
94             if (ies[i].id > 255 || ies[i].bytes.length > 255) {
95                 break;
96             }
97             ieRawData.add(BigInteger.valueOf(ies[i].id).toByteArray()[0]);
98             ieRawData.add(BigInteger.valueOf(ies[i].bytes.length).toByteArray()[0]);
99             for (int j = 0; j < ies[i].bytes.length; j++) {
100                 ieRawData.add(ies[i].bytes[j]);
101             }
102         }
103         return NativeUtil.byteArrayFromArrayList(ieRawData);
104     }
105 
106     /**
107      * Generates an array of random ScanDetails with the given frequencies, seeded by the provided
108      * seed value and test method name and class (annotated with @Test). This method will be
109      * consistent between calls in the same test across runs.
110      *
111      * @param seed combined with a hash of the test method this seeds the random number generator
112      * @param freqs list of frequencies for the generated scan results, these will map 1 to 1 to
113      *              to the returned scan details. Duplicates can be specified to create multiple
114      *              ScanDetails with the same frequency.
115      */
generateNativeResults(boolean needIE, int seed, int... freqs)116     private static ScanDetail[] generateNativeResults(boolean needIE, int seed, int... freqs) {
117         ScanDetail[] results = new ScanDetail[freqs.length];
118         // Seed the results based on the provided seed as well as the test method name
119         // This provides more varied scan results between individual tests that are very similar.
120         Random r = new Random(seed + WifiTestUtil.getTestMethod().hashCode());
121         for (int i = 0; i < freqs.length; ++i) {
122             int freq = freqs[i];
123             String ssid = new BigInteger(128, r).toString(36);
124             String bssid = generateBssid(r);
125             int rssi = r.nextInt(40) - 99; // -99 to -60
126             ScanResult.InformationElement[] ie;
127             if (needIE) {
128                 ie = new ScanResult.InformationElement[1];
129                 ie[0] = generateSsidIe(ssid);
130             } else {
131                 ie = new ScanResult.InformationElement[0];
132             }
133             List<String> anqpLines = new ArrayList<>();
134             NetworkDetail nd = new NetworkDetail(bssid, ie, anqpLines, freq);
135             ScanDetail detail = new ScanDetail(nd, WifiSsid.createFromAsciiEncoded(ssid),
136                     bssid, "", rssi, freq,
137                     Long.MAX_VALUE, /* needed so that scan results aren't rejected because
138                                         they are older than scan start */
139                     ie, anqpLines, generateIERawDatafromScanResultIE(ie));
140             results[i] = detail;
141         }
142         return results;
143     }
144 
145     /**
146      * Create scan results with no IE information.
147      */
generateNativeResults(int seed, int... freqs)148     public static ScanDetail[] generateNativeResults(int seed, int... freqs) {
149         return generateNativeResults(true, seed, freqs);
150     }
151 
152     /**
153      * Create a ScanResults with randomly generated results seeded by the id.
154      * @see #generateNativeResults for more details on how results are generated
155      */
create(int id, int bandScanned, int... freqs)156     public static ScanResults create(int id, int bandScanned, int... freqs) {
157         return create(id, bandScanned, generateNativeResults(id, freqs));
158     }
159 
create(int id, int bandScanned, ScanDetail... nativeResults)160     public static ScanResults create(int id, int bandScanned,
161             ScanDetail... nativeResults) {
162         return new ScanResults(id, bandScanned, -1, nativeResults);
163     }
164 
165     /**
166      * Create scan results that contain all results for the native results and
167      * full scan results, but limits the number of onResults results after sorting
168      * by RSSI
169      */
createOverflowing(int id, int bandScanned, int maxResults, ScanDetail... nativeResults)170     public static ScanResults createOverflowing(int id, int bandScanned, int maxResults,
171             ScanDetail... nativeResults) {
172         return new ScanResults(id, bandScanned, maxResults, nativeResults);
173     }
174 
ScanResults(int id, int bandScanned, int maxResults, ScanDetail... nativeResults)175     private ScanResults(int id, int bandScanned, int maxResults, ScanDetail... nativeResults) {
176         mScanResults = new ScanResult[nativeResults.length];
177         for (int i = 0; i < nativeResults.length; ++i) {
178             mScanDetails.add(nativeResults[i]);
179             mScanResults[i] = nativeResults[i].getScanResult();
180         }
181         ScanResult[] sortedScanResults = Arrays.copyOf(mScanResults, mScanResults.length);
182         Arrays.sort(sortedScanResults, SCAN_RESULT_RSSI_COMPARATOR);
183         mRawScanData = new ScanData(id, 0, 0, bandScanned, sortedScanResults);
184         if (maxResults == -1) {
185             mScanData = mRawScanData;
186         } else {
187             ScanResult[] reducedScanResults = Arrays.copyOf(sortedScanResults,
188                     Math.min(sortedScanResults.length, maxResults));
189             mScanData = new ScanData(id, 0, 0, bandScanned, reducedScanResults);
190         }
191     }
192 
getScanDetailArrayList()193     public ArrayList<ScanDetail> getScanDetailArrayList() {
194         return mScanDetails;
195     }
196 
getScanData()197     public ScanData getScanData() {
198         return mScanData;
199     }
200 
getRawScanResults()201     public ScanResult[] getRawScanResults() {
202         return mScanResults;
203     }
204 
getRawScanData()205     public ScanData getRawScanData() {
206         return mRawScanData;
207     }
208 }
209