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