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.uwb; 18 19 import static android.ranging.raw.RawRangingDevice.UPDATE_RATE_FREQUENT; 20 import static android.ranging.raw.RawRangingDevice.UPDATE_RATE_INFREQUENT; 21 import static android.ranging.raw.RawRangingDevice.UPDATE_RATE_NORMAL; 22 23 import static java.nio.charset.StandardCharsets.US_ASCII; 24 25 import android.ranging.RangingDevice; 26 import android.ranging.raw.RawRangingDevice; 27 import android.ranging.uwb.UwbAddress; 28 import android.ranging.uwb.UwbComplexChannel; 29 import android.ranging.uwb.UwbRangingParams; 30 31 import androidx.annotation.NonNull; 32 33 import com.android.ranging.uwb.backend.internal.RangingTimingParams; 34 import com.android.ranging.uwb.backend.internal.Utils; 35 import com.android.server.ranging.RangingTechnology; 36 import com.android.server.ranging.RangingUtils.Conversions; 37 import com.android.server.ranging.oob.SetConfigurationMessage; 38 import com.android.server.ranging.oob.TechnologyHeader; 39 40 import com.google.auto.value.AutoValue; 41 import com.google.common.base.Preconditions; 42 import com.google.common.collect.ImmutableBiMap; 43 44 import java.nio.ByteBuffer; 45 import java.util.Arrays; 46 47 /** Configuration for UWB sent as part SetConfigurationMessage for Finder OOB. */ 48 @AutoValue 49 public abstract class UwbOobConfig implements SetConfigurationMessage.TechnologyOobConfig { 50 51 private static final int MIN_SIZE_BYTES = 19; 52 53 // Size in bytes for properties when serialized. 54 private static final int UWB_ADDRESS_SIZE = 2; 55 private static final int SESSION_ID_SIZE = 4; 56 private static final int CONFIG_ID_SIZE = 1; 57 private static final int CHANNEL_SIZE = 1; 58 private static final int PREAMBLE_INDEX_SIZE = 1; 59 private static final int RANGING_INTERVAL_SIZE = 2; 60 private static final int SLOT_DURATION_SIZE = 1; 61 private static final int SESSION_KEY_LENGTH_SIZE = 1; 62 private static final int STS_SESSION_KEY_SIZE = 8; 63 private static final int PSTS_SHORT_SESSION_KEY_SIZE = 16; 64 private static final int PSTS_LONG_SESSION_KEY_SIZE = 32; 65 private static final int COUNTRY_CODE_SIZE = 2; 66 private static final int DEVICE_ROLE_SIZE = 1; 67 private static final int DEVICE_MODE_SIZE = 1; 68 69 /** Returns the size of the object in bytes when serialized. */ getSize()70 public final int getSize() { 71 return MIN_SIZE_BYTES + getSessionKeyLength(); 72 } 73 74 /** 75 * Parses the given byte array and returns {@link UwbConfig} object. Throws {@link 76 * IllegalArgumentException} on invalid input. 77 */ parseBytes(byte[] uwbConfigBytes)78 public static UwbOobConfig parseBytes(byte[] uwbConfigBytes) { 79 TechnologyHeader header = TechnologyHeader.parseBytes(uwbConfigBytes); 80 81 if (uwbConfigBytes.length < MIN_SIZE_BYTES) { 82 throw new IllegalArgumentException( 83 String.format("UwbConfig size is %d, expected at least %d", 84 uwbConfigBytes.length, MIN_SIZE_BYTES)); 85 } 86 87 if (uwbConfigBytes.length < header.getSize()) { 88 throw new IllegalArgumentException( 89 String.format( 90 "UwbConfig header size field is %d, but the size of the array is %d", 91 header.getSize(), uwbConfigBytes.length)); 92 } 93 94 if (header.getRangingTechnology() != RangingTechnology.UWB) { 95 throw new IllegalArgumentException( 96 String.format( 97 "UwbConfig header technology field is %s, expected %s", 98 header.getRangingTechnology(), RangingTechnology.UWB)); 99 } 100 101 int parseCursor = header.getHeaderSize(); 102 103 // Parse Uwb Address 104 UwbAddress uwbAddress = 105 UwbAddress.fromBytes(Arrays.copyOfRange( 106 uwbConfigBytes, parseCursor, parseCursor + UWB_ADDRESS_SIZE)); 107 parseCursor += UWB_ADDRESS_SIZE; 108 109 // Parse Session Id 110 int sessionId = 111 Conversions.byteArrayToInt(Arrays.copyOfRange( 112 uwbConfigBytes, parseCursor, parseCursor + SESSION_ID_SIZE)); 113 parseCursor += SESSION_ID_SIZE; 114 115 // Parse Config Id 116 int configId = uwbConfigBytes[parseCursor]; 117 parseCursor += CONFIG_ID_SIZE; 118 119 // Parse Channel 120 int channel = uwbConfigBytes[parseCursor]; 121 parseCursor += CHANNEL_SIZE; 122 123 // Parse Preamble Index 124 int preambleIndex = uwbConfigBytes[parseCursor]; 125 parseCursor += PREAMBLE_INDEX_SIZE; 126 127 // Parse Ranging Interval Ms 128 int rangingIntervalMs = 129 Conversions.byteArrayToInt(Arrays.copyOfRange( 130 uwbConfigBytes, parseCursor, parseCursor + RANGING_INTERVAL_SIZE)); 131 parseCursor += RANGING_INTERVAL_SIZE; 132 133 // Parse Slot Duration 134 int slotDurationMs = uwbConfigBytes[parseCursor]; 135 parseCursor += SLOT_DURATION_SIZE; 136 137 // Parse Session Key 138 int sessionKeyLength = uwbConfigBytes[parseCursor]; 139 parseCursor += SESSION_KEY_LENGTH_SIZE; 140 141 if (uwbConfigBytes.length < MIN_SIZE_BYTES + sessionKeyLength) { 142 throw new IllegalArgumentException( 143 "Failed to parse UwbConfig, invalid size. Bytes: " 144 + Arrays.toString(uwbConfigBytes)); 145 } 146 byte[] sessionKey = 147 Arrays.copyOfRange(uwbConfigBytes, parseCursor, parseCursor + sessionKeyLength); 148 parseCursor += sessionKeyLength; 149 150 // Parse Country Code 151 String countryCode = 152 new String(Arrays.copyOfRange( 153 uwbConfigBytes, parseCursor, parseCursor + COUNTRY_CODE_SIZE)); 154 parseCursor += COUNTRY_CODE_SIZE; 155 156 // Parse Device Role 157 int deviceRole = uwbConfigBytes[parseCursor]; 158 parseCursor += DEVICE_ROLE_SIZE; 159 160 // Parse Device Mode 161 int deviceMode = uwbConfigBytes[parseCursor]; 162 parseCursor += DEVICE_MODE_SIZE; 163 164 return builder() 165 .setUwbAddress(uwbAddress) 166 .setSessionId(sessionId) 167 .setSelectedConfigId(configId) 168 .setSelectedChannel(channel) 169 .setSelectedPreambleIndex(preambleIndex) 170 .setSelectedRangingIntervalMs(rangingIntervalMs) 171 .setSelectedSlotDurationMs(slotDurationMs) 172 .setSessionKey(sessionKey) 173 .setCountryCode(countryCode) 174 .setDeviceRole(deviceRole) 175 .setDeviceMode(deviceMode) 176 .build(); 177 } 178 179 /** Serializes this {@link UwbConfig} object to bytes. */ toBytes()180 public final byte[] toBytes() { 181 int size = MIN_SIZE_BYTES + getSessionKeyLength(); 182 return ByteBuffer.allocate(size) 183 .put(RangingTechnology.UWB.toByte()) 184 .put((byte) size) 185 .put(getUwbAddress().getAddressBytes()) 186 .put(Conversions.intToByteArray(getSessionId(), SESSION_ID_SIZE)) 187 .put(Conversions.intToByteArray(getSelectedConfigId(), CONFIG_ID_SIZE)) 188 .put(Conversions.intToByteArray(getSelectedChannel(), CHANNEL_SIZE)) 189 .put(Conversions.intToByteArray(getSelectedPreambleIndex(), PREAMBLE_INDEX_SIZE)) 190 .put(Conversions.intToByteArray( 191 getSelectedRangingIntervalMs(), RANGING_INTERVAL_SIZE)) 192 .put(Conversions.intToByteArray(getSelectedSlotDurationMs(), SLOT_DURATION_SIZE)) 193 .put(Conversions.intToByteArray(getSessionKeyLength(), SESSION_KEY_LENGTH_SIZE)) 194 .put(getSessionKey()) 195 .put(getCountryCode().getBytes(US_ASCII)) 196 .put(Conversions.intToByteArray(getDeviceRole(), DEVICE_ROLE_SIZE)) 197 .put(Conversions.intToByteArray(getDeviceMode(), DEVICE_MODE_SIZE)) 198 .array(); 199 } 200 201 /** 202 * Throws {@link IllegalArgumentException} if the conversion could not be completed successfully 203 */ toTechnologyConfig(UwbAddress localAddress, RangingDevice peer)204 public @NonNull UwbConfig toTechnologyConfig(UwbAddress localAddress, RangingDevice peer) { 205 return new UwbConfig.Builder( 206 new UwbRangingParams.Builder( 207 getSessionId(), getSelectedConfigId(), localAddress, getUwbAddress()) 208 .setSessionKeyInfo(getSessionKey()) 209 .setComplexChannel(new UwbComplexChannel.Builder() 210 .setChannel(getSelectedChannel()) 211 .setPreambleIndex(getSelectedPreambleIndex()) 212 .build()) 213 .setRangingUpdateRate(getUpdateRateFromIntervalMs()) 214 .setSlotDuration(getSelectedSlotDurationMs()) 215 .build()) 216 .setPeerAddresses(ImmutableBiMap.of(peer, getUwbAddress())) 217 .setDeviceRole(getDeviceRole()) 218 .build(); 219 } 220 221 /** 222 * Throws {@link IllegalArgumentException} if the ranging interval does not correspond to an 223 * update rate. 224 */ getUpdateRateFromIntervalMs()225 private @RawRangingDevice.RangingUpdateRate int getUpdateRateFromIntervalMs() { 226 RangingTimingParams timings = Utils.getRangingTimingParams((int) getSelectedConfigId()); 227 228 if (getSelectedRangingIntervalMs() == timings.getRangingIntervalFast()) { 229 return UPDATE_RATE_FREQUENT; 230 } else if (getSelectedRangingIntervalMs() == timings.getRangingIntervalNormal()) { 231 return UPDATE_RATE_NORMAL; 232 } else if (getSelectedRangingIntervalMs() == timings.getRangingIntervalInfrequent()) { 233 return UPDATE_RATE_INFREQUENT; 234 } else { 235 throw new IllegalArgumentException( 236 "Unsupported ranging interval ms " + getSelectedRangingIntervalMs()); 237 } 238 } 239 240 /** Returns {@link UwbAddress} of the device. */ getUwbAddress()241 public abstract UwbAddress getUwbAddress(); 242 243 /** Returns the session Id. */ getSessionId()244 public abstract int getSessionId(); 245 246 /** Returns the selected config Id. */ getSelectedConfigId()247 public abstract int getSelectedConfigId(); 248 249 /** Returns the selected channel. */ getSelectedChannel()250 public abstract int getSelectedChannel(); 251 252 /** Returns the selected preamble index. */ getSelectedPreambleIndex()253 public abstract int getSelectedPreambleIndex(); 254 255 /** Returns the selected ranging interval in ms. */ getSelectedRangingIntervalMs()256 public abstract int getSelectedRangingIntervalMs(); 257 258 /** Returns the selected slot duration in ms. */ getSelectedSlotDurationMs()259 public abstract int getSelectedSlotDurationMs(); 260 261 /** Returns the length of the session key. */ getSessionKeyLength()262 public final int getSessionKeyLength() { 263 return getSessionKey().length; 264 } 265 266 /** 267 * Returns the session key bytes. If S-STS is used then first two bytes are VENDOR ID and 268 * following 6 bytes are STATIC STS IV. If P-STS is used then this is either a 16 byte or 32 269 * byte session key. 270 */ 271 @SuppressWarnings("mutable") getSessionKey()272 public abstract byte[] getSessionKey(); 273 274 /** Returns ISO 3166-1 alpha-2 country code, represented by 2 ascii characters */ getCountryCode()275 public abstract String getCountryCode(); 276 277 /** Returns Device Role. */ getDeviceRole()278 public abstract @OobDeviceRole int getDeviceRole(); 279 280 /** Returns Device Mode. */ getDeviceMode()281 public abstract @OobDeviceMode int getDeviceMode(); 282 283 /** Returns a builder for {@link UwbConfig}. */ builder()284 public static Builder builder() { 285 return new AutoValue_UwbOobConfig.Builder().setSessionKey(new byte[] {}); 286 } 287 288 public @interface OobDeviceMode { 289 int UNKNOWN = 0; 290 int CONTROLLER = 1; 291 int CONTROLEE = 2; 292 } 293 294 public @interface OobDeviceRole { 295 int UNKNOWN = 0; 296 int INITIATOR = 1; 297 int RESPONDER = 2; 298 } 299 300 /** Builder for {@link UwbConfig}. */ 301 @AutoValue.Builder 302 public abstract static class Builder { setUwbAddress(UwbAddress uwbAddress)303 public abstract Builder setUwbAddress(UwbAddress uwbAddress); 304 setSessionId(int sessionId)305 public abstract Builder setSessionId(int sessionId); 306 setSelectedConfigId(int selectedConfigId)307 public abstract Builder setSelectedConfigId(int selectedConfigId); 308 setSelectedChannel(int selectedChannel)309 public abstract Builder setSelectedChannel(int selectedChannel); 310 setSelectedPreambleIndex(int selectedPreambleIndex)311 public abstract Builder setSelectedPreambleIndex(int selectedPreambleIndex); 312 setSelectedRangingIntervalMs(int selectedRangingIntervalMs)313 public abstract Builder setSelectedRangingIntervalMs(int selectedRangingIntervalMs); 314 setSelectedSlotDurationMs(int selectedSlotDurationMs)315 public abstract Builder setSelectedSlotDurationMs(int selectedSlotDurationMs); 316 setSessionKey(byte[] sessionKey)317 public abstract Builder setSessionKey(byte[] sessionKey); 318 setCountryCode(String countryCode)319 public abstract Builder setCountryCode(String countryCode); 320 setDeviceRole(@obDeviceRole int deviceRole)321 public abstract Builder setDeviceRole(@OobDeviceRole int deviceRole); 322 setDeviceMode(@obDeviceMode int deviceMode)323 public abstract Builder setDeviceMode(@OobDeviceMode int deviceMode); 324 autoBuild()325 abstract UwbOobConfig autoBuild(); 326 build()327 public UwbOobConfig build() { 328 UwbOobConfig uwbConfig = autoBuild(); 329 Preconditions.checkNotNull(uwbConfig.getUwbAddress(), "UwbAddress cannot be null"); 330 int sessionKeyLength = uwbConfig.getSessionKeyLength(); 331 Preconditions.checkArgument( 332 sessionKeyLength == STS_SESSION_KEY_SIZE 333 || sessionKeyLength == PSTS_SHORT_SESSION_KEY_SIZE 334 || sessionKeyLength == PSTS_LONG_SESSION_KEY_SIZE, 335 "Invalid session key length"); 336 Preconditions.checkArgument( 337 uwbConfig.getCountryCode().length() == COUNTRY_CODE_SIZE, 338 "Invalid country code length"); 339 return uwbConfig; 340 } 341 } 342 } 343