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
17package com.android.server.connectivity;
18
19import static android.net.metrics.INetdEventListener.EVENT_GETADDRINFO;
20import static android.net.metrics.INetdEventListener.EVENT_GETHOSTBYNAME;
21import static org.mockito.Mockito.mock;
22import static org.mockito.Mockito.timeout;
23import static org.mockito.Mockito.verify;
24import static org.mockito.Mockito.when;
25import static org.junit.Assert.assertEquals;
26import static org.junit.Assert.assertFalse;
27import static org.junit.Assert.assertTrue;
28import static org.junit.Assert.fail;
29
30import android.content.Context;
31import android.net.ConnectivityManager;
32import android.net.ConnectivityMetricsEvent;
33import android.net.IIpConnectivityMetrics;
34import android.net.IpPrefix;
35import android.net.LinkAddress;
36import android.net.LinkProperties;
37import android.net.RouteInfo;
38import android.net.Network;
39import android.net.NetworkCapabilities;
40import android.net.metrics.ApfProgramEvent;
41import android.net.metrics.ApfStats;
42import android.net.metrics.DefaultNetworkEvent;
43import android.net.metrics.DhcpClientEvent;
44import android.net.metrics.IpConnectivityLog;
45import android.net.metrics.IpManagerEvent;
46import android.net.metrics.IpReachabilityEvent;
47import android.net.metrics.RaEvent;
48import android.net.metrics.ValidationProbeEvent;
49import android.os.Parcelable;
50import android.support.test.runner.AndroidJUnit4;
51import android.system.OsConstants;
52import android.test.suitebuilder.annotation.SmallTest;
53import android.util.Base64;
54
55import com.android.internal.util.BitUtils;
56import com.android.server.connectivity.metrics.nano.IpConnectivityLogClass;
57
58import java.io.PrintWriter;
59import java.io.StringWriter;
60import java.util.Collections;
61import java.util.Comparator;
62import java.util.Iterator;
63import java.util.List;
64
65import org.mockito.ArgumentCaptor;
66import org.mockito.Mock;
67import org.mockito.MockitoAnnotations;
68import org.junit.Before;
69import org.junit.Test;
70import org.junit.runner.RunWith;
71
72@RunWith(AndroidJUnit4.class)
73@SmallTest
74public class IpConnectivityMetricsTest {
75    static final IpReachabilityEvent FAKE_EV =
76            new IpReachabilityEvent(IpReachabilityEvent.NUD_FAILED);
77
78    private static final String EXAMPLE_IPV4 = "192.0.2.1";
79    private static final String EXAMPLE_IPV6 = "2001:db8:1200::2:1";
80
81    private static final byte[] MAC_ADDR =
82            {(byte)0x84, (byte)0xc9, (byte)0xb2, (byte)0x6a, (byte)0xed, (byte)0x4b};
83
84    @Mock Context mCtx;
85    @Mock IIpConnectivityMetrics mMockService;
86    @Mock ConnectivityManager mCm;
87
88    IpConnectivityMetrics mService;
89    NetdEventListenerService mNetdListener;
90
91    @Before
92    public void setUp() {
93        MockitoAnnotations.initMocks(this);
94        mService = new IpConnectivityMetrics(mCtx, (ctx) -> 2000);
95        mNetdListener = new NetdEventListenerService(mCm);
96        mService.mNetdListener = mNetdListener;
97    }
98
99    @Test
100    public void testLoggingEvents() throws Exception {
101        IpConnectivityLog logger = new IpConnectivityLog(mMockService);
102
103        assertTrue(logger.log(1, FAKE_EV));
104        assertTrue(logger.log(2, FAKE_EV));
105        assertTrue(logger.log(3, FAKE_EV));
106
107        List<ConnectivityMetricsEvent> got = verifyEvents(3);
108        assertEventsEqual(expectedEvent(1), got.get(0));
109        assertEventsEqual(expectedEvent(2), got.get(1));
110        assertEventsEqual(expectedEvent(3), got.get(2));
111    }
112
113    @Test
114    public void testLoggingEventsWithMultipleCallers() throws Exception {
115        IpConnectivityLog logger = new IpConnectivityLog(mMockService);
116
117        final int nCallers = 10;
118        final int nEvents = 10;
119        for (int n = 0; n < nCallers; n++) {
120            final int i = n;
121            new Thread() {
122                public void run() {
123                    for (int j = 0; j < nEvents; j++) {
124                        assertTrue(logger.log(1 + i * 100 + j, FAKE_EV));
125                    }
126                }
127            }.start();
128        }
129
130        List<ConnectivityMetricsEvent> got = verifyEvents(nCallers * nEvents, 200);
131        Collections.sort(got, EVENT_COMPARATOR);
132        Iterator<ConnectivityMetricsEvent> iter = got.iterator();
133        for (int i = 0; i < nCallers; i++) {
134            for (int j = 0; j < nEvents; j++) {
135                int expectedTimestamp = 1 + i * 100 + j;
136                assertEventsEqual(expectedEvent(expectedTimestamp), iter.next());
137            }
138        }
139    }
140
141    @Test
142    public void testBufferFlushing() {
143        String output1 = getdump("flush");
144        assertEquals("", output1);
145
146        new IpConnectivityLog(mService.impl).log(1, FAKE_EV);
147        String output2 = getdump("flush");
148        assertFalse("".equals(output2));
149
150        String output3 = getdump("flush");
151        assertEquals("", output3);
152    }
153
154    @Test
155    public void testRateLimiting() {
156        final IpConnectivityLog logger = new IpConnectivityLog(mService.impl);
157        final ApfProgramEvent ev = new ApfProgramEvent();
158        final long fakeTimestamp = 1;
159
160        int attempt = 100; // More than burst quota, but less than buffer size.
161        for (int i = 0; i < attempt; i++) {
162            logger.log(ev);
163        }
164
165        String output1 = getdump("flush");
166        assertFalse("".equals(output1));
167
168        for (int i = 0; i < attempt; i++) {
169            assertFalse("expected event to be dropped", logger.log(fakeTimestamp, ev));
170        }
171
172        String output2 = getdump("flush");
173        assertEquals("", output2);
174    }
175
176    @Test
177    public void testDefaultNetworkEvents() throws Exception {
178        final long cell = BitUtils.packBits(new int[]{NetworkCapabilities.TRANSPORT_CELLULAR});
179        final long wifi = BitUtils.packBits(new int[]{NetworkCapabilities.TRANSPORT_WIFI});
180
181        NetworkAgentInfo[][] defaultNetworks = {
182            // nothing -> cell
183            {null, makeNai(100, 10, false, true, cell)},
184            // cell -> wifi
185            {makeNai(100, 50, true, true, cell), makeNai(101, 20, true, false, wifi)},
186            // wifi -> nothing
187            {makeNai(101, 60, true, false, wifi), null},
188            // nothing -> cell
189            {null, makeNai(102, 10, true, true, cell)},
190            // cell -> wifi
191            {makeNai(102, 50, true, true, cell), makeNai(103, 20, true, false, wifi)},
192        };
193
194        long timeMs = mService.mDefaultNetworkMetrics.creationTimeMs;
195        long durationMs = 1001;
196        for (NetworkAgentInfo[] pair : defaultNetworks) {
197            timeMs += durationMs;
198            durationMs += durationMs;
199            mService.mDefaultNetworkMetrics.logDefaultNetworkEvent(timeMs, pair[1], pair[0]);
200        }
201
202        String want = String.join("\n",
203                "dropped_events: 0",
204                "events <",
205                "  if_name: \"\"",
206                "  link_layer: 5",
207                "  network_id: 0",
208                "  time_ms: 0",
209                "  transports: 0",
210                "  default_network_event <",
211                "    default_network_duration_ms: 1001",
212                "    final_score: 0",
213                "    initial_score: 0",
214                "    ip_support: 0",
215                "    no_default_network_duration_ms: 0",
216                "    previous_default_network_link_layer: 0",
217                "    previous_network_ip_support: 0",
218                "    validation_duration_ms: 0",
219                "  >",
220                ">",
221                "events <",
222                "  if_name: \"\"",
223                "  link_layer: 2",
224                "  network_id: 100",
225                "  time_ms: 0",
226                "  transports: 1",
227                "  default_network_event <",
228                "    default_network_duration_ms: 2002",
229                "    final_score: 50",
230                "    initial_score: 10",
231                "    ip_support: 3",
232                "    no_default_network_duration_ms: 0",
233                "    previous_default_network_link_layer: 0",
234                "    previous_network_ip_support: 0",
235                "    validation_duration_ms: 2002",
236                "  >",
237                ">",
238                "events <",
239                "  if_name: \"\"",
240                "  link_layer: 4",
241                "  network_id: 101",
242                "  time_ms: 0",
243                "  transports: 2",
244                "  default_network_event <",
245                "    default_network_duration_ms: 4004",
246                "    final_score: 60",
247                "    initial_score: 20",
248                "    ip_support: 1",
249                "    no_default_network_duration_ms: 0",
250                "    previous_default_network_link_layer: 2",
251                "    previous_network_ip_support: 0",
252                "    validation_duration_ms: 4004",
253                "  >",
254                ">",
255                "events <",
256                "  if_name: \"\"",
257                "  link_layer: 5",
258                "  network_id: 0",
259                "  time_ms: 0",
260                "  transports: 0",
261                "  default_network_event <",
262                "    default_network_duration_ms: 8008",
263                "    final_score: 0",
264                "    initial_score: 0",
265                "    ip_support: 0",
266                "    no_default_network_duration_ms: 0",
267                "    previous_default_network_link_layer: 4",
268                "    previous_network_ip_support: 0",
269                "    validation_duration_ms: 0",
270                "  >",
271                ">",
272                "events <",
273                "  if_name: \"\"",
274                "  link_layer: 2",
275                "  network_id: 102",
276                "  time_ms: 0",
277                "  transports: 1",
278                "  default_network_event <",
279                "    default_network_duration_ms: 16016",
280                "    final_score: 50",
281                "    initial_score: 10",
282                "    ip_support: 3",
283                "    no_default_network_duration_ms: 0",
284                "    previous_default_network_link_layer: 4",
285                "    previous_network_ip_support: 0",
286                "    validation_duration_ms: 16016",
287                "  >",
288                ">",
289                "version: 2\n");
290
291        verifySerialization(want, getdump("flush"));
292    }
293
294    @Test
295    public void testEndToEndLogging() throws Exception {
296        // TODO: instead of comparing textpb to textpb, parse textpb and compare proto to proto.
297        IpConnectivityLog logger = new IpConnectivityLog(mService.impl);
298
299        NetworkCapabilities ncWifi = new NetworkCapabilities();
300        NetworkCapabilities ncCell = new NetworkCapabilities();
301        ncWifi.addTransportType(NetworkCapabilities.TRANSPORT_WIFI);
302        ncCell.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR);
303
304        when(mCm.getNetworkCapabilities(new Network(100))).thenReturn(ncWifi);
305        when(mCm.getNetworkCapabilities(new Network(101))).thenReturn(ncCell);
306
307        ApfStats apfStats = new ApfStats();
308        apfStats.durationMs = 45000;
309        apfStats.receivedRas = 10;
310        apfStats.matchingRas = 2;
311        apfStats.droppedRas = 2;
312        apfStats.parseErrors = 2;
313        apfStats.zeroLifetimeRas = 1;
314        apfStats.programUpdates = 4;
315        apfStats.programUpdatesAll = 7;
316        apfStats.programUpdatesAllowingMulticast = 3;
317        apfStats.maxProgramSize = 2048;
318
319        ValidationProbeEvent validationEv = new ValidationProbeEvent();
320        validationEv.durationMs = 40730;
321        validationEv.probeType = ValidationProbeEvent.PROBE_HTTP;
322        validationEv.returnCode = 204;
323
324        Parcelable[] events = {
325            new IpReachabilityEvent(IpReachabilityEvent.NUD_FAILED),
326            new DhcpClientEvent("SomeState", 192),
327            new IpManagerEvent(IpManagerEvent.PROVISIONING_OK, 5678),
328            validationEv,
329            apfStats,
330            new RaEvent(2000, 400, 300, -1, 1000, -1)
331        };
332
333        for (int i = 0; i < events.length; i++) {
334            ConnectivityMetricsEvent ev = new ConnectivityMetricsEvent();
335            ev.timestamp = 100 * (i + 1);
336            ev.ifname = "wlan0";
337            ev.data = events[i];
338            logger.log(ev);
339        }
340
341        // netId, errno, latency, destination
342        connectEvent(100, OsConstants.EALREADY, 0, EXAMPLE_IPV4);
343        connectEvent(100, OsConstants.EINPROGRESS, 0, EXAMPLE_IPV6);
344        connectEvent(100, 0, 110, EXAMPLE_IPV4);
345        connectEvent(101, 0, 23, EXAMPLE_IPV4);
346        connectEvent(101, 0, 45, EXAMPLE_IPV6);
347        connectEvent(100, OsConstants.EAGAIN, 0, EXAMPLE_IPV4);
348
349        // netId, type, return code, latency
350        dnsEvent(100, EVENT_GETADDRINFO, 0, 3456);
351        dnsEvent(100, EVENT_GETADDRINFO, 3, 45);
352        dnsEvent(100, EVENT_GETHOSTBYNAME, 0, 638);
353        dnsEvent(101, EVENT_GETADDRINFO, 0, 56);
354        dnsEvent(101, EVENT_GETHOSTBYNAME, 0, 34);
355
356        // iface, uid
357        final byte[] mac = {0x48, 0x7c, 0x2b, 0x6a, 0x3e, 0x4b};
358        final String srcIp = "192.168.2.1";
359        final String dstIp = "192.168.2.23";
360        final int sport = 2356;
361        final int dport = 13489;
362        final long now = 1001L;
363        final int v4 = 0x800;
364        final int tcp = 6;
365        final int udp = 17;
366        wakeupEvent("wlan0", 1000, v4, tcp, mac, srcIp, dstIp, sport, dport, 1001L);
367        wakeupEvent("wlan0", 10123, v4, tcp, mac, srcIp, dstIp, sport, dport, 1001L);
368        wakeupEvent("wlan0", 1000, v4, udp, mac, srcIp, dstIp, sport, dport, 1001L);
369        wakeupEvent("wlan0", 10008, v4, udp, mac, srcIp, dstIp, sport, dport, 1001L);
370        wakeupEvent("wlan0", -1, v4, udp, mac, srcIp, dstIp, sport, dport, 1001L);
371        wakeupEvent("wlan0", 10008, v4, tcp, mac, srcIp, dstIp, sport, dport, 1001L);
372
373        long timeMs = mService.mDefaultNetworkMetrics.creationTimeMs;
374        final long cell = BitUtils.packBits(new int[]{NetworkCapabilities.TRANSPORT_CELLULAR});
375        final long wifi = BitUtils.packBits(new int[]{NetworkCapabilities.TRANSPORT_WIFI});
376        NetworkAgentInfo cellNai = makeNai(100, 50, false, true, cell);
377        NetworkAgentInfo wifiNai = makeNai(101, 60, true, false, wifi);
378        mService.mDefaultNetworkMetrics.logDefaultNetworkEvent(timeMs + 200, cellNai, null);
379        mService.mDefaultNetworkMetrics.logDefaultNetworkEvent(timeMs + 300, wifiNai, cellNai);
380
381        String want = String.join("\n",
382                "dropped_events: 0",
383                "events <",
384                "  if_name: \"\"",
385                "  link_layer: 4",
386                "  network_id: 0",
387                "  time_ms: 100",
388                "  transports: 0",
389                "  ip_reachability_event <",
390                "    event_type: 512",
391                "    if_name: \"\"",
392                "  >",
393                ">",
394                "events <",
395                "  if_name: \"\"",
396                "  link_layer: 4",
397                "  network_id: 0",
398                "  time_ms: 200",
399                "  transports: 0",
400                "  dhcp_event <",
401                "    duration_ms: 192",
402                "    if_name: \"\"",
403                "    state_transition: \"SomeState\"",
404                "  >",
405                ">",
406                "events <",
407                "  if_name: \"\"",
408                "  link_layer: 4",
409                "  network_id: 0",
410                "  time_ms: 300",
411                "  transports: 0",
412                "  ip_provisioning_event <",
413                "    event_type: 1",
414                "    if_name: \"\"",
415                "    latency_ms: 5678",
416                "  >",
417                ">",
418                "events <",
419                "  if_name: \"\"",
420                "  link_layer: 4",
421                "  network_id: 0",
422                "  time_ms: 400",
423                "  transports: 0",
424                "  validation_probe_event <",
425                "    latency_ms: 40730",
426                "    probe_result: 204",
427                "    probe_type: 1",
428                "  >",
429                ">",
430                "events <",
431                "  if_name: \"\"",
432                "  link_layer: 4",
433                "  network_id: 0",
434                "  time_ms: 500",
435                "  transports: 0",
436                "  apf_statistics <",
437                "    dropped_ras: 2",
438                "    duration_ms: 45000",
439                "    matching_ras: 2",
440                "    max_program_size: 2048",
441                "    parse_errors: 2",
442                "    program_updates: 4",
443                "    program_updates_all: 7",
444                "    program_updates_allowing_multicast: 3",
445                "    received_ras: 10",
446                "    total_packet_dropped: 0",
447                "    total_packet_processed: 0",
448                "    zero_lifetime_ras: 1",
449                "  >",
450                ">",
451                "events <",
452                "  if_name: \"\"",
453                "  link_layer: 4",
454                "  network_id: 0",
455                "  time_ms: 600",
456                "  transports: 0",
457                "  ra_event <",
458                "    dnssl_lifetime: -1",
459                "    prefix_preferred_lifetime: 300",
460                "    prefix_valid_lifetime: 400",
461                "    rdnss_lifetime: 1000",
462                "    route_info_lifetime: -1",
463                "    router_lifetime: 2000",
464                "  >",
465                ">",
466                "events <",
467                "  if_name: \"\"",
468                "  link_layer: 5",
469                "  network_id: 0",
470                "  time_ms: 0",
471                "  transports: 0",
472                "  default_network_event <",
473                "    default_network_duration_ms: 200",
474                "    final_score: 0",
475                "    initial_score: 0",
476                "    ip_support: 0",
477                "    no_default_network_duration_ms: 0",
478                "    previous_default_network_link_layer: 0",
479                "    previous_network_ip_support: 0",
480                "    validation_duration_ms: 0",
481                "  >",
482                ">",
483                "events <",
484                "  if_name: \"\"",
485                "  link_layer: 2",
486                "  network_id: 100",
487                "  time_ms: 0",
488                "  transports: 1",
489                "  default_network_event <",
490                "    default_network_duration_ms: 100",
491                "    final_score: 50",
492                "    initial_score: 50",
493                "    ip_support: 2",
494                "    no_default_network_duration_ms: 0",
495                "    previous_default_network_link_layer: 0",
496                "    previous_network_ip_support: 0",
497                "    validation_duration_ms: 100",
498                "  >",
499                ">",
500                "events <",
501                "  if_name: \"\"",
502                "  link_layer: 4",
503                "  network_id: 100",
504                "  time_ms: 0",
505                "  transports: 2",
506                "  connect_statistics <",
507                "    connect_blocking_count: 1",
508                "    connect_count: 3",
509                "    errnos_counters <",
510                "      key: 11",
511                "      value: 1",
512                "    >",
513                "    ipv6_addr_count: 1",
514                "    latencies_ms: 110",
515                "  >",
516                ">",
517                "events <",
518                "  if_name: \"\"",
519                "  link_layer: 2",
520                "  network_id: 101",
521                "  time_ms: 0",
522                "  transports: 1",
523                "  connect_statistics <",
524                "    connect_blocking_count: 2",
525                "    connect_count: 2",
526                "    ipv6_addr_count: 1",
527                "    latencies_ms: 23",
528                "    latencies_ms: 45",
529                "  >",
530                ">",
531                "events <",
532                "  if_name: \"\"",
533                "  link_layer: 4",
534                "  network_id: 100",
535                "  time_ms: 0",
536                "  transports: 2",
537                "  dns_lookup_batch <",
538                "    event_types: 1",
539                "    event_types: 1",
540                "    event_types: 2",
541                "    getaddrinfo_error_count: 0",
542                "    getaddrinfo_query_count: 0",
543                "    gethostbyname_error_count: 0",
544                "    gethostbyname_query_count: 0",
545                "    latencies_ms: 3456",
546                "    latencies_ms: 45",
547                "    latencies_ms: 638",
548                "    return_codes: 0",
549                "    return_codes: 3",
550                "    return_codes: 0",
551                "  >",
552                ">",
553                "events <",
554                "  if_name: \"\"",
555                "  link_layer: 2",
556                "  network_id: 101",
557                "  time_ms: 0",
558                "  transports: 1",
559                "  dns_lookup_batch <",
560                "    event_types: 1",
561                "    event_types: 2",
562                "    getaddrinfo_error_count: 0",
563                "    getaddrinfo_query_count: 0",
564                "    gethostbyname_error_count: 0",
565                "    gethostbyname_query_count: 0",
566                "    latencies_ms: 56",
567                "    latencies_ms: 34",
568                "    return_codes: 0",
569                "    return_codes: 0",
570                "  >",
571                ">",
572                "events <",
573                "  if_name: \"\"",
574                "  link_layer: 4",
575                "  network_id: 0",
576                "  time_ms: 0",
577                "  transports: 0",
578                "  wakeup_stats <",
579                "    application_wakeups: 3",
580                "    duration_sec: 0",
581                "    ethertype_counts <",
582                "      key: 2048",
583                "      value: 6",
584                "    >",
585                "    ip_next_header_counts <",
586                "      key: 6",
587                "      value: 3",
588                "    >",
589                "    ip_next_header_counts <",
590                "      key: 17",
591                "      value: 3",
592                "    >",
593                "    l2_broadcast_count: 0",
594                "    l2_multicast_count: 0",
595                "    l2_unicast_count: 6",
596                "    no_uid_wakeups: 1",
597                "    non_application_wakeups: 0",
598                "    root_wakeups: 0",
599                "    system_wakeups: 2",
600                "    total_wakeups: 6",
601                "  >",
602                ">",
603                "version: 2\n");
604
605        verifySerialization(want, getdump("flush"));
606    }
607
608    String getdump(String ... command) {
609        StringWriter buffer = new StringWriter();
610        PrintWriter writer = new PrintWriter(buffer);
611        mService.impl.dump(null, writer, command);
612        return buffer.toString();
613    }
614
615    void connectEvent(int netid, int error, int latencyMs, String ipAddr) throws Exception {
616        mNetdListener.onConnectEvent(netid, error, latencyMs, ipAddr, 80, 1);
617    }
618
619    void dnsEvent(int netId, int type, int result, int latency) throws Exception {
620        mNetdListener.onDnsEvent(netId, type, result, latency, "", null, 0, 0);
621    }
622
623    void wakeupEvent(String iface, int uid, int ether, int ip, byte[] mac, String srcIp,
624            String dstIp, int sport, int dport, long now) throws Exception {
625        String prefix = NetdEventListenerService.WAKEUP_EVENT_IFACE_PREFIX + iface;
626        mNetdListener.onWakeupEvent(prefix, uid, ether, ip, mac, srcIp, dstIp, sport, dport, now);
627    }
628
629    NetworkAgentInfo makeNai(int netId, int score, boolean ipv4, boolean ipv6, long transports) {
630        NetworkAgentInfo nai = mock(NetworkAgentInfo.class);
631        when(nai.network()).thenReturn(new Network(netId));
632        when(nai.getCurrentScore()).thenReturn(score);
633        nai.linkProperties = new LinkProperties();
634        nai.networkCapabilities = new NetworkCapabilities();
635        nai.lastValidated = true;
636        for (int t : BitUtils.unpackBits(transports)) {
637            nai.networkCapabilities.addTransportType(t);
638        }
639        if (ipv4) {
640            nai.linkProperties.addLinkAddress(new LinkAddress("192.0.2.12/24"));
641            nai.linkProperties.addRoute(new RouteInfo(new IpPrefix("0.0.0.0/0")));
642        }
643        if (ipv6) {
644            nai.linkProperties.addLinkAddress(new LinkAddress("2001:db8:dead:beef:f00::a0/64"));
645            nai.linkProperties.addRoute(new RouteInfo(new IpPrefix("::/0")));
646        }
647        return nai;
648    }
649
650    List<ConnectivityMetricsEvent> verifyEvents(int n, int timeoutMs) throws Exception {
651        ArgumentCaptor<ConnectivityMetricsEvent> captor =
652                ArgumentCaptor.forClass(ConnectivityMetricsEvent.class);
653        verify(mMockService, timeout(timeoutMs).times(n)).logEvent(captor.capture());
654        return captor.getAllValues();
655    }
656
657    List<ConnectivityMetricsEvent> verifyEvents(int n) throws Exception {
658        return verifyEvents(n, 10);
659    }
660
661    static void verifySerialization(String want, String output) {
662        try {
663            byte[] got = Base64.decode(output, Base64.DEFAULT);
664            IpConnectivityLogClass.IpConnectivityLog log =
665                    IpConnectivityLogClass.IpConnectivityLog.parseFrom(got);
666            assertEquals(want, log.toString());
667        } catch (Exception e) {
668            fail(e.toString());
669        }
670    }
671
672    static String joinLines(String ... elems) {
673        StringBuilder b = new StringBuilder();
674        for (String s : elems) {
675            b.append(s).append("\n");
676        }
677        return b.toString();
678    }
679
680    static ConnectivityMetricsEvent expectedEvent(int timestamp) {
681        ConnectivityMetricsEvent ev = new ConnectivityMetricsEvent();
682        ev.timestamp = timestamp;
683        ev.data = FAKE_EV;
684        return ev;
685    }
686
687    /** Outer equality for ConnectivityMetricsEvent to avoid overriding equals() and hashCode(). */
688    static void assertEventsEqual(ConnectivityMetricsEvent expected, ConnectivityMetricsEvent got) {
689        assertEquals(expected.timestamp, got.timestamp);
690        assertEquals(expected.data, got.data);
691    }
692
693    static final Comparator<ConnectivityMetricsEvent> EVENT_COMPARATOR =
694        Comparator.comparingLong((ev) -> ev.timestamp);
695}
696