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 PAIRING_CONFIG = "pairing_config";
59     private static final String TTL_SEC = "TtlSec";
60     private static final String INSTANTMODE_ENABLE = "InstantModeEnabled";
61     private static final String BAND_5 = "5G";
62     // PublishConfig special
63     private static final String PUBLISH_TYPE = "publish_type";
64     private static final String RANGING_ENABLED = "ranging_enabled";
65     // AwarePairingConfig specific
66     private static final String PAIRING_CACHE_ENABLED = "pairing_cache_enabled";
67     private static final String PAIRING_SETUP_ENABLED = "pairing_setup_enabled";
68     private static final String PAIRING_VERIFICATION_ENABLED = "pairing_verification_enabled";
69     private static final String BOOTSTRAPPING_METHODS = "bootstrapping_methods";
70     // WifiAwareNetworkSpecifier specific
71     private static final String IS_ACCEPT_ANY = "is_accept_any";
72     private static final String PMK = "pmk";
73     private static final String CHANNEL_IN_MHZ = "channel_in_mhz";
74     private static final String CHANNEL_REQUIRE = "channel_require";
75     private static final String PSK_PASSPHRASE = "psk_passphrase";
76     private static final String PORT = "port";
77     private static final String TRANSPORT_PROTOCOL = "transport_protocol";
78     private static final String DATA_PATH_SECURITY_CONFIG = "data_path_security_config";
79     private static final String CHANNEL_FREQUENCY_M_HZ = "channel_frequency_m_hz";
80     //NetworkRequest specific
81     private static final String TRANSPORT_TYPE = "transport_type";
82     private static final String CAPABILITY = "capability";
83     private static final String NETWORK_SPECIFIER_PARCEL = "network_specifier_parcel";
84     //WifiAwareDataPathSecurityConfig specific
85     private static final String CIPHER_SUITE = "cipher_suite";
86     private static final String SECURITY_CONFIG_PMK = "pmk";
87     /** 2.4 GHz band */
88     public static final int WIFI_BAND_24_GHZ = 1;
89     /** 5 GHz band excluding DFS channels */
90     public static final int WIFI_BAND_5_GHZ = 1;
91     /** DFS channels from 5 GHz band only */
92     public static final int WIFI_BAND_5_GHZ_DFS_ONLY  = 1;
93 
94     // Fields for rangingRequest
95     private static final String RANGING_REQUEST_PEER_IDS = "peer_ids";
96     private static final String RANGING_REQUEST_PEER_MACS = "peer_mac_addresses";
97 
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(PAIRING_CONFIG)) {
155             JSONObject pairingConfigObject = jsonObject.getJSONObject(PAIRING_CONFIG);
156             AwarePairingConfig pairingConfig = jsonToAwarePairingConfig(pairingConfigObject);
157             builder.setPairingConfig(pairingConfig);
158         }
159         if (jsonObject.has(TTL_SEC)) {
160             builder.setTtlSec(jsonObject.getInt(TTL_SEC));
161         }
162         if (SdkLevel.isAtLeastT() && jsonObject.has(INSTANTMODE_ENABLE)) {
163             builder.setInstantCommunicationModeEnabled(true,
164                     Objects.equals(jsonObject.getString(INSTANTMODE_ENABLE), BAND_5)
165                             ? WIFI_BAND_5_GHZ :WIFI_BAND_24_GHZ);
166         }
167         return builder.build();
168     }
169 
170     /**
171      * Converts JSONObject to {@link AwarePairingConfig}.
172      *
173      * @param jsonObject corresponding to SubscribeConfig in
174      *                   tests/hostsidetests/multidevices/test/aware/constants.py
175      */
jsonToAwarePairingConfig(JSONObject jsonObject)176     private static AwarePairingConfig jsonToAwarePairingConfig(JSONObject jsonObject)
177             throws JSONException {
178         AwarePairingConfig.Builder builder = new AwarePairingConfig.Builder();
179         if (jsonObject == null) {
180             return builder.build();
181         }
182         if (jsonObject.has(PAIRING_CACHE_ENABLED)) {
183             boolean pairingCacheEnabled = jsonObject.getBoolean(PAIRING_CACHE_ENABLED);
184             builder.setPairingCacheEnabled(pairingCacheEnabled);
185         }
186         if (jsonObject.has(PAIRING_SETUP_ENABLED)) {
187             boolean pairingSetupEnabled = jsonObject.getBoolean(PAIRING_SETUP_ENABLED);
188             builder.setPairingSetupEnabled(pairingSetupEnabled);
189         }
190         if (jsonObject.has(PAIRING_VERIFICATION_ENABLED)) {
191             boolean pairingVerificationEnabled =
192                     jsonObject.getBoolean(PAIRING_VERIFICATION_ENABLED);
193             builder.setPairingVerificationEnabled(pairingVerificationEnabled);
194         }
195         if (jsonObject.has(BOOTSTRAPPING_METHODS)) {
196             int bootstrappingMethods = jsonObject.getInt(BOOTSTRAPPING_METHODS);
197             builder.setBootstrappingMethods(bootstrappingMethods);
198         }
199         return builder.build();
200     }
201 
202     /**
203      * Converts Python dict to {@link PublishConfig}.
204      *
205      * @param jsonObject corresponding to PublishConfig in
206      *                   tests/hostsidetests/multidevices/test/aware/constants.py
207      */
jsonToPublishConfig(JSONObject jsonObject)208     public static PublishConfig jsonToPublishConfig(JSONObject jsonObject) throws JSONException {
209         PublishConfig.Builder builder = new PublishConfig.Builder();
210         if (jsonObject == null) {
211             return builder.build();
212         }
213         if (jsonObject.has(SERVICE_NAME)) {
214             String serviceName = jsonObject.getString(SERVICE_NAME);
215             builder.setServiceName(serviceName);
216         }
217         if (jsonObject.has(SERVICE_SPECIFIC_INFO)) {
218             byte[] serviceSpecificInfo =
219                     jsonObject.getString(SERVICE_SPECIFIC_INFO).getBytes(StandardCharsets.UTF_8);
220             builder.setServiceSpecificInfo(serviceSpecificInfo);
221         }
222         if (jsonObject.has(MATCH_FILTER)) {
223             List<byte[]> matchFilter = new ArrayList<>();
224             for (int i = 0; i < jsonObject.getJSONArray(MATCH_FILTER).length(); i++) {
225                 matchFilter.add(jsonObject.getJSONArray(MATCH_FILTER).getString(i)
226                         .getBytes(StandardCharsets.UTF_8));
227             }
228             builder.setMatchFilter(matchFilter);
229         }
230         if (jsonObject.has(MATCH_FILTER_LIST)) {
231             byte[] bytes = Base64.decode(
232                     jsonObject.getString(MATCH_FILTER_LIST).getBytes(StandardCharsets.UTF_8),
233                      Base64.DEFAULT);
234             List<byte[]> mf = new TlvBufferUtils.TlvIterable(0, 1, bytes).toList();
235             builder.setMatchFilter(mf);
236         }
237         if (jsonObject.has(PUBLISH_TYPE)) {
238             int publishType = jsonObject.getInt(PUBLISH_TYPE);
239             builder.setPublishType(publishType);
240         }
241         if (jsonObject.has(TERMINATE_NOTIFICATION_ENABLED)) {
242             boolean terminateNotificationEnabled =
243                     jsonObject.getBoolean(TERMINATE_NOTIFICATION_ENABLED);
244             builder.setTerminateNotificationEnabled(terminateNotificationEnabled);
245         }
246         if (jsonObject.has(RANGING_ENABLED)) {
247             boolean rangingEnabled = jsonObject.getBoolean(RANGING_ENABLED);
248             builder.setRangingEnabled(rangingEnabled);
249         }
250         if (jsonObject.has(PAIRING_CONFIG)) {
251             JSONObject pairingConfigObject = jsonObject.getJSONObject(PAIRING_CONFIG);
252             AwarePairingConfig pairingConfig = jsonToAwarePairingConfig(pairingConfigObject);
253             builder.setPairingConfig(pairingConfig);
254         }
255         if (jsonObject.has(TTL_SEC)) {
256             builder.setTtlSec(jsonObject.getInt(TTL_SEC));
257         }
258         if (SdkLevel.isAtLeastT() && jsonObject.has(INSTANTMODE_ENABLE)) {
259             builder.setInstantCommunicationModeEnabled(true,
260                     Objects.equals(jsonObject.getString(INSTANTMODE_ENABLE), BAND_5)
261                             ? WIFI_BAND_5_GHZ :WIFI_BAND_24_GHZ);
262         }
263         return builder.build();
264     }
265 
266     /**
267      * Converts request from JSON object to {@link NetworkRequest}.
268      *
269      * @param jsonObject corresponding to WifiAwareNetworkSpecifier in
270      *                   tests/hostsidetests/multidevices/test/aware/constants.py
271      */
jsonToNetworkRequest(JSONObject jsonObject)272     public static NetworkRequest jsonToNetworkRequest(JSONObject jsonObject) throws JSONException {
273         NetworkRequest.Builder requestBuilder = new NetworkRequest.Builder();
274         if (jsonObject == null) {
275             return requestBuilder.build();
276         }
277         int transportType;
278         if (jsonObject.has(TRANSPORT_TYPE)) {
279             transportType = jsonObject.getInt(TRANSPORT_TYPE);
280         } else {
281             // Returns null for request of unknown type.
282             return null;
283         }
284         if (transportType == NetworkCapabilities.TRANSPORT_WIFI_AWARE) {
285             requestBuilder.addTransportType(transportType);
286             if (jsonObject.has(NETWORK_SPECIFIER_PARCEL)) {
287                 String specifierParcelableStr = jsonObject.getString(NETWORK_SPECIFIER_PARCEL);
288                 WifiAwareNetworkSpecifier wifiAwareNetworkSpecifier =
289                         SerializationUtil.stringToParcelable(
290                                 specifierParcelableStr,
291                                 WifiAwareNetworkSpecifier.CREATOR
292                         );
293                 // Set the network specifier in the request builder
294                 requestBuilder.setNetworkSpecifier(wifiAwareNetworkSpecifier);
295             }
296             if (jsonObject.has(CAPABILITY)) {
297                 int capability = jsonObject.getInt(CAPABILITY);
298                 requestBuilder.addCapability(capability);
299             }
300             return requestBuilder.build();
301         }
302         return null;
303     }
304 
305     /**
306      * Converts JSON object to {@link WifiAwareNetworkSpecifier}.
307      *
308      * @param jsonObject corresponding to WifiAwareNetworkSpecifier in
309      * @param builder    builder to build the WifiAwareNetworkSpecifier
310      * @return WifiAwareNetworkSpecifier object
311      */
jsonToNetworkSpecifier( JSONObject jsonObject, WifiAwareNetworkSpecifier.Builder builder )312     public static WifiAwareNetworkSpecifier jsonToNetworkSpecifier(
313             JSONObject jsonObject, WifiAwareNetworkSpecifier.Builder builder
314     ) throws JSONException {
315         if (jsonObject == null) {
316             return builder.build();
317         }
318         if (jsonObject.has(PSK_PASSPHRASE)) {
319             String pskPassphrase = jsonObject.getString(PSK_PASSPHRASE);
320             builder.setPskPassphrase(pskPassphrase);
321         }
322         if (jsonObject.has(PORT)) {
323             builder.setPort(jsonObject.getInt(PORT));
324         }
325         if (jsonObject.has(TRANSPORT_PROTOCOL)) {
326             builder.setTransportProtocol(jsonObject.getInt(TRANSPORT_PROTOCOL));
327         }
328         if (jsonObject.has(PMK)) {
329             builder.setPmk(jsonObject.getString(PMK).getBytes(StandardCharsets.UTF_8));
330         }
331         if (jsonObject.has(DATA_PATH_SECURITY_CONFIG)) {
332             builder.setDataPathSecurityConfig(jsonToDataPathSSecurityConfig(
333                     jsonObject.getJSONObject(DATA_PATH_SECURITY_CONFIG)));
334         }
335         if (jsonObject.has(CHANNEL_FREQUENCY_M_HZ)) {
336             builder.setChannelFrequencyMhz(jsonObject.getInt(CHANNEL_FREQUENCY_M_HZ), true);
337         }
338 
339         return builder.build();
340 
341     }
342 
343     /**
344      * Converts request from JSON object to {@link WifiAwareDataPathSecurityConfig}.
345      *
346      * @param jsonObject corresponding to WifiAwareNetworkSpecifier in
347      *                   tests/hostsidetests/multidevices/test/aware/constants.py
348      */
jsonToDataPathSSecurityConfig( @onNull JSONObject jsonObject )349     private static WifiAwareDataPathSecurityConfig jsonToDataPathSSecurityConfig(
350             @NonNull JSONObject jsonObject
351     ) throws JSONException {
352         WifiAwareDataPathSecurityConfig.Builder builder = null;
353 
354         if (jsonObject.has(CIPHER_SUITE)) {
355             int cipherSuite = jsonObject.getInt(CIPHER_SUITE);
356             builder = new WifiAwareDataPathSecurityConfig.Builder(cipherSuite);
357         } else {
358             throw new RuntimeException("Missing 'cipher_suite' in data path security jsonObject "
359                     + "config");
360         }
361         if (jsonObject.has(SECURITY_CONFIG_PMK)) {
362             byte[] pmk = jsonObject.getString(SECURITY_CONFIG_PMK).getBytes(StandardCharsets.UTF_8);
363             builder.setPmk(pmk);
364         }
365         return builder.build();
366 
367     }
368 
369     /**
370      * Converts the ranging request from JSONObject to {@link android.net.wifi.rtt.RangingRequest}.
371      * This converts peer IDs in the request to Wi-Fi Aware peer handles in
372      * {@link #mPeerHandles mPeerHandles}.
373      *
374      * @param jsonObject        The ranging request in JSONObject type.
375      * @param peerHandles       All Wi-Fi Aware peers.
376      * @return The converted ranging request.
377      */
jsonToRangingRequest( @onNull JSONObject jsonObject, ConcurrentHashMap<Integer, PeerHandle> peerHandles )378     public static RangingRequest jsonToRangingRequest(
379             @NonNull JSONObject jsonObject, ConcurrentHashMap<Integer, PeerHandle> peerHandles
380     ) throws JSONException, IllegalArgumentException {
381         RangingRequest.Builder builder = new RangingRequest.Builder();
382         if (jsonObject.has(RANGING_REQUEST_PEER_IDS)) {
383             JSONArray values = jsonObject.getJSONArray(RANGING_REQUEST_PEER_IDS);
384             for (int i = 0; i < values.length(); i++) {
385                 int peerId = values.getInt(i);
386                 PeerHandle handle = peerHandles.get(peerId);
387                 if (handle == null) {
388                     throw new IllegalArgumentException(
389                         "Got an invalid peerId. peerId: " + peerId + ", all peer Handles: "
390                             + peerHandles
391                     );
392                 }
393                 builder.addWifiAwarePeer(handle);
394             }
395         }
396         if (jsonObject.has(RANGING_REQUEST_PEER_MACS)) {
397             JSONArray values = jsonObject.getJSONArray(RANGING_REQUEST_PEER_MACS);
398             for (int i = 0; i < values.length(); i++) {
399                 String macAddressStr = values.getString(i);
400                 MacAddress macAddress = MacAddress.fromString(macAddressStr);
401                 builder.addWifiAwarePeer(macAddress);
402             }
403         }
404         return builder.build();
405     }
406 }
407