1 /* 2 * Copyright 2024 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.ranging.oob; 18 19 import com.android.server.ranging.RangingTechnology; 20 import com.android.server.ranging.blerssi.BleRssiOobCapabilities; 21 import com.android.server.ranging.cs.CsOobCapabilities; 22 import com.android.server.ranging.rtt.RttOobCapabilities; 23 import com.android.server.ranging.uwb.UwbOobCapabilities; 24 25 import com.google.auto.value.AutoValue; 26 import com.google.common.base.Preconditions; 27 import com.google.common.collect.ImmutableList; 28 import com.google.common.collect.Sets; 29 30 import java.nio.ByteBuffer; 31 import java.util.Arrays; 32 33 import javax.annotation.Nullable; 34 35 /** The Capability Response Message Additional Data for Finder OOB. */ 36 @AutoValue 37 public abstract class CapabilityResponseMessage { 38 39 // Size of properties in bytes when serialized. 40 private static final int MIN_SIZE_BYTES = 2; 41 42 private static final int RANGING_TECHNOLOGIES_SIZE_BYTES = 2; 43 44 /** 45 * Parses the given byte array and returns {@link CapabilityResponseMessage} object. Throws 46 * {@link 47 * IllegalArgumentException} on invalid input. 48 */ parseBytes(byte[] payload)49 public static CapabilityResponseMessage parseBytes(byte[] payload) { 50 OobHeader header = OobHeader.parseBytes(payload); 51 52 if (header.getMessageType() != MessageType.CAPABILITY_RESPONSE) { 53 throw new IllegalArgumentException( 54 String.format("Invalid message type: %s, expected %s", 55 header.getMessageType(), MessageType.CAPABILITY_RESPONSE)); 56 } 57 58 if (payload.length < header.getSize() + MIN_SIZE_BYTES) { 59 throw new IllegalArgumentException(String.format( 60 "CapabilityResponseMessage payload size is %d bytes", payload.length)); 61 } 62 63 int parseCursor = header.getSize(); 64 65 // Parse ranging technologies bitfield 66 byte[] rangingTechnologiesBytes = 67 Arrays.copyOfRange(payload, parseCursor, 68 parseCursor + RANGING_TECHNOLOGIES_SIZE_BYTES); 69 ImmutableList<RangingTechnology> rangingTechnologies = 70 RangingTechnology.fromBitmap(rangingTechnologiesBytes); 71 parseCursor += RANGING_TECHNOLOGIES_SIZE_BYTES; 72 73 // Parse Capability data for different ranging technologies 74 UwbOobCapabilities uwbCapabilities = null; 75 CsOobCapabilities csCapabilities = null; 76 RttOobCapabilities rttCapabilities = null; 77 BleRssiOobCapabilities bleRssiCapabilities = null; 78 ImmutableList.Builder<RangingTechnology> rangingTechnologiesPriority = 79 ImmutableList.builder(); 80 int countTechsParsed = 0; 81 while (parseCursor < payload.length && countTechsParsed++ < rangingTechnologies.size()) { 82 byte[] remainingBytes = Arrays.copyOfRange(payload, parseCursor, payload.length); 83 TechnologyHeader techHeader = TechnologyHeader.parseBytes(remainingBytes); 84 switch (techHeader.getRangingTechnology()) { 85 case UWB: 86 uwbCapabilities = UwbOobCapabilities.parseBytes(remainingBytes); 87 parseCursor += techHeader.getSize(); 88 rangingTechnologiesPriority.add(RangingTechnology.UWB); 89 break; 90 case CS: 91 csCapabilities = CsOobCapabilities.parseBytes(remainingBytes); 92 parseCursor += techHeader.getSize(); 93 rangingTechnologiesPriority.add(RangingTechnology.CS); 94 break; 95 case RTT: 96 rttCapabilities = RttOobCapabilities.parseBytes(remainingBytes); 97 parseCursor += techHeader.getSize(); 98 rangingTechnologiesPriority.add(RangingTechnology.RTT); 99 break; 100 case RSSI: 101 bleRssiCapabilities = BleRssiOobCapabilities.parseBytes(remainingBytes); 102 parseCursor += techHeader.getSize(); 103 rangingTechnologiesPriority.add(RangingTechnology.RSSI); 104 break; 105 default: 106 rangingTechnologiesPriority.add(techHeader.getRangingTechnology()); 107 parseCursor += techHeader.getSize(); 108 break; 109 } 110 } 111 112 return CapabilityResponseMessage.builder() 113 .setHeader(header) 114 .setSupportedRangingTechnologies(rangingTechnologies) 115 .setUwbCapabilities(uwbCapabilities) 116 .setCsCapabilities(csCapabilities) 117 .setRttCapabilities(rttCapabilities) 118 .setBleRssiCapabilities(bleRssiCapabilities) 119 .setRangingTechnologiesPriority(rangingTechnologiesPriority.build()) 120 .build(); 121 } 122 123 /** Serializes this {@link CapabilityResponseMessage} object to bytes. */ toBytes()124 public final byte[] toBytes() { 125 int size = MIN_SIZE_BYTES + getHeader().getSize(); 126 if (getUwbCapabilities() != null) { 127 size += UwbOobCapabilities.getSize(); 128 } 129 if (getCsCapabilities() != null) { 130 size += CsOobCapabilities.getSize(); 131 } 132 if (getRttCapabilities() != null) { 133 size += getRttCapabilities().toBytes().length; 134 } 135 if (getBleRssiCapabilities() != null) { 136 size += BleRssiOobCapabilities.getSize(); 137 } 138 ByteBuffer byteBuffer = ByteBuffer.allocate(size); 139 byteBuffer 140 .put(getHeader().toBytes()) 141 .put(RangingTechnology.toBitmap(getSupportedRangingTechnologies())); 142 for (RangingTechnology tech : getRangingTechnologiesPriority()) { 143 switch (tech) { 144 case UWB: 145 UwbOobCapabilities uwbCapabilities = getUwbCapabilities(); 146 if (uwbCapabilities != null) { 147 byteBuffer.put(uwbCapabilities.toBytes()); 148 } 149 break; 150 case CS: 151 CsOobCapabilities csCapabilities = getCsCapabilities(); 152 if (csCapabilities != null) { 153 byteBuffer.put(csCapabilities.toBytes()); 154 } 155 break; 156 case RTT: 157 RttOobCapabilities rttCapabilities = getRttCapabilities(); 158 if (rttCapabilities != null) { 159 byteBuffer.put(rttCapabilities.toBytes()); 160 } 161 break; 162 case RSSI: 163 BleRssiOobCapabilities rssiOobCapabilities = getBleRssiCapabilities(); 164 if (rssiOobCapabilities != null) { 165 byteBuffer.put(rssiOobCapabilities.toBytes()); 166 } 167 break; 168 default: 169 throw new UnsupportedOperationException("Not implemented"); 170 } 171 } 172 return byteBuffer.array(); 173 } 174 175 /** Returns the OOB header. */ getHeader()176 public abstract OobHeader getHeader(); 177 178 /** Returns the supported ranging technologies. */ getSupportedRangingTechnologies()179 public abstract ImmutableList<RangingTechnology> getSupportedRangingTechnologies(); 180 181 /** 182 * Returns the priority of requested ranging technologies, with earlier items in the list being 183 * of 184 * higher priority. 185 */ getRangingTechnologiesPriority()186 public abstract ImmutableList<RangingTechnology> getRangingTechnologiesPriority(); 187 188 /** Returns an Optional of UWB capability data. */ 189 @Nullable getUwbCapabilities()190 public abstract UwbOobCapabilities getUwbCapabilities(); 191 192 /** Returns an Optional of CS capability data. */ 193 @Nullable getCsCapabilities()194 public abstract CsOobCapabilities getCsCapabilities(); 195 196 @Nullable getRttCapabilities()197 public abstract RttOobCapabilities getRttCapabilities(); 198 199 @Nullable getBleRssiCapabilities()200 public abstract BleRssiOobCapabilities getBleRssiCapabilities(); 201 202 /** Returns a builder for {@link CapabilityResponseMessage}. */ builder()203 public static Builder builder() { 204 return new AutoValue_CapabilityResponseMessage.Builder() 205 .setRangingTechnologiesPriority(ImmutableList.of()); 206 } 207 208 /** Builder for {@link CapabilityResponseMessage}. */ 209 @AutoValue.Builder 210 public abstract static class Builder { 211 setHeader(OobHeader header)212 public abstract Builder setHeader(OobHeader header); 213 setSupportedRangingTechnologies( ImmutableList<RangingTechnology> rangingTechnologies)214 public abstract Builder setSupportedRangingTechnologies( 215 ImmutableList<RangingTechnology> rangingTechnologies); 216 setUwbCapabilities(@ullable UwbOobCapabilities uwbCapabilities)217 public abstract Builder setUwbCapabilities(@Nullable UwbOobCapabilities uwbCapabilities); 218 setCsCapabilities(@ullable CsOobCapabilities csCapabilities)219 public abstract Builder setCsCapabilities(@Nullable CsOobCapabilities csCapabilities); 220 setRttCapabilities(@ullable RttOobCapabilities rttCapabilities)221 public abstract Builder setRttCapabilities(@Nullable RttOobCapabilities rttCapabilities); 222 setBleRssiCapabilities( @ullable BleRssiOobCapabilities bleRssiCapabilities)223 public abstract Builder setBleRssiCapabilities( 224 @Nullable BleRssiOobCapabilities bleRssiCapabilities); 225 setRangingTechnologiesPriority( ImmutableList<RangingTechnology> rangingTechnologiesPriority )226 public abstract Builder setRangingTechnologiesPriority( 227 ImmutableList<RangingTechnology> rangingTechnologiesPriority 228 ); 229 autoBuild()230 abstract CapabilityResponseMessage autoBuild(); 231 build()232 public final CapabilityResponseMessage build() { 233 CapabilityResponseMessage capabilityResponseMessage = autoBuild(); 234 Preconditions.checkArgument( 235 (capabilityResponseMessage 236 .getRangingTechnologiesPriority() 237 .contains(RangingTechnology.UWB) 238 == (capabilityResponseMessage.getUwbCapabilities() != null)), 239 "Priority list doesn't match UWB capabilities set."); 240 Preconditions.checkArgument( 241 (capabilityResponseMessage.getRangingTechnologiesPriority().contains( 242 RangingTechnology.CS) 243 == (capabilityResponseMessage.getCsCapabilities() != null)), 244 "Priority list doesn't match CS capabilities set."); 245 Preconditions.checkArgument( 246 capabilityResponseMessage.getRangingTechnologiesPriority().size() 247 == Sets.newEnumSet( 248 capabilityResponseMessage.getRangingTechnologiesPriority(), 249 RangingTechnology.class) 250 .size(), 251 "Priority list contains duplicates."); 252 return capabilityResponseMessage; 253 } 254 } 255 } 256