• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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