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 setUp()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 assertResourceState( RefcountedResource<IResource> resource, int refCount, int userReleaseCallCount, int releaseReferenceCallCount, int invalidateCallCount, int freeUnderlyingResourcesCallCount)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 */ getTestRefcountedResource( RefcountedResource... children)89 private RefcountedResource<IResource> getTestRefcountedResource( 90 RefcountedResource... children) { 91 return getTestRefcountedResource(new Binder(), children); 92 } 93 94 /** Adds mockito instrumentation with provided binder */ getTestRefcountedResource( IBinder binder, RefcountedResource... children)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 testConstructor()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 testConstructorWithChildren()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 testFailLinkToDeath()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 testCleanupAndRelease()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 testMultipleCallsToCleanupAndRelease()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 testBinderDeathAfterCleanupAndReleaseDoesNothing()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 testBinderDeath()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 testCleanupParentDecrementsChildRefcount()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 testCleanupReferencedChildDoesNotTriggerRelease()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 testTwoParents()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 testTwoChildren()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 testSampleUdpEncapTranform()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 testSampleDualTransformEncapSocket()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 fuzzTest()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