1 /* 2 * Copyright (C) 2023 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 package android.net.apf; 17 18 import static android.net.apf.ApfJniUtils.apfSimulate; 19 import static android.system.OsConstants.AF_UNIX; 20 import static android.system.OsConstants.SOCK_STREAM; 21 22 import static org.junit.Assert.assertEquals; 23 import static org.junit.Assert.assertFalse; 24 import static org.junit.Assert.assertTrue; 25 import static org.junit.Assert.fail; 26 import static org.mockito.Mockito.mock; 27 28 import android.content.Context; 29 import android.net.LinkAddress; 30 import android.net.LinkProperties; 31 import android.net.apf.BaseApfGenerator.IllegalInstructionException; 32 import android.net.ip.IIpClientCallbacks; 33 import android.net.ip.IpClient; 34 import android.net.metrics.IpConnectivityLog; 35 import android.os.ConditionVariable; 36 import android.os.SystemClock; 37 import android.system.ErrnoException; 38 import android.system.Os; 39 import android.text.format.DateUtils; 40 41 import com.android.internal.annotations.GuardedBy; 42 import com.android.internal.util.HexDump; 43 import com.android.net.module.util.InterfaceParams; 44 import com.android.net.module.util.SharedLog; 45 import com.android.networkstack.apishim.NetworkInformationShimImpl; 46 import com.android.networkstack.metrics.NetworkQuirkMetrics; 47 48 import libcore.io.IoUtils; 49 50 import java.io.FileDescriptor; 51 import java.io.IOException; 52 import java.net.InetAddress; 53 import java.util.Arrays; 54 55 /** 56 * The util class for calling the APF interpreter and check the return value 57 */ 58 public class ApfTestUtils { 59 public static final int TIMEOUT_MS = 500; 60 public static final int PASS = 1; 61 public static final int DROP = 0; 62 // Interpreter will just accept packets without link layer headers, so pad fake packet to at 63 // least the minimum packet size. 64 public static final int MIN_PKT_SIZE = 15; 65 ApfTestUtils()66 private ApfTestUtils() { 67 } 68 label(int code)69 private static String label(int code) { 70 switch (code) { 71 case PASS: 72 return "PASS"; 73 case DROP: 74 return "DROP"; 75 default: 76 return "UNKNOWN"; 77 } 78 } 79 assertReturnCodesEqual(String msg, int expected, int got)80 private static void assertReturnCodesEqual(String msg, int expected, int got) { 81 assertEquals(msg, label(expected), label(got)); 82 } 83 assertReturnCodesEqual(int expected, int got)84 private static void assertReturnCodesEqual(int expected, int got) { 85 assertEquals(label(expected), label(got)); 86 } 87 assertVerdict(int apfVersion, int expected, byte[] program, byte[] packet, int filterAge)88 private static void assertVerdict(int apfVersion, int expected, byte[] program, byte[] packet, 89 int filterAge) { 90 final String msg = "Unexpected APF verdict. To debug:\n" 91 + " apf_run --program " + HexDump.toHexString(program) 92 + " --packet " + HexDump.toHexString(packet) 93 + " --age " + filterAge 94 + (apfVersion > 4 ? " --v6" : "") 95 + " --trace " + " | less\n "; 96 assertReturnCodesEqual(msg, expected, 97 apfSimulate(apfVersion, program, packet, null, filterAge)); 98 } 99 100 /** 101 * Runs the APF program and checks the return code is equals to expected value. If not, the 102 * customized message is printed. 103 */ assertVerdict(int apfVersion, String msg, int expected, byte[] program, byte[] packet, int filterAge)104 public static void assertVerdict(int apfVersion, String msg, int expected, byte[] program, 105 byte[] packet, int filterAge) { 106 assertReturnCodesEqual(msg, expected, 107 apfSimulate(apfVersion, program, packet, null, filterAge)); 108 } 109 110 /** 111 * Runs the APF program and checks the return code is equals to expected value. 112 */ assertVerdict(int apfVersion, int expected, byte[] program, byte[] packet)113 public static void assertVerdict(int apfVersion, int expected, byte[] program, byte[] packet) { 114 assertVerdict(apfVersion, expected, program, packet, 0); 115 } 116 117 /** 118 * Runs the APF program and checks the return code is PASS. 119 */ assertPass(int apfVersion, byte[] program, byte[] packet, int filterAge)120 public static void assertPass(int apfVersion, byte[] program, byte[] packet, int filterAge) { 121 assertVerdict(apfVersion, PASS, program, packet, filterAge); 122 } 123 124 /** 125 * Runs the APF program and checks the return code is PASS. 126 */ assertPass(int apfVersion, byte[] program, byte[] packet)127 public static void assertPass(int apfVersion, byte[] program, byte[] packet) { 128 assertVerdict(apfVersion, PASS, program, packet); 129 } 130 131 /** 132 * Runs the APF program and checks the return code is DROP. 133 */ assertDrop(int apfVersion, byte[] program, byte[] packet, int filterAge)134 public static void assertDrop(int apfVersion, byte[] program, byte[] packet, int filterAge) { 135 assertVerdict(apfVersion, DROP, program, packet, filterAge); 136 } 137 138 /** 139 * Runs the APF program and checks the return code is DROP. 140 */ assertDrop(int apfVersion, byte[] program, byte[] packet)141 public static void assertDrop(int apfVersion, byte[] program, byte[] packet) { 142 assertVerdict(apfVersion, DROP, program, packet); 143 } 144 145 /** 146 * Checks the generated APF program equals to the expected value. 147 */ assertProgramEquals(byte[] expected, byte[] program)148 public static void assertProgramEquals(byte[] expected, byte[] program) throws AssertionError { 149 // assertArrayEquals() would only print one byte, making debugging difficult. 150 if (!Arrays.equals(expected, program)) { 151 throw new AssertionError("\nexpected: " + HexDump.toHexString(expected) + "\nactual: " 152 + HexDump.toHexString(program)); 153 } 154 } 155 156 /** 157 * Runs the APF program and checks the return code and data regions equals to expected value. 158 */ assertDataMemoryContents(int apfVersion, int expected, byte[] program, byte[] packet, byte[] data, byte[] expectedData, boolean ignoreInterpreterVersion)159 public static void assertDataMemoryContents(int apfVersion, int expected, byte[] program, 160 byte[] packet, byte[] data, byte[] expectedData, boolean ignoreInterpreterVersion) 161 throws ApfV4Generator.IllegalInstructionException, Exception { 162 assertReturnCodesEqual(expected, 163 apfSimulate(apfVersion, program, packet, data, 0 /* filterAge */)); 164 165 if (ignoreInterpreterVersion) { 166 final int apfVersionIdx = ApfCounterTracker.Counter.totalSize() 167 + ApfCounterTracker.Counter.APF_VERSION.offset(); 168 final int apfProgramIdIdx = ApfCounterTracker.Counter.totalSize() 169 + ApfCounterTracker.Counter.APF_PROGRAM_ID.offset(); 170 for (int i = 0; i < 4; ++i) { 171 data[apfVersionIdx + i] = 0; 172 data[apfProgramIdIdx + i] = 0; 173 } 174 } 175 // assertArrayEquals() would only print one byte, making debugging difficult. 176 if (!Arrays.equals(expectedData, data)) { 177 throw new Exception("\nprogram: " + HexDump.toHexString(program) + "\ndata memory: " 178 + HexDump.toHexString(data) + "\nexpected: " + HexDump.toHexString( 179 expectedData)); 180 } 181 } 182 183 /** 184 * Runs the APF program with customized data region and checks the return code. 185 */ assertVerdict(int apfVersion, int expected, byte[] program, byte[] packet, byte[] data)186 public static void assertVerdict(int apfVersion, int expected, byte[] program, byte[] packet, 187 byte[] data) { 188 assertVerdict(apfVersion, expected, program, packet, data, 0 /* filterAge */); 189 } 190 assertVerdict(int apfVersion, int expected, ApfV4Generator gen, byte[] packet, int filterAge)191 private static void assertVerdict(int apfVersion, int expected, ApfV4Generator gen, 192 byte[] packet, int filterAge) throws ApfV4Generator.IllegalInstructionException { 193 assertVerdict(apfVersion, expected, gen.generate(), packet, null, filterAge); 194 } 195 assertVerdict(int apfVersion, int expected, byte[] program, byte[] packet, byte[] data, int filterAge)196 private static void assertVerdict(int apfVersion, int expected, byte[] program, byte[] packet, 197 byte[] data, int filterAge) { 198 final String msg = "Unexpected APF verdict. To debug:\n" 199 + " apf_run --program " + HexDump.toHexString(program) 200 + " --packet " + HexDump.toHexString(packet) 201 + (data != null ? " --data " + HexDump.toHexString(data) : "") 202 + " --age " + filterAge 203 + (apfVersion > 4 ? " --v6" : "") 204 + " --trace " + " | less\n "; 205 assertReturnCodesEqual(msg, expected, 206 apfSimulate(apfVersion, program, packet, data, filterAge)); 207 } 208 209 /** 210 * Runs the APF program and checks the return code is PASS. 211 */ assertPass(int apfVersion, ApfV4Generator gen, byte[] packet, int filterAge)212 public static void assertPass(int apfVersion, ApfV4Generator gen, byte[] packet, int filterAge) 213 throws ApfV4Generator.IllegalInstructionException { 214 assertVerdict(apfVersion, PASS, gen, packet, filterAge); 215 } 216 217 /** 218 * Runs the APF program and checks the return code is DROP. 219 */ assertDrop(int apfVersion, ApfV4Generator gen, byte[] packet, int filterAge)220 public static void assertDrop(int apfVersion, ApfV4Generator gen, byte[] packet, int filterAge) 221 throws ApfV4Generator.IllegalInstructionException { 222 assertVerdict(apfVersion, DROP, gen, packet, filterAge); 223 } 224 225 /** 226 * Runs the APF program and checks the return code is PASS. 227 */ assertPass(int apfVersion, ApfV4Generator gen)228 public static void assertPass(int apfVersion, ApfV4Generator gen) 229 throws ApfV4Generator.IllegalInstructionException { 230 assertVerdict(apfVersion, PASS, gen, new byte[MIN_PKT_SIZE], 0); 231 } 232 233 /** 234 * Runs the APF program and checks the return code is DROP. 235 */ assertDrop(int apfVersion, ApfV4Generator gen)236 public static void assertDrop(int apfVersion, ApfV4Generator gen) 237 throws ApfV4Generator.IllegalInstructionException { 238 assertVerdict(apfVersion, DROP, gen, new byte[MIN_PKT_SIZE], 0); 239 } 240 241 /** 242 * The Mock ip client callback class. 243 */ 244 public static class MockIpClientCallback extends IpClient.IpClientCallbacksWrapper { 245 private final ConditionVariable mGotApfProgram = new ConditionVariable(); 246 private byte[] mLastApfProgram; 247 private boolean mInstallPacketFilterReturn = true; 248 MockIpClientCallback()249 MockIpClientCallback() { 250 super(mock(IIpClientCallbacks.class), mock(SharedLog.class), mock(SharedLog.class), 251 NetworkInformationShimImpl.newInstance(), false); 252 } 253 MockIpClientCallback(boolean installPacketFilterReturn)254 MockIpClientCallback(boolean installPacketFilterReturn) { 255 super(mock(IIpClientCallbacks.class), mock(SharedLog.class), mock(SharedLog.class), 256 NetworkInformationShimImpl.newInstance(), false); 257 mInstallPacketFilterReturn = installPacketFilterReturn; 258 } 259 260 @Override installPacketFilter(byte[] filter)261 public boolean installPacketFilter(byte[] filter) { 262 mLastApfProgram = filter; 263 mGotApfProgram.open(); 264 return mInstallPacketFilterReturn; 265 } 266 267 /** 268 * Reset the apf program and wait for the next update. 269 */ resetApfProgramWait()270 public void resetApfProgramWait() { 271 mGotApfProgram.close(); 272 } 273 274 /** 275 * Assert the program is update within TIMEOUT_MS and return the program. 276 */ assertProgramUpdateAndGet()277 public byte[] assertProgramUpdateAndGet() { 278 assertTrue(mGotApfProgram.block(TIMEOUT_MS)); 279 return mLastApfProgram; 280 } 281 282 /** 283 * Assert the program is not update within TIMEOUT_MS. 284 */ assertNoProgramUpdate()285 public void assertNoProgramUpdate() { 286 assertFalse(mGotApfProgram.block(TIMEOUT_MS)); 287 } 288 } 289 290 /** 291 * The test apf filter class. 292 */ 293 public static class TestApfFilter extends ApfFilter implements TestAndroidPacketFilter { 294 public static final byte[] MOCK_MAC_ADDR = {2, 3, 4, 5, 6, 7}; 295 private static final byte[] MOCK_IPV4_ADDR = {10, 0, 0, 1}; 296 297 private FileDescriptor mWriteSocket; 298 private long mCurrentTimeMs = SystemClock.elapsedRealtime(); 299 private final MockIpClientCallback mMockIpClientCb; 300 private final boolean mThrowsExceptionWhenGeneratesProgram; 301 TestApfFilter(Context context, ApfConfiguration config, MockIpClientCallback ipClientCallback, NetworkQuirkMetrics networkQuirkMetrics, Dependencies dependencies)302 public TestApfFilter(Context context, ApfConfiguration config, 303 MockIpClientCallback ipClientCallback, NetworkQuirkMetrics networkQuirkMetrics, 304 Dependencies dependencies) throws Exception { 305 this(context, config, ipClientCallback, networkQuirkMetrics, dependencies, 306 false /* throwsExceptionWhenGeneratesProgram */, new ApfFilter.Clock()); 307 } 308 TestApfFilter(Context context, ApfConfiguration config, MockIpClientCallback ipClientCallback, NetworkQuirkMetrics networkQuirkMetrics, Dependencies dependencies, boolean throwsExceptionWhenGeneratesProgram)309 public TestApfFilter(Context context, ApfConfiguration config, 310 MockIpClientCallback ipClientCallback, NetworkQuirkMetrics networkQuirkMetrics, 311 Dependencies dependencies, boolean throwsExceptionWhenGeneratesProgram) 312 throws Exception { 313 this(context, config, ipClientCallback, networkQuirkMetrics, dependencies, 314 throwsExceptionWhenGeneratesProgram, new ApfFilter.Clock()); 315 } 316 TestApfFilter(Context context, ApfConfiguration config, MockIpClientCallback ipClientCallback, NetworkQuirkMetrics networkQuirkMetrics, Dependencies dependencies, ApfFilter.Clock clock)317 public TestApfFilter(Context context, ApfConfiguration config, 318 MockIpClientCallback ipClientCallback, NetworkQuirkMetrics networkQuirkMetrics, 319 Dependencies dependencies, ApfFilter.Clock clock) throws Exception { 320 this(context, config, ipClientCallback, networkQuirkMetrics, dependencies, 321 false /* throwsExceptionWhenGeneratesProgram */, clock); 322 } 323 TestApfFilter(Context context, ApfConfiguration config, MockIpClientCallback ipClientCallback, NetworkQuirkMetrics networkQuirkMetrics, Dependencies dependencies, boolean throwsExceptionWhenGeneratesProgram, ApfFilter.Clock clock)324 public TestApfFilter(Context context, ApfConfiguration config, 325 MockIpClientCallback ipClientCallback, NetworkQuirkMetrics networkQuirkMetrics, 326 Dependencies dependencies, boolean throwsExceptionWhenGeneratesProgram, 327 ApfFilter.Clock clock) throws Exception { 328 super(context, config, InterfaceParams.getByName("lo"), ipClientCallback, 329 networkQuirkMetrics, dependencies, clock); 330 mMockIpClientCb = ipClientCallback; 331 mThrowsExceptionWhenGeneratesProgram = throwsExceptionWhenGeneratesProgram; 332 } 333 334 /** 335 * Create a new test ApfFiler. 336 */ createTestApfFilter(Context context, MockIpClientCallback ipClientCallback, ApfConfiguration config, NetworkQuirkMetrics networkQuirkMetrics, ApfFilter.Dependencies dependencies)337 public static ApfFilter createTestApfFilter(Context context, 338 MockIpClientCallback ipClientCallback, ApfConfiguration config, 339 NetworkQuirkMetrics networkQuirkMetrics, ApfFilter.Dependencies dependencies) 340 throws Exception { 341 LinkAddress link = new LinkAddress(InetAddress.getByAddress(MOCK_IPV4_ADDR), 19); 342 LinkProperties lp = new LinkProperties(); 343 lp.addLinkAddress(link); 344 TestApfFilter apfFilter = new TestApfFilter(context, config, ipClientCallback, 345 networkQuirkMetrics, dependencies); 346 apfFilter.setLinkProperties(lp); 347 return apfFilter; 348 } 349 350 /** 351 * Pretend an RA packet has been received and show it to ApfFilter. 352 */ pretendPacketReceived(byte[] packet)353 public void pretendPacketReceived(byte[] packet) throws IOException, ErrnoException { 354 mMockIpClientCb.resetApfProgramWait(); 355 // ApfFilter's ReceiveThread will be waiting to read this. 356 Os.write(mWriteSocket, packet, 0, packet.length); 357 } 358 359 /** 360 * Simulate current time changes. 361 */ increaseCurrentTimeSeconds(int delta)362 public void increaseCurrentTimeSeconds(int delta) { 363 mCurrentTimeMs += delta * DateUtils.SECOND_IN_MILLIS; 364 } 365 366 @Override secondsSinceBoot()367 protected int secondsSinceBoot() { 368 return (int) (mCurrentTimeMs / DateUtils.SECOND_IN_MILLIS); 369 } 370 371 @Override maybeStartFilter()372 public synchronized void maybeStartFilter() { 373 mHardwareAddress = MOCK_MAC_ADDR; 374 installNewProgramLocked(); 375 376 // Create two sockets, "readSocket" and "mWriteSocket" and connect them together. 377 FileDescriptor readSocket = new FileDescriptor(); 378 mWriteSocket = new FileDescriptor(); 379 try { 380 Os.socketpair(AF_UNIX, SOCK_STREAM, 0, mWriteSocket, readSocket); 381 } catch (ErrnoException e) { 382 fail(); 383 return; 384 } 385 // Now pass readSocket to ReceiveThread as if it was setup to read raw RAs. 386 // This allows us to pretend RA packets have been received via pretendPacketReceived(). 387 mReceiveThread = new ReceiveThread(readSocket); 388 mReceiveThread.start(); 389 } 390 391 @Override shutdown()392 public synchronized void shutdown() { 393 super.shutdown(); 394 if (mReceiveThread != null) { 395 mReceiveThread.halt(); 396 mReceiveThread = null; 397 } 398 IoUtils.closeQuietly(mWriteSocket); 399 } 400 401 @Override 402 @GuardedBy("this") emitPrologueLocked()403 protected ApfV4GeneratorBase<?> emitPrologueLocked() throws IllegalInstructionException { 404 if (mThrowsExceptionWhenGeneratesProgram) { 405 throw new IllegalStateException(); 406 } 407 return super.emitPrologueLocked(); 408 } 409 } 410 411 /** 412 * The test legacy apf filter class. 413 */ 414 public static class TestLegacyApfFilter extends LegacyApfFilter 415 implements TestAndroidPacketFilter { 416 public static final byte[] MOCK_MAC_ADDR = {1, 2, 3, 4, 5, 6}; 417 private static final byte[] MOCK_IPV4_ADDR = {10, 0, 0, 1}; 418 419 private FileDescriptor mWriteSocket; 420 private final MockIpClientCallback mMockIpClientCb; 421 private final boolean mThrowsExceptionWhenGeneratesProgram; 422 TestLegacyApfFilter(Context context, ApfFilter.ApfConfiguration config, MockIpClientCallback ipClientCallback, IpConnectivityLog ipConnectivityLog, NetworkQuirkMetrics networkQuirkMetrics)423 public TestLegacyApfFilter(Context context, ApfFilter.ApfConfiguration config, 424 MockIpClientCallback ipClientCallback, IpConnectivityLog ipConnectivityLog, 425 NetworkQuirkMetrics networkQuirkMetrics) throws Exception { 426 this(context, config, ipClientCallback, ipConnectivityLog, networkQuirkMetrics, 427 new ApfFilter.Dependencies(context), 428 false /* throwsExceptionWhenGeneratesProgram */, new ApfFilter.Clock()); 429 } 430 TestLegacyApfFilter(Context context, ApfFilter.ApfConfiguration config, MockIpClientCallback ipClientCallback, IpConnectivityLog ipConnectivityLog, NetworkQuirkMetrics networkQuirkMetrics, ApfFilter.Dependencies dependencies, boolean throwsExceptionWhenGeneratesProgram)431 public TestLegacyApfFilter(Context context, ApfFilter.ApfConfiguration config, 432 MockIpClientCallback ipClientCallback, IpConnectivityLog ipConnectivityLog, 433 NetworkQuirkMetrics networkQuirkMetrics, ApfFilter.Dependencies dependencies, 434 boolean throwsExceptionWhenGeneratesProgram) throws Exception { 435 this(context, config, ipClientCallback, ipConnectivityLog, networkQuirkMetrics, 436 dependencies, throwsExceptionWhenGeneratesProgram, new ApfFilter.Clock()); 437 } 438 TestLegacyApfFilter(Context context, ApfFilter.ApfConfiguration config, MockIpClientCallback ipClientCallback, IpConnectivityLog ipConnectivityLog, NetworkQuirkMetrics networkQuirkMetrics, ApfFilter.Dependencies dependencies, ApfFilter.Clock clock)439 public TestLegacyApfFilter(Context context, ApfFilter.ApfConfiguration config, 440 MockIpClientCallback ipClientCallback, IpConnectivityLog ipConnectivityLog, 441 NetworkQuirkMetrics networkQuirkMetrics, ApfFilter.Dependencies dependencies, 442 ApfFilter.Clock clock) throws Exception { 443 this(context, config, ipClientCallback, ipConnectivityLog, networkQuirkMetrics, 444 dependencies, false /* throwsExceptionWhenGeneratesProgram */, clock); 445 } 446 TestLegacyApfFilter(Context context, ApfFilter.ApfConfiguration config, MockIpClientCallback ipClientCallback, IpConnectivityLog ipConnectivityLog, NetworkQuirkMetrics networkQuirkMetrics, ApfFilter.Dependencies dependencies, boolean throwsExceptionWhenGeneratesProgram, ApfFilter.Clock clock)447 public TestLegacyApfFilter(Context context, ApfFilter.ApfConfiguration config, 448 MockIpClientCallback ipClientCallback, IpConnectivityLog ipConnectivityLog, 449 NetworkQuirkMetrics networkQuirkMetrics, ApfFilter.Dependencies dependencies, 450 boolean throwsExceptionWhenGeneratesProgram, ApfFilter.Clock clock) 451 throws Exception { 452 super(context, config, InterfaceParams.getByName("lo"), ipClientCallback, 453 ipConnectivityLog, networkQuirkMetrics, dependencies, clock); 454 mMockIpClientCb = ipClientCallback; 455 mThrowsExceptionWhenGeneratesProgram = throwsExceptionWhenGeneratesProgram; 456 } 457 458 /** 459 * Pretend an RA packet has been received and show it to LegacyApfFilter. 460 */ pretendPacketReceived(byte[] packet)461 public void pretendPacketReceived(byte[] packet) throws IOException, ErrnoException { 462 mMockIpClientCb.resetApfProgramWait(); 463 // ApfFilter's ReceiveThread will be waiting to read this. 464 Os.write(mWriteSocket, packet, 0, packet.length); 465 } 466 467 @Override maybeStartFilter()468 public synchronized void maybeStartFilter() { 469 mHardwareAddress = MOCK_MAC_ADDR; 470 installNewProgramLocked(); 471 472 // Create two sockets, "readSocket" and "mWriteSocket" and connect them together. 473 FileDescriptor readSocket = new FileDescriptor(); 474 mWriteSocket = new FileDescriptor(); 475 try { 476 Os.socketpair(AF_UNIX, SOCK_STREAM, 0, mWriteSocket, readSocket); 477 } catch (ErrnoException e) { 478 fail(); 479 return; 480 } 481 // Now pass readSocket to ReceiveThread as if it was setup to read raw RAs. 482 // This allows us to pretend RA packets have been received via pretendPacketReceived(). 483 mReceiveThread = new ReceiveThread(readSocket); 484 mReceiveThread.start(); 485 } 486 487 @Override shutdown()488 public synchronized void shutdown() { 489 super.shutdown(); 490 if (mReceiveThread != null) { 491 mReceiveThread.halt(); 492 mReceiveThread = null; 493 } 494 IoUtils.closeQuietly(mWriteSocket); 495 } 496 497 @Override 498 @GuardedBy("this") emitPrologueLocked()499 protected ApfV4Generator emitPrologueLocked() throws IllegalInstructionException { 500 if (mThrowsExceptionWhenGeneratesProgram) { 501 throw new IllegalStateException(); 502 } 503 return super.emitPrologueLocked(); 504 } 505 } 506 } 507