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