1 /* 2 * Copyright (C) 2020 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.services.telephony.rcs; 18 19 import android.os.Binder; 20 import android.os.RemoteException; 21 import android.telephony.ims.DelegateMessageCallback; 22 import android.telephony.ims.DelegateRegistrationState; 23 import android.telephony.ims.FeatureTagState; 24 import android.telephony.ims.SipDelegateConfiguration; 25 import android.telephony.ims.SipDelegateImsConfiguration; 26 import android.telephony.ims.SipDelegateManager; 27 import android.telephony.ims.SipMessage; 28 import android.telephony.ims.aidl.ISipDelegate; 29 import android.telephony.ims.aidl.ISipDelegateMessageCallback; 30 import android.telephony.ims.stub.SipDelegate; 31 import android.util.LocalLog; 32 import android.util.Log; 33 34 import com.android.internal.annotations.VisibleForTesting; 35 import com.android.internal.telephony.SipMessageParsingUtils; 36 import com.android.internal.util.IndentingPrintWriter; 37 import com.android.phone.RcsProvisioningMonitor; 38 import com.android.services.telephony.rcs.validator.ValidationResult; 39 40 import java.io.PrintWriter; 41 import java.util.Set; 42 import java.util.concurrent.Executor; 43 import java.util.concurrent.ScheduledExecutorService; 44 import java.util.function.Consumer; 45 46 /** 47 * Wraps the SIP message path both from the IMS application to the SipDelegate and from the 48 * SipDelegate back to the IMS Application. 49 * <p> 50 * Queues incoming and outgoing SIP messages on an Executor and deliver to IMS application and 51 * SipDelegate in order. If there is an error delivering the message, the caller is notified. 52 * Uses {@link TransportSipMessageValidator} to track ongoing SIP dialogs and verify outgoing 53 * messages. 54 * <p> 55 * Note: This handles incoming binder calls, so all calls from other processes should be handled on 56 * the provided Executor. 57 */ 58 public class MessageTransportWrapper implements DelegateBinderStateManager.StateCallback { 59 private static final String TAG = "MessageTW"; 60 61 // SipDelegateConnection(IMS Application) -> SipDelegate(ImsService) 62 private final ISipDelegate.Stub mSipDelegateConnection = new ISipDelegate.Stub() { 63 /** 64 * The IMS application is acknowledging that it has successfully received and processed an 65 * incoming SIP message sent by the SipDelegate in 66 * {@link ISipDelegateMessageCallback#onMessageReceived(SipMessage)}. 67 */ 68 @Override 69 public void notifyMessageReceived(String viaTransactionId) { 70 long token = Binder.clearCallingIdentity(); 71 try { 72 mExecutor.execute(() -> { 73 if (mSipDelegate == null) { 74 logw("notifyMessageReceived called when SipDelegate is not associated for " 75 + "transaction id: " + viaTransactionId); 76 return; 77 } 78 try { 79 mSipSessionTracker.acknowledgePendingMessage(viaTransactionId); 80 mSipDelegate.notifyMessageReceived(viaTransactionId); 81 } catch (RemoteException e) { 82 logw("SipDelegate not available when notifyMessageReceived was called " 83 + "for transaction id: " + viaTransactionId); 84 } 85 }); 86 } finally { 87 Binder.restoreCallingIdentity(token); 88 } 89 } 90 91 /** 92 * The IMS application is acknowledging that it received an incoming SIP message sent by the 93 * SipDelegate in {@link ISipDelegateMessageCallback#onMessageReceived(SipMessage)} but it 94 * was unable to process it. 95 */ 96 @Override 97 public void notifyMessageReceiveError(String viaTransactionId, int reason) { 98 long token = Binder.clearCallingIdentity(); 99 try { 100 mExecutor.execute(() -> { 101 if (mSipDelegate == null) { 102 logw("notifyMessageReceiveError called when SipDelegate is not associated " 103 + "for transaction id: " + viaTransactionId); 104 return; 105 } 106 try { 107 mSipSessionTracker.notifyPendingMessageFailed(viaTransactionId); 108 mSipDelegate.notifyMessageReceiveError(viaTransactionId, reason); 109 } catch (RemoteException e) { 110 logw("SipDelegate not available when notifyMessageReceiveError was called " 111 + "for transaction id: " + viaTransactionId); 112 } 113 }); 114 } finally { 115 Binder.restoreCallingIdentity(token); 116 } 117 } 118 119 /** 120 * The IMS application is sending an outgoing SIP message to the SipDelegate to be processed 121 * and sent over the network. 122 */ 123 @Override 124 public void sendMessage(SipMessage sipMessage, long configVersion) { 125 long token = Binder.clearCallingIdentity(); 126 try { 127 mExecutor.execute(() -> { 128 ValidationResult result = 129 mSipSessionTracker.verifyOutgoingMessage(sipMessage, configVersion); 130 result = maybeOverrideValidationForTesting(result); 131 if (!result.isValidated) { 132 notifyDelegateSendError("Outgoing - " + result.logReason, 133 sipMessage, result.restrictedReason); 134 return; 135 } 136 try { 137 if (mSipDelegate == null) { 138 logw("sendMessage called when SipDelegate is not associated." 139 + sipMessage); 140 notifyDelegateSendError("No SipDelegate", sipMessage, 141 SipDelegateManager.MESSAGE_FAILURE_REASON_DELEGATE_DEAD); 142 143 return; 144 } 145 mSipDelegate.sendMessage(sipMessage, configVersion); 146 } catch (RemoteException e) { 147 notifyDelegateSendError("RemoteException: " + e, sipMessage, 148 SipDelegateManager.MESSAGE_FAILURE_REASON_DELEGATE_DEAD); 149 } 150 }); 151 } finally { 152 Binder.restoreCallingIdentity(token); 153 } 154 } 155 156 /** 157 * The SipDelegateConnection is requesting that the resources associated with an ongoing SIP 158 * dialog be released as the SIP dialog is now closed. 159 */ 160 @Override 161 public void cleanupSession(String callId) { 162 long token = Binder.clearCallingIdentity(); 163 try { 164 mExecutor.execute(() -> cleanupSessionInternal(callId)); 165 } finally { 166 Binder.restoreCallingIdentity(token); 167 } 168 } 169 }; 170 171 // SipDelegate(ImsService) -> SipDelegateConnection(IMS Application) 172 private final ISipDelegateMessageCallback.Stub mDelegateConnectionMessageCallback = 173 new ISipDelegateMessageCallback.Stub() { 174 /** 175 * An Incoming SIP Message has been received by the SipDelegate and is being routed 176 * to the IMS application for processing. 177 * <p> 178 * IMS application will call {@link ISipDelegate#notifyMessageReceived(String)} to 179 * acknowledge receipt of this incoming message. 180 */ 181 @Override 182 public void onMessageReceived(SipMessage message) { 183 long token = Binder.clearCallingIdentity(); 184 try { 185 mExecutor.execute(() -> { 186 ValidationResult result = mSipSessionTracker.verifyIncomingMessage(message); 187 if (!result.isValidated) { 188 notifyAppReceiveError("Incoming - " + result.logReason, message, 189 result.restrictedReason); 190 return; 191 } 192 try { 193 mAppCallback.onMessageReceived(message); 194 } catch (RemoteException e) { 195 notifyAppReceiveError("RemoteException: " + e, message, 196 SipDelegateManager.MESSAGE_FAILURE_REASON_DELEGATE_DEAD); 197 } 198 }); 199 } finally { 200 Binder.restoreCallingIdentity(token); 201 } 202 } 203 204 /** 205 * An outgoing SIP message sent previously by the SipDelegateConnection to the SipDelegate 206 * using {@link ISipDelegate#sendMessage(SipMessage, long)} as been successfully sent. 207 */ 208 @Override 209 public void onMessageSent(String viaTransactionId) { 210 long token = Binder.clearCallingIdentity(); 211 try { 212 mExecutor.execute(() -> { 213 if (mSipDelegate == null) { 214 logw("Unexpected state, onMessageSent called when SipDelegate is not " 215 + "associated"); 216 } 217 try { 218 mSipSessionTracker.acknowledgePendingMessage(viaTransactionId); 219 mAppCallback.onMessageSent(viaTransactionId); 220 } catch (RemoteException e) { 221 logw("Error sending onMessageSent to SipDelegateConnection, remote not" 222 + "available for transaction ID: " + viaTransactionId); 223 } 224 }); 225 } finally { 226 Binder.restoreCallingIdentity(token); 227 } 228 } 229 230 /** 231 * An outgoing SIP message sent previously by the SipDelegateConnection to the SipDelegate 232 * using {@link ISipDelegate#sendMessage(SipMessage, long)} failed to be sent. 233 */ 234 @Override 235 public void onMessageSendFailure(String viaTransactionId, int reason) { 236 long token = Binder.clearCallingIdentity(); 237 try { 238 mExecutor.execute(() -> { 239 if (mSipDelegate == null) { 240 logw("Unexpected state, onMessageSendFailure called when SipDelegate is not" 241 + "associated"); 242 } 243 try { 244 mSipSessionTracker.notifyPendingMessageFailed(viaTransactionId); 245 mAppCallback.onMessageSendFailure(viaTransactionId, reason); 246 } catch (RemoteException e) { 247 logw("Error sending onMessageSendFailure to SipDelegateConnection, remote" 248 + " not available for transaction ID: " + viaTransactionId); 249 } 250 }); 251 } finally { 252 Binder.restoreCallingIdentity(token); 253 } 254 } 255 }; 256 257 /** 258 * Interface for injecting validator override dependencies for testing. 259 */ 260 @VisibleForTesting 261 public interface ValidatorOverride { 262 /** 263 * @return {@code null} if the validation result should not be overridden, {@code true} if 264 * the validation result should always pass, {@code false} if the validation result should 265 * always fail. 266 */ getValidatorOverrideState()267 Boolean getValidatorOverrideState(); 268 } 269 270 private final ValidatorOverride mValidatorOverride; 271 private final ISipDelegateMessageCallback mAppCallback; 272 private final Executor mExecutor; 273 private final int mSubId; 274 private final TransportSipMessageValidator mSipSessionTracker; 275 private final LocalLog mLocalLog = new LocalLog(SipTransportController.LOG_SIZE); 276 277 private ISipDelegate mSipDelegate; 278 MessageTransportWrapper(int subId, ScheduledExecutorService executor, ISipDelegateMessageCallback appMessageCallback)279 public MessageTransportWrapper(int subId, ScheduledExecutorService executor, 280 ISipDelegateMessageCallback appMessageCallback) { 281 mSubId = subId; 282 mAppCallback = appMessageCallback; 283 mExecutor = executor; 284 mSipSessionTracker = new TransportSipMessageValidator(subId, executor); 285 mValidatorOverride = () -> RcsProvisioningMonitor.getInstance() 286 .getImsFeatureValidationOverride(mSubId); 287 } 288 289 /** 290 * Mock out dependencies for unit testing. 291 */ 292 @VisibleForTesting MessageTransportWrapper(int subId, ScheduledExecutorService executor, ISipDelegateMessageCallback appMessageCallback, TransportSipMessageValidator sipSessionTracker)293 public MessageTransportWrapper(int subId, ScheduledExecutorService executor, 294 ISipDelegateMessageCallback appMessageCallback, 295 TransportSipMessageValidator sipSessionTracker) { 296 mSubId = subId; 297 mAppCallback = appMessageCallback; 298 mExecutor = executor; 299 mSipSessionTracker = sipSessionTracker; 300 // Remove links to static methods calls querying overrides for testing. 301 mValidatorOverride = () -> null; 302 } 303 304 @Override onRegistrationStateChanged(DelegateRegistrationState registrationState)305 public void onRegistrationStateChanged(DelegateRegistrationState registrationState) { 306 mSipSessionTracker.onRegistrationStateChanged((callIds) -> { 307 for (String id : callIds) { 308 cleanupSessionInternal(id); 309 } 310 }, registrationState); 311 } 312 313 @Override onImsConfigurationChanged(SipDelegateImsConfiguration config)314 public void onImsConfigurationChanged(SipDelegateImsConfiguration config) { 315 mSipSessionTracker.onImsConfigurationChanged(config); 316 } 317 318 @Override onConfigurationChanged(SipDelegateConfiguration config)319 public void onConfigurationChanged(SipDelegateConfiguration config) { 320 mSipSessionTracker.onConfigurationChanged(config); 321 } 322 323 /** 324 * Open the transport and allow SIP messages to be sent/received on the delegate specified. 325 * @param delegate The delegate connection to send SIP messages to on the ImsService. 326 * @param supportedFeatureTags Feature tags that are supported. Outgoing SIP messages relating 327 * to these tags will be allowed. 328 * @param deniedFeatureTags Feature tags that have been denied. Outgoing SIP messages relating 329 * to these tags will be denied. 330 */ openTransport(ISipDelegate delegate, Set<String> supportedFeatureTags, Set<FeatureTagState> deniedFeatureTags)331 public void openTransport(ISipDelegate delegate, Set<String> supportedFeatureTags, 332 Set<FeatureTagState> deniedFeatureTags) { 333 logi("openTransport: delegate=" + delegate + ", supportedTags=" + supportedFeatureTags 334 + ", deniedTags=" + deniedFeatureTags); 335 mSipSessionTracker.onTransportOpened(supportedFeatureTags, deniedFeatureTags); 336 mSipDelegate = delegate; 337 } 338 339 /** Dump state about this tracker that should be included in the dumpsys */ dump(PrintWriter printWriter)340 public void dump(PrintWriter printWriter) { 341 IndentingPrintWriter pw = new IndentingPrintWriter(printWriter, " "); 342 pw.println("Most recent logs:"); 343 mLocalLog.dump(printWriter); 344 pw.println(); 345 pw.println("Dialog Tracker:"); 346 pw.increaseIndent(); 347 mSipSessionTracker.dump(pw); 348 pw.decreaseIndent(); 349 } 350 351 /** 352 * @return SipDelegate implementation to be sent to IMS application. 353 */ getDelegateConnection()354 public ISipDelegate getDelegateConnection() { 355 return mSipDelegateConnection; 356 } 357 358 /** 359 * @return The remote application's message callback. 360 */ getAppMessageCallback()361 public ISipDelegateMessageCallback getAppMessageCallback() { 362 return mAppCallback; 363 } 364 365 /** 366 * @return MessageCallback implementation to be sent to the ImsService. 367 */ getMessageCallback()368 public ISipDelegateMessageCallback getMessageCallback() { 369 return mDelegateConnectionMessageCallback; 370 } 371 372 /** 373 * Gradually close all SIP Sessions by: 374 * 1) denying all new outgoing SIP Dialog requests with the reason specified and 375 * 2) only allowing existing SIP Sessions to continue. 376 * <p> 377 * This will allow traffic to continue on existing SIP Sessions until a BYE is sent and the 378 * corresponding SIP Dialogs are closed or a timeout is hit and 379 * {@link SipDelegate#cleanupSession(String)} (String)} is forcefully called on all open SIP 380 * sessions. 381 * <p> 382 * Any outgoing out-of-dialog traffic on this transport will be denied with the provided reason. 383 * <p> 384 * Incoming out-of-dialog traffic will continue to be set up until the SipDelegate is fully 385 * closed. 386 * @param delegateClosingReason The reason code to return to 387 * {@link DelegateMessageCallback#onMessageSendFailure(String, int)} if a new out-of-dialog SIP 388 * message is received while waiting for existing Dialogs. 389 * @param closedReason reason to return to new outgoing SIP messages via 390 * {@link SipDelegate#notifyMessageReceiveError(String, int)} once the transport 391 * transitions to the fully closed state. 392 * @param resultConsumer The consumer called when the message transport has been closed. It will 393 * return {@code true} if the procedure completed successfully or {@link false} if the 394 * transport needed to be closed forcefully due to the application not responding before 395 * a timeout occurred. 396 */ closeGracefully(int delegateClosingReason, int closedReason, Consumer<Boolean> resultConsumer)397 public void closeGracefully(int delegateClosingReason, int closedReason, 398 Consumer<Boolean> resultConsumer) { 399 logi("closeGracefully: closingReason=" + delegateClosingReason + ", closedReason=" 400 + closedReason + ", resultConsumer(" + resultConsumer.hashCode() + ")"); 401 mSipSessionTracker.closeSessionsGracefully((openCallIds) -> { 402 logi("closeGracefully resultConsumer(" + resultConsumer.hashCode() 403 + "): open call IDs:{" + openCallIds + "}"); 404 closeTransport(openCallIds); 405 // propagate event to the consumer 406 resultConsumer.accept(openCallIds.isEmpty() /*successfullyClosed*/); 407 }, delegateClosingReason, closedReason); 408 } 409 410 /** 411 * Close all ongoing SIP sessions immediately and respond to any incoming/outgoing messages with 412 * the provided reason. 413 * @param closedReason The failure reason to provide to incoming/outgoing SIP messages 414 * if an attempt is made to send/receive a message after this method is called. 415 */ close(int closedReason)416 public void close(int closedReason) { 417 Set<String> openSessions = mSipSessionTracker.closeSessions(closedReason); 418 logi("close: closedReason=" + closedReason + "open call IDs:{" + openSessions + "}"); 419 closeTransport(openSessions); 420 } 421 422 // Clean up all state related to the existing SipDelegate immediately. closeTransport(Set<String> openCallIds)423 private void closeTransport(Set<String> openCallIds) { 424 for (String id : openCallIds) { 425 cleanupSessionInternal(id); 426 } 427 mSipDelegate = null; 428 } 429 cleanupSessionInternal(String callId)430 private void cleanupSessionInternal(String callId) { 431 logi("cleanupSessionInternal: clean up session with callId: " + callId); 432 try { 433 if (mSipDelegate == null) { 434 logw("cleanupSessionInternal: SipDelegate is not associated, callId: " + callId); 435 } else { 436 // This will close the transport, so call cleanup on ImsService first. 437 mSipDelegate.cleanupSession(callId); 438 } 439 } catch (RemoteException e) { 440 logw("cleanupSessionInternal: remote not available when cleanupSession was called " 441 + "for call id: " + callId); 442 } 443 mSipSessionTracker.onSipSessionCleanup(callId); 444 } 445 maybeOverrideValidationForTesting(ValidationResult result)446 private ValidationResult maybeOverrideValidationForTesting(ValidationResult result) { 447 Boolean isValidatedOverride = mValidatorOverride.getValidatorOverrideState(); 448 if (isValidatedOverride == null) { 449 return result; 450 } 451 if (isValidatedOverride) { 452 return ValidationResult.SUCCESS; 453 } else if (result.isValidated) { 454 // if override is set to false and the original result was validated, return a new 455 // restricted result with UNKNOWN reason. 456 return new ValidationResult(SipDelegateManager.MESSAGE_FAILURE_REASON_UNKNOWN, 457 "validation failed due to a testing override being set"); 458 } 459 return result; 460 } 461 notifyDelegateSendError(String logReason, SipMessage message, int reasonCode)462 private void notifyDelegateSendError(String logReason, SipMessage message, int reasonCode) { 463 String transactionId = SipMessageParsingUtils.getTransactionId(message.getHeaderSection()); 464 logi("Error sending SipMessage[id: " + transactionId + ", code: " + reasonCode 465 + "] -> SipDelegate for reason: " + logReason); 466 try { 467 mAppCallback.onMessageSendFailure(transactionId, reasonCode); 468 } catch (RemoteException e) { 469 logw("notifyDelegateSendError, SipDelegate is not available: " + e); 470 } 471 } 472 notifyAppReceiveError(String logReason, SipMessage message, int reasonCode)473 private void notifyAppReceiveError(String logReason, SipMessage message, int reasonCode) { 474 String transactionId = SipMessageParsingUtils.getTransactionId(message.getHeaderSection()); 475 logi("Error sending SipMessage[id: " + transactionId + ", code: " + reasonCode + "] -> " 476 + "SipDelegateConnection for reason: " + logReason); 477 try { 478 mSipDelegate.notifyMessageReceiveError(transactionId, reasonCode); 479 } catch (RemoteException e) { 480 logw("notifyAppReceiveError, SipDelegate is not available: " + e); 481 } 482 } 483 484 /** 485 * This is a listener to handle SipDialog state of delegate 486 * @param listener {@link SipDialogsStateListener} 487 * @param isNeedNotify It indicates whether the current dialogs state should be notified. 488 */ setSipDialogsListener(SipDialogsStateListener listener, boolean isNeedNotify)489 public void setSipDialogsListener(SipDialogsStateListener listener, 490 boolean isNeedNotify) { 491 mSipSessionTracker.setSipDialogsListener(listener, isNeedNotify); 492 } 493 logi(String log)494 private void logi(String log) { 495 Log.i(SipTransportController.LOG_TAG, TAG + "[" + mSubId + "] " + log); 496 mLocalLog.log("[I] " + log); 497 } 498 logw(String log)499 private void logw(String log) { 500 Log.w(SipTransportController.LOG_TAG, TAG + "[" + mSubId + "] " + log); 501 mLocalLog.log("[W] " + log); 502 } 503 } 504