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