1/*
2 * Copyright (C) 2017 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
17package com.android.server.connectivity.tethering;
18
19import static android.net.ConnectivityManager.TYPE_MOBILE_DUN;
20import static android.net.ConnectivityManager.TYPE_MOBILE_HIPRI;
21import static android.net.ConnectivityManager.TYPE_NONE;
22import static android.net.ConnectivityManager.TYPE_WIFI;
23import static android.net.NetworkCapabilities.NET_CAPABILITY_DUN;
24import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
25import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
26import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
27import static org.junit.Assert.assertEquals;
28import static org.junit.Assert.assertFalse;
29import static org.junit.Assert.assertTrue;
30import static org.junit.Assert.fail;
31import static org.mockito.Mockito.any;
32import static org.mockito.Mockito.anyInt;
33import static org.mockito.Mockito.anyString;
34import static org.mockito.Mockito.reset;
35import static org.mockito.Mockito.spy;
36import static org.mockito.Mockito.times;
37import static org.mockito.Mockito.verify;
38import static org.mockito.Mockito.verifyNoMoreInteractions;
39import static org.mockito.Mockito.when;
40
41import android.content.Context;
42import android.os.Handler;
43import android.os.Message;
44import android.net.ConnectivityManager;
45import android.net.ConnectivityManager.NetworkCallback;
46import android.net.IConnectivityManager;
47import android.net.IpPrefix;
48import android.net.LinkAddress;
49import android.net.LinkProperties;
50import android.net.Network;
51import android.net.NetworkCapabilities;
52import android.net.NetworkRequest;
53import android.net.NetworkState;
54import android.net.util.SharedLog;
55
56import android.support.test.filters.SmallTest;
57import android.support.test.runner.AndroidJUnit4;
58
59import com.android.internal.util.State;
60import com.android.internal.util.StateMachine;
61
62import org.junit.After;
63import org.junit.Before;
64import org.junit.runner.RunWith;
65import org.junit.Test;
66import org.mockito.Mock;
67import org.mockito.Mockito;
68import org.mockito.MockitoAnnotations;
69
70import java.util.ArrayList;
71import java.util.Collection;
72import java.util.Collections;
73import java.util.HashMap;
74import java.util.HashSet;
75import java.util.Map;
76import java.util.Set;
77
78
79@RunWith(AndroidJUnit4.class)
80@SmallTest
81public class UpstreamNetworkMonitorTest {
82    private static final int EVENT_UNM_UPDATE = 1;
83
84    private static final boolean INCLUDES = true;
85    private static final boolean EXCLUDES = false;
86
87    @Mock private Context mContext;
88    @Mock private IConnectivityManager mCS;
89    @Mock private SharedLog mLog;
90
91    private TestStateMachine mSM;
92    private TestConnectivityManager mCM;
93    private UpstreamNetworkMonitor mUNM;
94
95    @Before public void setUp() throws Exception {
96        MockitoAnnotations.initMocks(this);
97        reset(mContext);
98        reset(mCS);
99        reset(mLog);
100        when(mLog.forSubComponent(anyString())).thenReturn(mLog);
101
102        mCM = spy(new TestConnectivityManager(mContext, mCS));
103        mSM = new TestStateMachine();
104        mUNM = new UpstreamNetworkMonitor(
105                (ConnectivityManager) mCM, mSM, mLog, EVENT_UNM_UPDATE);
106    }
107
108    @After public void tearDown() throws Exception {
109        if (mSM != null) {
110            mSM.quit();
111            mSM = null;
112        }
113    }
114
115    @Test
116    public void testDoesNothingBeforeStarted() {
117        assertTrue(mCM.hasNoCallbacks());
118        assertFalse(mUNM.mobileNetworkRequested());
119
120        mUNM.updateMobileRequiresDun(true);
121        assertTrue(mCM.hasNoCallbacks());
122        mUNM.updateMobileRequiresDun(false);
123        assertTrue(mCM.hasNoCallbacks());
124    }
125
126    @Test
127    public void testDefaultNetworkIsTracked() throws Exception {
128        assertEquals(0, mCM.trackingDefault.size());
129
130        mUNM.start();
131        assertEquals(1, mCM.trackingDefault.size());
132
133        mUNM.stop();
134        assertTrue(mCM.hasNoCallbacks());
135    }
136
137    @Test
138    public void testListensForAllNetworks() throws Exception {
139        assertTrue(mCM.listening.isEmpty());
140
141        mUNM.start();
142        assertFalse(mCM.listening.isEmpty());
143        assertTrue(mCM.isListeningForAll());
144
145        mUNM.stop();
146        assertTrue(mCM.hasNoCallbacks());
147    }
148
149    @Test
150    public void testRequestsMobileNetwork() throws Exception {
151        assertFalse(mUNM.mobileNetworkRequested());
152        assertEquals(0, mCM.requested.size());
153
154        mUNM.start();
155        assertFalse(mUNM.mobileNetworkRequested());
156        assertEquals(0, mCM.requested.size());
157
158        mUNM.updateMobileRequiresDun(false);
159        assertFalse(mUNM.mobileNetworkRequested());
160        assertEquals(0, mCM.requested.size());
161
162        mUNM.registerMobileNetworkRequest();
163        assertTrue(mUNM.mobileNetworkRequested());
164        assertUpstreamTypeRequested(TYPE_MOBILE_HIPRI);
165        assertFalse(mCM.isDunRequested());
166
167        mUNM.stop();
168        assertFalse(mUNM.mobileNetworkRequested());
169        assertTrue(mCM.hasNoCallbacks());
170    }
171
172    @Test
173    public void testDuplicateMobileRequestsIgnored() throws Exception {
174        assertFalse(mUNM.mobileNetworkRequested());
175        assertEquals(0, mCM.requested.size());
176
177        mUNM.start();
178        verify(mCM, Mockito.times(1)).registerNetworkCallback(
179                any(NetworkRequest.class), any(NetworkCallback.class), any(Handler.class));
180        verify(mCM, Mockito.times(1)).registerDefaultNetworkCallback(
181                any(NetworkCallback.class), any(Handler.class));
182        assertFalse(mUNM.mobileNetworkRequested());
183        assertEquals(0, mCM.requested.size());
184
185        mUNM.updateMobileRequiresDun(true);
186        mUNM.registerMobileNetworkRequest();
187        verify(mCM, Mockito.times(1)).requestNetwork(
188                any(NetworkRequest.class), any(NetworkCallback.class), anyInt(), anyInt(),
189                any(Handler.class));
190
191        assertTrue(mUNM.mobileNetworkRequested());
192        assertUpstreamTypeRequested(TYPE_MOBILE_DUN);
193        assertTrue(mCM.isDunRequested());
194
195        // Try a few things that must not result in any state change.
196        mUNM.registerMobileNetworkRequest();
197        mUNM.updateMobileRequiresDun(true);
198        mUNM.registerMobileNetworkRequest();
199
200        assertTrue(mUNM.mobileNetworkRequested());
201        assertUpstreamTypeRequested(TYPE_MOBILE_DUN);
202        assertTrue(mCM.isDunRequested());
203
204        mUNM.stop();
205        verify(mCM, times(3)).unregisterNetworkCallback(any(NetworkCallback.class));
206
207        verifyNoMoreInteractions(mCM);
208    }
209
210    @Test
211    public void testRequestsDunNetwork() throws Exception {
212        assertFalse(mUNM.mobileNetworkRequested());
213        assertEquals(0, mCM.requested.size());
214
215        mUNM.start();
216        assertFalse(mUNM.mobileNetworkRequested());
217        assertEquals(0, mCM.requested.size());
218
219        mUNM.updateMobileRequiresDun(true);
220        assertFalse(mUNM.mobileNetworkRequested());
221        assertEquals(0, mCM.requested.size());
222
223        mUNM.registerMobileNetworkRequest();
224        assertTrue(mUNM.mobileNetworkRequested());
225        assertUpstreamTypeRequested(TYPE_MOBILE_DUN);
226        assertTrue(mCM.isDunRequested());
227
228        mUNM.stop();
229        assertFalse(mUNM.mobileNetworkRequested());
230        assertTrue(mCM.hasNoCallbacks());
231    }
232
233    @Test
234    public void testUpdateMobileRequiresDun() throws Exception {
235        mUNM.start();
236
237        // Test going from no-DUN to DUN correctly re-registers callbacks.
238        mUNM.updateMobileRequiresDun(false);
239        mUNM.registerMobileNetworkRequest();
240        assertTrue(mUNM.mobileNetworkRequested());
241        assertUpstreamTypeRequested(TYPE_MOBILE_HIPRI);
242        assertFalse(mCM.isDunRequested());
243        mUNM.updateMobileRequiresDun(true);
244        assertTrue(mUNM.mobileNetworkRequested());
245        assertUpstreamTypeRequested(TYPE_MOBILE_DUN);
246        assertTrue(mCM.isDunRequested());
247
248        // Test going from DUN to no-DUN correctly re-registers callbacks.
249        mUNM.updateMobileRequiresDun(false);
250        assertTrue(mUNM.mobileNetworkRequested());
251        assertUpstreamTypeRequested(TYPE_MOBILE_HIPRI);
252        assertFalse(mCM.isDunRequested());
253
254        mUNM.stop();
255        assertFalse(mUNM.mobileNetworkRequested());
256    }
257
258    @Test
259    public void testSelectPreferredUpstreamType() throws Exception {
260        final Collection<Integer> preferredTypes = new ArrayList<>();
261        preferredTypes.add(TYPE_WIFI);
262
263        mUNM.start();
264        // There are no networks, so there is nothing to select.
265        assertSatisfiesLegacyType(TYPE_NONE, mUNM.selectPreferredUpstreamType(preferredTypes));
266
267        final TestNetworkAgent wifiAgent = new TestNetworkAgent(mCM, TRANSPORT_WIFI);
268        wifiAgent.fakeConnect();
269        // WiFi is up, we should prefer it.
270        assertSatisfiesLegacyType(TYPE_WIFI, mUNM.selectPreferredUpstreamType(preferredTypes));
271        wifiAgent.fakeDisconnect();
272        // There are no networks, so there is nothing to select.
273        assertSatisfiesLegacyType(TYPE_NONE, mUNM.selectPreferredUpstreamType(preferredTypes));
274
275        final TestNetworkAgent cellAgent = new TestNetworkAgent(mCM, TRANSPORT_CELLULAR);
276        cellAgent.fakeConnect();
277        assertSatisfiesLegacyType(TYPE_NONE, mUNM.selectPreferredUpstreamType(preferredTypes));
278
279        preferredTypes.add(TYPE_MOBILE_DUN);
280        // This is coupled with preferred types in TetheringConfiguration.
281        mUNM.updateMobileRequiresDun(true);
282        // DUN is available, but only use regular cell: no upstream selected.
283        assertSatisfiesLegacyType(TYPE_NONE, mUNM.selectPreferredUpstreamType(preferredTypes));
284        preferredTypes.remove(TYPE_MOBILE_DUN);
285        // No WiFi, but our preferred flavour of cell is up.
286        preferredTypes.add(TYPE_MOBILE_HIPRI);
287        // This is coupled with preferred types in TetheringConfiguration.
288        mUNM.updateMobileRequiresDun(false);
289        assertSatisfiesLegacyType(TYPE_MOBILE_HIPRI,
290                mUNM.selectPreferredUpstreamType(preferredTypes));
291        // Check to see we filed an explicit request.
292        assertEquals(1, mCM.requested.size());
293        NetworkRequest netReq = (NetworkRequest) mCM.requested.values().toArray()[0];
294        assertTrue(netReq.networkCapabilities.hasTransport(TRANSPORT_CELLULAR));
295        assertFalse(netReq.networkCapabilities.hasCapability(NET_CAPABILITY_DUN));
296
297        wifiAgent.fakeConnect();
298        // WiFi is up, and we should prefer it over cell.
299        assertSatisfiesLegacyType(TYPE_WIFI, mUNM.selectPreferredUpstreamType(preferredTypes));
300        assertEquals(0, mCM.requested.size());
301
302        preferredTypes.remove(TYPE_MOBILE_HIPRI);
303        preferredTypes.add(TYPE_MOBILE_DUN);
304        // This is coupled with preferred types in TetheringConfiguration.
305        mUNM.updateMobileRequiresDun(true);
306        assertSatisfiesLegacyType(TYPE_WIFI, mUNM.selectPreferredUpstreamType(preferredTypes));
307
308        final TestNetworkAgent dunAgent = new TestNetworkAgent(mCM, TRANSPORT_CELLULAR);
309        dunAgent.networkCapabilities.addCapability(NET_CAPABILITY_DUN);
310        dunAgent.fakeConnect();
311
312        // WiFi is still preferred.
313        assertSatisfiesLegacyType(TYPE_WIFI, mUNM.selectPreferredUpstreamType(preferredTypes));
314
315        // WiFi goes down, cell and DUN are still up but only DUN is preferred.
316        wifiAgent.fakeDisconnect();
317        assertSatisfiesLegacyType(TYPE_MOBILE_DUN,
318                mUNM.selectPreferredUpstreamType(preferredTypes));
319        // Check to see we filed an explicit request.
320        assertEquals(1, mCM.requested.size());
321        netReq = (NetworkRequest) mCM.requested.values().toArray()[0];
322        assertTrue(netReq.networkCapabilities.hasTransport(TRANSPORT_CELLULAR));
323        assertTrue(netReq.networkCapabilities.hasCapability(NET_CAPABILITY_DUN));
324    }
325
326    @Test
327    public void testLocalPrefixes() throws Exception {
328        mUNM.start();
329
330        // [0] Test minimum set of local prefixes.
331        Set<IpPrefix> local = mUNM.getLocalPrefixes();
332        assertTrue(local.isEmpty());
333
334        final Set<String> alreadySeen = new HashSet<>();
335
336        // [1] Pretend Wi-Fi connects.
337        final TestNetworkAgent wifiAgent = new TestNetworkAgent(mCM, TRANSPORT_WIFI);
338        final LinkProperties wifiLp = new LinkProperties();
339        wifiLp.setInterfaceName("wlan0");
340        final String[] WIFI_ADDRS = {
341                "fe80::827a:bfff:fe6f:374d", "100.112.103.18",
342                "2001:db8:4:fd00:827a:bfff:fe6f:374d",
343                "2001:db8:4:fd00:6dea:325a:fdae:4ef4",
344                "fd6a:a640:60bf:e985::123",  // ULA address for good measure.
345        };
346        for (String addrStr : WIFI_ADDRS) {
347            final String cidr = addrStr.contains(":") ? "/64" : "/20";
348            wifiLp.addLinkAddress(new LinkAddress(addrStr + cidr));
349        }
350        wifiAgent.fakeConnect();
351        wifiAgent.sendLinkProperties(wifiLp);
352
353        local = mUNM.getLocalPrefixes();
354        assertPrefixSet(local, INCLUDES, alreadySeen);
355        final String[] wifiLinkPrefixes = {
356                // Link-local prefixes are excluded and dealt with elsewhere.
357                "100.112.96.0/20", "2001:db8:4:fd00::/64", "fd6a:a640:60bf:e985::/64",
358        };
359        assertPrefixSet(local, INCLUDES, wifiLinkPrefixes);
360        Collections.addAll(alreadySeen, wifiLinkPrefixes);
361        assertEquals(alreadySeen.size(), local.size());
362
363        // [2] Pretend mobile connects.
364        final TestNetworkAgent cellAgent = new TestNetworkAgent(mCM, TRANSPORT_CELLULAR);
365        final LinkProperties cellLp = new LinkProperties();
366        cellLp.setInterfaceName("rmnet_data0");
367        final String[] CELL_ADDRS = {
368                "10.102.211.48", "2001:db8:0:1:b50e:70d9:10c9:433d",
369        };
370        for (String addrStr : CELL_ADDRS) {
371            final String cidr = addrStr.contains(":") ? "/64" : "/27";
372            cellLp.addLinkAddress(new LinkAddress(addrStr + cidr));
373        }
374        cellAgent.fakeConnect();
375        cellAgent.sendLinkProperties(cellLp);
376
377        local = mUNM.getLocalPrefixes();
378        assertPrefixSet(local, INCLUDES, alreadySeen);
379        final String[] cellLinkPrefixes = { "10.102.211.32/27", "2001:db8:0:1::/64" };
380        assertPrefixSet(local, INCLUDES, cellLinkPrefixes);
381        Collections.addAll(alreadySeen, cellLinkPrefixes);
382        assertEquals(alreadySeen.size(), local.size());
383
384        // [3] Pretend DUN connects.
385        final TestNetworkAgent dunAgent = new TestNetworkAgent(mCM, TRANSPORT_CELLULAR);
386        dunAgent.networkCapabilities.addCapability(NET_CAPABILITY_DUN);
387        final LinkProperties dunLp = new LinkProperties();
388        dunLp.setInterfaceName("rmnet_data1");
389        final String[] DUN_ADDRS = {
390                "192.0.2.48", "2001:db8:1:2:b50e:70d9:10c9:433d",
391        };
392        for (String addrStr : DUN_ADDRS) {
393            final String cidr = addrStr.contains(":") ? "/64" : "/27";
394            cellLp.addLinkAddress(new LinkAddress(addrStr + cidr));
395        }
396        dunAgent.fakeConnect();
397        dunAgent.sendLinkProperties(dunLp);
398
399        local = mUNM.getLocalPrefixes();
400        assertPrefixSet(local, INCLUDES, alreadySeen);
401        final String[] dunLinkPrefixes = { "192.0.2.32/27", "2001:db8:1:2::/64" };
402        assertPrefixSet(local, INCLUDES, dunLinkPrefixes);
403        Collections.addAll(alreadySeen, dunLinkPrefixes);
404        assertEquals(alreadySeen.size(), local.size());
405
406        // [4] Pretend Wi-Fi disconnected.  It's addresses/prefixes should no
407        // longer be included (should be properly removed).
408        wifiAgent.fakeDisconnect();
409        local = mUNM.getLocalPrefixes();
410        assertPrefixSet(local, EXCLUDES, wifiLinkPrefixes);
411        assertPrefixSet(local, INCLUDES, cellLinkPrefixes);
412        assertPrefixSet(local, INCLUDES, dunLinkPrefixes);
413    }
414
415    private void assertSatisfiesLegacyType(int legacyType, NetworkState ns) {
416        if (legacyType == TYPE_NONE) {
417            assertTrue(ns == null);
418            return;
419        }
420
421        final NetworkCapabilities nc = ConnectivityManager.networkCapabilitiesForType(legacyType);
422        assertTrue(nc.satisfiedByNetworkCapabilities(ns.networkCapabilities));
423    }
424
425    private void assertUpstreamTypeRequested(int upstreamType) throws Exception {
426        assertEquals(1, mCM.requested.size());
427        assertEquals(1, mCM.legacyTypeMap.size());
428        assertEquals(Integer.valueOf(upstreamType),
429                mCM.legacyTypeMap.values().iterator().next());
430    }
431
432    public static class TestConnectivityManager extends ConnectivityManager {
433        public Map<NetworkCallback, Handler> allCallbacks = new HashMap<>();
434        public Set<NetworkCallback> trackingDefault = new HashSet<>();
435        public Map<NetworkCallback, NetworkRequest> listening = new HashMap<>();
436        public Map<NetworkCallback, NetworkRequest> requested = new HashMap<>();
437        public Map<NetworkCallback, Integer> legacyTypeMap = new HashMap<>();
438
439        private int mNetworkId = 100;
440
441        public TestConnectivityManager(Context ctx, IConnectivityManager svc) {
442            super(ctx, svc);
443        }
444
445        boolean hasNoCallbacks() {
446            return allCallbacks.isEmpty() &&
447                   trackingDefault.isEmpty() &&
448                   listening.isEmpty() &&
449                   requested.isEmpty() &&
450                   legacyTypeMap.isEmpty();
451        }
452
453        boolean isListeningForAll() {
454            final NetworkCapabilities empty = new NetworkCapabilities();
455            empty.clearAll();
456
457            for (NetworkRequest req : listening.values()) {
458                if (req.networkCapabilities.equalRequestableCapabilities(empty)) {
459                    return true;
460                }
461            }
462            return false;
463        }
464
465        boolean isDunRequested() {
466            for (NetworkRequest req : requested.values()) {
467                if (req.networkCapabilities.hasCapability(NET_CAPABILITY_DUN)) {
468                    return true;
469                }
470            }
471            return false;
472        }
473
474        int getNetworkId() { return ++mNetworkId; }
475
476        @Override
477        public void requestNetwork(NetworkRequest req, NetworkCallback cb, Handler h) {
478            assertFalse(allCallbacks.containsKey(cb));
479            allCallbacks.put(cb, h);
480            assertFalse(requested.containsKey(cb));
481            requested.put(cb, req);
482        }
483
484        @Override
485        public void requestNetwork(NetworkRequest req, NetworkCallback cb) {
486            fail("Should never be called.");
487        }
488
489        @Override
490        public void requestNetwork(NetworkRequest req, NetworkCallback cb,
491                int timeoutMs, int legacyType, Handler h) {
492            assertFalse(allCallbacks.containsKey(cb));
493            allCallbacks.put(cb, h);
494            assertFalse(requested.containsKey(cb));
495            requested.put(cb, req);
496            assertFalse(legacyTypeMap.containsKey(cb));
497            if (legacyType != ConnectivityManager.TYPE_NONE) {
498                legacyTypeMap.put(cb, legacyType);
499            }
500        }
501
502        @Override
503        public void registerNetworkCallback(NetworkRequest req, NetworkCallback cb, Handler h) {
504            assertFalse(allCallbacks.containsKey(cb));
505            allCallbacks.put(cb, h);
506            assertFalse(listening.containsKey(cb));
507            listening.put(cb, req);
508        }
509
510        @Override
511        public void registerNetworkCallback(NetworkRequest req, NetworkCallback cb) {
512            fail("Should never be called.");
513        }
514
515        @Override
516        public void registerDefaultNetworkCallback(NetworkCallback cb, Handler h) {
517            assertFalse(allCallbacks.containsKey(cb));
518            allCallbacks.put(cb, h);
519            assertFalse(trackingDefault.contains(cb));
520            trackingDefault.add(cb);
521        }
522
523        @Override
524        public void registerDefaultNetworkCallback(NetworkCallback cb) {
525            fail("Should never be called.");
526        }
527
528        @Override
529        public void unregisterNetworkCallback(NetworkCallback cb) {
530            if (trackingDefault.contains(cb)) {
531                trackingDefault.remove(cb);
532            } else if (listening.containsKey(cb)) {
533                listening.remove(cb);
534            } else if (requested.containsKey(cb)) {
535                requested.remove(cb);
536                legacyTypeMap.remove(cb);
537            } else {
538                fail("Unexpected callback removed");
539            }
540            allCallbacks.remove(cb);
541
542            assertFalse(allCallbacks.containsKey(cb));
543            assertFalse(trackingDefault.contains(cb));
544            assertFalse(listening.containsKey(cb));
545            assertFalse(requested.containsKey(cb));
546        }
547    }
548
549    public static class TestNetworkAgent {
550        public final TestConnectivityManager cm;
551        public final Network networkId;
552        public final int transportType;
553        public final NetworkCapabilities networkCapabilities;
554
555        public TestNetworkAgent(TestConnectivityManager cm, int transportType) {
556            this.cm = cm;
557            this.networkId = new Network(cm.getNetworkId());
558            this.transportType = transportType;
559            networkCapabilities = new NetworkCapabilities();
560            networkCapabilities.addTransportType(transportType);
561            networkCapabilities.addCapability(NET_CAPABILITY_INTERNET);
562        }
563
564        public void fakeConnect() {
565            for (NetworkCallback cb : cm.listening.keySet()) {
566                cb.onAvailable(networkId);
567                cb.onCapabilitiesChanged(networkId, copy(networkCapabilities));
568            }
569        }
570
571        public void fakeDisconnect() {
572            for (NetworkCallback cb : cm.listening.keySet()) {
573                cb.onLost(networkId);
574            }
575        }
576
577        public void sendLinkProperties(LinkProperties lp) {
578            for (NetworkCallback cb : cm.listening.keySet()) {
579                cb.onLinkPropertiesChanged(networkId, lp);
580            }
581        }
582    }
583
584    public static class TestStateMachine extends StateMachine {
585        public final ArrayList<Message> messages = new ArrayList<>();
586        private final State mLoggingState = new LoggingState();
587
588        class LoggingState extends State {
589            @Override public void enter() { messages.clear(); }
590
591            @Override public void exit() { messages.clear(); }
592
593            @Override public boolean processMessage(Message msg) {
594                messages.add(msg);
595                return true;
596            }
597        }
598
599        public TestStateMachine() {
600            super("UpstreamNetworkMonitor.TestStateMachine");
601            addState(mLoggingState);
602            setInitialState(mLoggingState);
603            super.start();
604        }
605    }
606
607    static NetworkCapabilities copy(NetworkCapabilities nc) {
608        return new NetworkCapabilities(nc);
609    }
610
611    static void assertPrefixSet(Set<IpPrefix> prefixes, boolean expectation, String... expected) {
612        final Set<String> expectedSet = new HashSet<>();
613        Collections.addAll(expectedSet, expected);
614        assertPrefixSet(prefixes, expectation, expectedSet);
615    }
616
617    static void assertPrefixSet(Set<IpPrefix> prefixes, boolean expectation, Set<String> expected) {
618        for (String expectedPrefix : expected) {
619            final String errStr = expectation ? "did not find" : "found";
620            assertEquals(
621                    String.format("Failed expectation: %s prefix: %s", errStr, expectedPrefix),
622                    expectation, prefixes.contains(new IpPrefix(expectedPrefix)));
623        }
624    }
625}
626