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