1 /* 2 * Copyright (C) 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 18 package com.google.snippet.wifi.aware; 19 20 import android.net.MacAddress; 21 import android.net.NetworkCapabilities; 22 import android.net.NetworkRequest; 23 import android.net.wifi.aware.AwarePairingConfig; 24 import android.net.wifi.aware.PeerHandle; 25 import android.net.wifi.aware.PublishConfig; 26 import android.net.wifi.aware.SubscribeConfig; 27 import android.net.wifi.aware.WifiAwareDataPathSecurityConfig; 28 import android.net.wifi.aware.WifiAwareNetworkSpecifier; 29 import android.net.wifi.rtt.RangingRequest; 30 import android.util.Base64; 31 32 import androidx.annotation.NonNull; 33 34 import com.android.modules.utils.build.SdkLevel; 35 36 import org.json.JSONArray; 37 import org.json.JSONException; 38 import org.json.JSONObject; 39 40 import java.nio.charset.StandardCharsets; 41 import java.util.ArrayList; 42 import java.util.List; 43 import java.util.Objects; 44 import java.util.concurrent.ConcurrentHashMap; 45 46 /** 47 * Deserializes JSONObject into data objects defined in Wi-Fi Aware API. 48 */ 49 public class WifiAwareJsonDeserializer { 50 51 private static final String SERVICE_NAME = "service_name"; 52 private static final String SERVICE_SPECIFIC_INFO = "service_specific_info"; 53 private static final String MATCH_FILTER = "match_filter"; 54 private static final String MATCH_FILTER_LIST = "MatchFilterList"; 55 private static final String SUBSCRIBE_TYPE = "subscribe_type"; 56 private static final String TERMINATE_NOTIFICATION_ENABLED = "terminate_notification_enabled"; 57 private static final String MAX_DISTANCE_MM = "max_distance_mm"; 58 private static final String MIN_DISTANCE_MM = "min_distance_mm"; 59 private static final String PAIRING_CONFIG = "pairing_config"; 60 private static final String TTL_SEC = "TtlSec"; 61 private static final String INSTANTMODE_ENABLE = "InstantModeEnabled"; 62 private static final String BAND_5 = "5G"; 63 // PublishConfig special 64 private static final String PUBLISH_TYPE = "publish_type"; 65 private static final String RANGING_ENABLED = "ranging_enabled"; 66 // AwarePairingConfig specific 67 private static final String PAIRING_CACHE_ENABLED = "pairing_cache_enabled"; 68 private static final String PAIRING_SETUP_ENABLED = "pairing_setup_enabled"; 69 private static final String PAIRING_VERIFICATION_ENABLED = "pairing_verification_enabled"; 70 private static final String BOOTSTRAPPING_METHODS = "bootstrapping_methods"; 71 // WifiAwareNetworkSpecifier specific 72 private static final String IS_ACCEPT_ANY = "is_accept_any"; 73 private static final String PMK = "pmk"; 74 private static final String CHANNEL_IN_MHZ = "channel_in_mhz"; 75 private static final String CHANNEL_REQUIRE = "channel_require"; 76 private static final String PSK_PASSPHRASE = "psk_passphrase"; 77 private static final String PORT = "port"; 78 private static final String TRANSPORT_PROTOCOL = "transport_protocol"; 79 private static final String DATA_PATH_SECURITY_CONFIG = "data_path_security_config"; 80 private static final String CHANNEL_FREQUENCY_M_HZ = "channel_frequency_m_hz"; 81 //NetworkRequest specific 82 private static final String TRANSPORT_TYPE = "transport_type"; 83 private static final String CAPABILITY = "capability"; 84 private static final String NETWORK_SPECIFIER_PARCEL = "network_specifier_parcel"; 85 //WifiAwareDataPathSecurityConfig specific 86 private static final String CIPHER_SUITE = "cipher_suite"; 87 private static final String SECURITY_CONFIG_PMK = "pmk"; 88 /** 2.4 GHz band */ 89 public static final int WIFI_BAND_24_GHZ = 1; 90 /** 5 GHz band excluding DFS channels */ 91 public static final int WIFI_BAND_5_GHZ = 1; 92 /** DFS channels from 5 GHz band only */ 93 public static final int WIFI_BAND_5_GHZ_DFS_ONLY = 1; 94 95 // Fields for rangingRequest 96 private static final String RANGING_REQUEST_PEER_IDS = "peer_ids"; 97 private static final String RANGING_REQUEST_PEER_MACS = "peer_mac_addresses"; 98 WifiAwareJsonDeserializer()99 private WifiAwareJsonDeserializer() { 100 } 101 102 /** 103 * Converts Python dict to {@link SubscribeConfig}. 104 * 105 * @param jsonObject corresponding to SubscribeConfig in 106 * tests/hostsidetests/multidevices/test/aware/constants.py 107 */ jsonToSubscribeConfig(JSONObject jsonObject)108 public static SubscribeConfig jsonToSubscribeConfig(JSONObject jsonObject) 109 throws JSONException { 110 SubscribeConfig.Builder builder = new SubscribeConfig.Builder(); 111 if (jsonObject == null) { 112 return builder.build(); 113 } 114 if (jsonObject.has(SERVICE_NAME)) { 115 String serviceName = jsonObject.getString(SERVICE_NAME); 116 builder.setServiceName(serviceName); 117 } 118 if (jsonObject.has(SERVICE_SPECIFIC_INFO)) { 119 byte[] serviceSpecificInfo = 120 jsonObject.getString(SERVICE_SPECIFIC_INFO).getBytes(StandardCharsets.UTF_8); 121 builder.setServiceSpecificInfo(serviceSpecificInfo); 122 } 123 if (jsonObject.has(MATCH_FILTER)) { 124 List<byte[]> matchFilter = new ArrayList<>(); 125 for (int i = 0; i < jsonObject.getJSONArray(MATCH_FILTER).length(); i++) { 126 matchFilter.add(jsonObject.getJSONArray(MATCH_FILTER).getString(i) 127 .getBytes(StandardCharsets.UTF_8)); 128 } 129 builder.setMatchFilter(matchFilter); 130 } 131 if (jsonObject.has(MATCH_FILTER_LIST)) { 132 byte[] bytes = Base64.decode( 133 jsonObject.getString(MATCH_FILTER_LIST).getBytes(StandardCharsets.UTF_8), 134 Base64.DEFAULT); 135 136 List<byte[]> mf = new TlvBufferUtils.TlvIterable(0, 1, bytes).toList(); 137 builder.setMatchFilter(mf); 138 } 139 if (jsonObject.has(SUBSCRIBE_TYPE)) { 140 int subscribeType = jsonObject.getInt(SUBSCRIBE_TYPE); 141 builder.setSubscribeType(subscribeType); 142 } 143 if (jsonObject.has(TERMINATE_NOTIFICATION_ENABLED)) { 144 boolean terminateNotificationEnabled = 145 jsonObject.getBoolean(TERMINATE_NOTIFICATION_ENABLED); 146 builder.setTerminateNotificationEnabled(terminateNotificationEnabled); 147 } 148 if (jsonObject.has(MAX_DISTANCE_MM)) { 149 int maxDistanceMm = jsonObject.getInt(MAX_DISTANCE_MM); 150 if (maxDistanceMm > 0) { 151 builder.setMaxDistanceMm(maxDistanceMm); 152 } 153 } 154 if (jsonObject.has(MIN_DISTANCE_MM)) { 155 int minDistanceMm = jsonObject.getInt(MIN_DISTANCE_MM); 156 if (minDistanceMm >= 0) { 157 builder.setMinDistanceMm(minDistanceMm); 158 } 159 } 160 if (jsonObject.has(PAIRING_CONFIG)) { 161 JSONObject pairingConfigObject = jsonObject.getJSONObject(PAIRING_CONFIG); 162 AwarePairingConfig pairingConfig = jsonToAwarePairingConfig(pairingConfigObject); 163 builder.setPairingConfig(pairingConfig); 164 } 165 if (jsonObject.has(TTL_SEC)) { 166 builder.setTtlSec(jsonObject.getInt(TTL_SEC)); 167 } 168 if (SdkLevel.isAtLeastT() && jsonObject.has(INSTANTMODE_ENABLE)) { 169 builder.setInstantCommunicationModeEnabled(true, 170 Objects.equals(jsonObject.getString(INSTANTMODE_ENABLE), BAND_5) 171 ? WIFI_BAND_5_GHZ :WIFI_BAND_24_GHZ); 172 } 173 return builder.build(); 174 } 175 176 /** 177 * Converts JSONObject to {@link AwarePairingConfig}. 178 * 179 * @param jsonObject corresponding to SubscribeConfig in 180 * tests/hostsidetests/multidevices/test/aware/constants.py 181 */ jsonToAwarePairingConfig(JSONObject jsonObject)182 private static AwarePairingConfig jsonToAwarePairingConfig(JSONObject jsonObject) 183 throws JSONException { 184 AwarePairingConfig.Builder builder = new AwarePairingConfig.Builder(); 185 if (jsonObject == null) { 186 return builder.build(); 187 } 188 if (jsonObject.has(PAIRING_CACHE_ENABLED)) { 189 boolean pairingCacheEnabled = jsonObject.getBoolean(PAIRING_CACHE_ENABLED); 190 builder.setPairingCacheEnabled(pairingCacheEnabled); 191 } 192 if (jsonObject.has(PAIRING_SETUP_ENABLED)) { 193 boolean pairingSetupEnabled = jsonObject.getBoolean(PAIRING_SETUP_ENABLED); 194 builder.setPairingSetupEnabled(pairingSetupEnabled); 195 } 196 if (jsonObject.has(PAIRING_VERIFICATION_ENABLED)) { 197 boolean pairingVerificationEnabled = 198 jsonObject.getBoolean(PAIRING_VERIFICATION_ENABLED); 199 builder.setPairingVerificationEnabled(pairingVerificationEnabled); 200 } 201 if (jsonObject.has(BOOTSTRAPPING_METHODS)) { 202 int bootstrappingMethods = jsonObject.getInt(BOOTSTRAPPING_METHODS); 203 builder.setBootstrappingMethods(bootstrappingMethods); 204 } 205 return builder.build(); 206 } 207 208 /** 209 * Converts Python dict to {@link PublishConfig}. 210 * 211 * @param jsonObject corresponding to PublishConfig in 212 * tests/hostsidetests/multidevices/test/aware/constants.py 213 */ jsonToPublishConfig(JSONObject jsonObject)214 public static PublishConfig jsonToPublishConfig(JSONObject jsonObject) throws JSONException { 215 PublishConfig.Builder builder = new PublishConfig.Builder(); 216 if (jsonObject == null) { 217 return builder.build(); 218 } 219 if (jsonObject.has(SERVICE_NAME)) { 220 String serviceName = jsonObject.getString(SERVICE_NAME); 221 builder.setServiceName(serviceName); 222 } 223 if (jsonObject.has(SERVICE_SPECIFIC_INFO)) { 224 byte[] serviceSpecificInfo = 225 jsonObject.getString(SERVICE_SPECIFIC_INFO).getBytes(StandardCharsets.UTF_8); 226 builder.setServiceSpecificInfo(serviceSpecificInfo); 227 } 228 if (jsonObject.has(MATCH_FILTER)) { 229 List<byte[]> matchFilter = new ArrayList<>(); 230 for (int i = 0; i < jsonObject.getJSONArray(MATCH_FILTER).length(); i++) { 231 matchFilter.add(jsonObject.getJSONArray(MATCH_FILTER).getString(i) 232 .getBytes(StandardCharsets.UTF_8)); 233 } 234 builder.setMatchFilter(matchFilter); 235 } 236 if (jsonObject.has(MATCH_FILTER_LIST)) { 237 byte[] bytes = Base64.decode( 238 jsonObject.getString(MATCH_FILTER_LIST).getBytes(StandardCharsets.UTF_8), 239 Base64.DEFAULT); 240 List<byte[]> mf = new TlvBufferUtils.TlvIterable(0, 1, bytes).toList(); 241 builder.setMatchFilter(mf); 242 } 243 if (jsonObject.has(PUBLISH_TYPE)) { 244 int publishType = jsonObject.getInt(PUBLISH_TYPE); 245 builder.setPublishType(publishType); 246 } 247 if (jsonObject.has(TERMINATE_NOTIFICATION_ENABLED)) { 248 boolean terminateNotificationEnabled = 249 jsonObject.getBoolean(TERMINATE_NOTIFICATION_ENABLED); 250 builder.setTerminateNotificationEnabled(terminateNotificationEnabled); 251 } 252 if (jsonObject.has(RANGING_ENABLED)) { 253 boolean rangingEnabled = jsonObject.getBoolean(RANGING_ENABLED); 254 builder.setRangingEnabled(rangingEnabled); 255 } 256 if (jsonObject.has(PAIRING_CONFIG)) { 257 JSONObject pairingConfigObject = jsonObject.getJSONObject(PAIRING_CONFIG); 258 AwarePairingConfig pairingConfig = jsonToAwarePairingConfig(pairingConfigObject); 259 builder.setPairingConfig(pairingConfig); 260 } 261 if (jsonObject.has(TTL_SEC)) { 262 builder.setTtlSec(jsonObject.getInt(TTL_SEC)); 263 } 264 if (SdkLevel.isAtLeastT() && jsonObject.has(INSTANTMODE_ENABLE)) { 265 builder.setInstantCommunicationModeEnabled(true, 266 Objects.equals(jsonObject.getString(INSTANTMODE_ENABLE), BAND_5) 267 ? WIFI_BAND_5_GHZ :WIFI_BAND_24_GHZ); 268 } 269 return builder.build(); 270 } 271 272 /** 273 * Converts request from JSON object to {@link NetworkRequest}. 274 * 275 * @param jsonObject corresponding to WifiAwareNetworkSpecifier in 276 * tests/hostsidetests/multidevices/test/aware/constants.py 277 */ jsonToNetworkRequest(JSONObject jsonObject)278 public static NetworkRequest jsonToNetworkRequest(JSONObject jsonObject) throws JSONException { 279 NetworkRequest.Builder requestBuilder = new NetworkRequest.Builder(); 280 if (jsonObject == null) { 281 return requestBuilder.build(); 282 } 283 int transportType; 284 if (jsonObject.has(TRANSPORT_TYPE)) { 285 transportType = jsonObject.getInt(TRANSPORT_TYPE); 286 } else { 287 // Returns null for request of unknown type. 288 return null; 289 } 290 if (transportType == NetworkCapabilities.TRANSPORT_WIFI_AWARE) { 291 requestBuilder.addTransportType(transportType); 292 if (jsonObject.has(NETWORK_SPECIFIER_PARCEL)) { 293 String specifierParcelableStr = jsonObject.getString(NETWORK_SPECIFIER_PARCEL); 294 WifiAwareNetworkSpecifier wifiAwareNetworkSpecifier = 295 SerializationUtil.stringToParcelable( 296 specifierParcelableStr, 297 WifiAwareNetworkSpecifier.CREATOR 298 ); 299 // Set the network specifier in the request builder 300 requestBuilder.setNetworkSpecifier(wifiAwareNetworkSpecifier); 301 } 302 if (jsonObject.has(CAPABILITY)) { 303 int capability = jsonObject.getInt(CAPABILITY); 304 requestBuilder.addCapability(capability); 305 } 306 return requestBuilder.build(); 307 } 308 return null; 309 } 310 311 /** 312 * Converts JSON object to {@link WifiAwareNetworkSpecifier}. 313 * 314 * @param jsonObject corresponding to WifiAwareNetworkSpecifier in 315 * @param builder builder to build the WifiAwareNetworkSpecifier 316 * @return WifiAwareNetworkSpecifier object 317 */ jsonToNetworkSpecifier( JSONObject jsonObject, WifiAwareNetworkSpecifier.Builder builder )318 public static WifiAwareNetworkSpecifier jsonToNetworkSpecifier( 319 JSONObject jsonObject, WifiAwareNetworkSpecifier.Builder builder 320 ) throws JSONException { 321 if (jsonObject == null) { 322 return builder.build(); 323 } 324 if (jsonObject.has(PSK_PASSPHRASE)) { 325 String pskPassphrase = jsonObject.getString(PSK_PASSPHRASE); 326 builder.setPskPassphrase(pskPassphrase); 327 } 328 if (jsonObject.has(PORT)) { 329 builder.setPort(jsonObject.getInt(PORT)); 330 } 331 if (jsonObject.has(TRANSPORT_PROTOCOL)) { 332 builder.setTransportProtocol(jsonObject.getInt(TRANSPORT_PROTOCOL)); 333 } 334 if (jsonObject.has(PMK)) { 335 builder.setPmk(jsonObject.getString(PMK).getBytes(StandardCharsets.UTF_8)); 336 } 337 if (jsonObject.has(DATA_PATH_SECURITY_CONFIG)) { 338 builder.setDataPathSecurityConfig(jsonToDataPathSSecurityConfig( 339 jsonObject.getJSONObject(DATA_PATH_SECURITY_CONFIG))); 340 } 341 if (jsonObject.has(CHANNEL_FREQUENCY_M_HZ)) { 342 builder.setChannelFrequencyMhz(jsonObject.getInt(CHANNEL_FREQUENCY_M_HZ), true); 343 } 344 345 return builder.build(); 346 347 } 348 349 /** 350 * Converts request from JSON object to {@link WifiAwareDataPathSecurityConfig}. 351 * 352 * @param jsonObject corresponding to WifiAwareNetworkSpecifier in 353 * tests/hostsidetests/multidevices/test/aware/constants.py 354 */ jsonToDataPathSSecurityConfig( @onNull JSONObject jsonObject )355 private static WifiAwareDataPathSecurityConfig jsonToDataPathSSecurityConfig( 356 @NonNull JSONObject jsonObject 357 ) throws JSONException { 358 WifiAwareDataPathSecurityConfig.Builder builder = null; 359 360 if (jsonObject.has(CIPHER_SUITE)) { 361 int cipherSuite = jsonObject.getInt(CIPHER_SUITE); 362 builder = new WifiAwareDataPathSecurityConfig.Builder(cipherSuite); 363 } else { 364 throw new RuntimeException("Missing 'cipher_suite' in data path security jsonObject " 365 + "config"); 366 } 367 if (jsonObject.has(SECURITY_CONFIG_PMK)) { 368 byte[] pmk = jsonObject.getString(SECURITY_CONFIG_PMK).getBytes(StandardCharsets.UTF_8); 369 builder.setPmk(pmk); 370 } 371 return builder.build(); 372 373 } 374 375 /** 376 * Converts the ranging request from JSONObject to {@link android.net.wifi.rtt.RangingRequest}. 377 * This converts peer IDs in the request to Wi-Fi Aware peer handles in 378 * {@link #mPeerHandles mPeerHandles}. 379 * 380 * @param jsonObject The ranging request in JSONObject type. 381 * @param peerHandles All Wi-Fi Aware peers. 382 * @return The converted ranging request. 383 */ jsonToRangingRequest( @onNull JSONObject jsonObject, ConcurrentHashMap<Integer, PeerHandle> peerHandles )384 public static RangingRequest jsonToRangingRequest( 385 @NonNull JSONObject jsonObject, ConcurrentHashMap<Integer, PeerHandle> peerHandles 386 ) throws JSONException, IllegalArgumentException { 387 RangingRequest.Builder builder = new RangingRequest.Builder(); 388 if (jsonObject.has(RANGING_REQUEST_PEER_IDS)) { 389 JSONArray values = jsonObject.getJSONArray(RANGING_REQUEST_PEER_IDS); 390 for (int i = 0; i < values.length(); i++) { 391 int peerId = values.getInt(i); 392 PeerHandle handle = peerHandles.get(peerId); 393 if (handle == null) { 394 throw new IllegalArgumentException( 395 "Got an invalid peerId. peerId: " + peerId + ", all peer Handles: " 396 + peerHandles 397 ); 398 } 399 builder.addWifiAwarePeer(handle); 400 } 401 } 402 if (jsonObject.has(RANGING_REQUEST_PEER_MACS)) { 403 JSONArray values = jsonObject.getJSONArray(RANGING_REQUEST_PEER_MACS); 404 for (int i = 0; i < values.length(); i++) { 405 String macAddressStr = values.getString(i); 406 MacAddress macAddress = MacAddress.fromString(macAddressStr); 407 builder.addWifiAwarePeer(macAddress); 408 } 409 } 410 return builder.build(); 411 } 412 } 413