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 
26 import com.google.common.primitives.Bytes;
27 
28 import java.nio.ByteBuffer;
29 import java.util.Arrays;
30 import java.util.HashMap;
31 import java.util.Map;
32 
33 /**
34  * Holds data of the FiRa UWB Connector Message according to FiRa BLE OOB v1.0 and CSML v1.0
35  * specification.
36  */
37 public class FiraConnectorMessage {
38     private static final String TAG = FiraConnectorMessage.class.getSimpleName();
39 
40     /** Bit position and mask for the message header fields. */
41     private static final int MESSAGE_TYPE_BITPOS = 6;
42 
43     private static final int MESSAGE_TYPE_BITMASK = 0x03;
44     private static final int INSTRUCTION_CODE_BITMASK = 0x3F;
45 
46     /** Type of Fira Connector Message. */
47     public enum MessageType {
48         COMMAND(0),
49         EVENT(1),
50         COMMAND_RESPOND(2);
51         // 3 Reserved for future use.
52 
53         @IntRange(from = 0, to = 2)
54         private final int mValue;
55 
56         private static Map sMap = new HashMap<>();
57 
MessageType(int value)58         MessageType(int value) {
59             this.mValue = value;
60         }
61 
62         static {
63             for (MessageType type : MessageType.values()) {
sMap.put(type.mValue, type)64                 sMap.put(type.mValue, type);
65             }
66         }
67 
68         /**
69          * Get the MessageType based on the given value.
70          *
71          * @param value type value defined by FiRa.
72          * @return {@link MessageType} associated with the value, else null if invalid.
73          */
74         @Nullable
valueOf(int value)75         public static MessageType valueOf(int value) {
76             return (MessageType) sMap.get(value);
77         }
78 
getValue()79         public int getValue() {
80             return mValue;
81         }
82     }
83 
84     @NonNull public final MessageType messageType;
85 
86     /** Fira Connector Message Instruction Code. */
87     public enum InstructionCode {
88         DATA_EXCHANGE(0),
89         ERROR_INDICATION(1);
90         // 2-63 Reserved for future use.
91 
92         @IntRange(from = 0, to = 1)
93         private final int mValue;
94 
95         private static Map sMap = new HashMap<>();
96 
InstructionCode(int value)97         InstructionCode(int value) {
98             this.mValue = value;
99         }
100 
101         static {
102             for (InstructionCode type : InstructionCode.values()) {
sMap.put(type.mValue, type)103                 sMap.put(type.mValue, type);
104             }
105         }
106 
107         /**
108          * Get the InstructionCode based on the given value.
109          *
110          * @param value Instruction code defined by FiRa.
111          * @return {@link InstructionCode} associated with the value, else null if invalid.
112          */
113         @Nullable
valueOf(int value)114         public static InstructionCode valueOf(int value) {
115             return (InstructionCode) sMap.get(value);
116         }
117 
getValue()118         public int getValue() {
119             return mValue;
120         }
121     }
122 
123     @NonNull public final InstructionCode instructionCode;
124 
125     /** Contains actual application data defined by upper layer. */
126     @NonNull public final byte[] payload;
127 
128     /**
129      * Generate the FiraConnectorMessage from raw bytes array.
130      *
131      * @param bytes byte array containing the FiRa UWB Connector Message encoding based on the FiRa
132      *     specification.
133      * @return decode bytes into {@link FiraConnectorMessage}, else null if invalid.
134      */
135     @Nullable
fromBytes(@onNull byte[] bytes)136     public static FiraConnectorMessage fromBytes(@NonNull byte[] bytes) {
137         if (ArrayUtils.isEmpty(bytes)) {
138             Log.w(TAG, "Failed to convert empty into FiRa Connector Message.");
139             return null;
140         }
141 
142         ByteBuffer byteBuffer = ByteBuffer.wrap(bytes);
143         byte header = byteBuffer.get();
144         MessageType messageType =
145                 MessageType.valueOf((header >>> MESSAGE_TYPE_BITPOS) & MESSAGE_TYPE_BITMASK);
146         InstructionCode instructionCode =
147                 InstructionCode.valueOf(header & INSTRUCTION_CODE_BITMASK);
148 
149         byte[] payload = new byte[byteBuffer.remaining()];
150         byteBuffer.get(payload);
151 
152         return new FiraConnectorMessage(messageType, instructionCode, payload);
153     }
154 
155     /**
156      * Generate raw bytes array from FiraConnectorMessage.
157      *
158      * @return encoded bytes into byte array based on the FiRa specification.
159      */
toBytes()160     public byte[] toBytes() {
161         byte[] header =
162                 new byte[] {
163                     (byte)
164                             (((messageType.getValue() & MESSAGE_TYPE_BITMASK)
165                                             << MESSAGE_TYPE_BITPOS)
166                                     | (instructionCode.getValue() & INSTRUCTION_CODE_BITMASK))
167                 };
168         return Bytes.concat(header, payload);
169     }
170 
171     @Override
toString()172     public String toString() {
173         StringBuilder sb = new StringBuilder();
174         sb.append("FiraConnectorMessage: messageType=")
175                 .append(messageType)
176                 .append(" instructionCode=")
177                 .append(instructionCode)
178                 .append(" payload=")
179                 .append(Arrays.toString(payload));
180         return sb.toString();
181     }
182 
FiraConnectorMessage( MessageType messageType, InstructionCode instructionCode, byte[] payload)183     public FiraConnectorMessage(
184             MessageType messageType, InstructionCode instructionCode, byte[] payload) {
185         if (messageType == null) {
186             throw new IllegalArgumentException("messageType is null");
187         }
188         if (instructionCode == null) {
189             throw new IllegalArgumentException("instructionCode is null");
190         }
191         this.messageType = messageType;
192         this.instructionCode = instructionCode;
193         this.payload = payload;
194     }
195 }
196