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.discovery.info;
18 
19 import android.annotation.IntRange;
20 import android.annotation.NonNull;
21 import android.annotation.Nullable;
22 import android.util.Log;
23 
24 import com.android.server.uwb.util.ArrayUtils;
25 import com.android.server.uwb.util.DataTypeConversionUtil;
26 
27 import java.util.HashMap;
28 import java.util.Map;
29 
30 /**
31  * Holds data of the FiRa OOB administrative error message according to FiRa BLE OOB v1.0
32  * specification.
33  */
34 public class AdminErrorMessage extends FiraConnectorMessage {
35     private static final String TAG = AdminErrorMessage.class.getSimpleName();
36 
37     /** Type of OOB administrative error defined by FiRa BLE OOB v1.0 . */
38     public enum ErrorType {
39         /**
40          * Sender: CS or CP, Receiver: CP or CS
41          *
42          * <p>Sent when last Data Packet received overflows resources allocated by FiRa Connector at
43          * given moment. When such error indication is received, the FiRa Device should re-transmit
44          * the packet with shorter size (might result in Packet Header change).
45          */
46         DATA_PACKET_LENGTH_OVERFLOW(0x8001),
47         /**
48          * Sender: CS or CP, Receiver: CP or CS
49          *
50          * <p>Sent when last Data Packet received overflows resources allocated by FiRa Connector
51          * for FiRa Connector Message at given moment. When such error indication is received, the
52          * FiRa Device should re-transmit the entire fragmented Message session but with shorter
53          * Message size (might result in several Data Packets).
54          */
55         MESSAGE_LENGTH_OVERFLOW(0x8002),
56         /**
57          * Sender: CS, Receiver: CP
58          *
59          * <p>Sent when last Data Packet received opens new fragmented Message session which exceeds
60          * “Maximum number of concurrent fragmented Message session supported” parameter set in FiRa
61          * Connector Capabilities. When such error indication is received, the FiRa Device should
62          * wait with Message transport until there is at least one Message session completed.
63          */
64         TOO_MANY_CONCURRENT_FRAGMENTED_MESSAGE_SESSIONS(0x8003),
65         /**
66          * Sender: CS, Receiver: CP
67          *
68          * <p>Sent when last Data Packet was referencing SECID which isn’t currently existing on CS
69          * side. When such error indication is received, the CP should re-sent the Data Packet with
70          * correct SECID (e.g. after refreshing FiRa Connector Capabilities) or abort the Message
71          * transfer and report error to FiRa-enabled Application.
72          */
73         SECID_INVALID(0x8004),
74         /**
75          * Sender: CP, Receiver: CS
76          *
77          * <p>Sent when last Data Packet was referencing SECID which isn’t expecting any Response on
78          * FiRa Connector Message level. When such error indication is received, the CS should
79          * re-sent the Data Packet with correct SECID or abort the Message transfer.
80          */
81         SECID_INVALID_FOR_RESPONSE(0x8005),
82         /**
83          * Sender: CS, Receiver: CP
84          *
85          * <p>Sent when last Data Packet was referencing SECID which isn’t currently available for
86          * receiving Messages. When such error indication is received, the CP should re-sent the
87          * Data Packet later or abort the Message transfer and report error to FiRa-enabled
88          * Application.
89          */
90         SECID_BUSY(0x8006),
91         /**
92          * Sender: CS or CP, Receiver: CP or CS
93          *
94          * <p>Sent when last Data Packet was completing Message transfer and that was rejected by
95          * the Secure Component (on CS side) or FiRa-enabled Application (on CP side) due to
96          * protocol inconsistency. When such error indication is received by CS, it should abort the
97          * Message transfer. When such error indication is received by CP, it should abort the
98          * Message transfer and report error to FiRa-enabled Application.
99          */
100         SECID_PROTOCOL_ERROR(0x8007),
101         /**
102          * Sender: CS or CP, Receiver: CP or CS
103          *
104          * <p>Sent when last Data Packet was completing Message transfer and that was rejected by
105          * the Secure Component (on CS side) or FiRa-enabled Application (on CP side) due to
106          * internal processing error (unspecified reason). When such error indication is received by
107          * CS, it should abort the Message transfer. When such error indication is received by CP,
108          * it should abort the Message transfer and report error to FiRa-enabled Application.
109          */
110         SECID_INTERNAL_ERROR(0x8008);
111 
112         @IntRange(from = 0x8001, to = 0x8008)
113         private final int mValue;
114 
115         private static Map sMap = new HashMap<>();
116 
ErrorType(int value)117         ErrorType(int value) {
118             this.mValue = value;
119         }
120 
121         static {
122             for (ErrorType type : ErrorType.values()) {
sMap.put(type.mValue, type)123                 sMap.put(type.mValue, type);
124             }
125         }
126 
127         /**
128          * Get the ErrorType based on the given value.
129          *
130          * @param value type value defined by FiRa.
131          * @return {@link ErrorType} associated with the value, else null if invalid.
132          */
133         @Nullable
valueOf(int value)134         public static ErrorType valueOf(int value) {
135             return (ErrorType) sMap.get(value);
136         }
137 
getValue()138         public int getValue() {
139             return mValue;
140         }
141     }
142 
143     @NonNull public final ErrorType errorType;
144 
145     @Override
toString()146     public String toString() {
147         StringBuilder sb = new StringBuilder();
148         sb.append("AdminErrorMessage: errorType=").append(errorType);
149         return sb.toString();
150     }
151 
152     /**
153      * Convert the FiraConnectorMessage to an AdminErrorMessage if valid.
154      *
155      * @param firaConnectorMessage FiraConnectorMessage
156      * @return AdminErrorMessage if the message is an administrative error message.
157      */
convertToAdminErrorMessage( @onNull FiraConnectorMessage firaConnectorMessage)158     public static AdminErrorMessage convertToAdminErrorMessage(
159             @NonNull FiraConnectorMessage firaConnectorMessage) {
160         if (firaConnectorMessage == null) {
161             throw new IllegalArgumentException("firaConnectorMessage is null");
162         }
163         if (!isAdminErrorMessage(firaConnectorMessage)) {
164             throw new IllegalArgumentException("firaConnectorMessage is not an AdminEventMessage");
165         }
166         return new AdminErrorMessage(extractErrorType(firaConnectorMessage.payload));
167     }
168 
AdminErrorMessage(@onNull ErrorType errorType)169     public AdminErrorMessage(@NonNull ErrorType errorType) {
170         super(
171                 MessageType.COMMAND_RESPOND,
172                 InstructionCode.ERROR_INDICATION,
173                 generatePayload(errorType));
174         this.errorType = errorType;
175     }
176 
177     /**
178      * Check if the message is an administrative error message.
179      *
180      * @param message FiraConnectorMessage
181      * @return true if the message is an administrative error message.
182      */
isAdminErrorMessage(@onNull FiraConnectorMessage message)183     public static boolean isAdminErrorMessage(@NonNull FiraConnectorMessage message) {
184         return (message.messageType == MessageType.COMMAND_RESPOND
185                 && message.instructionCode == InstructionCode.ERROR_INDICATION
186                 && extractErrorType(message.payload) != null);
187     }
188 
generatePayload(@onNull ErrorType errorType)189     private static byte[] generatePayload(@NonNull ErrorType errorType) {
190         if (errorType == null) {
191             throw new IllegalArgumentException("errorType is null");
192         }
193         byte[] errorTypeBytes = DataTypeConversionUtil.i32ToByteArray(errorType.getValue());
194         return new byte[] {errorTypeBytes[2], errorTypeBytes[3]};
195     }
196 
extractErrorType(@onNull byte[] payload)197     private static ErrorType extractErrorType(@NonNull byte[] payload) {
198         if (ArrayUtils.isEmpty(payload)) {
199             Log.e(TAG, "Failed to extract ErrorType from empty payload.");
200             return null;
201         }
202         ErrorType errorType =
203                 ErrorType.valueOf(DataTypeConversionUtil.arbitraryByteArrayToI32(payload));
204         if (errorType == null) {
205             Log.e(TAG, "Failed to extract invalid ErrorType.");
206         }
207         return errorType;
208     }
209 }
210