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 com.google.common.primitives.Bytes;
28 
29 import java.nio.ByteBuffer;
30 import java.util.Arrays;
31 import java.util.HashMap;
32 import java.util.Map;
33 
34 /**
35  * Holds data of the FiRa OOB administrative event message according to FiRa BLE OOB v1.0
36  * specification.
37  */
38 public class AdminEventMessage extends FiraConnectorMessage {
39     private static final String TAG = AdminEventMessage.class.getSimpleName();
40 
41     private static final int EVENT_NUMBER_FIELD_SIZE = 2;
42 
43     /** Type of OOB administrative event. */
44     public enum EventType {
45         /**
46          * Sender: CS, Receiver: CP
47          *
48          * <p>The FiRa Device shell issue this event immediately when any part of FiRa Connector
49          * Capabilities chances. It is applicable only to configuration where CS is implemented on
50          * top of GATT Server.
51          */
52         CAPABILITIES_CHANGED(0x0001);
53 
54         @IntRange(from = 0x0001, to = 0x0001)
55         private final int mValue;
56 
57         private static Map sMap = new HashMap<>();
58 
EventType(int value)59         EventType(int value) {
60             this.mValue = value;
61         }
62 
63         static {
64             for (EventType type : EventType.values()) {
sMap.put(type.mValue, type)65                 sMap.put(type.mValue, type);
66             }
67         }
68 
69         /**
70          * Get the EventType based on the given value.
71          *
72          * @param value type value defined by FiRa.
73          * @return {@link EventType} associated with the value, else null if invalid.
74          */
75         @Nullable
valueOf(int value)76         public static EventType valueOf(int value) {
77             return (EventType) sMap.get(value);
78         }
79 
getValue()80         public int getValue() {
81             return mValue;
82         }
83     }
84 
85     @NonNull public final EventType eventType;
86     @NonNull public final byte[] additionalData;
87 
88     @Override
toString()89     public String toString() {
90         StringBuilder sb = new StringBuilder();
91         sb.append("AdminEventMessage: EventType=")
92                 .append(eventType)
93                 .append(" additionalData=")
94                 .append(Arrays.toString(additionalData));
95         return sb.toString();
96     }
97     /**
98      * Convert the FiraConnectorMessage to an AdminEventMessage if valid.
99      *
100      * @param firaConnectorMessage FiraConnectorMessage
101      * @return AdminEventMessage if the message is an administrative error message.
102      */
convertToAdminEventMessage( @onNull FiraConnectorMessage firaConnectorMessage)103     public static AdminEventMessage convertToAdminEventMessage(
104             @NonNull FiraConnectorMessage firaConnectorMessage) {
105         if (firaConnectorMessage == null) {
106             throw new IllegalArgumentException("firaConnectorMessage is null");
107         }
108         if (!isAdminEventMessage(firaConnectorMessage)) {
109             throw new IllegalArgumentException("firaConnectorMessage is not an AdminEventMessage");
110         }
111         return new AdminEventMessage(
112                 extractEventType(firaConnectorMessage.payload),
113                 extractAdditionalData(firaConnectorMessage.payload));
114     }
115 
AdminEventMessage(@onNull EventType eventType, @NonNull byte[] additionalData)116     public AdminEventMessage(@NonNull EventType eventType, @NonNull byte[] additionalData) {
117         super(
118                 MessageType.EVENT,
119                 InstructionCode.DATA_EXCHANGE,
120                 generatePayload(eventType, additionalData));
121         this.eventType = eventType;
122         this.additionalData = additionalData;
123     }
124 
125     /**
126      * Check if the message is an administrative event message.
127      *
128      * @param message FiraConnectorMessage
129      * @return true if the message is an administrative event message.
130      */
isAdminEventMessage(@onNull FiraConnectorMessage message)131     public static boolean isAdminEventMessage(@NonNull FiraConnectorMessage message) {
132         return (message.messageType == MessageType.EVENT
133                 && message.instructionCode == InstructionCode.DATA_EXCHANGE
134                 && extractEventType(message.payload) != null
135                 && extractAdditionalData(message.payload) != null);
136     }
137 
generatePayload( @onNull EventType eventType, @NonNull byte[] additionalData)138     private static byte[] generatePayload(
139             @NonNull EventType eventType, @NonNull byte[] additionalData) {
140         if (eventType == null) {
141             throw new IllegalArgumentException("eventType is null");
142         }
143         if (additionalData == null) {
144             throw new IllegalArgumentException("additionalData is null");
145         }
146         byte[] eventTypeBytes = DataTypeConversionUtil.i32ToByteArray(eventType.getValue());
147         return Bytes.concat(new byte[] {eventTypeBytes[2], eventTypeBytes[3]}, additionalData);
148     }
149 
extractEventType(@onNull byte[] payload)150     private static EventType extractEventType(@NonNull byte[] payload) {
151         if (ArrayUtils.isEmpty(payload)) {
152             Log.e(TAG, "Failed to extract EventType from empty payload.");
153             return null;
154         }
155         EventType eventType =
156                 EventType.valueOf(DataTypeConversionUtil.arbitraryByteArrayToI32(payload));
157         if (eventType == null) {
158             Log.w(TAG, "Failed to extract invalid EventType.");
159         }
160         return eventType;
161     }
162 
extractAdditionalData(@onNull byte[] payload)163     private static byte[] extractAdditionalData(@NonNull byte[] payload) {
164         if (ArrayUtils.isEmpty(payload)) {
165             Log.e(TAG, "Failed to extract AdditionalData from empty payload.");
166             return null;
167         }
168         ByteBuffer buffer =
169                 ByteBuffer.wrap(
170                         payload, EVENT_NUMBER_FIELD_SIZE, payload.length - EVENT_NUMBER_FIELD_SIZE);
171         byte[] additionalData = new byte[buffer.remaining()];
172         buffer.get(additionalData);
173         return additionalData;
174     }
175 }
176