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