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