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