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 android.net.ConnectivityManager.NetworkCallback;
20import android.net.ConnectivityManager;
21import android.net.Network;
22import android.net.metrics.DnsEvent;
23import android.net.metrics.IDnsEventListener;
24import android.net.metrics.IpConnectivityLog;
25
26import junit.framework.TestCase;
27import org.junit.Before;
28import org.junit.Test;
29import static org.junit.Assert.assertArrayEquals;
30import static org.junit.Assert.assertTrue;
31
32import org.mockito.ArgumentCaptor;
33import org.mockito.Mock;
34import org.mockito.Mockito;
35import org.mockito.MockitoAnnotations;
36import static org.mockito.Mockito.any;
37import static org.mockito.Mockito.anyInt;
38import static org.mockito.Mockito.eq;
39import static org.mockito.Mockito.timeout;
40import static org.mockito.Mockito.times;
41import static org.mockito.Mockito.verify;
42
43import java.io.FileOutputStream;
44import java.io.PrintWriter;
45import java.util.Arrays;
46import java.util.List;
47import java.util.OptionalInt;
48import java.util.stream.IntStream;
49
50public class DnsEventListenerServiceTest extends TestCase {
51
52    // TODO: read from DnsEventListenerService after this constant is read from system property
53    static final int BATCH_SIZE = 100;
54    static final int EVENT_TYPE = IDnsEventListener.EVENT_GETADDRINFO;
55    // TODO: read from IDnsEventListener
56    static final int RETURN_CODE = 1;
57
58    static final byte[] EVENT_TYPES  = new byte[BATCH_SIZE];
59    static final byte[] RETURN_CODES = new byte[BATCH_SIZE];
60    static final int[] LATENCIES     = new int[BATCH_SIZE];
61    static {
62        for (int i = 0; i < BATCH_SIZE; i++) {
63            EVENT_TYPES[i] = EVENT_TYPE;
64            RETURN_CODES[i] = RETURN_CODE;
65            LATENCIES[i] = i;
66        }
67    }
68
69    DnsEventListenerService mDnsService;
70
71    @Mock ConnectivityManager mCm;
72    @Mock IpConnectivityLog mLog;
73    ArgumentCaptor<NetworkCallback> mCallbackCaptor;
74    ArgumentCaptor<DnsEvent> mEvCaptor;
75
76    public void setUp() {
77        MockitoAnnotations.initMocks(this);
78        mCallbackCaptor = ArgumentCaptor.forClass(NetworkCallback.class);
79        mEvCaptor = ArgumentCaptor.forClass(DnsEvent.class);
80        mDnsService = new DnsEventListenerService(mCm, mLog);
81
82        verify(mCm, times(1)).registerNetworkCallback(any(), mCallbackCaptor.capture());
83    }
84
85    public void testOneBatch() throws Exception {
86        log(105, LATENCIES);
87        log(106, Arrays.copyOf(LATENCIES, BATCH_SIZE - 1)); // one lookup short of a batch event
88
89        verifyLoggedEvents(new DnsEvent(105, EVENT_TYPES, RETURN_CODES, LATENCIES));
90
91        log(106, Arrays.copyOfRange(LATENCIES, BATCH_SIZE - 1, BATCH_SIZE));
92
93        mEvCaptor = ArgumentCaptor.forClass(DnsEvent.class); // reset argument captor
94        verifyLoggedEvents(
95            new DnsEvent(105, EVENT_TYPES, RETURN_CODES, LATENCIES),
96            new DnsEvent(106, EVENT_TYPES, RETURN_CODES, LATENCIES));
97    }
98
99    public void testSeveralBatches() throws Exception {
100        log(105, LATENCIES);
101        log(106, LATENCIES);
102        log(105, LATENCIES);
103        log(107, LATENCIES);
104
105        verifyLoggedEvents(
106            new DnsEvent(105, EVENT_TYPES, RETURN_CODES, LATENCIES),
107            new DnsEvent(106, EVENT_TYPES, RETURN_CODES, LATENCIES),
108            new DnsEvent(105, EVENT_TYPES, RETURN_CODES, LATENCIES),
109            new DnsEvent(107, EVENT_TYPES, RETURN_CODES, LATENCIES));
110    }
111
112    public void testBatchAndNetworkLost() throws Exception {
113        byte[] eventTypes = Arrays.copyOf(EVENT_TYPES, 20);
114        byte[] returnCodes = Arrays.copyOf(RETURN_CODES, 20);
115        int[] latencies = Arrays.copyOf(LATENCIES, 20);
116
117        log(105, LATENCIES);
118        log(105, latencies);
119        mCallbackCaptor.getValue().onLost(new Network(105));
120        log(105, LATENCIES);
121
122        verifyLoggedEvents(
123            new DnsEvent(105, eventTypes, returnCodes, latencies),
124            new DnsEvent(105, EVENT_TYPES, RETURN_CODES, LATENCIES),
125            new DnsEvent(105, EVENT_TYPES, RETURN_CODES, LATENCIES));
126    }
127
128    public void testConcurrentBatchesAndDumps() throws Exception {
129        final long stop = System.currentTimeMillis() + 100;
130        final PrintWriter pw = new PrintWriter(new FileOutputStream("/dev/null"));
131        new Thread() {
132            public void run() {
133                while (System.currentTimeMillis() < stop) {
134                    mDnsService.dump(pw);
135                }
136            }
137        }.start();
138
139        logAsync(105, LATENCIES);
140        logAsync(106, LATENCIES);
141        logAsync(107, LATENCIES);
142
143        verifyLoggedEvents(500,
144            new DnsEvent(105, EVENT_TYPES, RETURN_CODES, LATENCIES),
145            new DnsEvent(106, EVENT_TYPES, RETURN_CODES, LATENCIES),
146            new DnsEvent(107, EVENT_TYPES, RETURN_CODES, LATENCIES));
147    }
148
149    public void testConcurrentBatchesAndNetworkLoss() throws Exception {
150        logAsync(105, LATENCIES);
151        Thread.sleep(10L);
152        // call onLost() asynchronously to logAsync's onDnsEvent() calls.
153        mCallbackCaptor.getValue().onLost(new Network(105));
154
155        // do not verify unpredictable batch
156        verify(mLog, timeout(500).times(1)).log(any());
157    }
158
159    void log(int netId, int[] latencies) {
160        for (int l : latencies) {
161            mDnsService.onDnsEvent(netId, EVENT_TYPE, RETURN_CODE, l);
162        }
163    }
164
165    void logAsync(int netId, int[] latencies) {
166        new Thread() {
167            public void run() {
168                log(netId, latencies);
169            }
170        }.start();
171    }
172
173    void verifyLoggedEvents(DnsEvent... expected) {
174        verifyLoggedEvents(0, expected);
175    }
176
177    void verifyLoggedEvents(int wait, DnsEvent... expectedEvents) {
178        verify(mLog, timeout(wait).times(expectedEvents.length)).log(mEvCaptor.capture());
179        for (DnsEvent got : mEvCaptor.getAllValues()) {
180            OptionalInt index = IntStream.range(0, expectedEvents.length)
181                    .filter(i -> eventsEqual(expectedEvents[i], got))
182                    .findFirst();
183            // Don't match same expected event more than once.
184            index.ifPresent(i -> expectedEvents[i] = null);
185            assertTrue(index.isPresent());
186        }
187    }
188
189    /** equality function for DnsEvent to avoid overriding equals() and hashCode(). */
190    static boolean eventsEqual(DnsEvent expected, DnsEvent got) {
191        return (expected == got) || ((expected != null) && (got != null)
192                && (expected.netId == got.netId)
193                && Arrays.equals(expected.eventTypes, got.eventTypes)
194                && Arrays.equals(expected.returnCodes, got.returnCodes)
195                && Arrays.equals(expected.latenciesMs, got.latenciesMs));
196    }
197}
198