1 /*
2  * Copyright (C) 2016, The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.server.connectivity;
18 
19 import static android.net.metrics.INetdEventListener.EVENT_GETADDRINFO;
20 import static android.net.metrics.INetdEventListener.EVENT_GETHOSTBYNAME;
21 import static com.android.testutils.MiscAsserts.assertStringContains;
22 import static org.junit.Assert.assertEquals;
23 import static org.junit.Assert.fail;
24 import static org.mockito.ArgumentMatchers.any;
25 import static org.mockito.Mockito.doReturn;
26 import static org.mockito.Mockito.mock;
27 import static org.mockito.Mockito.verify;
28 
29 import android.content.Context;
30 import android.net.ConnectivityManager;
31 import android.net.Network;
32 import android.net.NetworkCapabilities;
33 import android.os.Build;
34 import android.system.OsConstants;
35 import android.util.Base64;
36 import androidx.test.filters.SmallTest;
37 import com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.IpConnectivityEvent;
38 import com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.IpConnectivityLog;
39 import com.android.testutils.DevSdkIgnoreRule;
40 import com.android.testutils.DevSdkIgnoreRunner;
41 import java.io.FileOutputStream;
42 import java.io.PrintWriter;
43 import java.io.StringWriter;
44 import java.util.Arrays;
45 import java.util.Comparator;
46 import java.util.List;
47 import libcore.util.EmptyArray;
48 import org.junit.Before;
49 import org.junit.Test;
50 import org.junit.runner.RunWith;
51 import org.mockito.ArgumentCaptor;
52 
53 @RunWith(DevSdkIgnoreRunner.class)
54 @SmallTest
55 @DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
56 public class NetdEventListenerServiceTest {
57     private static final String EXAMPLE_IPV4 = "192.0.2.1";
58     private static final String EXAMPLE_IPV6 = "2001:db8:1200::2:1";
59 
60     private static final Network TEST_WIFI_NETWORK = new Network(5391);
61     private static final Network TEST_CELL_NETWORK = new Network(5832);
62 
63     private static final byte[] MAC_ADDR =
64             {(byte)0x84, (byte)0xc9, (byte)0xb2, (byte)0x6a, (byte)0xed, (byte)0x4b};
65 
66     NetdEventListenerService mService;
67     ConnectivityManager mCm;
68     private static final NetworkCapabilities CAPABILITIES_WIFI = new NetworkCapabilities.Builder()
69             .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
70             .build();
71     private static final NetworkCapabilities CAPABILITIES_CELL = new NetworkCapabilities.Builder()
72             .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
73             .build();
74 
75     @Before
setUp()76     public void setUp() {
77         mCm = mock(ConnectivityManager.class);
78         mService = new NetdEventListenerService(mCm);
79         doReturn(CAPABILITIES_WIFI).when(mCm).getNetworkCapabilities(TEST_WIFI_NETWORK);
80         doReturn(CAPABILITIES_CELL).when(mCm).getNetworkCapabilities(TEST_CELL_NETWORK);
81     }
82 
83     @Test
testWakeupEventLogging()84     public void testWakeupEventLogging() throws Exception {
85         final int BUFFER_LENGTH = NetdEventListenerService.WAKEUP_EVENT_BUFFER_LENGTH;
86         final long now = System.currentTimeMillis();
87         final String iface = "wlan0";
88         final byte[] mac = MAC_ADDR;
89         final String srcIp = "192.168.2.1";
90         final String dstIp = "192.168.2.23";
91         final String srcIp6 = "2001:db8:4:fd00:a585:13d1:6a23:4fb4";
92         final String dstIp6 = "2001:db8:4006:807::200a";
93         final int sport = 2356;
94         final int dport = 13489;
95 
96         final int v4 = 0x800;
97         final int v6 = 0x86dd;
98         final int tcp = 6;
99         final int udp = 17;
100         final int icmp6 = 58;
101 
102         // Baseline without any event
103         String[] baseline = listNetdEvent();
104 
105         int[] uids = {10001, 10002, 10004, 1000, 10052, 10023, 10002, 10123, 10004};
106         wakeupEvent(iface, uids[0], v4, tcp, mac, srcIp, dstIp, sport, dport, now);
107         wakeupEvent(iface, uids[1], v6, udp, mac, srcIp6, dstIp6, sport, dport, now);
108         wakeupEvent(iface, uids[2], v6, udp, mac, srcIp6, dstIp6, sport, dport, now);
109         wakeupEvent(iface, uids[3], v4, icmp6, mac, srcIp, dstIp, sport, dport, now);
110         wakeupEvent(iface, uids[4], v6, tcp, mac, srcIp6, dstIp6, sport, dport, now);
111         wakeupEvent(iface, uids[5], v4, tcp, mac, srcIp, dstIp, sport, dport, now);
112         wakeupEvent(iface, uids[6], v6, udp, mac, srcIp6, dstIp6, sport, dport, now);
113         wakeupEvent(iface, uids[7], v6, tcp, mac, srcIp6, dstIp6, sport, dport, now);
114         wakeupEvent("rmnet0", uids[8], v6, udp, EmptyArray.BYTE, srcIp6, dstIp6, sport, dport, now,
115                 TEST_CELL_NETWORK);
116 
117         String[] events2 = remove(listNetdEvent(), baseline);
118         int expectedLength2 = uids.length + 2; // +2 for the WakeupStats headers
119         assertEquals(expectedLength2, events2.length);
120 
121         assertStringContains(events2[0], "WakeupStats");
122         assertStringContains(events2[0], "rmnet0");
123         assertStringContains(events2[0], "0x86dd");
124 
125         assertStringContains(events2[1], "WakeupStats");
126         assertStringContains(events2[1], "wlan0");
127         assertStringContains(events2[1], "0x800");
128         assertStringContains(events2[1], "0x86dd");
129         for (int i = 0; i < uids.length; i++) {
130             String got = events2[i + 2];
131             assertStringContains(got, "WakeupEvent");
132             assertStringContains(got, ((i == 8) ? "rmnet0" : "wlan0"));
133             assertStringContains(got, "uid: " + uids[i]);
134         }
135 
136         int uid = 20000;
137         for (int i = 0; i < BUFFER_LENGTH * 2; i++) {
138             long ts = now + 10;
139             wakeupEvent(iface, uid, 0x800, 6, mac, srcIp, dstIp, 23, 24, ts);
140         }
141 
142         String[] events3 = remove(listNetdEvent(), baseline);
143         int expectedLength3 = BUFFER_LENGTH + 2; // +2 for the WakeupStats headers
144         assertEquals(expectedLength3, events3.length);
145         assertStringContains(events3[0], "WakeupStats");
146         assertStringContains(events3[0], "rmnet0");
147         assertStringContains(events3[1], "WakeupStats");
148         assertStringContains(events3[1], "wlan0");
149         for (int i = 2; i < expectedLength3; i++) {
150             String got = events3[i];
151             assertStringContains(got, "WakeupEvent");
152             assertStringContains(got, "wlan0");
153             assertStringContains(got, "uid: " + uid);
154         }
155 
156         uid = 45678;
157         wakeupEvent(iface, uid, 0x800, 6, mac, srcIp, dstIp, 23, 24, now);
158 
159         String[] events4 = remove(listNetdEvent(), baseline);
160         String lastEvent = events4[events4.length - 1];
161         assertStringContains(lastEvent, "WakeupEvent");
162         assertStringContains(lastEvent, "wlan0");
163         assertStringContains(lastEvent, "uid: " + uid);
164     }
165 
166     @Test
testWakeupStatsLogging()167     public void testWakeupStatsLogging() throws Exception {
168         final byte[] mac = MAC_ADDR;
169         final String srcIp = "192.168.2.1";
170         final String dstIp = "192.168.2.23";
171         final String srcIp6 = "2401:fa00:4:fd00:a585:13d1:6a23:4fb4";
172         final String dstIp6 = "2404:6800:4006:807::200a";
173         final int sport = 2356;
174         final int dport = 13489;
175         final long now = 1001L;
176 
177         final int v4 = 0x800;
178         final int v6 = 0x86dd;
179         final int tcp = 6;
180         final int udp = 17;
181         final int icmp6 = 58;
182 
183         wakeupEvent("wlan0", 1000, v4, tcp, mac, srcIp, dstIp, sport, dport, now);
184         wakeupEvent("rmnet0", 10123, v4, tcp, mac, srcIp, dstIp, sport, dport, now,
185                 TEST_CELL_NETWORK);
186         wakeupEvent("wlan0", 1000, v4, udp, mac, srcIp, dstIp, sport, dport, now);
187         wakeupEvent("rmnet0", 10008, v4, tcp, EmptyArray.BYTE, srcIp, dstIp, sport, dport, now,
188                 TEST_CELL_NETWORK);
189         wakeupEvent("wlan0", -1, v6, icmp6, mac, srcIp6, dstIp6, sport, dport, now);
190         wakeupEvent("wlan0", 10008, v4, tcp, mac, srcIp, dstIp, sport, dport, now);
191         wakeupEvent("rmnet0", 1000, v4, tcp, mac, srcIp, dstIp, sport, dport, now,
192                 TEST_CELL_NETWORK);
193         wakeupEvent("wlan0", 10004, v4, udp, mac, srcIp, dstIp, sport, dport, now);
194         wakeupEvent("wlan0", 1000, v6, tcp, mac, srcIp6, dstIp6, sport, dport, now);
195         wakeupEvent("wlan0", 0, v6, udp, mac, srcIp6, dstIp6, sport, dport, now);
196         wakeupEvent("wlan0", -1, v6, icmp6, mac, srcIp6, dstIp6, sport, dport, now);
197         wakeupEvent("rmnet0", 10052, v4, tcp, mac, srcIp, dstIp, sport, dport, now,
198                 TEST_CELL_NETWORK);
199         wakeupEvent("wlan0", 0, v6, udp, mac, srcIp6, dstIp6, sport, dport, now);
200         wakeupEvent("rmnet0", 1000, v6, tcp, null, srcIp6, dstIp6, sport, dport, now,
201                 TEST_CELL_NETWORK);
202         wakeupEvent("wlan0", 1010, v4, udp, mac, srcIp, dstIp, sport, dport, now);
203 
204         String got = flushStatistics();
205         String want = String.join("\n",
206                 "dropped_events: 0",
207                 "events <",
208                 "  if_name: \"\"",
209                 "  link_layer: 2",
210                 "  network_id: 0",
211                 "  time_ms: 0",
212                 "  transports: 0",
213                 "  wakeup_stats <",
214                 "    application_wakeups: 3",
215                 "    duration_sec: 0",
216                 "    ethertype_counts <",
217                 "      key: 2048",
218                 "      value: 4",
219                 "    >",
220                 "    ethertype_counts <",
221                 "      key: 34525",
222                 "      value: 1",
223                 "    >",
224                 "    ip_next_header_counts <",
225                 "      key: 6",
226                 "      value: 5",
227                 "    >",
228                 "    l2_broadcast_count: 0",
229                 "    l2_multicast_count: 0",
230                 "    l2_unicast_count: 3",
231                 "    no_uid_wakeups: 0",
232                 "    non_application_wakeups: 0",
233                 "    root_wakeups: 0",
234                 "    system_wakeups: 2",
235                 "    total_wakeups: 5",
236                 "  >",
237                 ">",
238                 "events <",
239                 "  if_name: \"\"",
240                 "  link_layer: 4",
241                 "  network_id: 0",
242                 "  time_ms: 0",
243                 "  transports: 0",
244                 "  wakeup_stats <",
245                 "    application_wakeups: 2",
246                 "    duration_sec: 0",
247                 "    ethertype_counts <",
248                 "      key: 2048",
249                 "      value: 5",
250                 "    >",
251                 "    ethertype_counts <",
252                 "      key: 34525",
253                 "      value: 5",
254                 "    >",
255                 "    ip_next_header_counts <",
256                 "      key: 6",
257                 "      value: 3",
258                 "    >",
259                 "    ip_next_header_counts <",
260                 "      key: 17",
261                 "      value: 5",
262                 "    >",
263                 "    ip_next_header_counts <",
264                 "      key: 58",
265                 "      value: 2",
266                 "    >",
267                 "    l2_broadcast_count: 0",
268                 "    l2_multicast_count: 0",
269                 "    l2_unicast_count: 10",
270                 "    no_uid_wakeups: 2",
271                 "    non_application_wakeups: 1",
272                 "    root_wakeups: 2",
273                 "    system_wakeups: 3",
274                 "    total_wakeups: 10",
275                 "  >",
276                 ">",
277                 "version: 2\n");
278         assertEquals(want, got);
279     }
280 
281     @Test
testDnsLogging()282     public void testDnsLogging() throws Exception {
283         asyncDump(100);
284 
285         dnsEvent(100, EVENT_GETADDRINFO, 0, 3456);
286         dnsEvent(100, EVENT_GETADDRINFO, 0, 267);
287         dnsEvent(100, EVENT_GETHOSTBYNAME, 22, 1230);
288         dnsEvent(100, EVENT_GETADDRINFO, 3, 45);
289         dnsEvent(100, EVENT_GETADDRINFO, 1, 2111);
290         dnsEvent(100, EVENT_GETADDRINFO, 0, 450);
291         dnsEvent(100, EVENT_GETHOSTBYNAME, 200, 638);
292         dnsEvent(100, EVENT_GETHOSTBYNAME, 178, 1300);
293         dnsEvent(101, EVENT_GETADDRINFO, 0, 56);
294         dnsEvent(101, EVENT_GETADDRINFO, 0, 78);
295         dnsEvent(101, EVENT_GETADDRINFO, 0, 14);
296         dnsEvent(101, EVENT_GETHOSTBYNAME, 0, 56);
297         dnsEvent(101, EVENT_GETADDRINFO, 0, 78);
298         dnsEvent(101, EVENT_GETADDRINFO, 0, 14);
299 
300         String got = flushStatistics();
301         String want = String.join("\n",
302                 "dropped_events: 0",
303                 "events <",
304                 "  if_name: \"\"",
305                 "  link_layer: 4",
306                 "  network_id: 100",
307                 "  time_ms: 0",
308                 "  transports: 2",
309                 "  dns_lookup_batch <",
310                 "    event_types: 1",
311                 "    event_types: 1",
312                 "    event_types: 2",
313                 "    event_types: 1",
314                 "    event_types: 1",
315                 "    event_types: 1",
316                 "    event_types: 2",
317                 "    event_types: 2",
318                 "    getaddrinfo_error_count: 0",
319                 "    getaddrinfo_query_count: 0",
320                 "    gethostbyname_error_count: 0",
321                 "    gethostbyname_query_count: 0",
322                 "    latencies_ms: 3456",
323                 "    latencies_ms: 267",
324                 "    latencies_ms: 1230",
325                 "    latencies_ms: 45",
326                 "    latencies_ms: 2111",
327                 "    latencies_ms: 450",
328                 "    latencies_ms: 638",
329                 "    latencies_ms: 1300",
330                 "    return_codes: 0",
331                 "    return_codes: 0",
332                 "    return_codes: 22",
333                 "    return_codes: 3",
334                 "    return_codes: 1",
335                 "    return_codes: 0",
336                 "    return_codes: 200",
337                 "    return_codes: 178",
338                 "  >",
339                 ">",
340                 "events <",
341                 "  if_name: \"\"",
342                 "  link_layer: 2",
343                 "  network_id: 101",
344                 "  time_ms: 0",
345                 "  transports: 1",
346                 "  dns_lookup_batch <",
347                 "    event_types: 1",
348                 "    event_types: 1",
349                 "    event_types: 1",
350                 "    event_types: 2",
351                 "    event_types: 1",
352                 "    event_types: 1",
353                 "    getaddrinfo_error_count: 0",
354                 "    getaddrinfo_query_count: 0",
355                 "    gethostbyname_error_count: 0",
356                 "    gethostbyname_query_count: 0",
357                 "    latencies_ms: 56",
358                 "    latencies_ms: 78",
359                 "    latencies_ms: 14",
360                 "    latencies_ms: 56",
361                 "    latencies_ms: 78",
362                 "    latencies_ms: 14",
363                 "    return_codes: 0",
364                 "    return_codes: 0",
365                 "    return_codes: 0",
366                 "    return_codes: 0",
367                 "    return_codes: 0",
368                 "    return_codes: 0",
369                 "  >",
370                 ">",
371                 "version: 2\n");
372         assertEquals(want, got);
373     }
374 
375     @Test
testConnectLogging()376     public void testConnectLogging() throws Exception {
377         asyncDump(100);
378 
379         final int OK = 0;
380         Thread[] logActions = {
381             // ignored
382             connectEventAction(100, OsConstants.EALREADY, 0, EXAMPLE_IPV4),
383             connectEventAction(100, OsConstants.EALREADY, 0, EXAMPLE_IPV6),
384             connectEventAction(100, OsConstants.EINPROGRESS, 0, EXAMPLE_IPV4),
385             connectEventAction(101, OsConstants.EINPROGRESS, 0, EXAMPLE_IPV6),
386             connectEventAction(101, OsConstants.EINPROGRESS, 0, EXAMPLE_IPV6),
387             // valid latencies
388             connectEventAction(100, OK, 110, EXAMPLE_IPV4),
389             connectEventAction(100, OK, 23, EXAMPLE_IPV4),
390             connectEventAction(100, OK, 45, EXAMPLE_IPV4),
391             connectEventAction(101, OK, 56, EXAMPLE_IPV4),
392             connectEventAction(101, OK, 523, EXAMPLE_IPV6),
393             connectEventAction(101, OK, 214, EXAMPLE_IPV6),
394             connectEventAction(101, OK, 67, EXAMPLE_IPV6),
395             // errors
396             connectEventAction(100, OsConstants.EPERM, 0, EXAMPLE_IPV4),
397             connectEventAction(101, OsConstants.EPERM, 0, EXAMPLE_IPV4),
398             connectEventAction(100, OsConstants.EAGAIN, 0, EXAMPLE_IPV4),
399             connectEventAction(100, OsConstants.EACCES, 0, EXAMPLE_IPV4),
400             connectEventAction(101, OsConstants.EACCES, 0, EXAMPLE_IPV4),
401             connectEventAction(101, OsConstants.EACCES, 0, EXAMPLE_IPV6),
402             connectEventAction(100, OsConstants.EADDRINUSE, 0, EXAMPLE_IPV4),
403             connectEventAction(101, OsConstants.ETIMEDOUT, 0, EXAMPLE_IPV4),
404             connectEventAction(100, OsConstants.ETIMEDOUT, 0, EXAMPLE_IPV6),
405             connectEventAction(100, OsConstants.ETIMEDOUT, 0, EXAMPLE_IPV6),
406             connectEventAction(101, OsConstants.ECONNREFUSED, 0, EXAMPLE_IPV4),
407         };
408 
409         for (Thread t : logActions) {
410             t.start();
411         }
412         for (Thread t : logActions) {
413             t.join();
414         }
415 
416         String got = flushStatistics();
417         String want = String.join("\n",
418                 "dropped_events: 0",
419                 "events <",
420                 "  if_name: \"\"",
421                 "  link_layer: 4",
422                 "  network_id: 100",
423                 "  time_ms: 0",
424                 "  transports: 2",
425                 "  connect_statistics <",
426                 "    connect_blocking_count: 3",
427                 "    connect_count: 6",
428                 "    errnos_counters <",
429                 "      key: 1",
430                 "      value: 1",
431                 "    >",
432                 "    errnos_counters <",
433                 "      key: 11",
434                 "      value: 1",
435                 "    >",
436                 "    errnos_counters <",
437                 "      key: 13",
438                 "      value: 1",
439                 "    >",
440                 "    errnos_counters <",
441                 "      key: 98",
442                 "      value: 1",
443                 "    >",
444                 "    errnos_counters <",
445                 "      key: 110",
446                 "      value: 2",
447                 "    >",
448                 "    ipv6_addr_count: 1",
449                 "    latencies_ms: 23",
450                 "    latencies_ms: 45",
451                 "    latencies_ms: 110",
452                 "  >",
453                 ">",
454                 "events <",
455                 "  if_name: \"\"",
456                 "  link_layer: 2",
457                 "  network_id: 101",
458                 "  time_ms: 0",
459                 "  transports: 1",
460                 "  connect_statistics <",
461                 "    connect_blocking_count: 4",
462                 "    connect_count: 6",
463                 "    errnos_counters <",
464                 "      key: 1",
465                 "      value: 1",
466                 "    >",
467                 "    errnos_counters <",
468                 "      key: 13",
469                 "      value: 2",
470                 "    >",
471                 "    errnos_counters <",
472                 "      key: 110",
473                 "      value: 1",
474                 "    >",
475                 "    errnos_counters <",
476                 "      key: 111",
477                 "      value: 1",
478                 "    >",
479                 "    ipv6_addr_count: 5",
480                 "    latencies_ms: 56",
481                 "    latencies_ms: 67",
482                 "    latencies_ms: 214",
483                 "    latencies_ms: 523",
484                 "  >",
485                 ">",
486                 "version: 2\n");
487         assertEquals(want, got);
488     }
489 
setCapabilities(int netId)490     private void setCapabilities(int netId) {
491         final ArgumentCaptor<ConnectivityManager.NetworkCallback> networkCallback =
492                 ArgumentCaptor.forClass(ConnectivityManager.NetworkCallback.class);
493         verify(mCm).registerNetworkCallback(any(), networkCallback.capture());
494         networkCallback.getValue().onCapabilitiesChanged(new Network(netId),
495                 netId == 100 ? CAPABILITIES_WIFI : CAPABILITIES_CELL);
496     }
497 
connectEventAction(int netId, int error, int latencyMs, String ipAddr)498     Thread connectEventAction(int netId, int error, int latencyMs, String ipAddr) {
499         setCapabilities(netId);
500         return new Thread(() -> {
501             try {
502                 mService.onConnectEvent(netId, error, latencyMs, ipAddr, 80, 1);
503             } catch (Exception e) {
504                 fail(e.toString());
505             }
506         });
507     }
508 
509     void dnsEvent(int netId, int type, int result, int latency) throws Exception {
510         setCapabilities(netId);
511         mService.onDnsEvent(netId, type, result, latency, "", null, 0, 0);
512     }
513 
514     void wakeupEvent(String iface, int uid, int ether, int ip, byte[] mac, String srcIp,
515             String dstIp, int sport, int dport, long now) {
516         wakeupEvent(iface, uid, ether, ip, mac, srcIp, dstIp, sport, dport, now, TEST_WIFI_NETWORK);
517     }
518 
519     void wakeupEvent(String iface, int uid, int ether, int ip, byte[] mac, String srcIp,
520             String dstIp, int sport, int dport, long now, Network network) {
521         String prefix = network.getNetworkHandle() + ":" + iface;
522         mService.onWakeupEvent(prefix, uid, ether, ip, mac, srcIp, dstIp, sport, dport, now);
523     }
524 
525     void asyncDump(long durationMs) throws Exception {
526         final long stop = System.currentTimeMillis() + durationMs;
527         final PrintWriter pw = new PrintWriter(new FileOutputStream("/dev/null"));
528         new Thread(() -> {
529             while (System.currentTimeMillis() < stop) {
530                 mService.list(pw);
531             }
532         }).start();
533     }
534 
535     // TODO: instead of comparing textpb to textpb, parse textpb and compare proto to proto.
536     String flushStatistics() throws Exception {
537         IpConnectivityMetrics metricsService =
538                 new IpConnectivityMetrics(mock(Context.class), (ctx) -> 2000);
539         metricsService.mNetdListener = mService;
540 
541         StringWriter buffer = new StringWriter();
542         PrintWriter writer = new PrintWriter(buffer);
543         metricsService.impl.dump(null, writer, new String[]{"flush"});
544         byte[] bytes = Base64.decode(buffer.toString(), Base64.DEFAULT);
545         IpConnectivityLog log = IpConnectivityLog.parseFrom(bytes);
546         for (IpConnectivityEvent ev : log.events) {
547             if (ev.getConnectStatistics() == null) {
548                 continue;
549             }
550             // Sort repeated fields of connect() events arriving in non-deterministic order.
551             Arrays.sort(ev.getConnectStatistics().latenciesMs);
552             Arrays.sort(ev.getConnectStatistics().errnosCounters,
553                     Comparator.comparingInt((p) -> p.key));
554         }
555         return log.toString();
556     }
557 
558     String[] listNetdEvent() throws Exception {
559         StringWriter buffer = new StringWriter();
560         PrintWriter writer = new PrintWriter(buffer);
561         mService.list(writer);
562         return buffer.toString().split("\\n");
563     }
564 
565     static <T> T[] remove(T[] array, T[] filtered) {
566         List<T> c = Arrays.asList(filtered);
567         int next = 0;
568         for (int i = 0; i < array.length; i++) {
569             if (c.contains(array[i])) {
570                 continue;
571             }
572             array[next++] = array[i];
573         }
574         return Arrays.copyOf(array, next);
575     }
576 }
577