• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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