blob: 68ff777a0160d34ae8f054fde8d85deb80933757 [file] [log] [blame]
Benedict Wong409c8ca2017-10-26 19:41:43 -07001/*
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;
18
19import static org.junit.Assert.assertEquals;
20import static org.junit.Assert.assertNull;
21import static org.mockito.Matchers.anyInt;
22import static org.mockito.Matchers.anyObject;
23import static org.mockito.Matchers.eq;
24import static org.mockito.Mockito.doThrow;
25import static org.mockito.Mockito.mock;
26import static org.mockito.Mockito.spy;
27import static org.mockito.Mockito.times;
28import static org.mockito.Mockito.verify;
29
30import android.content.Context;
31import android.os.Binder;
32import android.os.IBinder;
33import android.os.RemoteException;
Brett Chabot1ae2aa62019-03-04 14:14:56 -080034
35import androidx.test.filters.SmallTest;
36import androidx.test.runner.AndroidJUnit4;
Benedict Wong409c8ca2017-10-26 19:41:43 -070037
38import com.android.server.IpSecService.IResource;
39import com.android.server.IpSecService.RefcountedResource;
40
Brett Chabot1ae2aa62019-03-04 14:14:56 -080041import org.junit.Before;
42import org.junit.Test;
43import org.junit.runner.RunWith;
44
Benedict Wong409c8ca2017-10-26 19:41:43 -070045import java.util.ArrayList;
46import java.util.HashSet;
47import java.util.List;
48import java.util.Set;
49import java.util.concurrent.ThreadLocalRandom;
50
Benedict Wong409c8ca2017-10-26 19:41:43 -070051/** Unit tests for {@link IpSecService.RefcountedResource}. */
52@SmallTest
53@RunWith(AndroidJUnit4.class)
54public class IpSecServiceRefcountedResourceTest {
55 Context mMockContext;
56 IpSecService.IpSecServiceConfiguration mMockIpSecSrvConfig;
57 IpSecService mIpSecService;
58
59 @Before
60 public void setUp() throws Exception {
61 mMockContext = mock(Context.class);
62 mMockIpSecSrvConfig = mock(IpSecService.IpSecServiceConfiguration.class);
63 mIpSecService = new IpSecService(mMockContext, mMockIpSecSrvConfig);
64 }
65
66 private void assertResourceState(
67 RefcountedResource<IResource> resource,
68 int refCount,
69 int userReleaseCallCount,
70 int releaseReferenceCallCount,
71 int invalidateCallCount,
72 int freeUnderlyingResourcesCallCount)
73 throws RemoteException {
74 // Check refcount on RefcountedResource
75 assertEquals(refCount, resource.mRefCount);
76
77 // Check call count of RefcountedResource
78 verify(resource, times(userReleaseCallCount)).userRelease();
79 verify(resource, times(releaseReferenceCallCount)).releaseReference();
80
81 // Check call count of IResource
82 verify(resource.getResource(), times(invalidateCallCount)).invalidate();
83 verify(resource.getResource(), times(freeUnderlyingResourcesCallCount))
84 .freeUnderlyingResources();
85 }
86
87 /** Adds mockito instrumentation */
88 private RefcountedResource<IResource> getTestRefcountedResource(
89 RefcountedResource... children) {
90 return getTestRefcountedResource(new Binder(), children);
91 }
92
93 /** Adds mockito instrumentation with provided binder */
94 private RefcountedResource<IResource> getTestRefcountedResource(
95 IBinder binder, RefcountedResource... children) {
96 return spy(
97 mIpSecService
98 .new RefcountedResource<IResource>(mock(IResource.class), binder, children));
99 }
100
101 @Test
102 public void testConstructor() throws RemoteException {
103 IBinder binderMock = mock(IBinder.class);
104 RefcountedResource<IResource> resource = getTestRefcountedResource(binderMock);
105
106 // Verify resource's refcount starts at 1 (for user-reference)
107 assertResourceState(resource, 1, 0, 0, 0, 0);
108
109 // Verify linking to binder death
110 verify(binderMock).linkToDeath(anyObject(), anyInt());
111 }
112
113 @Test
114 public void testConstructorWithChildren() throws RemoteException {
115 IBinder binderMockChild = mock(IBinder.class);
116 IBinder binderMockParent = mock(IBinder.class);
117 RefcountedResource<IResource> childResource = getTestRefcountedResource(binderMockChild);
118 RefcountedResource<IResource> parentResource =
119 getTestRefcountedResource(binderMockParent, childResource);
120
121 // Verify parent's refcount starts at 1 (for user-reference)
122 assertResourceState(parentResource, 1, 0, 0, 0, 0);
123
124 // Verify child's refcounts were incremented
125 assertResourceState(childResource, 2, 0, 0, 0, 0);
126
127 // Verify linking to binder death
128 verify(binderMockChild).linkToDeath(anyObject(), anyInt());
129 verify(binderMockParent).linkToDeath(anyObject(), anyInt());
130 }
131
132 @Test
133 public void testFailLinkToDeath() throws RemoteException {
134 IBinder binderMock = mock(IBinder.class);
135 doThrow(new RemoteException()).when(binderMock).linkToDeath(anyObject(), anyInt());
136
137 RefcountedResource<IResource> refcountedResource = getTestRefcountedResource(binderMock);
138
139 // Verify that cleanup is performed (Spy limitations prevent verification of method calls
140 // for binder death scenario; check refcount to determine if cleanup was performed.)
141 assertEquals(-1, refcountedResource.mRefCount);
142 }
143
144 @Test
145 public void testCleanupAndRelease() throws RemoteException {
146 IBinder binderMock = mock(IBinder.class);
147 RefcountedResource<IResource> refcountedResource = getTestRefcountedResource(binderMock);
148
149 // Verify user-initiated cleanup path decrements refcount and calls full cleanup flow
150 refcountedResource.userRelease();
151 assertResourceState(refcountedResource, -1, 1, 1, 1, 1);
152
153 // Verify user-initated cleanup path unlinks from binder
154 verify(binderMock).unlinkToDeath(eq(refcountedResource), eq(0));
155 assertNull(refcountedResource.mBinder);
156 }
157
158 @Test
159 public void testMultipleCallsToCleanupAndRelease() throws RemoteException {
160 RefcountedResource<IResource> refcountedResource = getTestRefcountedResource();
161
162 // Verify calling userRelease multiple times does not trigger any other cleanup
163 // methods
164 refcountedResource.userRelease();
165 assertResourceState(refcountedResource, -1, 1, 1, 1, 1);
166
167 refcountedResource.userRelease();
168 refcountedResource.userRelease();
169 assertResourceState(refcountedResource, -1, 3, 1, 1, 1);
170 }
171
172 @Test
173 public void testBinderDeathAfterCleanupAndReleaseDoesNothing() throws RemoteException {
174 RefcountedResource<IResource> refcountedResource = getTestRefcountedResource();
175
176 refcountedResource.userRelease();
177 assertResourceState(refcountedResource, -1, 1, 1, 1, 1);
178
179 // Verify binder death call does not trigger any other cleanup methods if called after
180 // userRelease()
181 refcountedResource.binderDied();
182 assertResourceState(refcountedResource, -1, 2, 1, 1, 1);
183 }
184
185 @Test
186 public void testBinderDeath() throws RemoteException {
187 RefcountedResource<IResource> refcountedResource = getTestRefcountedResource();
188
189 // Verify binder death caused cleanup
190 refcountedResource.binderDied();
191 verify(refcountedResource, times(1)).binderDied();
192 assertResourceState(refcountedResource, -1, 1, 1, 1, 1);
193 assertNull(refcountedResource.mBinder);
194 }
195
196 @Test
197 public void testCleanupParentDecrementsChildRefcount() throws RemoteException {
198 RefcountedResource<IResource> childResource = getTestRefcountedResource();
199 RefcountedResource<IResource> parentResource = getTestRefcountedResource(childResource);
200
201 parentResource.userRelease();
202
203 // Verify parent gets cleaned up properly, and triggers releaseReference on
204 // child
205 assertResourceState(childResource, 1, 0, 1, 0, 0);
206 assertResourceState(parentResource, -1, 1, 1, 1, 1);
207 }
208
209 @Test
210 public void testCleanupReferencedChildDoesNotTriggerRelease() throws RemoteException {
211 RefcountedResource<IResource> childResource = getTestRefcountedResource();
212 RefcountedResource<IResource> parentResource = getTestRefcountedResource(childResource);
213
214 childResource.userRelease();
215
216 // Verify that child does not clean up kernel resources and quota.
217 assertResourceState(childResource, 1, 1, 1, 1, 0);
218 assertResourceState(parentResource, 1, 0, 0, 0, 0);
219 }
220
221 @Test
222 public void testTwoParents() throws RemoteException {
223 RefcountedResource<IResource> childResource = getTestRefcountedResource();
224 RefcountedResource<IResource> parentResource1 = getTestRefcountedResource(childResource);
225 RefcountedResource<IResource> parentResource2 = getTestRefcountedResource(childResource);
226
227 // Verify that child does not cleanup kernel resources and quota until all references
228 // have been released. Assumption: parents release correctly based on
229 // testCleanupParentDecrementsChildRefcount()
230 childResource.userRelease();
231 assertResourceState(childResource, 2, 1, 1, 1, 0);
232
233 parentResource1.userRelease();
234 assertResourceState(childResource, 1, 1, 2, 1, 0);
235
236 parentResource2.userRelease();
237 assertResourceState(childResource, -1, 1, 3, 1, 1);
238 }
239
240 @Test
241 public void testTwoChildren() throws RemoteException {
242 RefcountedResource<IResource> childResource1 = getTestRefcountedResource();
243 RefcountedResource<IResource> childResource2 = getTestRefcountedResource();
244 RefcountedResource<IResource> parentResource =
245 getTestRefcountedResource(childResource1, childResource2);
246
247 childResource1.userRelease();
248 assertResourceState(childResource1, 1, 1, 1, 1, 0);
249 assertResourceState(childResource2, 2, 0, 0, 0, 0);
250
251 parentResource.userRelease();
252 assertResourceState(childResource1, -1, 1, 2, 1, 1);
253 assertResourceState(childResource2, 1, 0, 1, 0, 0);
254
255 childResource2.userRelease();
256 assertResourceState(childResource1, -1, 1, 2, 1, 1);
257 assertResourceState(childResource2, -1, 1, 2, 1, 1);
258 }
259
260 @Test
261 public void testSampleUdpEncapTranform() throws RemoteException {
262 RefcountedResource<IResource> spi1 = getTestRefcountedResource();
263 RefcountedResource<IResource> spi2 = getTestRefcountedResource();
264 RefcountedResource<IResource> udpEncapSocket = getTestRefcountedResource();
265 RefcountedResource<IResource> transform =
266 getTestRefcountedResource(spi1, spi2, udpEncapSocket);
267
268 // Pretend one SPI goes out of reference (releaseManagedResource -> userRelease)
269 spi1.userRelease();
270
271 // User called releaseManagedResource on udpEncap socket
272 udpEncapSocket.userRelease();
273
274 // User dies, and binder kills the rest
275 spi2.binderDied();
276 transform.binderDied();
277
278 // Check resource states
279 assertResourceState(spi1, -1, 1, 2, 1, 1);
280 assertResourceState(spi2, -1, 1, 2, 1, 1);
281 assertResourceState(udpEncapSocket, -1, 1, 2, 1, 1);
282 assertResourceState(transform, -1, 1, 1, 1, 1);
283 }
284
285 @Test
286 public void testSampleDualTransformEncapSocket() throws RemoteException {
287 RefcountedResource<IResource> spi1 = getTestRefcountedResource();
288 RefcountedResource<IResource> spi2 = getTestRefcountedResource();
289 RefcountedResource<IResource> spi3 = getTestRefcountedResource();
290 RefcountedResource<IResource> spi4 = getTestRefcountedResource();
291 RefcountedResource<IResource> udpEncapSocket = getTestRefcountedResource();
292 RefcountedResource<IResource> transform1 =
293 getTestRefcountedResource(spi1, spi2, udpEncapSocket);
294 RefcountedResource<IResource> transform2 =
295 getTestRefcountedResource(spi3, spi4, udpEncapSocket);
296
297 // Pretend one SPIs goes out of reference (releaseManagedResource -> userRelease)
298 spi1.userRelease();
299
300 // User called releaseManagedResource on udpEncap socket and spi4
301 udpEncapSocket.userRelease();
302 spi4.userRelease();
303
304 // User dies, and binder kills the rest
305 spi2.binderDied();
306 spi3.binderDied();
307 transform2.binderDied();
308 transform1.binderDied();
309
310 // Check resource states
311 assertResourceState(spi1, -1, 1, 2, 1, 1);
312 assertResourceState(spi2, -1, 1, 2, 1, 1);
313 assertResourceState(spi3, -1, 1, 2, 1, 1);
314 assertResourceState(spi4, -1, 1, 2, 1, 1);
315 assertResourceState(udpEncapSocket, -1, 1, 3, 1, 1);
316 assertResourceState(transform1, -1, 1, 1, 1, 1);
317 assertResourceState(transform2, -1, 1, 1, 1, 1);
318 }
319
320 @Test
321 public void fuzzTest() throws RemoteException {
322 List<RefcountedResource<IResource>> resources = new ArrayList<>();
323
324 // Build a tree of resources
325 for (int i = 0; i < 100; i++) {
326 // Choose a random number of children from the existing list
327 int numChildren = ThreadLocalRandom.current().nextInt(0, resources.size() + 1);
328
329 // Build a (random) list of children
330 Set<RefcountedResource<IResource>> children = new HashSet<>();
331 for (int j = 0; j < numChildren; j++) {
332 int childIndex = ThreadLocalRandom.current().nextInt(0, resources.size());
333 children.add(resources.get(childIndex));
334 }
335
336 RefcountedResource<IResource> newRefcountedResource =
337 getTestRefcountedResource(
338 children.toArray(new RefcountedResource[children.size()]));
339 resources.add(newRefcountedResource);
340 }
341
342 // Cleanup all resources in a random order
343 List<RefcountedResource<IResource>> clonedResources =
344 new ArrayList<>(resources); // shallow copy
345 while (!clonedResources.isEmpty()) {
346 int index = ThreadLocalRandom.current().nextInt(0, clonedResources.size());
347 RefcountedResource<IResource> refcountedResource = clonedResources.get(index);
348 refcountedResource.userRelease();
349 clonedResources.remove(index);
350 }
351
352 // Verify all resources were cleaned up properly
353 for (RefcountedResource<IResource> refcountedResource : resources) {
354 assertEquals(-1, refcountedResource.mRefCount);
355 }
356 }
357}