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