1 /*
2  * Copyright (C) 2021 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.google.uwb.support.fira;
18 
19 import static com.google.common.base.Preconditions.checkArgument;
20 
21 import static java.util.Objects.requireNonNull;
22 
23 import android.os.PersistableBundle;
24 import android.uwb.RangingSession;
25 import android.uwb.UwbAddress;
26 
27 import androidx.annotation.Nullable;
28 
29 /**
30  * UWB parameters used to add/remove controlees for a FiRa session
31  *
32  * <p>This is passed as a bundle to the service API {@link RangingSession#addControlee} and
33  * {@link RangingSession#removeControlee}.
34  */
35 public class FiraControleeParams extends FiraParams {
36     private static final int BUNDLE_VERSION_1 = 1;
37     private static final int BUNDLE_VERSION_CURRENT = BUNDLE_VERSION_1;
38 
39     @MulticastListUpdateAction private final int mAction;
40     @Nullable private final UwbAddress[] mAddressList;
41     @Nullable private final int[] mSubSessionIdList;
42     @Nullable private final byte[] mSubSessionKeyList;
43     private static final String KEY_ACTION = "action";
44     private static final String KEY_MAC_ADDRESS_MODE = "mac_address_mode";
45     private static final String KEY_ADDRESS_LIST = "address_list";
46     private static final String KEY_SUB_SESSION_ID_LIST = "sub_session_id_list";
47     private static final String KEY_SUB_SESSION_KEY_LIST = "sub_session_key_list";
48 
FiraControleeParams( @ulticastListUpdateAction int action, @Nullable UwbAddress[] addressList, @Nullable int[] subSessionIdList, @Nullable byte[] subSessionKeyList)49     private FiraControleeParams(
50             @MulticastListUpdateAction int action,
51             @Nullable UwbAddress[] addressList,
52             @Nullable int[] subSessionIdList,
53             @Nullable byte[] subSessionKeyList) {
54         mAction = action;
55         mAddressList = addressList;
56         mSubSessionIdList = subSessionIdList;
57         mSubSessionKeyList = subSessionKeyList;
58     }
59 
60     @Override
getBundleVersion()61     protected int getBundleVersion() {
62         return BUNDLE_VERSION_CURRENT;
63     }
64 
65     @MulticastListUpdateAction
getAction()66     public int getAction() {
67         return mAction;
68     }
69 
70     @Nullable
getAddressList()71     public UwbAddress[] getAddressList() {
72         return mAddressList;
73     }
74 
75     @Nullable
getSubSessionIdList()76     public int[] getSubSessionIdList() {
77         return mSubSessionIdList;
78     }
79 
80     /**
81      * TODO(b/232453347): Needs to accept an array of arrays for supporting multiple controlees.
82      */
83     @Nullable
getSubSessionKeyList()84     public byte[] getSubSessionKeyList() {
85         return mSubSessionKeyList;
86     }
87 
88     @Nullable
byteArrayToIntArray(@ullable byte[] bytes)89     private static int[] byteArrayToIntArray(@Nullable byte[] bytes) {
90         if (bytes == null) {
91             return null;
92         }
93 
94         int[] values = new int[bytes.length];
95         for (int i = 0; i < values.length; i++) {
96             values[i] = bytes[i];
97         }
98         return values;
99     }
100 
101     @Nullable
intArrayToByteArray(@ullable int[] values)102     private static byte[] intArrayToByteArray(@Nullable int[] values) {
103         if (values == null) {
104             return null;
105         }
106         byte[] bytes = new byte[values.length];
107         for (int i = 0; i < values.length; i++) {
108             bytes[i] = (byte) values[i];
109         }
110         return bytes;
111     }
112 
113     @Override
toBundle()114     public PersistableBundle toBundle() {
115         PersistableBundle bundle = super.toBundle();
116         requireNonNull(mAddressList);
117         bundle.putInt(KEY_ACTION, mAction);
118 
119         long[] addressList = new long[mAddressList.length];
120         int i = 0;
121         for (UwbAddress address : mAddressList) {
122             addressList[i++] = uwbAddressToLong(address);
123         }
124         int macAddressMode = MAC_ADDRESS_MODE_2_BYTES;
125         if (mAddressList[0].size() == UwbAddress.EXTENDED_ADDRESS_BYTE_LENGTH) {
126             macAddressMode = MAC_ADDRESS_MODE_8_BYTES;
127         }
128         bundle.putInt(KEY_MAC_ADDRESS_MODE, macAddressMode);
129         bundle.putLongArray(KEY_ADDRESS_LIST, addressList);
130         bundle.putIntArray(KEY_SUB_SESSION_ID_LIST, mSubSessionIdList);
131         bundle.putIntArray(KEY_SUB_SESSION_KEY_LIST, byteArrayToIntArray(mSubSessionKeyList));
132         return bundle;
133     }
134 
fromBundle(PersistableBundle bundle)135     public static FiraControleeParams fromBundle(PersistableBundle bundle) {
136         if (!isCorrectProtocol(bundle)) {
137             throw new IllegalArgumentException("Invalid protocol");
138         }
139 
140         switch (getBundleVersion(bundle)) {
141             case BUNDLE_VERSION_1:
142                 return parseVersion1(bundle);
143 
144             default:
145                 throw new IllegalArgumentException("Invalid bundle version");
146         }
147     }
148 
parseVersion1(PersistableBundle bundle)149     private static FiraControleeParams parseVersion1(PersistableBundle bundle) {
150         FiraControleeParams.Builder builder = new FiraControleeParams.Builder();
151         int action = bundle.getInt(KEY_ACTION, MULTICAST_LIST_UPDATE_ACTION_ADD);
152         int macAddressMode = bundle.getInt(KEY_MAC_ADDRESS_MODE);
153         int addressByteLength = UwbAddress.SHORT_ADDRESS_BYTE_LENGTH;
154         if (macAddressMode == MAC_ADDRESS_MODE_8_BYTES) {
155             addressByteLength = UwbAddress.EXTENDED_ADDRESS_BYTE_LENGTH;
156         }
157         long[] addresses = bundle.getLongArray(KEY_ADDRESS_LIST);
158         UwbAddress[] addressList = new UwbAddress[addresses.length];
159         for (int i = 0; i < addresses.length; i++) {
160             addressList[i] = longToUwbAddress(addresses[i], addressByteLength);
161         }
162         builder.setAction(action);
163         builder.setAddressList(addressList);
164         builder.setSubSessionIdList(bundle.getIntArray(KEY_SUB_SESSION_ID_LIST));
165         builder.setSubSessionKeyList(
166                 intArrayToByteArray(bundle.getIntArray(KEY_SUB_SESSION_KEY_LIST)));
167         return builder.build();
168     }
169 
170     /** Builder */
171     public static class Builder {
172         private int mAction = MULTICAST_LIST_UPDATE_ACTION_ADD;
173         @Nullable private UwbAddress[] mAddressList = null;
174         @Nullable private int[] mSubSessionIdList = null;
175         @Nullable private byte[] mSubSessionKeyList = null;
176 
setAction(@ulticastListUpdateAction int action)177         public FiraControleeParams.Builder setAction(@MulticastListUpdateAction int action) {
178             mAction = action;
179             return this;
180         }
181 
setAddressList(UwbAddress[] addressList)182         public FiraControleeParams.Builder setAddressList(UwbAddress[] addressList) {
183             mAddressList = addressList;
184             return this;
185         }
186 
setSubSessionIdList(int[] subSessionIdList)187         public FiraControleeParams.Builder setSubSessionIdList(int[] subSessionIdList) {
188             mSubSessionIdList = subSessionIdList;
189             return this;
190         }
191 
192         /** Sub Session Key List setter. This is a 2D array of keys represented as 1D array */
setSubSessionKeyList(byte[] subSessionKeyList)193         public FiraControleeParams.Builder setSubSessionKeyList(byte[] subSessionKeyList) {
194             mSubSessionKeyList = subSessionKeyList;
195             return this;
196         }
197 
checkAddressList()198         private void checkAddressList() {
199             checkArgument(mAddressList != null && mAddressList.length > 0);
200             for (UwbAddress uwbAddress : mAddressList) {
201                 requireNonNull(uwbAddress);
202                 checkArgument(uwbAddress.size() == UwbAddress.SHORT_ADDRESS_BYTE_LENGTH);
203             }
204 
205             checkArgument(
206                     mSubSessionIdList == null
207                             || mSubSessionIdList.length == mAddressList.length);
208 
209             // SubSessionKey may not always be present as it can be read from secure component
210             // also.
211             if (mSubSessionKeyList != null) {
212                 if (mAction == P_STS_MULTICAST_LIST_UPDATE_ACTION_ADD_16_BYTE) {
213                     checkArgument(
214                          mSubSessionKeyList.length == 16 * mSubSessionIdList.length);
215                 } else if (mAction == P_STS_MULTICAST_LIST_UPDATE_ACTION_ADD_32_BYTE) {
216                     checkArgument(
217                          mSubSessionKeyList.length == 32 * mSubSessionIdList.length);
218                 }
219             }
220         }
221 
build()222         public FiraControleeParams build() {
223             checkAddressList();
224             return new FiraControleeParams(
225                     mAction,
226                     mAddressList,
227                     mSubSessionIdList,
228                     mSubSessionKeyList);
229         }
230     }
231 }
232