WifiAwareNativeApi.java revision 9a4b9d97dc46509222e6192596fbdb37a2b8fca6
1/*
2 * Copyright (C) 2016 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
17package com.android.server.wifi.aware;
18
19import android.hardware.wifi.V1_0.IWifiNanIface;
20import android.hardware.wifi.V1_0.NanBandIndex;
21import android.hardware.wifi.V1_0.NanBandSpecificConfig;
22import android.hardware.wifi.V1_0.NanCipherSuiteType;
23import android.hardware.wifi.V1_0.NanConfigRequest;
24import android.hardware.wifi.V1_0.NanDataPathSecurityType;
25import android.hardware.wifi.V1_0.NanEnableRequest;
26import android.hardware.wifi.V1_0.NanInitiateDataPathRequest;
27import android.hardware.wifi.V1_0.NanMatchAlg;
28import android.hardware.wifi.V1_0.NanPublishRequest;
29import android.hardware.wifi.V1_0.NanRespondToDataPathIndicationRequest;
30import android.hardware.wifi.V1_0.NanSubscribeRequest;
31import android.hardware.wifi.V1_0.NanTransmitFollowupRequest;
32import android.hardware.wifi.V1_0.NanTxType;
33import android.hardware.wifi.V1_0.WifiStatus;
34import android.hardware.wifi.V1_0.WifiStatusCode;
35import android.net.wifi.aware.ConfigRequest;
36import android.net.wifi.aware.PublishConfig;
37import android.net.wifi.aware.SubscribeConfig;
38import android.os.RemoteException;
39import android.os.ShellCommand;
40import android.util.Log;
41
42import libcore.util.HexEncoding;
43
44import java.io.FileDescriptor;
45import java.io.PrintWriter;
46import java.nio.charset.StandardCharsets;
47import java.util.ArrayList;
48import java.util.HashMap;
49import java.util.Map;
50
51/**
52 * Translates Wi-Fi Aware requests from the framework to the HAL (HIDL).
53 *
54 * Delegates the management of the NAN interface to WifiAwareNativeManager.
55 */
56public class WifiAwareNativeApi implements WifiAwareShellCommand.DelegatedShellCommand {
57    private static final String TAG = "WifiAwareNativeApi";
58    private static final boolean DBG = false;
59    private static final boolean VDBG = false; // STOPSHIP if true
60
61    private static final String SERVICE_NAME_FOR_OOB_DATA_PATH = "Wi-Fi Aware Data Path";
62
63    private final WifiAwareNativeManager mHal;
64
65    public WifiAwareNativeApi(WifiAwareNativeManager wifiAwareNativeManager) {
66        mHal = wifiAwareNativeManager;
67        onReset();
68    }
69
70    /*
71     * Parameters settable through the shell command.
72     * see wifi/1.0/types.hal NanBandSpecificConfig.discoveryWindowIntervalVal for description
73     */
74    public static final String PARAM_DW_DEFAULT_24GHZ = "dw_default_24ghz";
75    public static final int PARAM_DW_DEFAULT_24GHZ_DEFAULT = -1; // Firmware default
76    public static final String PARAM_DW_DEFAULT_5GHZ = "dw_default_5ghz";
77    public static final int PARAM_DW_DEFAULT_5GHZ_DEFAULT = -1; // Firmware default
78    public static final String PARAM_DW_ON_INACTIVE_24GHZ = "dw_on_inactive_24ghz";
79    public static final int PARAM_DW_ON_INACTIVE_24GHZ_DEFAULT = 4; // 4 -> DW=8, latency=4s
80    public static final String PARAM_DW_ON_INACTIVE_5GHZ = "dw_on_inactive_5ghz";
81    public static final int PARAM_DW_ON_INACTIVE_5GHZ_DEFAULT = 0; // 0 = disabled
82    public static final String PARAM_DW_ON_IDLE_24GHZ = "dw_on_idle_24ghz";
83    public static final int PARAM_DW_ON_IDLE_24GHZ_DEFAULT = -1; // NOP (but disabling on IDLE)
84    public static final String PARAM_DW_ON_IDLE_5GHZ = "dw_on_idle_5ghz";
85    public static final int PARAM_DW_ON_IDLE_5GHZ_DEFAULT = -1; // NOP (but disabling on IDLE)
86
87    public static final String PARAM_MAC_RANDOM_INTERVAL_SEC = "mac_random_interval_sec";
88    public static final int PARAM_MAC_RANDOM_INTERVAL_SEC_DEFAULT = 1800; // 30 minutes
89
90    private Map<String, Integer> mSettableParameters = new HashMap<>();
91
92    /**
93     * Interpreter of adb shell command 'adb shell wifiaware native_api ...'.
94     *
95     * @return -1 if parameter not recognized or invalid value, 0 otherwise.
96     */
97    @Override
98    public int onCommand(ShellCommand parentShell) {
99        final PrintWriter pw = parentShell.getErrPrintWriter();
100
101        String subCmd = parentShell.getNextArgRequired();
102        if (VDBG) Log.v(TAG, "onCommand: subCmd='" + subCmd + "'");
103        switch (subCmd) {
104            case "set": {
105                String name = parentShell.getNextArgRequired();
106                if (VDBG) Log.v(TAG, "onCommand: name='" + name + "'");
107                if (!mSettableParameters.containsKey(name)) {
108                    pw.println("Unknown parameter name -- '" + name + "'");
109                    return -1;
110                }
111
112                String valueStr = parentShell.getNextArgRequired();
113                if (VDBG) Log.v(TAG, "onCommand: valueStr='" + valueStr + "'");
114                int value;
115                try {
116                    value = Integer.valueOf(valueStr);
117                } catch (NumberFormatException e) {
118                    pw.println("Can't convert value to integer -- '" + valueStr + "'");
119                    return -1;
120                }
121                mSettableParameters.put(name, value);
122                return 0;
123            }
124            case "get": {
125                String name = parentShell.getNextArgRequired();
126                if (VDBG) Log.v(TAG, "onCommand: name='" + name + "'");
127                if (!mSettableParameters.containsKey(name)) {
128                    pw.println("Unknown parameter name -- '" + name + "'");
129                    return -1;
130                }
131
132                parentShell.getOutPrintWriter().println((int) mSettableParameters.get(name));
133                return 0;
134            }
135            default:
136                pw.println("Unknown 'wifiaware native_api <cmd>'");
137        }
138
139        return -1;
140    }
141
142    @Override
143    public void onReset() {
144        mSettableParameters.put(PARAM_DW_DEFAULT_24GHZ, PARAM_DW_DEFAULT_24GHZ_DEFAULT);
145        mSettableParameters.put(PARAM_DW_DEFAULT_5GHZ, PARAM_DW_DEFAULT_5GHZ_DEFAULT);
146        mSettableParameters.put(PARAM_DW_ON_INACTIVE_24GHZ, PARAM_DW_ON_INACTIVE_24GHZ_DEFAULT);
147        mSettableParameters.put(PARAM_DW_ON_INACTIVE_5GHZ, PARAM_DW_ON_INACTIVE_5GHZ_DEFAULT);
148        mSettableParameters.put(PARAM_DW_ON_IDLE_24GHZ, PARAM_DW_ON_IDLE_24GHZ_DEFAULT);
149        mSettableParameters.put(PARAM_DW_ON_IDLE_5GHZ, PARAM_DW_ON_IDLE_5GHZ_DEFAULT);
150
151        mSettableParameters.put(PARAM_MAC_RANDOM_INTERVAL_SEC,
152                PARAM_MAC_RANDOM_INTERVAL_SEC_DEFAULT);
153    }
154
155    @Override
156    public void onHelp(String command, ShellCommand parentShell) {
157        final PrintWriter pw = parentShell.getOutPrintWriter();
158
159        pw.println("  " + command);
160        pw.println("    set <name> <value>: sets named parameter to value. Names: "
161                + mSettableParameters.keySet());
162        pw.println("    get <name>: gets named parameter value. Names: "
163                + mSettableParameters.keySet());
164    }
165
166    /**
167     * Query the firmware's capabilities.
168     *
169     * @param transactionId Transaction ID for the transaction - used in the async callback to
170     *                      match with the original request.
171     */
172    public boolean getCapabilities(short transactionId) {
173        if (VDBG) Log.v(TAG, "getCapabilities: transactionId=" + transactionId);
174
175        IWifiNanIface iface = mHal.getWifiNanIface();
176        if (iface == null) {
177            Log.e(TAG, "getCapabilities: null interface");
178            return false;
179        }
180
181        try {
182            WifiStatus status = iface.getCapabilitiesRequest(transactionId);
183            if (status.code == WifiStatusCode.SUCCESS) {
184                return true;
185            } else {
186                Log.e(TAG, "getCapabilities: error: " + statusString(status));
187                return false;
188            }
189        } catch (RemoteException e) {
190            Log.e(TAG, "getCapabilities: exception: " + e);
191            return false;
192        }
193    }
194
195    /**
196     * Enable and configure Aware.
197     *
198     * @param transactionId Transaction ID for the transaction - used in the
199     *            async callback to match with the original request.
200     * @param configRequest Requested Aware configuration.
201     * @param notifyIdentityChange Indicates whether or not to get address change callbacks.
202     * @param initialConfiguration Specifies whether initial configuration
203     *            (true) or an update (false) to the configuration.
204     * @param isInteractive PowerManager.isInteractive
205     * @param isIdle PowerManager.isIdle
206     */
207    public boolean enableAndConfigure(short transactionId, ConfigRequest configRequest,
208            boolean notifyIdentityChange, boolean initialConfiguration, boolean isInteractive,
209            boolean isIdle) {
210        if (VDBG) {
211            Log.v(TAG, "enableAndConfigure: transactionId=" + transactionId + ", configRequest="
212                    + configRequest + ", notifyIdentityChange=" + notifyIdentityChange
213                    + ", initialConfiguration=" + initialConfiguration
214                    + ", isInteractive=" + isInteractive + ", isIdle=" + isIdle);
215        }
216
217        IWifiNanIface iface = mHal.getWifiNanIface();
218        if (iface == null) {
219            Log.e(TAG, "enableAndConfigure: null interface");
220            return false;
221        }
222
223        try {
224            WifiStatus status;
225            if (initialConfiguration) {
226                // translate framework to HIDL configuration
227                NanEnableRequest req = new NanEnableRequest();
228
229                req.operateInBand[NanBandIndex.NAN_BAND_24GHZ] = true;
230                req.operateInBand[NanBandIndex.NAN_BAND_5GHZ] = configRequest.mSupport5gBand;
231                req.hopCountMax = 2;
232                req.configParams.masterPref = (byte) configRequest.mMasterPreference;
233                req.configParams.disableDiscoveryAddressChangeIndication = !notifyIdentityChange;
234                req.configParams.disableStartedClusterIndication = !notifyIdentityChange;
235                req.configParams.disableJoinedClusterIndication = !notifyIdentityChange;
236                req.configParams.includePublishServiceIdsInBeacon = true;
237                req.configParams.numberOfPublishServiceIdsInBeacon = 0;
238                req.configParams.includeSubscribeServiceIdsInBeacon = true;
239                req.configParams.numberOfSubscribeServiceIdsInBeacon = 0;
240                req.configParams.rssiWindowSize = 8;
241                req.configParams.macAddressRandomizationIntervalSec = mSettableParameters.get(
242                        PARAM_MAC_RANDOM_INTERVAL_SEC);
243
244                NanBandSpecificConfig config24 = new NanBandSpecificConfig();
245                config24.rssiClose = 60;
246                config24.rssiMiddle = 70;
247                config24.rssiCloseProximity = 60;
248                config24.dwellTimeMs = (byte) 200;
249                config24.scanPeriodSec = 20;
250                if (configRequest.mDiscoveryWindowInterval[ConfigRequest.NAN_BAND_24GHZ]
251                        == ConfigRequest.DW_INTERVAL_NOT_INIT) {
252                    config24.validDiscoveryWindowIntervalVal = false;
253                } else {
254                    config24.validDiscoveryWindowIntervalVal = true;
255                    config24.discoveryWindowIntervalVal =
256                            (byte) configRequest.mDiscoveryWindowInterval[ConfigRequest
257                                    .NAN_BAND_24GHZ];
258                }
259                req.configParams.bandSpecificConfig[NanBandIndex.NAN_BAND_24GHZ] = config24;
260
261                NanBandSpecificConfig config5 = new NanBandSpecificConfig();
262                config5.rssiClose = 60;
263                config5.rssiMiddle = 75;
264                config5.rssiCloseProximity = 60;
265                config5.dwellTimeMs = (byte) 200;
266                config5.scanPeriodSec = 20;
267                if (configRequest.mDiscoveryWindowInterval[ConfigRequest.NAN_BAND_5GHZ]
268                        == ConfigRequest.DW_INTERVAL_NOT_INIT) {
269                    config5.validDiscoveryWindowIntervalVal = false;
270                } else {
271                    config5.validDiscoveryWindowIntervalVal = true;
272                    config5.discoveryWindowIntervalVal =
273                            (byte) configRequest.mDiscoveryWindowInterval[ConfigRequest
274                                    .NAN_BAND_5GHZ];
275                }
276                req.configParams.bandSpecificConfig[NanBandIndex.NAN_BAND_5GHZ] = config5;
277
278                req.debugConfigs.validClusterIdVals = true;
279                req.debugConfigs.clusterIdTopRangeVal = (short) configRequest.mClusterHigh;
280                req.debugConfigs.clusterIdBottomRangeVal = (short) configRequest.mClusterLow;
281                req.debugConfigs.validIntfAddrVal = false;
282                req.debugConfigs.validOuiVal = false;
283                req.debugConfigs.ouiVal = 0;
284                req.debugConfigs.validRandomFactorForceVal = false;
285                req.debugConfigs.randomFactorForceVal = 0;
286                req.debugConfigs.validHopCountForceVal = false;
287                req.debugConfigs.hopCountForceVal = 0;
288                req.debugConfigs.validDiscoveryChannelVal = false;
289                req.debugConfigs.discoveryChannelMhzVal[NanBandIndex.NAN_BAND_24GHZ] = 0;
290                req.debugConfigs.discoveryChannelMhzVal[NanBandIndex.NAN_BAND_5GHZ] = 0;
291                req.debugConfigs.validUseBeaconsInBandVal = false;
292                req.debugConfigs.useBeaconsInBandVal[NanBandIndex.NAN_BAND_24GHZ] = true;
293                req.debugConfigs.useBeaconsInBandVal[NanBandIndex.NAN_BAND_5GHZ] = true;
294                req.debugConfigs.validUseSdfInBandVal = false;
295                req.debugConfigs.useSdfInBandVal[NanBandIndex.NAN_BAND_24GHZ] = true;
296                req.debugConfigs.useSdfInBandVal[NanBandIndex.NAN_BAND_5GHZ] = true;
297
298                updateConfigForPowerSettings(req.configParams, isInteractive, isIdle);
299
300                status = iface.enableRequest(transactionId, req);
301            } else {
302                NanConfigRequest req = new NanConfigRequest();
303                req.masterPref = (byte) configRequest.mMasterPreference;
304                req.disableDiscoveryAddressChangeIndication = !notifyIdentityChange;
305                req.disableStartedClusterIndication = !notifyIdentityChange;
306                req.disableJoinedClusterIndication = !notifyIdentityChange;
307                req.includePublishServiceIdsInBeacon = true;
308                req.numberOfPublishServiceIdsInBeacon = 0;
309                req.includeSubscribeServiceIdsInBeacon = true;
310                req.numberOfSubscribeServiceIdsInBeacon = 0;
311                req.rssiWindowSize = 8;
312                req.macAddressRandomizationIntervalSec = mSettableParameters.get(
313                        PARAM_MAC_RANDOM_INTERVAL_SEC);
314
315                NanBandSpecificConfig config24 = new NanBandSpecificConfig();
316                config24.rssiClose = 60;
317                config24.rssiMiddle = 70;
318                config24.rssiCloseProximity = 60;
319                config24.dwellTimeMs = (byte) 200;
320                config24.scanPeriodSec = 20;
321                if (configRequest.mDiscoveryWindowInterval[ConfigRequest.NAN_BAND_24GHZ]
322                        == ConfigRequest.DW_INTERVAL_NOT_INIT) {
323                    config24.validDiscoveryWindowIntervalVal = false;
324                } else {
325                    config24.validDiscoveryWindowIntervalVal = true;
326                    config24.discoveryWindowIntervalVal =
327                            (byte) configRequest.mDiscoveryWindowInterval[ConfigRequest
328                                    .NAN_BAND_24GHZ];
329                }
330                req.bandSpecificConfig[NanBandIndex.NAN_BAND_24GHZ] = config24;
331
332                NanBandSpecificConfig config5 = new NanBandSpecificConfig();
333                config5.rssiClose = 60;
334                config5.rssiMiddle = 75;
335                config5.rssiCloseProximity = 60;
336                config5.dwellTimeMs = (byte) 200;
337                config5.scanPeriodSec = 20;
338                if (configRequest.mDiscoveryWindowInterval[ConfigRequest.NAN_BAND_5GHZ]
339                        == ConfigRequest.DW_INTERVAL_NOT_INIT) {
340                    config5.validDiscoveryWindowIntervalVal = false;
341                } else {
342                    config5.validDiscoveryWindowIntervalVal = true;
343                    config5.discoveryWindowIntervalVal =
344                            (byte) configRequest.mDiscoveryWindowInterval[ConfigRequest
345                                    .NAN_BAND_5GHZ];
346                }
347                req.bandSpecificConfig[NanBandIndex.NAN_BAND_5GHZ] = config5;
348
349                updateConfigForPowerSettings(req, isInteractive, isIdle);
350
351                status = iface.configRequest(transactionId, req);
352            }
353            if (status.code == WifiStatusCode.SUCCESS) {
354                return true;
355            } else {
356                Log.e(TAG, "enableAndConfigure: error: " + statusString(status));
357                return false;
358            }
359        } catch (RemoteException e) {
360            Log.e(TAG, "enableAndConfigure: exception: " + e);
361            return false;
362        }
363    }
364
365    /**
366     * Disable Aware.
367     *
368     * @param transactionId transactionId Transaction ID for the transaction -
369     *            used in the async callback to match with the original request.
370     */
371    public boolean disable(short transactionId) {
372        if (VDBG) Log.d(TAG, "disable");
373
374        IWifiNanIface iface = mHal.getWifiNanIface();
375        if (iface == null) {
376            Log.e(TAG, "disable: null interface");
377            return false;
378        }
379
380        try {
381            WifiStatus status = iface.disableRequest(transactionId);
382            if (status.code == WifiStatusCode.SUCCESS) {
383                return true;
384            } else {
385                Log.e(TAG, "disable: error: " + statusString(status));
386                return false;
387            }
388        } catch (RemoteException e) {
389            Log.e(TAG, "disable: exception: " + e);
390            return false;
391        }
392    }
393
394    /**
395     * Start or modify a service publish session.
396     *
397     * @param transactionId transactionId Transaction ID for the transaction -
398     *            used in the async callback to match with the original request.
399     * @param publishId ID of the requested session - 0 to request a new publish
400     *            session.
401     * @param publishConfig Configuration of the discovery session.
402     */
403    public boolean publish(short transactionId, byte publishId, PublishConfig publishConfig) {
404        if (VDBG) {
405            Log.d(TAG, "publish: transactionId=" + transactionId + ", publishId=" + publishId
406                    + ", config=" + publishConfig);
407        }
408
409        IWifiNanIface iface = mHal.getWifiNanIface();
410        if (iface == null) {
411            Log.e(TAG, "publish: null interface");
412            return false;
413        }
414
415        NanPublishRequest req = new NanPublishRequest();
416        req.baseConfigs.sessionId = publishId;
417        req.baseConfigs.ttlSec = (short) publishConfig.mTtlSec;
418        req.baseConfigs.discoveryWindowPeriod = 1;
419        req.baseConfigs.discoveryCount = 0;
420        convertNativeByteArrayToArrayList(publishConfig.mServiceName, req.baseConfigs.serviceName);
421        req.baseConfigs.discoveryMatchIndicator = NanMatchAlg.MATCH_NEVER;
422        convertNativeByteArrayToArrayList(publishConfig.mServiceSpecificInfo,
423                req.baseConfigs.serviceSpecificInfo);
424        convertNativeByteArrayToArrayList(publishConfig.mMatchFilter,
425                publishConfig.mPublishType == PublishConfig.PUBLISH_TYPE_UNSOLICITED
426                        ? req.baseConfigs.txMatchFilter : req.baseConfigs.rxMatchFilter);
427        req.baseConfigs.useRssiThreshold = false;
428        req.baseConfigs.disableDiscoveryTerminationIndication =
429                !publishConfig.mEnableTerminateNotification;
430        req.baseConfigs.disableMatchExpirationIndication = true;
431        req.baseConfigs.disableFollowupReceivedIndication = false;
432
433        // TODO: configure ranging and security
434        req.baseConfigs.securityConfig.securityType = NanDataPathSecurityType.OPEN;
435        req.baseConfigs.rangingRequired = false;
436        req.autoAcceptDataPathRequests = false;
437
438        req.publishType = publishConfig.mPublishType;
439        req.txType = NanTxType.BROADCAST;
440
441        try {
442            WifiStatus status = iface.startPublishRequest(transactionId, req);
443            if (status.code == WifiStatusCode.SUCCESS) {
444                return true;
445            } else {
446                Log.e(TAG, "publish: error: " + statusString(status));
447                return false;
448            }
449        } catch (RemoteException e) {
450            Log.e(TAG, "publish: exception: " + e);
451            return false;
452        }
453    }
454
455    /**
456     * Start or modify a service subscription session.
457     *
458     * @param transactionId transactionId Transaction ID for the transaction -
459     *            used in the async callback to match with the original request.
460     * @param subscribeId ID of the requested session - 0 to request a new
461     *            subscribe session.
462     * @param subscribeConfig Configuration of the discovery session.
463     */
464    public boolean subscribe(short transactionId, byte subscribeId,
465            SubscribeConfig subscribeConfig) {
466        if (VDBG) {
467            Log.d(TAG, "subscribe: transactionId=" + transactionId + ", subscribeId=" + subscribeId
468                    + ", config=" + subscribeConfig);
469        }
470
471        IWifiNanIface iface = mHal.getWifiNanIface();
472        if (iface == null) {
473            Log.e(TAG, "subscribe: null interface");
474            return false;
475        }
476
477        NanSubscribeRequest req = new NanSubscribeRequest();
478        req.baseConfigs.sessionId = subscribeId;
479        req.baseConfigs.ttlSec = (short) subscribeConfig.mTtlSec;
480        req.baseConfigs.discoveryWindowPeriod = 1;
481        req.baseConfigs.discoveryCount = 0;
482        convertNativeByteArrayToArrayList(subscribeConfig.mServiceName,
483                req.baseConfigs.serviceName);
484        req.baseConfigs.discoveryMatchIndicator = NanMatchAlg.MATCH_ONCE;
485        convertNativeByteArrayToArrayList(subscribeConfig.mServiceSpecificInfo,
486                req.baseConfigs.serviceSpecificInfo);
487        convertNativeByteArrayToArrayList(subscribeConfig.mMatchFilter,
488                subscribeConfig.mSubscribeType == SubscribeConfig.SUBSCRIBE_TYPE_ACTIVE
489                        ? req.baseConfigs.txMatchFilter : req.baseConfigs.rxMatchFilter);
490        req.baseConfigs.useRssiThreshold = false;
491        req.baseConfigs.disableDiscoveryTerminationIndication =
492                !subscribeConfig.mEnableTerminateNotification;
493        req.baseConfigs.disableMatchExpirationIndication = true;
494        req.baseConfigs.disableFollowupReceivedIndication = false;
495
496        // TODO: configure ranging and security
497        req.baseConfigs.securityConfig.securityType = NanDataPathSecurityType.OPEN;
498        req.baseConfigs.rangingRequired = false;
499
500        req.subscribeType = subscribeConfig.mSubscribeType;
501
502        try {
503            WifiStatus status = iface.startSubscribeRequest(transactionId, req);
504            if (status.code == WifiStatusCode.SUCCESS) {
505                return true;
506            } else {
507                Log.e(TAG, "subscribe: error: " + statusString(status));
508                return false;
509            }
510        } catch (RemoteException e) {
511            Log.e(TAG, "subscribe: exception: " + e);
512            return false;
513        }
514    }
515
516    /**
517     * Send a message through an existing discovery session.
518     *
519     * @param transactionId transactionId Transaction ID for the transaction -
520     *            used in the async callback to match with the original request.
521     * @param pubSubId ID of the existing publish/subscribe session.
522     * @param requestorInstanceId ID of the peer to communicate with - obtained
523     *            through a previous discovery (match) operation with that peer.
524     * @param dest MAC address of the peer to communicate with - obtained
525     *            together with requestorInstanceId.
526     * @param message Message.
527     * @param messageId Arbitary integer from host (not sent to HAL - useful for
528     *                  testing/debugging at this level)
529     */
530    public boolean sendMessage(short transactionId, byte pubSubId, int requestorInstanceId,
531            byte[] dest, byte[] message, int messageId) {
532        if (VDBG) {
533            Log.d(TAG,
534                    "sendMessage: transactionId=" + transactionId + ", pubSubId=" + pubSubId
535                            + ", requestorInstanceId=" + requestorInstanceId + ", dest="
536                            + String.valueOf(HexEncoding.encode(dest)) + ", messageId=" + messageId
537                            + ", message=" + (message == null ? "<null>"
538                            : HexEncoding.encode(message)) + ", message.length=" + (message == null
539                            ? 0 : message.length));
540        }
541
542        IWifiNanIface iface = mHal.getWifiNanIface();
543        if (iface == null) {
544            Log.e(TAG, "sendMessage: null interface");
545            return false;
546        }
547
548        NanTransmitFollowupRequest req = new NanTransmitFollowupRequest();
549        req.discoverySessionId = pubSubId;
550        req.peerId = requestorInstanceId;
551        copyArray(dest, req.addr);
552        req.isHighPriority = false;
553        req.shouldUseDiscoveryWindow = true;
554        convertNativeByteArrayToArrayList(message, req.serviceSpecificInfo);
555        req.disableFollowupResultIndication = false;
556
557        try {
558            WifiStatus status = iface.transmitFollowupRequest(transactionId, req);
559            if (status.code == WifiStatusCode.SUCCESS) {
560                return true;
561            } else {
562                Log.e(TAG, "sendMessage: error: " + statusString(status));
563                return false;
564            }
565        } catch (RemoteException e) {
566            Log.e(TAG, "sendMessage: exception: " + e);
567            return false;
568        }
569    }
570
571    /**
572     * Terminate a publish discovery session.
573     *
574     * @param transactionId transactionId Transaction ID for the transaction -
575     *            used in the async callback to match with the original request.
576     * @param pubSubId ID of the publish/subscribe session - obtained when
577     *            creating a session.
578     */
579    public boolean stopPublish(short transactionId, byte pubSubId) {
580        if (VDBG) {
581            Log.d(TAG, "stopPublish: transactionId=" + transactionId + ", pubSubId=" + pubSubId);
582        }
583
584        IWifiNanIface iface = mHal.getWifiNanIface();
585        if (iface == null) {
586            Log.e(TAG, "stopPublish: null interface");
587            return false;
588        }
589
590        try {
591            WifiStatus status = iface.stopPublishRequest(transactionId, pubSubId);
592            if (status.code == WifiStatusCode.SUCCESS) {
593                return true;
594            } else {
595                Log.e(TAG, "stopPublish: error: " + statusString(status));
596                return false;
597            }
598        } catch (RemoteException e) {
599            Log.e(TAG, "stopPublish: exception: " + e);
600            return false;
601        }
602    }
603
604    /**
605     * Terminate a subscribe discovery session.
606     *
607     * @param transactionId transactionId Transaction ID for the transaction -
608     *            used in the async callback to match with the original request.
609     * @param pubSubId ID of the publish/subscribe session - obtained when
610     *            creating a session.
611     */
612    public boolean stopSubscribe(short transactionId, byte pubSubId) {
613        if (VDBG) {
614            Log.d(TAG, "stopSubscribe: transactionId=" + transactionId + ", pubSubId=" + pubSubId);
615        }
616
617        IWifiNanIface iface = mHal.getWifiNanIface();
618        if (iface == null) {
619            Log.e(TAG, "stopSubscribe: null interface");
620            return false;
621        }
622
623        try {
624            WifiStatus status = iface.stopSubscribeRequest(transactionId, pubSubId);
625            if (status.code == WifiStatusCode.SUCCESS) {
626                return true;
627            } else {
628                Log.e(TAG, "stopSubscribe: error: " + statusString(status));
629                return false;
630            }
631        } catch (RemoteException e) {
632            Log.e(TAG, "stopSubscribe: exception: " + e);
633            return false;
634        }
635    }
636
637    /**
638     * Create a Aware network interface. This only creates the Linux interface - it doesn't actually
639     * create the data connection.
640     *
641     * @param transactionId Transaction ID for the transaction - used in the async callback to
642     *                      match with the original request.
643     * @param interfaceName The name of the interface, e.g. "aware0".
644     */
645    public boolean createAwareNetworkInterface(short transactionId, String interfaceName) {
646        if (VDBG) {
647            Log.v(TAG, "createAwareNetworkInterface: transactionId=" + transactionId + ", "
648                    + "interfaceName=" + interfaceName);
649        }
650
651        IWifiNanIface iface = mHal.getWifiNanIface();
652        if (iface == null) {
653            Log.e(TAG, "createAwareNetworkInterface: null interface");
654            return false;
655        }
656
657        try {
658            WifiStatus status = iface.createDataInterfaceRequest(transactionId, interfaceName);
659            if (status.code == WifiStatusCode.SUCCESS) {
660                return true;
661            } else {
662                Log.e(TAG, "createAwareNetworkInterface: error: " + statusString(status));
663                return false;
664            }
665        } catch (RemoteException e) {
666            Log.e(TAG, "createAwareNetworkInterface: exception: " + e);
667            return false;
668        }
669    }
670
671    /**
672     * Deletes a Aware network interface. The data connection can (should?) be torn down previously.
673     *
674     * @param transactionId Transaction ID for the transaction - used in the async callback to
675     *                      match with the original request.
676     * @param interfaceName The name of the interface, e.g. "aware0".
677     */
678    public boolean deleteAwareNetworkInterface(short transactionId, String interfaceName) {
679        if (VDBG) {
680            Log.v(TAG, "deleteAwareNetworkInterface: transactionId=" + transactionId + ", "
681                    + "interfaceName=" + interfaceName);
682        }
683
684        IWifiNanIface iface = mHal.getWifiNanIface();
685        if (iface == null) {
686            Log.e(TAG, "deleteAwareNetworkInterface: null interface");
687            return false;
688        }
689
690        try {
691            WifiStatus status = iface.deleteDataInterfaceRequest(transactionId, interfaceName);
692            if (status.code == WifiStatusCode.SUCCESS) {
693                return true;
694            } else {
695                Log.e(TAG, "deleteAwareNetworkInterface: error: " + statusString(status));
696                return false;
697            }
698        } catch (RemoteException e) {
699            Log.e(TAG, "deleteAwareNetworkInterface: exception: " + e);
700            return false;
701        }
702    }
703
704    /**
705     * Initiates setting up a data-path between device and peer. Security is provided by either
706     * PMK or Passphrase (not both) - if both are null then an open (unencrypted) link is set up.
707     *
708     * @param transactionId      Transaction ID for the transaction - used in the async callback to
709     *                           match with the original request.
710     * @param peerId             ID of the peer ID to associate the data path with. A value of 0
711     *                           indicates that not associated with an existing session.
712     * @param channelRequestType Indicates whether the specified channel is available, if available
713     *                           requested or forced (resulting in failure if cannot be
714     *                           accommodated).
715     * @param channel            The channel on which to set up the data-path.
716     * @param peer               The MAC address of the peer to create a connection with.
717     * @param interfaceName      The interface on which to create the data connection.
718     * @param pmk Pairwise master key (PMK - see IEEE 802.11i) for the data-path.
719     * @param passphrase  Passphrase for the data-path.
720     * @param capabilities The capabilities of the firmware.
721     */
722    public boolean initiateDataPath(short transactionId, int peerId, int channelRequestType,
723            int channel, byte[] peer, String interfaceName, byte[] pmk, String passphrase,
724            boolean isOutOfBand, Capabilities capabilities) {
725        if (VDBG) {
726            Log.v(TAG, "initiateDataPath: transactionId=" + transactionId + ", peerId=" + peerId
727                    + ", channelRequestType=" + channelRequestType + ", channel=" + channel
728                    + ", peer=" + String.valueOf(HexEncoding.encode(peer)) + ", interfaceName="
729                    + interfaceName);
730        }
731
732        IWifiNanIface iface = mHal.getWifiNanIface();
733        if (iface == null) {
734            Log.e(TAG, "initiateDataPath: null interface");
735            return false;
736        }
737
738        if (capabilities == null) {
739            Log.e(TAG, "initiateDataPath: null capabilities");
740            return false;
741        }
742
743        NanInitiateDataPathRequest req = new NanInitiateDataPathRequest();
744        req.peerId = peerId;
745        copyArray(peer, req.peerDiscMacAddr);
746        req.channelRequestType = channelRequestType;
747        req.channel = channel;
748        req.ifaceName = interfaceName;
749        req.securityConfig.securityType = NanDataPathSecurityType.OPEN;
750        if (pmk != null && pmk.length != 0) {
751            req.securityConfig.cipherType = getStrongestCipherSuiteType(
752                    capabilities.supportedCipherSuites);
753            req.securityConfig.securityType = NanDataPathSecurityType.PMK;
754            copyArray(pmk, req.securityConfig.pmk);
755        }
756        if (passphrase != null && passphrase.length() != 0) {
757            req.securityConfig.cipherType = getStrongestCipherSuiteType(
758                    capabilities.supportedCipherSuites);
759            req.securityConfig.securityType = NanDataPathSecurityType.PASSPHRASE;
760            convertNativeByteArrayToArrayList(passphrase.getBytes(), req.securityConfig.passphrase);
761
762            if (isOutOfBand) { // only relevant when using passphrase
763                convertNativeByteArrayToArrayList(
764                        SERVICE_NAME_FOR_OOB_DATA_PATH.getBytes(StandardCharsets.UTF_8),
765                        req.serviceNameOutOfBand);
766            }
767        }
768
769        try {
770            WifiStatus status = iface.initiateDataPathRequest(transactionId, req);
771            if (status.code == WifiStatusCode.SUCCESS) {
772                return true;
773            } else {
774                Log.e(TAG, "initiateDataPath: error: " + statusString(status));
775                return false;
776            }
777        } catch (RemoteException e) {
778            Log.e(TAG, "initiateDataPath: exception: " + e);
779            return false;
780        }
781    }
782
783    /**
784     * Responds to a data request from a peer. Security is provided by either PMK or Passphrase (not
785     * both) - if both are null then an open (unencrypted) link is set up.
786     *
787     * @param transactionId Transaction ID for the transaction - used in the async callback to
788     *                      match with the original request.
789     * @param accept Accept (true) or reject (false) the original call.
790     * @param ndpId The NDP (Aware data path) ID. Obtained from the request callback.
791     * @param interfaceName The interface on which the data path will be setup. Obtained from the
792     *                      request callback.
793     * @param pmk Pairwise master key (PMK - see IEEE 802.11i) for the data-path.
794     * @param passphrase  Passphrase for the data-path.
795     * @param isOutOfBand Is the data-path out-of-band (i.e. without a corresponding Aware discovery
796     *                    session).
797     * @param capabilities The capabilities of the firmware.
798     */
799    public boolean respondToDataPathRequest(short transactionId, boolean accept, int ndpId,
800            String interfaceName, byte[] pmk, String passphrase, boolean isOutOfBand,
801            Capabilities capabilities) {
802        if (VDBG) {
803            Log.v(TAG, "respondToDataPathRequest: transactionId=" + transactionId + ", accept="
804                    + accept + ", int ndpId=" + ndpId + ", interfaceName=" + interfaceName);
805        }
806
807        IWifiNanIface iface = mHal.getWifiNanIface();
808        if (iface == null) {
809            Log.e(TAG, "respondToDataPathRequest: null interface");
810            return false;
811        }
812
813        if (capabilities == null) {
814            Log.e(TAG, "initiateDataPath: null capabilities");
815            return false;
816        }
817
818        NanRespondToDataPathIndicationRequest req = new NanRespondToDataPathIndicationRequest();
819        req.acceptRequest = accept;
820        req.ndpInstanceId = ndpId;
821        req.ifaceName = interfaceName;
822        req.securityConfig.securityType = NanDataPathSecurityType.OPEN;
823        if (pmk != null && pmk.length != 0) {
824            req.securityConfig.cipherType = getStrongestCipherSuiteType(
825                    capabilities.supportedCipherSuites);
826            req.securityConfig.securityType = NanDataPathSecurityType.PMK;
827            copyArray(pmk, req.securityConfig.pmk);
828        }
829        if (passphrase != null && passphrase.length() != 0) {
830            req.securityConfig.cipherType = getStrongestCipherSuiteType(
831                    capabilities.supportedCipherSuites);
832            req.securityConfig.securityType = NanDataPathSecurityType.PASSPHRASE;
833            convertNativeByteArrayToArrayList(passphrase.getBytes(), req.securityConfig.passphrase);
834
835            if (isOutOfBand) { // only relevant when using passphrase
836                convertNativeByteArrayToArrayList(
837                        SERVICE_NAME_FOR_OOB_DATA_PATH.getBytes(StandardCharsets.UTF_8),
838                        req.serviceNameOutOfBand);
839            }
840        }
841
842        try {
843            WifiStatus status = iface.respondToDataPathIndicationRequest(transactionId, req);
844            if (status.code == WifiStatusCode.SUCCESS) {
845                return true;
846            } else {
847                Log.e(TAG, "respondToDataPathRequest: error: " + statusString(status));
848                return false;
849            }
850        } catch (RemoteException e) {
851            Log.e(TAG, "respondToDataPathRequest: exception: " + e);
852            return false;
853        }
854    }
855
856    /**
857     * Terminate an existing data-path (does not delete the interface).
858     *
859     * @param transactionId Transaction ID for the transaction - used in the async callback to
860     *                      match with the original request.
861     * @param ndpId The NDP (Aware data path) ID to be terminated.
862     */
863    public boolean endDataPath(short transactionId, int ndpId) {
864        if (VDBG) {
865            Log.v(TAG, "endDataPath: transactionId=" + transactionId + ", ndpId=" + ndpId);
866        }
867
868        IWifiNanIface iface = mHal.getWifiNanIface();
869        if (iface == null) {
870            Log.e(TAG, "endDataPath: null interface");
871            return false;
872        }
873
874        try {
875            WifiStatus status = iface.terminateDataPathRequest(transactionId, ndpId);
876            if (status.code == WifiStatusCode.SUCCESS) {
877                return true;
878            } else {
879                Log.e(TAG, "endDataPath: error: " + statusString(status));
880                return false;
881            }
882        } catch (RemoteException e) {
883            Log.e(TAG, "endDataPath: exception: " + e);
884            return false;
885        }
886    }
887
888
889    // utilities
890
891    /**
892     * Update the NAN configuration to reflect the current power settings.
893     */
894    private void updateConfigForPowerSettings(NanConfigRequest req, boolean isInteractive,
895            boolean isIdle) {
896        if (isIdle) { // lowest power state: doze
897            updateSingleConfigForPowerSettings(req.bandSpecificConfig[NanBandIndex.NAN_BAND_5GHZ],
898                    mSettableParameters.get(PARAM_DW_ON_IDLE_5GHZ));
899            updateSingleConfigForPowerSettings(req.bandSpecificConfig[NanBandIndex.NAN_BAND_24GHZ],
900                    mSettableParameters.get(PARAM_DW_ON_IDLE_24GHZ));
901        } else if (!isInteractive) { // intermediate power state: inactive
902            updateSingleConfigForPowerSettings(req.bandSpecificConfig[NanBandIndex.NAN_BAND_5GHZ],
903                    mSettableParameters.get(PARAM_DW_ON_INACTIVE_5GHZ));
904            updateSingleConfigForPowerSettings(req.bandSpecificConfig[NanBandIndex.NAN_BAND_24GHZ],
905                    mSettableParameters.get(PARAM_DW_ON_INACTIVE_24GHZ));
906        } else { // the default state
907            updateSingleConfigForPowerSettings(req.bandSpecificConfig[NanBandIndex.NAN_BAND_5GHZ],
908                    mSettableParameters.get(PARAM_DW_DEFAULT_5GHZ));
909            updateSingleConfigForPowerSettings(req.bandSpecificConfig[NanBandIndex.NAN_BAND_24GHZ],
910                    mSettableParameters.get(PARAM_DW_DEFAULT_24GHZ));
911        }
912
913        // else do nothing - normal power state
914    }
915
916    private void updateSingleConfigForPowerSettings(NanBandSpecificConfig cfg, int override) {
917        if (override != -1) {
918            cfg.validDiscoveryWindowIntervalVal = true;
919            cfg.discoveryWindowIntervalVal = (byte) override;
920        }
921    }
922
923    /**
924     * Returns the strongest supported cipher suite.
925     *
926     * Baseline is very simple: 256 > 128 > 0.
927     */
928    private int getStrongestCipherSuiteType(int supportedCipherSuites) {
929        if ((supportedCipherSuites & NanCipherSuiteType.SHARED_KEY_256_MASK) != 0) {
930            return NanCipherSuiteType.SHARED_KEY_256_MASK;
931        }
932        if ((supportedCipherSuites & NanCipherSuiteType.SHARED_KEY_128_MASK) != 0) {
933            return NanCipherSuiteType.SHARED_KEY_128_MASK;
934        }
935        return NanCipherSuiteType.NONE;
936    }
937
938    /**
939     * Converts a byte[] to an ArrayList<Byte>. Fills in the entries of the 'to' array if
940     * provided (non-null), otherwise creates and returns a new ArrayList<>.
941     *
942     * @param from The input byte[] to convert from.
943     * @param to An optional ArrayList<> to fill in from 'from'.
944     *
945     * @return A newly allocated ArrayList<> if 'to' is null, otherwise null.
946     */
947    private ArrayList<Byte> convertNativeByteArrayToArrayList(byte[] from, ArrayList<Byte> to) {
948        if (from == null) {
949            from = new byte[0];
950        }
951
952        if (to == null) {
953            to = new ArrayList<>(from.length);
954        } else {
955            to.ensureCapacity(from.length);
956        }
957        for (int i = 0; i < from.length; ++i) {
958            to.add(from[i]);
959        }
960        return to;
961    }
962
963    private void copyArray(byte[] from, byte[] to) {
964        if (from == null || to == null || from.length != to.length) {
965            Log.e(TAG, "copyArray error: from=" + from + ", to=" + to);
966            return;
967        }
968        for (int i = 0; i < from.length; ++i) {
969            to[i] = from[i];
970        }
971    }
972
973    private static String statusString(WifiStatus status) {
974        if (status == null) {
975            return "status=null";
976        }
977        StringBuilder sb = new StringBuilder();
978        sb.append(status.code).append(" (").append(status.description).append(")");
979        return sb.toString();
980    }
981
982    /**
983     * Dump the internal state of the class.
984     */
985    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
986        pw.println("WifiAwareNativeApi:");
987        pw.println("  mSettableParameters: " + mSettableParameters);
988        mHal.dump(fd, pw, args);
989    }
990}
991