1 /*
2  * Copyright (C) 2022 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.uwb.secure;
18 
19 import static com.android.server.uwb.secure.csml.DispatchResponse.NOTIFICATION_EVENT_ID_SECURE_SESSION_ABORTED;
20 
21 import android.os.Looper;
22 import android.util.Log;
23 
24 import androidx.annotation.NonNull;
25 
26 import com.android.server.uwb.pm.RunningProfileSessionInfo;
27 import com.android.server.uwb.secure.csml.DispatchResponse;
28 import com.android.server.uwb.secure.iso7816.StatusWord;
29 
30 import java.util.Optional;
31 
32 /**
33  * Dynamic STS session for responder.
34  */
35 public abstract class ResponderSession extends SecureSession {
36     private static final String LOG_TAG = "ResponderSession";
37 
ResponderSession( @onNull Looper workLooper, @NonNull FiRaSecureChannel fiRaSecureChannel, @NonNull Callback sessionCallback, @NonNull RunningProfileSessionInfo runningProfileSessionInfo)38     ResponderSession(
39             @NonNull Looper workLooper,
40             @NonNull FiRaSecureChannel fiRaSecureChannel,
41             @NonNull Callback sessionCallback,
42             @NonNull RunningProfileSessionInfo runningProfileSessionInfo) {
43         super(workLooper, fiRaSecureChannel, sessionCallback, runningProfileSessionInfo);
44     }
45 
46     /**
47      * Process the dispatch response if it is expected.
48      * @return true if the response is expected and processed, false otherwise.
49      */
onDispatchResponseReceived( @onNull DispatchResponse dispatchResponse)50     protected abstract boolean onDispatchResponseReceived(
51             @NonNull DispatchResponse dispatchResponse);
52 
onUnsolicitedDataToHostReceived(@onNull byte[] data)53     protected abstract void onUnsolicitedDataToHostReceived(@NonNull byte[] data);
54 
55     @Override
handleDispatchCommandFailure()56     protected final void handleDispatchCommandFailure() {
57         // can do nothing for responder. ignore it.
58         logw("a dispatch command wasn't handled correctly.");
59     }
60 
61     @Override
handleDispatchResponse(@onNull DispatchResponse dispatchResponse)62     protected final void handleDispatchResponse(@NonNull DispatchResponse dispatchResponse) {
63         if (!dispatchResponse.statusWord.equals(StatusWord.SW_NO_ERROR)) {
64             logw("Wrong DispatchResponse sw: " + dispatchResponse.statusWord);
65             terminateSession();
66             mSessionCallback.onSessionAborted();
67             return;
68         }
69         // once session is aborted, nothing else in the response.
70         for (DispatchResponse.Notification notification : dispatchResponse.notifications) {
71             switch (notification.notificationEventId) {
72                 case NOTIFICATION_EVENT_ID_SECURE_SESSION_ABORTED:
73                     mFiRaSecureChannel.cleanUpTerminatedOrAbortedSession();
74                     mSessionCallback.onSessionAborted();
75                     return;
76                 default:
77                     logw("unhandled notification from dispatch response: "
78                             + notification.notificationEventId);
79                     break;
80             }
81         }
82 
83         Optional<DispatchResponse.OutboundData> outboundData = dispatchResponse.getOutboundData();
84         if (outboundData.isPresent()
85                 && outboundData.get().target == DispatchResponse.OUTBOUND_TARGET_REMOTE) {
86             logd("send response back to remote.");
87             mFiRaSecureChannel.sendRawDataToRemote(outboundData.get().data);
88         }
89         if (onDispatchResponseReceived(dispatchResponse)) {
90             return;
91         }
92 
93         if (outboundData.isPresent()
94                 && outboundData.get().target == DispatchResponse.OUTBOUND_TARGET_HOST) {
95             onUnsolicitedDataToHostReceived(outboundData.get().data);
96         }
97     }
98 
99     @Override
handleFiRaSecureChannelEstablished()100     protected void handleFiRaSecureChannelEstablished() {
101         // do nothing, wait for request from initiator.
102         // TODO: add a time out protection
103     }
104 
105     @Override
terminateSession()106     public final void terminateSession() {
107         mWorkHandler.post(() -> mFiRaSecureChannel.terminateLocally());
108     }
109 
logd(@onNull String dbgMsg)110     private void logd(@NonNull String dbgMsg) {
111         Log.d(LOG_TAG, dbgMsg);
112     }
113 
logw(@onNull String dbgMsg)114     private void logw(@NonNull String dbgMsg) {
115         Log.w(LOG_TAG, dbgMsg);
116     }
117 }
118