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_RDS_AVAILABLE;
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.CsmlUtil;
28 import com.android.server.uwb.secure.csml.DispatchResponse;
29 import com.android.server.uwb.secure.csml.GetDoCommand;
30 import com.android.server.uwb.secure.csml.PutDoCommand;
31 import com.android.server.uwb.secure.csml.SessionData;
32 import com.android.server.uwb.secure.iso7816.TlvDatum;
33 import com.android.server.uwb.secure.iso7816.TlvParser;
34 import com.android.server.uwb.util.DataTypeConversionUtil;
35 
36 import java.util.Optional;
37 
38 /**
39  * The responder of dynamic STS session managed by the UWB controlee.
40  */
41 public class ControleeResponderSession extends ResponderSession {
42     private static final String LOG_TAG = "ControleeResponder";
43 
ControleeResponderSession( @onNull Looper workLooper, @NonNull FiRaSecureChannel fiRaSecureChannel, @NonNull Callback sessionCallback, @NonNull RunningProfileSessionInfo runningProfileSessionInfo)44     public ControleeResponderSession(
45             @NonNull Looper workLooper,
46             @NonNull FiRaSecureChannel fiRaSecureChannel,
47             @NonNull Callback sessionCallback,
48             @NonNull RunningProfileSessionInfo runningProfileSessionInfo) {
49         super(workLooper, fiRaSecureChannel, sessionCallback, runningProfileSessionInfo);
50     }
51 
52     @Override
handleFiRaSecureChannelEstablished()53     protected void handleFiRaSecureChannelEstablished() {
54         super.handleFiRaSecureChannelEstablished();
55 
56         PutDoCommand putControleeInfoCommand = PutDoCommand.build(
57                 CsmlUtil.constructGetOrPutDoTlv(
58                         new TlvDatum(CsmlUtil.CONTROLEE_INFO_DO_TAG,
59                                 mRunningProfileSessionInfo.controleeInfo.get().toBytes())));
60         mFiRaSecureChannel.sendLocalFiRaCommand(putControleeInfoCommand,
61                 new FiRaSecureChannel.ExternalRequestCallback() {
62                     @Override
63                     public void onSuccess(@NonNull byte[] responseData) {
64                         logd("controlee info is sent to applet.");
65                     }
66 
67                     @Override
68                     public void onFailure() {
69                         logw("failed to send controlee info to applet.");
70                         terminateSession();
71                         mSessionCallback.onSessionAborted();
72                     }
73                 });
74     }
75 
76     @Override
onDispatchResponseReceived(@onNull DispatchResponse dispatchResponse)77     protected boolean onDispatchResponseReceived(@NonNull DispatchResponse dispatchResponse) {
78         DispatchResponse.RdsAvailableNotification rdsAvailable = null;
79         for (DispatchResponse.Notification notification : dispatchResponse.notifications) {
80             switch (notification.notificationEventId) {
81                 case NOTIFICATION_EVENT_ID_RDS_AVAILABLE:
82                     // Responder notification
83                     rdsAvailable = (DispatchResponse.RdsAvailableNotification) notification;
84                     break;
85                 default:
86                     logw(
87                             "Unexpected notification from dispatch response: "
88                                     + notification.notificationEventId);
89             }
90         }
91         if (rdsAvailable != null) {
92             if (mIsDefaultUniqueSessionId && mUniqueSessionId.get() != rdsAvailable.sessionId) {
93                 logw("The default session Id is changed, which is not expected.");
94             }
95             mUniqueSessionId = Optional.of(rdsAvailable.sessionId);
96 
97             if (rdsAvailable.arbitraryData.isPresent()
98                     && CsmlUtil.isSessionDataDo(rdsAvailable.arbitraryData.get())) {
99                 logd("SessionData is in RDS available notification.");
100                 mSessionData = SessionData.fromBytes(
101                         TlvParser.parseOneTlv(rdsAvailable.arbitraryData.get()).value);
102                 mSessionCallback.onSessionDataReady(
103                         mUniqueSessionId.get(),
104                         Optional.of(mSessionData),
105                         /* isSessionTerminated= */ false);
106             } else {
107                 logd("try to read SessionData in applet.");
108                 GetDoCommand getSessionDataCommand =
109                         GetDoCommand.build(CsmlUtil.constructSessionDataGetDoTlv());
110                 mFiRaSecureChannel.sendLocalFiRaCommand(getSessionDataCommand,
111                         new FiRaSecureChannel.ExternalRequestCallback() {
112                             @Override
113                             public void onSuccess(byte[] responseData) {
114                                 logd("success to get session data from local FiRa applet.");
115                                 if (!CsmlUtil.isSessionDataDo(responseData)) {
116                                     logw("session data is expected from applet.");
117                                     terminateSession();
118                                     mSessionCallback.onSessionAborted();
119                                     return;
120                                 }
121                                 mSessionData = SessionData.fromBytes(
122                                         TlvParser.parseOneTlv(responseData).value);
123                                 mSessionCallback.onSessionDataReady(
124                                         mUniqueSessionId.get(),
125                                         Optional.of(mSessionData),
126                                         /* isSessionTerminated= */ false);
127                             }
128 
129                             @Override
130                             public void onFailure() {
131                                 logw("failed to get session data from applet.");
132                                 terminateSession();
133                                 mSessionCallback.onSessionAborted();
134                             }
135                         });
136             }
137 
138             return true;
139         }
140         return false;
141     }
142 
143     @Override
onUnsolicitedDataToHostReceived(@onNull byte[] data)144     protected void onUnsolicitedDataToHostReceived(@NonNull byte[] data) {
145         logd("unsolicited data received: " + DataTypeConversionUtil.byteArrayToHexString(data));
146     }
147 
logd(@onNull String dbgMsg)148     private void logd(@NonNull String dbgMsg) {
149         Log.d(LOG_TAG, dbgMsg);
150     }
151 
logw(@onNull String dbgMsg)152     private void logw(@NonNull String dbgMsg) {
153         Log.d(LOG_TAG, dbgMsg);
154     }
155 }
156