1 /*
2  * Copyright (C) 2017 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.cts.verifier.wifiaware.testcase;
18 
19 import android.content.Context;
20 import android.net.MacAddress;
21 import android.net.wifi.aware.DiscoverySession;
22 import android.net.wifi.aware.PeerHandle;
23 import android.net.wifi.aware.PublishConfig;
24 import android.net.wifi.aware.SubscribeConfig;
25 import android.net.wifi.aware.WifiAwareSession;
26 import android.util.Log;
27 import android.util.Pair;
28 
29 import com.android.cts.verifier.R;
30 import com.android.cts.verifier.wifiaware.BaseTestCase;
31 import com.android.cts.verifier.wifiaware.CallbackUtils;
32 
33 import java.util.ArrayList;
34 import java.util.Arrays;
35 import java.util.List;
36 
37 /**
38  * Base test case providing utilities for Discovery:
39  *
40  * Subscribe test sequence:
41  * 1. Attach
42  *    wait for results (session)
43  * 2. Subscribe
44  *    wait for results (subscribe session)
45  * 3. Wait for discovery (possibly with ranging)
46  * 4. Send message
47  *    Wait for success
48  *
49  * Publish test sequence:
50  * 1. Attach
51  *    wait for results (session)
52  * 2. Publish
53  *    wait for results (publish session)
54  * 3. Wait for rx message
55  */
56 public abstract class DiscoveryBaseTestCase extends BaseTestCase {
57     private static final String TAG = "DiscoveryBaseTestCase";
58     private static final boolean DBG = true;
59 
60     private static final String SERVICE_NAME = "CtsVerifierTestService";
61     private static final byte[] MATCH_FILTER_BYTES = "bytes used for matching".getBytes();
62     private static final byte[] PUB_SSI = "Extra bytes in the publisher discovery".getBytes();
63     private static final byte[] SUB_SSI = "Arbitrary bytes for the subscribe discovery".getBytes();
64     private static final byte[] MSG_SUB_TO_PUB = "Let's talk".getBytes();
65     protected static final int MESSAGE_ID = 1234;
66     protected static final int LARGE_ENOUGH_DISTANCE = 100000; // 100 meters
67 
68     protected boolean mIsUnsolicited;
69     protected boolean mIsRangingRequired;
70 
71     protected final Object mLock = new Object();
72 
73     private String mFailureReason;
74     protected WifiAwareSession mWifiAwareSession;
75     protected DiscoverySession mWifiAwareDiscoverySession;
76     protected CallbackUtils.DiscoveryCb mDiscoveryCb;
77     protected PeerHandle mPeerHandle;
78     protected MacAddress mMyMacAddress;
79     protected MacAddress mPeerMacAddress;
80 
DiscoveryBaseTestCase(Context context, boolean isUnsolicited, boolean isRangingRequired)81     public DiscoveryBaseTestCase(Context context, boolean isUnsolicited,
82             boolean isRangingRequired) {
83         super(context);
84 
85         mIsUnsolicited = isUnsolicited;
86         mIsRangingRequired = isRangingRequired;
87     }
88 
executeAttach()89     private boolean executeAttach() throws InterruptedException {
90         // attach (optionally with an identity listener)
91         CallbackUtils.AttachCb attachCb = new CallbackUtils.AttachCb();
92         CallbackUtils.IdentityListenerSingleShot identityL = new CallbackUtils
93                 .IdentityListenerSingleShot();
94         if (mIsRangingRequired) {
95             mWifiAwareManager.attach(attachCb, identityL, mHandler);
96         } else {
97             mWifiAwareManager.attach(attachCb, mHandler);
98         }
99         Pair<Integer, WifiAwareSession> results = attachCb.waitForAttach();
100         switch (results.first) {
101             case CallbackUtils.AttachCb.TIMEOUT:
102                 setFailureReason(mContext.getString(R.string.aware_status_attach_timeout));
103                 Log.e(TAG, "executeTest: attach TIMEOUT");
104                 return false;
105             case CallbackUtils.AttachCb.ON_ATTACH_FAILED:
106                 setFailureReason(mContext.getString(R.string.aware_status_attach_fail));
107                 Log.e(TAG, "executeTest: attach ON_ATTACH_FAILED");
108                 return false;
109         }
110         mWifiAwareSession = results.second;
111         if (mWifiAwareSession == null) {
112             setFailureReason(mContext.getString(R.string.aware_status_attach_fail));
113             Log.e(TAG, "executeTest: attach callback succeeded but null session returned!?");
114             return false;
115         }
116         mListener.onTestMsgReceived(mContext.getString(R.string.aware_status_attached));
117         if (DBG) {
118             Log.d(TAG, "executeTest: attach succeeded");
119         }
120 
121         // 1.5 optionally wait for identity (necessary in ranging cases)
122         if (mIsRangingRequired) {
123             byte[] mac = identityL.waitForMac();
124             if (mac == null) {
125                 setFailureReason(mContext.getString(R.string.aware_status_identity_fail));
126                 Log.e(TAG, "executeAttach: identity callback not triggered");
127                 return false;
128             }
129             mMyMacAddress = MacAddress.fromBytes(mac);
130             mListener.onTestMsgReceived(mResources.getString(R.string.aware_status_identity,
131                     mMyMacAddress));
132             if (DBG) {
133                 Log.d(TAG, "executeAttach: identity received: " + mMyMacAddress.toString());
134             }
135         }
136 
137         return true;
138     }
139 
executeSubscribe()140     protected boolean executeSubscribe() throws InterruptedException {
141         // 1. attach
142         if (!executeAttach()) {
143             return false;
144         }
145 
146         mDiscoveryCb = new CallbackUtils.DiscoveryCb();
147 
148         // 2. subscribe
149         List<byte[]> matchFilter = new ArrayList<>();
150         matchFilter.add(MATCH_FILTER_BYTES);
151         SubscribeConfig.Builder builder = new SubscribeConfig.Builder().setServiceName(
152                 SERVICE_NAME).setServiceSpecificInfo(SUB_SSI).setMatchFilter(
153                 matchFilter).setSubscribeType(
154                 mIsUnsolicited ? SubscribeConfig.SUBSCRIBE_TYPE_PASSIVE
155                         : SubscribeConfig.SUBSCRIBE_TYPE_ACTIVE).setTerminateNotificationEnabled(
156                 true);
157         if (mIsRangingRequired) {
158             // set up a distance that will always trigger - i.e. that we're already in that range
159             builder.setMaxDistanceMm(LARGE_ENOUGH_DISTANCE);
160         }
161         SubscribeConfig subscribeConfig = builder.build();
162         if (DBG) Log.d(TAG, "executeTestSubscriber: subscribeConfig=" + subscribeConfig);
163         mWifiAwareSession.subscribe(subscribeConfig, mDiscoveryCb, mHandler);
164 
165         //    wait for results - subscribe session
166         CallbackUtils.DiscoveryCb.CallbackData callbackData = mDiscoveryCb.waitForCallbacks(
167                 CallbackUtils.DiscoveryCb.ON_SUBSCRIBE_STARTED
168                         | CallbackUtils.DiscoveryCb.ON_SESSION_CONFIG_FAILED);
169         switch (callbackData.callback) {
170             case CallbackUtils.DiscoveryCb.TIMEOUT:
171                 setFailureReason(mContext.getString(R.string.aware_status_subscribe_timeout));
172                 Log.e(TAG, "executeTestSubscriber: subscribe TIMEOUT");
173                 return false;
174             case CallbackUtils.DiscoveryCb.ON_SESSION_CONFIG_FAILED:
175                 setFailureReason(mContext.getString(R.string.aware_status_subscribe_failed));
176                 Log.e(TAG, "executeTestSubscriber: subscribe ON_SESSION_CONFIG_FAILED");
177                 return false;
178         }
179         mWifiAwareDiscoverySession = callbackData.subscribeDiscoverySession;
180         if (mWifiAwareDiscoverySession == null) {
181             setFailureReason(mContext.getString(R.string.aware_status_subscribe_null_session));
182             Log.e(TAG, "executeTestSubscriber: subscribe succeeded but null session returned");
183             return false;
184         }
185         mListener.onTestMsgReceived(mContext.getString(R.string.aware_status_subscribe_started));
186         if (DBG) Log.d(TAG, "executeTestSubscriber: subscribe succeeded");
187 
188         // 3. wait for discovery
189         callbackData = mDiscoveryCb.waitForCallbacks(
190                 mIsRangingRequired ? CallbackUtils.DiscoveryCb.ON_SERVICE_DISCOVERED_WITH_RANGE
191                         : CallbackUtils.DiscoveryCb.ON_SERVICE_DISCOVERED);
192         switch (callbackData.callback) {
193             case CallbackUtils.DiscoveryCb.TIMEOUT:
194                 setFailureReason(mContext.getString(R.string.aware_status_discovery_timeout));
195                 Log.e(TAG, "executeTestSubscriber: waiting for discovery TIMEOUT");
196                 return false;
197         }
198         mPeerHandle = callbackData.peerHandle;
199         if (!mIsRangingRequired) {
200             mListener.onTestMsgReceived(mContext.getString(R.string.aware_status_discovery));
201             if (DBG) Log.d(TAG, "executeTestSubscriber: discovery");
202         } else {
203             if (DBG) {
204                 Log.d(TAG, "executeTestSubscriber: discovery with range="
205                         + callbackData.distanceMm);
206             }
207         }
208 
209         //    validate discovery parameters match
210         if (mIsRangingRequired) {
211             try {
212                 mPeerMacAddress = MacAddress.fromBytes(callbackData.serviceSpecificInfo);
213             } catch (IllegalArgumentException e) {
214                 setFailureReason(mContext.getString(R.string.aware_status_discovery_fail));
215                 Log.e(TAG, "executeTestSubscriber: invalid MAC received in SSI: rx='" + new String(
216                         callbackData.serviceSpecificInfo) + "'");
217                 return false;
218             }
219             mListener.onTestMsgReceived(
220                     mResources.getString(R.string.aware_status_discovery_with_info,
221                             mPeerMacAddress));
222         } else {
223             if (!Arrays.equals(PUB_SSI, callbackData.serviceSpecificInfo)) {
224                 setFailureReason(mContext.getString(R.string.aware_status_discovery_fail));
225                 Log.e(TAG, "executeTestSubscriber: discovery but SSI mismatch: rx='" + new String(
226                         callbackData.serviceSpecificInfo) + "'");
227                 return false;
228             }
229         }
230         if (callbackData.matchFilter.size() != 1 || !Arrays.equals(MATCH_FILTER_BYTES,
231                 callbackData.matchFilter.get(0))) {
232             setFailureReason(mContext.getString(R.string.aware_status_discovery_fail));
233             StringBuffer sb = new StringBuffer();
234             sb.append("size=").append(callbackData.matchFilter.size());
235             for (byte[] mf: callbackData.matchFilter) {
236                 sb.append(", e='").append(new String(mf)).append("'");
237             }
238             Log.e(TAG, "executeTestSubscriber: discovery but matchFilter mismatch: "
239                     + sb.toString());
240             return false;
241         }
242         if (mPeerHandle == null) {
243             setFailureReason(mContext.getString(R.string.aware_status_discovery_fail));
244             Log.e(TAG, "executeTestSubscriber: discovery but null peerHandle");
245             return false;
246         }
247 
248         // 4. send message & wait for send status
249         mWifiAwareDiscoverySession.sendMessage(mPeerHandle, MESSAGE_ID,
250                 mIsRangingRequired ? mMyMacAddress.toByteArray() : MSG_SUB_TO_PUB);
251         callbackData = mDiscoveryCb.waitForCallbacks(
252                 CallbackUtils.DiscoveryCb.ON_MESSAGE_SEND_SUCCEEDED
253                         | CallbackUtils.DiscoveryCb.ON_MESSAGE_SEND_FAILED);
254         switch (callbackData.callback) {
255             case CallbackUtils.DiscoveryCb.TIMEOUT:
256                 setFailureReason(mContext.getString(R.string.aware_status_send_timeout));
257                 Log.e(TAG, "executeTestSubscriber: send message TIMEOUT");
258                 return false;
259             case CallbackUtils.DiscoveryCb.ON_MESSAGE_SEND_FAILED:
260                 setFailureReason(mContext.getString(R.string.aware_status_send_failed));
261                 Log.e(TAG, "executeTestSubscriber: send message ON_MESSAGE_SEND_FAILED");
262                 return false;
263         }
264         mListener.onTestMsgReceived(mContext.getString(R.string.aware_status_send_success));
265         if (DBG) Log.d(TAG, "executeTestSubscriber: send message succeeded");
266 
267         if (callbackData.messageId != MESSAGE_ID) {
268             setFailureReason(mContext.getString(R.string.aware_status_send_fail_parameter));
269             Log.e(TAG, "executeTestSubscriber: send message message ID mismatch: "
270                     + callbackData.messageId);
271             return false;
272         }
273 
274         return true;
275     }
276 
executePublish()277     protected boolean executePublish() throws InterruptedException {
278         // 1. attach
279         if (!executeAttach()) {
280             return false;
281         }
282 
283         mDiscoveryCb = new CallbackUtils.DiscoveryCb();
284 
285         // 2. publish
286         List<byte[]> matchFilter = new ArrayList<>();
287         matchFilter.add(MATCH_FILTER_BYTES);
288         PublishConfig publishConfig = new PublishConfig.Builder().setServiceName(
289                 SERVICE_NAME).setServiceSpecificInfo(
290                 mIsRangingRequired ? mMyMacAddress.toByteArray() : PUB_SSI).setMatchFilter(
291                 matchFilter).setPublishType(mIsUnsolicited ? PublishConfig.PUBLISH_TYPE_UNSOLICITED
292                 : PublishConfig.PUBLISH_TYPE_SOLICITED).setTerminateNotificationEnabled(
293                 true).setRangingEnabled(mIsRangingRequired).build();
294         if (DBG) Log.d(TAG, "executeTestPublisher: publishConfig=" + publishConfig);
295         mWifiAwareSession.publish(publishConfig, mDiscoveryCb, mHandler);
296 
297         //    wait for results - publish session
298         CallbackUtils.DiscoveryCb.CallbackData callbackData = mDiscoveryCb.waitForCallbacks(
299                 CallbackUtils.DiscoveryCb.ON_PUBLISH_STARTED
300                         | CallbackUtils.DiscoveryCb.ON_SESSION_CONFIG_FAILED);
301         switch (callbackData.callback) {
302             case CallbackUtils.DiscoveryCb.TIMEOUT:
303                 setFailureReason(mContext.getString(R.string.aware_status_publish_timeout));
304                 Log.e(TAG, "executeTestPublisher: publish TIMEOUT");
305                 return false;
306             case CallbackUtils.DiscoveryCb.ON_SESSION_CONFIG_FAILED:
307                 setFailureReason(mContext.getString(R.string.aware_status_publish_failed));
308                 Log.e(TAG, "executeTestPublisher: publish ON_SESSION_CONFIG_FAILED");
309                 return false;
310         }
311         mWifiAwareDiscoverySession = callbackData.publishDiscoverySession;
312         if (mWifiAwareDiscoverySession == null) {
313             setFailureReason(mContext.getString(R.string.aware_status_publish_null_session));
314             Log.e(TAG, "executeTestPublisher: publish succeeded but null session returned");
315             return false;
316         }
317         mListener.onTestMsgReceived(mContext.getString(R.string.aware_status_publish_started));
318         if (DBG) Log.d(TAG, "executeTestPublisher: publish succeeded");
319 
320         // 3. wait to receive message: no timeout since this depends on (human) operator starting
321         //    the test on the subscriber device.
322         callbackData = mDiscoveryCb.waitForCallbacksNoTimeout(
323                 CallbackUtils.DiscoveryCb.ON_MESSAGE_RECEIVED);
324         mPeerHandle = callbackData.peerHandle;
325         mListener.onTestMsgReceived(mContext.getString(R.string.aware_status_received));
326         if (DBG) Log.d(TAG, "executeTestPublisher: received message");
327 
328         //    validate that received the expected message
329         if (mIsRangingRequired) {
330             try {
331                 mPeerMacAddress = MacAddress.fromBytes(callbackData.serviceSpecificInfo);
332             } catch (IllegalArgumentException e) {
333                 setFailureReason(mContext.getString(R.string.aware_status_discovery_fail));
334                 Log.e(TAG, "executeTestSubscriber: invalid MAC received in SSI: rx='" + new String(
335                         callbackData.serviceSpecificInfo) + "'");
336                 return false;
337             }
338             mListener.onTestMsgReceived(mResources.getString(R.string.aware_status_received_mac,
339                     mPeerMacAddress));
340         } else {
341             if (!Arrays.equals(MSG_SUB_TO_PUB, callbackData.serviceSpecificInfo)) {
342                 setFailureReason(mContext.getString(R.string.aware_status_receive_failure));
343                 Log.e(TAG, "executeTestPublisher: receive message message content mismatch: rx='"
344                         + new String(callbackData.serviceSpecificInfo) + "'");
345                 return false;
346             }
347         }
348         if (mPeerHandle == null) {
349             setFailureReason(mContext.getString(R.string.aware_status_receive_failure));
350             Log.e(TAG, "executeTestPublisher: received message but peerHandle is null!?");
351             return false;
352         }
353 
354         return true;
355     }
356 
setFailureReason(String reason)357     protected void setFailureReason(String reason) {
358         synchronized (mLock) {
359             mFailureReason = reason;
360         }
361     }
362 
363     @Override
getFailureReason()364     protected String getFailureReason() {
365         synchronized (mLock) {
366             return mFailureReason;
367         }
368     }
369 
370     @Override
tearDown()371     protected void tearDown() {
372         if (mWifiAwareDiscoverySession != null) {
373             mWifiAwareDiscoverySession.close();
374             mWifiAwareDiscoverySession = null;
375         }
376         if (mWifiAwareSession != null) {
377             mWifiAwareSession.close();
378             mWifiAwareSession = null;
379         }
380         super.tearDown();
381     }
382 }
383