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_CONTROLEE_INFO_AVAILABLE;
20 import static com.android.server.uwb.secure.csml.DispatchResponse.NOTIFICATION_EVENT_ID_RDS_AVAILABLE;
21 
22 import android.os.Looper;
23 import android.util.Log;
24 
25 import androidx.annotation.NonNull;
26 
27 import com.android.server.uwb.pm.RunningProfileSessionInfo;
28 import com.android.server.uwb.secure.csml.ControleeInfo;
29 import com.android.server.uwb.secure.csml.CsmlUtil;
30 import com.android.server.uwb.secure.csml.DispatchResponse;
31 import com.android.server.uwb.secure.csml.GetDoCommand;
32 import com.android.server.uwb.secure.csml.PutDoCommand;
33 import com.android.server.uwb.secure.iso7816.TlvDatum;
34 import com.android.server.uwb.secure.iso7816.TlvParser;
35 import com.android.server.uwb.util.DataTypeConversionUtil;
36 
37 import java.util.Optional;
38 
39 /**
40  * The responder of dynamic STS session managed by the UWB controller.
41  */
42 public class ControllerResponderSession extends ResponderSession {
43     private static final String LOG_TAG = "ControllerResponder";
44 
ControllerResponderSession( @onNull Looper workLooper, @NonNull FiRaSecureChannel fiRaSecureChannel, @NonNull Callback sessionCallback, @NonNull RunningProfileSessionInfo runningProfileSessionInfo)45     public ControllerResponderSession(
46             @NonNull Looper workLooper,
47             @NonNull FiRaSecureChannel fiRaSecureChannel,
48             @NonNull Callback sessionCallback,
49             @NonNull RunningProfileSessionInfo runningProfileSessionInfo) {
50         super(workLooper, fiRaSecureChannel, sessionCallback, runningProfileSessionInfo);
51         mIsController = true;
52     }
53 
54     @Override
onDispatchResponseReceived(@onNull DispatchResponse dispatchResponse)55     protected boolean onDispatchResponseReceived(@NonNull DispatchResponse dispatchResponse) {
56         DispatchResponse.RdsAvailableNotification rdsAvailable = null;
57         DispatchResponse.ControleeInfoAvailableNotification controleeInfoAvailable = null;
58         for (DispatchResponse.Notification notification : dispatchResponse.notifications) {
59             switch (notification.notificationEventId) {
60                 case NOTIFICATION_EVENT_ID_RDS_AVAILABLE:
61                     // Responder notification
62                     rdsAvailable = (DispatchResponse.RdsAvailableNotification) notification;
63                     break;
64                 case NOTIFICATION_EVENT_ID_CONTROLEE_INFO_AVAILABLE:
65                     controleeInfoAvailable =
66                             (DispatchResponse.ControleeInfoAvailableNotification) notification;
67                     break;
68                 default:
69                     logw(
70                             "Unexpected notification from dispatch response: "
71                                     + notification.notificationEventId);
72             }
73         }
74         if (controleeInfoAvailable != null) {
75             handleControleeInfoAvailable(controleeInfoAvailable);
76             return true;
77         }
78         if (rdsAvailable != null) {
79             handleRdsAvailable(rdsAvailable);
80             return true;
81         }
82         return false;
83     }
84 
handleControleeInfoAvailable( @onNull DispatchResponse.ControleeInfoAvailableNotification controleeInfoAvailable)85     private void handleControleeInfoAvailable(
86             @NonNull DispatchResponse.ControleeInfoAvailableNotification controleeInfoAvailable) {
87         if (CsmlUtil.isControleeInfoDo(controleeInfoAvailable.arbitraryData)) {
88             ControleeInfo controleeInfo =
89                     ControleeInfo.fromBytes(TlvParser.parseOneTlv(
90                             controleeInfoAvailable.arbitraryData).value);
91             generateAndPutSessionDataToApplet(controleeInfo);
92         } else {
93             logd("try to get ControleeInfo from applet.");
94             GetDoCommand getControleeInfoCommand =
95                     GetDoCommand.build(CsmlUtil.constructGetDoTlv(CsmlUtil.CONTROLEE_INFO_DO_TAG));
96             mFiRaSecureChannel.sendLocalFiRaCommand(getControleeInfoCommand,
97                     new FiRaSecureChannel.ExternalRequestCallback() {
98                         @Override
99                         public void onSuccess(@NonNull byte[] responseData) {
100                             ControleeInfo controleeInfo =
101                                     ControleeInfo.fromBytes(
102                                             TlvParser.parseOneTlv(responseData).value);
103                             generateAndPutSessionDataToApplet(controleeInfo);
104                         }
105 
106                         @Override
107                         public void onFailure() {
108                             logw("ControleeInfo is not available in applet.");
109                             terminateSession();
110                             mSessionCallback.onSessionAborted();
111                         }
112                     });
113         }
114     }
115 
generateAndPutSessionDataToApplet(@onNull ControleeInfo controleeInfo)116     private void generateAndPutSessionDataToApplet(@NonNull ControleeInfo controleeInfo) {
117         try {
118             mSessionData = CsmlUtil.generateSessionData(
119                     mRunningProfileSessionInfo.uwbCapability,
120                     controleeInfo,
121                     mRunningProfileSessionInfo.sharedPrimarySessionId,
122                     mRunningProfileSessionInfo.sharedPrimarySessionKeyInfo,
123                     mUniqueSessionId.get(),
124                     !mIsDefaultUniqueSessionId);
125         } catch (IllegalStateException e) {
126             logw("Uwb capability may not be compatible: " + e);
127             terminateSession();
128             mSessionCallback.onSessionAborted();
129         }
130         // put session data
131 
132         PutDoCommand putSessionDataCommand =
133                 PutDoCommand.build(CsmlUtil.constructGetOrPutDoTlv(
134                         new TlvDatum(CsmlUtil.SESSION_DATA_DO_TAG, mSessionData.toBytes())));
135         mFiRaSecureChannel.sendLocalFiRaCommand(putSessionDataCommand,
136                 new FiRaSecureChannel.ExternalRequestCallback() {
137                     @Override
138                     public void onSuccess(@NonNull byte[] responseData) {
139                         // do nothing, wait 'GetSessionData' command from remote.
140                     }
141 
142                     @Override
143                     public void onFailure() {
144                         logw("failed to put session data to applet.");
145                         terminateSession();
146                         mSessionCallback.onSessionAborted();
147                     }
148                 });
149     }
150 
handleRdsAvailable(DispatchResponse.RdsAvailableNotification rdsAvailable)151     private void handleRdsAvailable(DispatchResponse.RdsAvailableNotification rdsAvailable) {
152         if (mSessionData == null) {
153             logw("session data is not available.");
154             terminateSession();
155             mSessionCallback.onSessionAborted();
156             return;
157         }
158         if (mUniqueSessionId.isPresent() && mUniqueSessionId.get() != rdsAvailable.sessionId) {
159             logw("unique session id was present, shouldn't be updated as "
160                     + rdsAvailable.sessionId);
161             mUniqueSessionId = Optional.of(rdsAvailable.sessionId);
162         }
163         mSessionCallback.onSessionDataReady(mUniqueSessionId.get(),
164                 Optional.of(mSessionData), /*isSessionTerminated=*/ false);
165     }
166 
167     @Override
onUnsolicitedDataToHostReceived(@onNull byte[] data)168     protected void onUnsolicitedDataToHostReceived(@NonNull byte[] data) {
169         logd("unsolicited data received: " + DataTypeConversionUtil.byteArrayToHexString(data));
170     }
171 
logd(@onNull String dbgMsg)172     private void logd(@NonNull String dbgMsg) {
173         Log.d(LOG_TAG, dbgMsg);
174     }
175 
logw(@onNull String dbgMsg)176     private void logw(@NonNull String dbgMsg) {
177         Log.d(LOG_TAG, dbgMsg);
178     }
179 }
180