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