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 static org.junit.Assert.*;
20 import static org.junit.Assume.*;
21 import static org.mockito.Mockito.*;
22 
23 import android.net.wifi.ScanResult;
24 import android.net.wifi.WifiScanner;
25 import android.net.wifi.WifiScanner.ScanData;
26 import android.net.wifi.WifiSsid;
27 
28 import org.hamcrest.Description;
29 import org.hamcrest.Matcher;
30 import org.hamcrest.TypeSafeDiagnosingMatcher;
31 
32 import java.util.Arrays;
33 import java.util.HashSet;
34 import java.util.Set;
35 
36 /**
37  * Utilities for testing Wifi Scanning
38  */
39 public class ScanTestUtil {
40 
setupMockChannels(WifiNative wifiNative, int[] channels24, int[] channels5, int[] channelsDfs)41     public static void setupMockChannels(WifiNative wifiNative, int[] channels24, int[] channels5,
42             int[] channelsDfs) throws Exception {
43         when(wifiNative.getChannelsForBand(WifiScanner.WIFI_BAND_24_GHZ))
44                 .thenReturn(channels24);
45         when(wifiNative.getChannelsForBand(WifiScanner.WIFI_BAND_5_GHZ))
46                 .thenReturn(channels5);
47         when(wifiNative.getChannelsForBand(WifiScanner.WIFI_BAND_5_GHZ_DFS_ONLY))
48                 .thenReturn(channelsDfs);
49     }
50 
createRequest(WifiScanner.ChannelSpec[] channels, int period, int batch, int bssidsPerScan, int reportEvents)51     public static WifiScanner.ScanSettings createRequest(WifiScanner.ChannelSpec[] channels,
52             int period, int batch, int bssidsPerScan, int reportEvents) {
53         WifiScanner.ScanSettings request = new WifiScanner.ScanSettings();
54         request.band = WifiScanner.WIFI_BAND_UNSPECIFIED;
55         request.channels = channels;
56         request.periodInMs = period;
57         request.numBssidsPerScan = bssidsPerScan;
58         request.maxScansToCache = batch;
59         request.reportEvents = reportEvents;
60         return request;
61     }
62 
createRequest(int type, int band, int period, int batch, int bssidsPerScan, int reportEvents)63     public static WifiScanner.ScanSettings createRequest(int type, int band, int period, int batch,
64             int bssidsPerScan, int reportEvents) {
65         return createRequest(WifiScanner.TYPE_HIGH_ACCURACY, band, period, 0, 0,
66                 batch, bssidsPerScan, reportEvents);
67     }
68 
createRequest(int band, int period, int batch, int bssidsPerScan, int reportEvents)69     public static WifiScanner.ScanSettings createRequest(int band, int period, int batch,
70             int bssidsPerScan, int reportEvents) {
71         return createRequest(WifiScanner.TYPE_HIGH_ACCURACY, band, period, 0, 0, batch,
72                 bssidsPerScan, reportEvents);
73     }
74 
75     /**
76      * Create an exponential back off scan request if maxPeriod != period && maxPeriod != 0.
77      */
createRequest(int type, int band, int period, int maxPeriod, int stepCount, int batch, int bssidsPerScan, int reportEvents)78     public static WifiScanner.ScanSettings createRequest(int type, int band, int period,
79             int maxPeriod, int stepCount, int batch, int bssidsPerScan, int reportEvents) {
80         WifiScanner.ScanSettings request = new WifiScanner.ScanSettings();
81         request.type = type;
82         request.band = band;
83         request.channels = null;
84         request.periodInMs = period;
85         request.maxPeriodInMs = maxPeriod;
86         request.stepCount = stepCount;
87         request.numBssidsPerScan = bssidsPerScan;
88         request.maxScansToCache = batch;
89         request.reportEvents = reportEvents;
90         return request;
91     }
92 
93     /**
94      * Builder to create WifiNative.ScanSettings objects for testing
95      */
96     public static class NativeScanSettingsBuilder {
97         private final WifiNative.ScanSettings mSettings = new WifiNative.ScanSettings();
NativeScanSettingsBuilder()98         public NativeScanSettingsBuilder() {
99             mSettings.scanType = WifiNative.SCAN_TYPE_LOW_LATENCY;
100             mSettings.buckets = new WifiNative.BucketSettings[0];
101             mSettings.num_buckets = 0;
102             mSettings.report_threshold_percent = 100;
103         }
104 
withType(int type)105         public NativeScanSettingsBuilder withType(int type) {
106             mSettings.scanType = type;
107             return this;
108         }
withBasePeriod(int basePeriod)109         public NativeScanSettingsBuilder withBasePeriod(int basePeriod) {
110             mSettings.base_period_ms = basePeriod;
111             return this;
112         }
withMaxApPerScan(int maxAp)113         public NativeScanSettingsBuilder withMaxApPerScan(int maxAp) {
114             mSettings.max_ap_per_scan = maxAp;
115             return this;
116         }
withMaxScansToCache(int maxScans)117         public NativeScanSettingsBuilder withMaxScansToCache(int maxScans) {
118             mSettings.report_threshold_num_scans = maxScans;
119             return this;
120         }
withMaxPercentToCache(int percent)121         public NativeScanSettingsBuilder withMaxPercentToCache(int percent) {
122             mSettings.report_threshold_percent = percent;
123             return this;
124         }
125 
126         /**
127          * Add the provided hidden network SSIDs to scan request.
128          * @param networkSSIDs List of hidden network SSIDs
129          * @return builder object
130          */
withHiddenNetworkSSIDs(String[] networkSSIDs)131         public NativeScanSettingsBuilder withHiddenNetworkSSIDs(String[] networkSSIDs) {
132             mSettings.hiddenNetworks = new WifiNative.HiddenNetwork[networkSSIDs.length];
133             for (int i = 0; i < networkSSIDs.length; i++) {
134                 mSettings.hiddenNetworks[i] = new WifiNative.HiddenNetwork();
135                 mSettings.hiddenNetworks[i].ssid = networkSSIDs[i];
136             }
137             return this;
138         }
139 
addBucketWithBand( int period, int reportEvents, int band)140         public NativeScanSettingsBuilder addBucketWithBand(
141                 int period, int reportEvents, int band) {
142             WifiNative.BucketSettings bucket = new WifiNative.BucketSettings();
143             bucket.bucket = mSettings.num_buckets;
144             bucket.band = band;
145             bucket.period_ms = period;
146             bucket.report_events = reportEvents;
147             return addBucket(bucket);
148         }
149 
addBucketWithChannels( int period, int reportEvents, WifiScanner.ChannelSpec... channels)150         public NativeScanSettingsBuilder addBucketWithChannels(
151                 int period, int reportEvents, WifiScanner.ChannelSpec... channels) {
152             int[] channelFreqs = new int[channels.length];
153             for (int i = 0; i < channels.length; ++i) {
154                 channelFreqs[i] = channels[i].frequency;
155             }
156             return addBucketWithChannels(period, reportEvents, channelFreqs);
157         }
158 
addBucketWithChannels( int period, int reportEvents, int... channels)159         public NativeScanSettingsBuilder addBucketWithChannels(
160                 int period, int reportEvents, int... channels) {
161             WifiNative.BucketSettings bucket = new WifiNative.BucketSettings();
162             bucket.bucket = mSettings.num_buckets;
163             bucket.band = WifiScanner.WIFI_BAND_UNSPECIFIED;
164             bucket.num_channels = channels.length;
165             bucket.channels = channelsToNativeSettings(channels);
166             bucket.period_ms = period;
167             bucket.report_events = reportEvents;
168             return addBucket(bucket);
169         }
170 
addBucket(WifiNative.BucketSettings bucket)171         public NativeScanSettingsBuilder addBucket(WifiNative.BucketSettings bucket) {
172             mSettings.buckets = Arrays.copyOf(mSettings.buckets, mSettings.num_buckets + 1);
173             mSettings.buckets[mSettings.num_buckets] = bucket;
174             mSettings.num_buckets = mSettings.num_buckets + 1;
175             return this;
176         }
177 
build()178         public WifiNative.ScanSettings build() {
179             return mSettings;
180         }
181 
182     }
183 
getNativeScanType(int type)184     private static int getNativeScanType(int type) {
185         switch(type) {
186             case WifiScanner.TYPE_LOW_LATENCY:
187                 return WifiNative.SCAN_TYPE_LOW_LATENCY;
188             case WifiScanner.TYPE_LOW_POWER:
189                 return WifiNative.SCAN_TYPE_LOW_POWER;
190             case WifiScanner.TYPE_HIGH_ACCURACY:
191                 return WifiNative.SCAN_TYPE_HIGH_ACCURACY;
192             default:
193                 fail();
194                 return -1;
195         }
196     }
197 
198     /**
199      * Compute the expected native scan settings that are expected for the given
200      * WifiScanner.ScanSettings.
201      */
computeSingleScanNativeSettings( WifiScanner.ScanSettings requestSettings)202     public static WifiNative.ScanSettings computeSingleScanNativeSettings(
203             WifiScanner.ScanSettings requestSettings) {
204         int reportEvents = requestSettings.reportEvents | WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN;
205         NativeScanSettingsBuilder builder = new NativeScanSettingsBuilder()
206                 .withBasePeriod(0)
207                 .withMaxApPerScan(0)
208                 .withMaxPercentToCache(0)
209                 .withMaxScansToCache(0)
210                 .withType(getNativeScanType(requestSettings.type));
211         if (requestSettings.band == WifiScanner.WIFI_BAND_UNSPECIFIED) {
212             builder.addBucketWithChannels(0, reportEvents, requestSettings.channels);
213         } else {
214             builder.addBucketWithBand(0, reportEvents, requestSettings.band);
215         }
216 
217         return builder.build();
218     }
219 
220     /**
221      * Compute the expected native scan settings that are expected for the given channels.
222      */
createSingleScanNativeSettingsForChannels( int reportEvents, WifiScanner.ChannelSpec... channels)223     public static WifiNative.ScanSettings createSingleScanNativeSettingsForChannels(
224             int reportEvents, WifiScanner.ChannelSpec... channels) {
225         return createSingleScanNativeSettingsForChannels(
226             WifiNative.SCAN_TYPE_LOW_LATENCY, reportEvents, channels);
227     }
228 
229     /**
230      * Compute the expected native scan settings that are expected for the given channels & type.
231      */
createSingleScanNativeSettingsForChannels( int nativeScanType, int reportEvents, WifiScanner.ChannelSpec... channels)232     public static WifiNative.ScanSettings createSingleScanNativeSettingsForChannels(
233             int nativeScanType, int reportEvents, WifiScanner.ChannelSpec... channels) {
234         int actualReportEvents = reportEvents | WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN;
235         return new NativeScanSettingsBuilder()
236                 .withBasePeriod(0)
237                 .withMaxApPerScan(0)
238                 .withMaxPercentToCache(0)
239                 .withMaxScansToCache(0)
240                 .addBucketWithChannels(0, actualReportEvents, channels)
241                 .withType(nativeScanType)
242                 .build();
243     }
244 
createFreqSet(int... elements)245     public static Set<Integer> createFreqSet(int... elements) {
246         Set<Integer> set = new HashSet<>();
247         for (int e : elements) {
248             set.add(e);
249         }
250         return set;
251     }
252 
createScanResult(int freq)253     public static ScanResult createScanResult(int freq) {
254         return new ScanResult(WifiSsid.createFromAsciiEncoded("AN SSID"), "00:00:00:00:00:00", 0L,
255                 -1, null, "", 0, freq, 0);
256     }
257 
createScanData(int[] freqs, int bucketsScanned, int bandScanned)258     private static ScanData createScanData(int[] freqs, int bucketsScanned, int bandScanned) {
259         ScanResult[] results = new ScanResult[freqs.length];
260         for (int i = 0; i < freqs.length; ++i) {
261             results[i] = createScanResult(freqs[i]);
262         }
263         return new ScanData(0, 0, bucketsScanned, bandScanned, results);
264     }
265 
createScanData(int[] freqs, int bucketsScanned)266     private static ScanData createScanData(int[] freqs, int bucketsScanned) {
267         return createScanData(freqs, bucketsScanned, WifiScanner.WIFI_BAND_UNSPECIFIED);
268     }
269 
createScanDatas( int[][] freqs, int[] bucketsScanned, int[] bandsScanned)270     public static ScanData[] createScanDatas(
271             int[][] freqs, int[] bucketsScanned, int[] bandsScanned) {
272         assumeTrue(freqs.length == bucketsScanned.length);
273         assumeTrue(freqs.length == bandsScanned.length);
274         ScanData[] data = new ScanData[freqs.length];
275         for (int i = 0; i < freqs.length; ++i) {
276             data[i] = createScanData(freqs[i], bucketsScanned[i], bandsScanned[i]);
277         }
278         return data;
279     }
280 
createScanDatas(int[][] freqs, int[] bucketsScanned)281     public static ScanData[] createScanDatas(int[][] freqs, int[] bucketsScanned) {
282         assumeTrue(freqs.length == bucketsScanned.length);
283         ScanData[] data = new ScanData[freqs.length];
284         for (int i = 0; i < freqs.length; ++i) {
285             data[i] = createScanData(freqs[i], bucketsScanned[i]);
286         }
287         return data;
288     }
289 
createScanDatas(int[][] freqs)290     public static ScanData[] createScanDatas(int[][] freqs) {
291         return createScanDatas(freqs, new int[freqs.length] /* defaults all 0 */);
292     }
293 
assertScanResultEquals( String prefix, ScanResult expected, ScanResult actual)294     private static void assertScanResultEquals(
295             String prefix, ScanResult expected, ScanResult actual) {
296         assertEquals(prefix + "SSID", expected.SSID, actual.SSID);
297         assertEquals(prefix + "wifiSsid", expected.wifiSsid.toString(), actual.wifiSsid.toString());
298         assertEquals(prefix + "BSSID", expected.BSSID, actual.BSSID);
299         assertEquals(prefix + "capabilities", expected.capabilities, actual.capabilities);
300         assertEquals(prefix + "level", expected.level, actual.level);
301         assertEquals(prefix + "frequency", expected.frequency, actual.frequency);
302         assertEquals(prefix + "timestamp", expected.timestamp, actual.timestamp);
303         assertEquals(prefix + "seen", expected.seen, actual.seen);
304     }
305 
assertScanResultsEquals(String prefix, ScanResult[] expected, ScanResult[] actual)306     private static void assertScanResultsEquals(String prefix, ScanResult[] expected,
307             ScanResult[] actual) {
308         assertNotNull(prefix + "expected ScanResults was null", expected);
309         assertNotNull(prefix + "actual ScanResults was null", actual);
310         assertEquals(prefix + "results.length", expected.length, actual.length);
311         for (int j = 0; j < expected.length; ++j) {
312             ScanResult expectedResult = expected[j];
313             ScanResult actualResult = actual[j];
314             assertScanResultEquals(prefix + "results[" + j + "]", actualResult, expectedResult);
315         }
316     }
317 
318     /**
319      * Asserts if the provided scan results are the same.
320      */
assertScanResultEquals(ScanResult expected, ScanResult actual)321     public static void assertScanResultEquals(ScanResult expected, ScanResult actual) {
322         assertScanResultEquals("", expected, actual);
323     }
324 
325     /**
326      * Asserts if the provided scan result arrays are the same.
327      */
assertScanResultsEquals(ScanResult[] expected, ScanResult[] actual)328     public static void assertScanResultsEquals(ScanResult[] expected, ScanResult[] actual) {
329         assertScanResultsEquals("", expected, actual);
330     }
331 
assertScanDataEquals(String prefix, ScanData expected, ScanData actual)332     private static void assertScanDataEquals(String prefix, ScanData expected, ScanData actual) {
333         assertNotNull(prefix + "expected ScanData was null", expected);
334         assertNotNull(prefix + "actual ScanData was null", actual);
335         assertEquals(prefix + "id", expected.getId(), actual.getId());
336         assertEquals(prefix + "flags", expected.getFlags(), actual.getFlags());
337         assertEquals(prefix + "band", expected.getBandScanned(),
338                 actual.getBandScanned());
339         assertScanResultsEquals(prefix, expected.getResults(), actual.getResults());
340     }
341 
assertScanDataEquals(ScanData expected, ScanData actual)342     public static void assertScanDataEquals(ScanData expected, ScanData actual) {
343         assertScanDataEquals("", expected, actual);
344     }
345 
assertScanDatasEquals(String prefix, ScanData[] expected, ScanData[] actual)346     public static void assertScanDatasEquals(String prefix, ScanData[] expected, ScanData[] actual) {
347         assertNotNull("expected " + prefix + "ScanData[] was null", expected);
348         assertNotNull("actaul " + prefix + "ScanData[] was null", actual);
349         assertEquals(prefix + "ScanData.length", expected.length, actual.length);
350         for (int i = 0; i < expected.length; ++i) {
351             assertScanDataEquals(prefix + "ScanData[" + i + "].", expected[i], actual[i]);
352         }
353     }
354 
assertScanDatasEquals(ScanData[] expected, ScanData[] actual)355     public static void assertScanDatasEquals(ScanData[] expected, ScanData[] actual) {
356         assertScanDatasEquals("", expected, actual);
357     }
358 
channelsToSpec(int... channels)359     public static WifiScanner.ChannelSpec[] channelsToSpec(int... channels) {
360         WifiScanner.ChannelSpec[] channelSpecs = new WifiScanner.ChannelSpec[channels.length];
361         for (int i = 0; i < channels.length; ++i) {
362             channelSpecs[i] = new WifiScanner.ChannelSpec(channels[i]);
363         }
364         return channelSpecs;
365     }
366 
assertNativeScanSettingsEquals(WifiNative.ScanSettings expected, WifiNative.ScanSettings actual)367     public static void assertNativeScanSettingsEquals(WifiNative.ScanSettings expected,
368             WifiNative.ScanSettings actual) {
369         assertEquals("scan type", expected.scanType, actual.scanType);
370         assertEquals("bssids per scan", expected.max_ap_per_scan, actual.max_ap_per_scan);
371         assertEquals("scans to cache", expected.report_threshold_num_scans,
372                 actual.report_threshold_num_scans);
373         assertEquals("percent to cache", expected.report_threshold_percent,
374                 actual.report_threshold_percent);
375         assertEquals("base period", expected.base_period_ms, actual.base_period_ms);
376 
377         assertEquals("number of buckets", expected.num_buckets, actual.num_buckets);
378         assertNotNull("buckets was null", actual.buckets);
379         for (int i = 0; i < expected.buckets.length; ++i) {
380             assertNotNull("buckets[" + i + "] was null", actual.buckets[i]);
381             assertEquals("buckets[" + i + "].period",
382                     expected.buckets[i].period_ms, actual.buckets[i].period_ms);
383             assertEquals("buckets[" + i + "].reportEvents",
384                     expected.buckets[i].report_events, actual.buckets[i].report_events);
385 
386             assertEquals("buckets[" + i + "].band",
387                     expected.buckets[i].band, actual.buckets[i].band);
388             if (expected.buckets[i].band == WifiScanner.WIFI_BAND_UNSPECIFIED) {
389                 Set<Integer> expectedChannels = new HashSet<>();
390                 for (WifiNative.ChannelSettings channel : expected.buckets[i].channels) {
391                     expectedChannels.add(channel.frequency);
392                 }
393                 Set<Integer> actualChannels = new HashSet<>();
394                 for (WifiNative.ChannelSettings channel : actual.buckets[i].channels) {
395                     actualChannels.add(channel.frequency);
396                 }
397                 assertEquals("channels", expectedChannels, actualChannels);
398             } else {
399                 // since num_channels and channels are ignored when band is not
400                 // WifiScanner.WIFI_BAND_UNSPECIFIED just assert that there are no channels
401                 // the band equality was already checked above
402                 assertEquals("buckets[" + i + "].num_channels not 0", 0,
403                         actual.buckets[i].num_channels);
404                 assertTrue("buckets[" + i + "].channels not null or empty",
405                         actual.buckets[i].channels == null
406                         || actual.buckets[i].channels.length == 0);
407             }
408         }
409     }
410 
411     /**
412      * Asserts if the provided pno settings are the same.
413      */
assertNativePnoSettingsEquals(WifiNative.PnoSettings expected, WifiNative.PnoSettings actual)414     public static void assertNativePnoSettingsEquals(WifiNative.PnoSettings expected,
415             WifiNative.PnoSettings actual) {
416         assertNotNull("expected was null", expected);
417         assertNotNull("actaul was null", actual);
418         assertEquals("min5GHzRssi", expected.min5GHzRssi, actual.min5GHzRssi);
419         assertEquals("min24GHzRssi", expected.min24GHzRssi, actual.min24GHzRssi);
420         assertEquals("initialScoreMax", expected.initialScoreMax, actual.initialScoreMax);
421         assertEquals("currentConnectionBonus", expected.currentConnectionBonus,
422                 actual.currentConnectionBonus);
423         assertEquals("sameNetworkBonus", expected.sameNetworkBonus, actual.sameNetworkBonus);
424         assertEquals("secureBonus", expected.secureBonus, actual.secureBonus);
425         assertEquals("band5GHzBonus", expected.band5GHzBonus, actual.band5GHzBonus);
426         assertEquals("isConnected", expected.isConnected, actual.isConnected);
427         assertNotNull("expected networkList was null", expected.networkList);
428         assertNotNull("actual networkList was null", actual.networkList);
429         assertEquals("networkList.length", expected.networkList.length, actual.networkList.length);
430         for (int i = 0; i < expected.networkList.length; i++) {
431             assertEquals("networkList[" + i + "].ssid",
432                     expected.networkList[i].ssid, actual.networkList[i].ssid);
433             assertEquals("networkList[" + i + "].flags",
434                     expected.networkList[i].flags, actual.networkList[i].flags);
435             assertEquals("networkList[" + i + "].auth_bit_field",
436                     expected.networkList[i].auth_bit_field, actual.networkList[i].auth_bit_field);
437         }
438     }
439 
440     /**
441      * Convert a list of channel frequencies to an array of equivalent WifiNative.ChannelSettings
442      */
channelsToNativeSettings(int... channels)443     public static WifiNative.ChannelSettings[] channelsToNativeSettings(int... channels) {
444         WifiNative.ChannelSettings[] channelSpecs = new WifiNative.ChannelSettings[channels.length];
445         for (int i = 0; i < channels.length; ++i) {
446             channelSpecs[i] = new WifiNative.ChannelSettings();
447             channelSpecs[i].frequency = channels[i];
448         }
449         return channelSpecs;
450     }
451 
452     /**
453      * Matcher to check that a BucketSettings has the given band
454      */
bandIs(final int expectedBand)455     public static Matcher<WifiNative.BucketSettings> bandIs(final int expectedBand) {
456         return new TypeSafeDiagnosingMatcher<WifiNative.BucketSettings>() {
457             @Override
458             public boolean matchesSafely(WifiNative.BucketSettings bucketSettings,
459                     Description mismatchDescription) {
460                 if (bucketSettings.band != expectedBand) {
461                     mismatchDescription
462                             .appendText("did not have expected band ").appendValue(expectedBand)
463                             .appendText(", was ").appendValue(bucketSettings.band);
464                     return false;
465                 } else {
466                     return true;
467                 }
468             }
469 
470             @Override
471             public void describeTo(final Description description) {
472                 description.appendText("bucket band is ").appendValue(expectedBand);
473             }
474         };
475     }
476 
477     /**
478      * Matcher to check that a BucketSettings has exactly the given channels
479      */
480     public static Matcher<WifiNative.BucketSettings> channelsAre(final int... expectedChannels) {
481         return new TypeSafeDiagnosingMatcher<WifiNative.BucketSettings>() {
482             @Override
483             public boolean matchesSafely(WifiNative.BucketSettings bucketSettings,
484                     Description mismatchDescription) {
485                 if (bucketSettings.band != WifiScanner.WIFI_BAND_UNSPECIFIED) {
486                     mismatchDescription.appendText("did not have expected unspecified band, was ")
487                             .appendValue(bucketSettings.band);
488                     return false;
489                 } else if (bucketSettings.num_channels != expectedChannels.length) {
490                     mismatchDescription
491                             .appendText("did not have expected num_channels ")
492                             .appendValue(expectedChannels.length)
493                             .appendText(", was ").appendValue(bucketSettings.num_channels);
494                     return false;
495                 } else if (bucketSettings.channels == null) {
496                     mismatchDescription.appendText("had null channels array");
497                     return false;
498                 } else if (bucketSettings.channels.length != expectedChannels.length) {
499                     mismatchDescription
500                             .appendText("did not have channels array length matching excepted ")
501                             .appendValue(expectedChannels.length)
502                             .appendText(", was ").appendValue(bucketSettings.channels.length);
503                     return false;
504                 } else {
505                     Set<Integer> foundChannelsSet = new HashSet<>();
506                     for (int i = 0; i < bucketSettings.channels.length; ++i) {
507                         foundChannelsSet.add(bucketSettings.channels[i].frequency);
508                     }
509                     Set<Integer> expectedChannelsSet = new HashSet<>();
510                     for (int i = 0; i < expectedChannels.length; ++i) {
511                         expectedChannelsSet.add(expectedChannels[i]);
512                     }
513 
514                     if (!foundChannelsSet.containsAll(expectedChannelsSet)
515                             || foundChannelsSet.size() != expectedChannelsSet.size()) {
516                         Set<Integer> extraChannelsSet = new HashSet<>(foundChannelsSet);
517                         extraChannelsSet.removeAll(expectedChannelsSet);
518                         expectedChannelsSet.removeAll(foundChannelsSet);
519                         mismatchDescription
520                                 .appendText("does not contain expected channels ")
521                                 .appendValue(expectedChannelsSet);
522                         if (extraChannelsSet.size() > 0) {
523                             mismatchDescription
524                                     .appendText(", but contains extra channels ")
525                                     .appendValue(extraChannelsSet);
526                         }
527                         return false;
528                     } else {
529                         return true;
530                     }
531                 }
532             }
533 
534             @Override
535             public void describeTo(final Description description) {
536                 description.appendText("bucket channels are ").appendValue(expectedChannels);
537             }
538         };
539     }
540 }
541