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