1 /* 2 * Copyright (C) 2021 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 package com.android.server.uwb.secure.csml; 17 18 import android.annotation.IntDef; 19 import android.util.Log; 20 21 import androidx.annotation.NonNull; 22 import androidx.annotation.Nullable; 23 import androidx.annotation.VisibleForTesting; 24 25 import com.android.server.uwb.secure.iso7816.ResponseApdu; 26 import com.android.server.uwb.secure.iso7816.TlvDatum; 27 import com.android.server.uwb.secure.iso7816.TlvDatum.Tag; 28 import com.android.server.uwb.secure.iso7816.TlvParser; 29 import com.android.server.uwb.util.DataTypeConversionUtil; 30 import com.android.server.uwb.util.ObjectIdentifier; 31 32 import java.lang.annotation.Retention; 33 import java.lang.annotation.RetentionPolicy; 34 import java.util.ArrayList; 35 import java.util.List; 36 import java.util.Map; 37 import java.util.Optional; 38 39 /** 40 * Response of Dispatch APDU, See CSML 1.0 - 8.2.2.14.2.9 41 */ 42 public class DispatchResponse extends FiRaResponse { 43 private static final String LOG_TAG = "DispatchResponse"; 44 @VisibleForTesting 45 static final Tag STATUS_TAG = new Tag((byte) 0x80); 46 @VisibleForTesting 47 static final Tag DATA_TAG = new Tag((byte) 0x81); 48 @VisibleForTesting 49 static final Tag NOTIFICATION_TAG = new Tag((byte) 0xE1); 50 @VisibleForTesting 51 static final Tag NOTIFICATION_FORMAT_TAG = new Tag((byte) 0x80); 52 @VisibleForTesting 53 static final Tag NOTIFICATION_EVENT_ID_TAG = new Tag((byte) 0x81); 54 @VisibleForTesting 55 static final Tag NOTIFICATION_DATA_TAG = new Tag((byte) 0x82); 56 57 @IntDef(prefix = { "TRANSACTION_STATUS_" }, value = { 58 TRANSACTION_STATUS_UNDEFINED, 59 TRANSACTION_STATUS_COMPLETE, 60 TRANSACTION_STATUS_FORWARD_TO_REMOTE, 61 TRANSACTION_STATUS_FORWARD_TO_HOST, 62 TRANSACTION_STATUS_WITH_ERROR, 63 }) 64 @Retention(RetentionPolicy.SOURCE) 65 private @interface TransactionStatus {} 66 67 private static final int TRANSACTION_STATUS_UNDEFINED = -1; 68 private static final int TRANSACTION_STATUS_COMPLETE = 0; 69 private static final int TRANSACTION_STATUS_FORWARD_TO_REMOTE = 1; 70 private static final int TRANSACTION_STATUS_FORWARD_TO_HOST = 2; 71 private static final int TRANSACTION_STATUS_WITH_ERROR = 3; 72 73 74 @IntDef(prefix = { "NOTIFICATION_EVENT_ID_" }, value = { 75 NOTIFICATION_EVENT_ID_ADF_SELECTED, 76 NOTIFICATION_EVENT_ID_SECURE_CHANNEL_ESTABLISHED, 77 NOTIFICATION_EVENT_ID_RDS_AVAILABLE, 78 NOTIFICATION_EVENT_ID_SECURE_SESSION_ABORTED, 79 NOTIFICATION_EVENT_ID_CONTROLEE_INFO_AVAILABLE, 80 }) 81 @Retention(RetentionPolicy.SOURCE) 82 public @interface NotificationEventId {} 83 84 public static final int NOTIFICATION_EVENT_ID_ADF_SELECTED = 0; 85 public static final int NOTIFICATION_EVENT_ID_SECURE_CHANNEL_ESTABLISHED = 1; 86 public static final int NOTIFICATION_EVENT_ID_RDS_AVAILABLE = 2; 87 public static final int NOTIFICATION_EVENT_ID_SECURE_SESSION_ABORTED = 3; 88 public static final int NOTIFICATION_EVENT_ID_CONTROLEE_INFO_AVAILABLE = 4; 89 90 /** 91 * The base class of notification from the FiRa applet. 92 */ 93 public static class Notification { 94 @NotificationEventId 95 public final int notificationEventId; 96 Notification(@otificationEventId int notificationEventId)97 protected Notification(@NotificationEventId int notificationEventId) { 98 this.notificationEventId = notificationEventId; 99 } 100 } 101 102 /** 103 * The notification of ADF selected. 104 */ 105 public static class AdfSelectedNotification extends Notification { 106 @NonNull 107 public final ObjectIdentifier adfOid; 108 AdfSelectedNotification(@onNull ObjectIdentifier adfOid)109 private AdfSelectedNotification(@NonNull ObjectIdentifier adfOid) { 110 super(NOTIFICATION_EVENT_ID_ADF_SELECTED); 111 112 this.adfOid = adfOid; 113 } 114 } 115 116 /** 117 * The notification of the secure channel established. 118 */ 119 public static class SecureChannelEstablishedNotification extends Notification { 120 public final Optional<Integer> defaultSessionId; 121 SecureChannelEstablishedNotification(Optional<Integer> defaultSessionId)122 private SecureChannelEstablishedNotification(Optional<Integer> defaultSessionId) { 123 super(NOTIFICATION_EVENT_ID_SECURE_CHANNEL_ESTABLISHED); 124 125 this.defaultSessionId = defaultSessionId; 126 } 127 } 128 129 /** 130 * The notification of the secure session aborted for internal error. 131 */ 132 public static class SecureSessionAbortedNotification extends Notification { SecureSessionAbortedNotification()133 private SecureSessionAbortedNotification() { 134 super(NOTIFICATION_EVENT_ID_SECURE_SESSION_ABORTED); 135 } 136 } 137 138 /** 139 * The notification of RDS available to be used. 140 */ 141 public static class RdsAvailableNotification extends Notification { 142 public final int sessionId; 143 144 @NonNull 145 public final Optional<byte[]> arbitraryData; 146 RdsAvailableNotification( int sessionId, @Nullable byte[] arbitraryData)147 private RdsAvailableNotification( 148 int sessionId, @Nullable byte[] arbitraryData) { 149 super(NOTIFICATION_EVENT_ID_RDS_AVAILABLE); 150 this.sessionId = sessionId; 151 if (arbitraryData == null) { 152 this.arbitraryData = Optional.empty(); 153 } else { 154 this.arbitraryData = Optional.of(arbitraryData); 155 } 156 } 157 } 158 159 /** 160 * The notification of the controlee info available. 161 */ 162 public static class ControleeInfoAvailableNotification extends Notification { 163 public final byte[] arbitraryData; 164 ControleeInfoAvailableNotification(@onNull byte[] arbitraryData)165 private ControleeInfoAvailableNotification(@NonNull byte[] arbitraryData) { 166 super(NOTIFICATION_EVENT_ID_CONTROLEE_INFO_AVAILABLE); 167 this.arbitraryData = arbitraryData; 168 } 169 } 170 171 @TransactionStatus 172 private int mTransactionStatus = TRANSACTION_STATUS_UNDEFINED; 173 174 /** 175 * The data should be sent to the peer device or host. 176 */ 177 @NonNull 178 private Optional<OutboundData> mOutboundData = Optional.empty(); 179 getOutboundData()180 public Optional<OutboundData> getOutboundData() { 181 return mOutboundData; 182 } 183 184 /** 185 * The notifications got from the Dispatch response. 186 */ 187 @NonNull 188 public final List<Notification> notifications; 189 DispatchResponse(@onNull ResponseApdu responseApdu)190 private DispatchResponse(@NonNull ResponseApdu responseApdu) { 191 super(responseApdu.getStatusWord()); 192 notifications = new ArrayList<Notification>(); 193 if (!isSuccess()) { 194 return; 195 } 196 Map<Tag, List<TlvDatum>> proprietaryTlvsMap = TlvParser.parseTlvs(responseApdu); 197 List<TlvDatum> proprietaryTlv = proprietaryTlvsMap.get(PROPRIETARY_RESPONSE_TAG); 198 if (proprietaryTlv == null || proprietaryTlv.size() == 0) { 199 logw("no valid dispatch response, root tag is empty."); 200 return; 201 } 202 203 Map<Tag, List<TlvDatum>> tlvsMap = TlvParser.parseTlvs(proprietaryTlv.get(0).value); 204 205 notifications.addAll(parseNotification(tlvsMap.get(NOTIFICATION_TAG))); 206 207 List<TlvDatum> statusTlvs = tlvsMap.get(STATUS_TAG); 208 if (statusTlvs == null || statusTlvs.size() == 0) { 209 logw("no status tag is attached, required by FiRa"); 210 return; 211 } 212 mTransactionStatus = parseTransactionStatus(statusTlvs.get(0).value); 213 switch (mTransactionStatus) { 214 case TRANSACTION_STATUS_WITH_ERROR: 215 notifications.add(new SecureSessionAbortedNotification()); 216 break; 217 case TRANSACTION_STATUS_FORWARD_TO_HOST: 218 // fall through 219 case TRANSACTION_STATUS_FORWARD_TO_REMOTE: 220 List<TlvDatum> dataTlvs = tlvsMap.get(DATA_TAG); 221 if (dataTlvs.size() == 0) { 222 break; 223 } 224 if (mTransactionStatus == TRANSACTION_STATUS_FORWARD_TO_HOST) { 225 mOutboundData = Optional.of( 226 new OutboundData(OUTBOUND_TARGET_HOST, 227 dataTlvs.get(0).value)); 228 } else { 229 mOutboundData = Optional.of( 230 new OutboundData(OUTBOUND_TARGET_REMOTE, 231 dataTlvs.get(0).value)); 232 } 233 break; 234 case TRANSACTION_STATUS_UNDEFINED: 235 // fall through 236 case TRANSACTION_STATUS_COMPLETE: 237 // fall through 238 default: 239 logd("Dispatch response: transaction status: " + mTransactionStatus); 240 break; 241 } 242 } 243 244 @TransactionStatus parseTransactionStatus(@ullable byte[] status)245 private int parseTransactionStatus(@Nullable byte[] status) { 246 if (status == null || status.length < 1) { 247 return TRANSACTION_STATUS_UNDEFINED; 248 } 249 switch (status[0]) { 250 case (byte) 0x00: 251 return TRANSACTION_STATUS_COMPLETE; 252 case (byte) 0x80: 253 return TRANSACTION_STATUS_FORWARD_TO_REMOTE; 254 case (byte) 0x81: 255 return TRANSACTION_STATUS_FORWARD_TO_HOST; 256 case (byte) 0xFF: 257 return TRANSACTION_STATUS_WITH_ERROR; 258 default: 259 return TRANSACTION_STATUS_UNDEFINED; 260 } 261 } 262 263 // throw IllegalStateException 264 @NonNull parseNotification( @ullable List<TlvDatum> notificationTlvs)265 private List<Notification> parseNotification( 266 @Nullable List<TlvDatum> notificationTlvs) { 267 List<Notification> notificationList = new ArrayList<>(); 268 if (notificationTlvs == null || notificationTlvs.size() == 0) { 269 return notificationList; 270 } 271 272 for (TlvDatum tlv : notificationTlvs) { 273 Map<Tag, List<TlvDatum>> curTlvs = TlvParser.parseTlvs(tlv.value); 274 List<TlvDatum> eventIdTlvs = curTlvs.get(NOTIFICATION_EVENT_ID_TAG); 275 if (eventIdTlvs == null || eventIdTlvs.size() == 0) { 276 throw new IllegalStateException("Notification event ID is not available."); 277 } 278 byte[] eventIdValue = eventIdTlvs.get(0).value; 279 if (eventIdValue == null || eventIdValue.length == 0) { 280 throw new IllegalStateException("Notification event ID value is not available."); 281 } 282 switch (eventIdValue[0]) { 283 case (byte) 0x00: 284 // parse OID 285 List<TlvDatum> notificationDataTlvs = curTlvs.get(NOTIFICATION_DATA_TAG); 286 if (notificationDataTlvs == null || notificationDataTlvs.size() == 0) { 287 throw new IllegalStateException("Notification data - OID is not available"); 288 } 289 290 byte[] adfOidBytes = notificationDataTlvs.get(0).value; 291 ObjectIdentifier adfOid = 292 ObjectIdentifier.fromBytes(adfOidBytes); 293 294 notificationList.add(new AdfSelectedNotification(adfOid)); 295 break; 296 case (byte) 0x01: 297 // TODO: not defined by CSML, may be changed. 298 Optional<Integer> defaultSessionId = Optional.empty(); 299 notificationDataTlvs = curTlvs.get(NOTIFICATION_DATA_TAG); 300 if (notificationDataTlvs != null && notificationDataTlvs.size() != 0) { 301 // try to get the default session Id from the notification. 302 byte[] payload = notificationDataTlvs.get(0).value; 303 if (payload == null || payload.length < 2 304 || payload.length < 1 + payload[0]) { 305 logd("not valid session id in sc established notification."); 306 } else { 307 int sessionIdLen = payload[0]; 308 byte[] sessionId = new byte[sessionIdLen]; 309 System.arraycopy(payload, 1, sessionId, 0, sessionIdLen); 310 defaultSessionId = Optional.of( 311 DataTypeConversionUtil.arbitraryByteArrayToI32(sessionId)); 312 } 313 } 314 notificationList.add( 315 new SecureChannelEstablishedNotification(defaultSessionId)); 316 break; 317 case (byte) 0x02: 318 // parse sessionId and arbitrary data 319 notificationDataTlvs = curTlvs.get(NOTIFICATION_DATA_TAG); 320 if (notificationDataTlvs == null || notificationDataTlvs.size() == 0) { 321 throw new IllegalStateException( 322 "RDS Notification data - sessionId is not available"); 323 } 324 byte[] payload = notificationDataTlvs.get(0).value; 325 if (payload == null || payload.length < 2 || payload.length < 1 + payload[0]) { 326 throw new IllegalStateException( 327 "RDS Notification data - bad payload"); 328 } 329 int sessionIdLen = payload[0]; 330 byte[] sessionId = new byte[sessionIdLen]; 331 System.arraycopy(payload, 1, sessionId, 0, sessionIdLen); 332 333 byte[] arbitraryData = new byte[0]; 334 int arbitraryDataOffset = sessionIdLen + 1; 335 if (payload.length > arbitraryDataOffset) { 336 int arbitraryDataLen = payload[arbitraryDataOffset]; 337 if (payload.length == 2 + sessionIdLen + arbitraryDataLen) { 338 arbitraryData = new byte[arbitraryDataLen]; 339 System.arraycopy(payload, arbitraryDataOffset + 1, 340 arbitraryData, 0, arbitraryDataLen); 341 } 342 } 343 344 notificationList.add( 345 new RdsAvailableNotification( 346 DataTypeConversionUtil.arbitraryByteArrayToI32(sessionId), 347 arbitraryData)); 348 break; 349 case (byte) 0x03: 350 // TODO: change it according to the final CSML spec, this is not defined yet. 351 // use 0x03 and controlee info data as notification data. 352 notificationDataTlvs = curTlvs.get(NOTIFICATION_DATA_TAG); 353 arbitraryData = new byte[0]; 354 if (notificationDataTlvs != null && notificationDataTlvs.size() != 0) { 355 payload = notificationDataTlvs.get(0).value; 356 if (payload == null || payload.length == 0) { 357 throw new IllegalStateException( 358 "payload of controlee info available notification is bad."); 359 } 360 arbitraryData = new byte[payload.length]; 361 System.arraycopy(payload, 0, arbitraryData, 0, payload.length); 362 } 363 notificationList.add(new ControleeInfoAvailableNotification(arbitraryData)); 364 break; 365 default: 366 } 367 } 368 369 return notificationList; 370 } 371 372 /** 373 * Parse the response of DispatchCommand. 374 */ 375 @NonNull fromResponseApdu(@onNull ResponseApdu responseApdu)376 public static DispatchResponse fromResponseApdu(@NonNull ResponseApdu responseApdu) { 377 return new DispatchResponse(responseApdu); 378 } 379 380 @IntDef(prefix = { "OUTBOUND_TARGET_" }, value = { 381 OUTBOUND_TARGET_HOST, 382 OUTBOUND_TARGET_REMOTE, 383 }) 384 @Retention(RetentionPolicy.SOURCE) 385 public @interface OutboundTarget {} 386 387 public static final int OUTBOUND_TARGET_HOST = 0; 388 public static final int OUTBOUND_TARGET_REMOTE = 1; 389 390 /** 391 * The outbound data from the DispatchResponse. 392 */ 393 public static class OutboundData { 394 @OutboundTarget 395 public final int target; 396 public final byte[] data; 397 OutboundData(@utboundTarget int target, byte[] data)398 private OutboundData(@OutboundTarget int target, byte[] data) { 399 this.target = target; 400 this.data = data; 401 } 402 } 403 logw(@onNull String dbgMsg)404 private void logw(@NonNull String dbgMsg) { 405 Log.w(LOG_TAG, dbgMsg); 406 } logd(@onNull String dbgMsg)407 private void logd(@NonNull String dbgMsg) { 408 Log.d(LOG_TAG, dbgMsg); 409 } 410 } 411