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