blob: 737234a18137873c7ae1633b98c55887e4f670f2 [file] [log] [blame]
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.cts.verifier.wifiaware.testcase;
import static com.android.cts.verifier.wifiaware.CallbackUtils.CALLBACK_TIMEOUT_SEC;
import android.content.Context;
import android.net.ConnectivityManager;
import android.net.Network;
import android.net.NetworkCapabilities;
import android.net.NetworkRequest;
import android.net.wifi.aware.PublishDiscoverySession;
import android.net.wifi.aware.WifiAwareNetworkInfo;
import android.net.wifi.aware.WifiAwareNetworkSpecifier;
import android.util.Log;
import android.util.Pair;
import com.android.cts.verifier.R;
import com.android.cts.verifier.wifiaware.CallbackUtils;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Inet6Address;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Arrays;
/**
* Test case for data-path, in-band test cases:
* open/passphrase * solicited/unsolicited * publish/subscribe.
*
* Subscribe test sequence:
* 1. Attach
* wait for results (session)
* 2. Subscribe
* wait for results (subscribe session)
* 3. Wait for discovery (possibly with ranging)
* 4. Send message
* Wait for success
* 5. Wait for rx message
* 6. Request network
* Wait for network
* 7. Create socket and bind to server
* 8. Send/receive data to validate connection
* 9. Destroy session
*
* Publish test sequence:
* 1. Attach
* wait for results (session)
* 2. Publish
* wait for results (publish session)
* 3. Wait for rx message
* 4. Start a ServerSocket
* 5. Request network
* 6. Send message
* Wait for success
* 7. Wait for network
* 8. Receive/Send data to validate connection
* 9. Destroy session
*/
public class DataPathInBandTestCase extends DiscoveryBaseTestCase {
private static final String TAG = "DataPathInBandTestCase";
private static final boolean DBG = true;
private static final byte[] MSG_PUB_TO_SUB = "Ready".getBytes();
private static final String PASSPHRASE = "Some super secret password";
private static final byte[] PMK = "01234567890123456789012345678901".getBytes();
private static final byte[] MSG_CLIENT_TO_SERVER = "GET SOME BYTES".getBytes();
private static final byte[] MSG_SERVER_TO_CLIENT = "PUT SOME OTHER BYTES".getBytes();
private boolean mIsSecurityOpen;
private boolean mUsePmk;
private boolean mIsPublish;
private boolean mIsAcceptAny;
private Thread mClientServerThread;
private ConnectivityManager mCm;
private CallbackUtils.NetworkCb mNetworkCb;
private static int sSDKLevel = android.os.Build.VERSION.SDK_INT;
public DataPathInBandTestCase(Context context, boolean isSecurityOpen, boolean isPublish,
boolean isUnsolicited, boolean usePmk, boolean acceptAny) {
super(context, isUnsolicited, false);
mIsSecurityOpen = isSecurityOpen;
mUsePmk = usePmk;
mIsPublish = isPublish;
mIsAcceptAny = acceptAny;
}
@Override
protected boolean executeTest() throws InterruptedException {
if (DBG) {
Log.d(TAG,
"executeTest: mIsSecurityOpen=" + mIsSecurityOpen + ", mIsPublish=" + mIsPublish
+ ", mIsUnsolicited=" + mIsUnsolicited);
}
mCm = (ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
mClientServerThread = null;
mNetworkCb = null;
boolean success;
if (mIsPublish) {
success = executeTestPublisher();
} else {
success = executeTestSubscriber();
}
if (!success) {
return false;
}
// destroy session
mWifiAwareDiscoverySession.close();
mWifiAwareDiscoverySession = null;
mListener.onTestMsgReceived(mContext.getString(R.string.aware_status_lifecycle_ok));
return true;
}
@Override
protected void tearDown() {
if (mClientServerThread != null) {
mClientServerThread.interrupt();
}
if (mNetworkCb != null) {
mCm.unregisterNetworkCallback(mNetworkCb);
}
super.tearDown();
}
private boolean executeTestSubscriber() throws InterruptedException {
if (DBG) Log.d(TAG, "executeTestSubscriber");
if (!executeSubscribe()) {
return false;
}
// 5. wait to receive message
CallbackUtils.DiscoveryCb.CallbackData callbackData = mDiscoveryCb.waitForCallbacks(
CallbackUtils.DiscoveryCb.ON_MESSAGE_RECEIVED);
switch (callbackData.callback) {
case CallbackUtils.DiscoveryCb.TIMEOUT:
setFailureReason(mContext.getString(R.string.aware_status_receive_timeout));
Log.e(TAG, "executeTestSubscriber: receive message TIMEOUT");
return false;
}
mListener.onTestMsgReceived(mContext.getString(R.string.aware_status_received));
if (DBG) Log.d(TAG, "executeTestSubscriber: received message");
// validate that received the expected message
if (!Arrays.equals(MSG_PUB_TO_SUB, callbackData.serviceSpecificInfo)) {
setFailureReason(mContext.getString(R.string.aware_status_receive_failure));
Log.e(TAG, "executeTestSubscriber: receive message message content mismatch: rx='"
+ new String(callbackData.serviceSpecificInfo) + "'");
return false;
}
// 6. request network
WifiAwareNetworkSpecifier.Builder nsBuilder =
new WifiAwareNetworkSpecifier.Builder(mWifiAwareDiscoverySession, mPeerHandle);
if (!mIsSecurityOpen) {
if (mUsePmk) {
nsBuilder.setPmk(PMK);
} else {
nsBuilder.setPskPassphrase(PASSPHRASE);
}
}
NetworkRequest nr = new NetworkRequest.Builder().addTransportType(
NetworkCapabilities.TRANSPORT_WIFI_AWARE).setNetworkSpecifier(
nsBuilder.build()).build();
mNetworkCb = new CallbackUtils.NetworkCb();
mCm.requestNetwork(nr, mNetworkCb, CALLBACK_TIMEOUT_SEC * 1000);
mListener.onTestMsgReceived(
mContext.getString(R.string.aware_status_network_requested));
if (DBG) Log.d(TAG, "executeTestSubscriber: requested network");
// 7. wait for network
Pair<Network, NetworkCapabilities> info = mNetworkCb.waitForNetworkCapabilities();
if (info == null) {
setFailureReason(mContext.getString(R.string.aware_status_network_failed));
Log.e(TAG, "executeTestSubscriber: network request rejected or timed-out");
return false;
}
if (info.first == null || info.second == null) {
setFailureReason(mContext.getString(R.string.aware_status_network_failed));
Log.e(TAG, "executeTestSubscriber: received a null Network or NetworkCapabilities!?");
return false;
}
if (sSDKLevel <= android.os.Build.VERSION_CODES.P) {
if (info.second.getNetworkSpecifier() != null) {
setFailureReason(mContext.getString(R.string.aware_status_network_failed_leak));
Log.e(TAG, "executeTestSubscriber: network request accepted - but leaks NS!");
return false;
}
}
mListener.onTestMsgReceived(mContext.getString(R.string.aware_status_network_success));
if (DBG) Log.d(TAG, "executeTestSubscriber: network request granted - AVAILABLE");
if (!mIsSecurityOpen) {
if (!(info.second.getTransportInfo() instanceof WifiAwareNetworkInfo)) {
setFailureReason(mContext.getString(R.string.aware_status_network_failed));
Log.e(TAG, "executeTestSubscriber: did not get WifiAwareNetworkInfo from peer!?");
return false;
}
WifiAwareNetworkInfo peerAwareInfo =
(WifiAwareNetworkInfo) info.second.getTransportInfo();
Inet6Address peerIpv6 = peerAwareInfo.getPeerIpv6Addr();
int peerPort = peerAwareInfo.getPort();
int peerTransportProtocol = peerAwareInfo.getTransportProtocol();
mListener.onTestMsgReceived(
mContext.getString(R.string.aware_status_socket_server_info_rx,
peerIpv6.toString(),
peerPort));
if (DBG) {
Log.d(TAG,
"executeTestPublisher: rx peer info IPv6=" + peerIpv6 + ", port=" + peerPort
+ ", transportProtocol=" + peerTransportProtocol);
}
if (peerTransportProtocol != 6) { // 6 == TCP: hard coded at peer
setFailureReason(mContext.getString(R.string.aware_status_network_failed));
Log.e(TAG, "executeTestSubscriber: Got incorrect transport protocol from peer");
return false;
}
if (peerPort <= 0) {
setFailureReason(mContext.getString(R.string.aware_status_network_failed));
Log.e(TAG, "executeTestSubscriber: Got invalid port from peer (<=0)");
return false;
}
// 8. send/receive - can happen inline here - no need for another thread
String currentMethod = "";
try {
currentMethod = "createSocket";
Socket socket = info.first.getSocketFactory().createSocket(peerIpv6, peerPort);
// simple interaction: write X bytes, read Y bytes
currentMethod = "getOutputStream()";
OutputStream os = socket.getOutputStream();
currentMethod = "write()";
os.write(MSG_CLIENT_TO_SERVER, 0, MSG_CLIENT_TO_SERVER.length);
byte[] buffer = new byte[1024];
currentMethod = "getInputStream()";
InputStream is = socket.getInputStream();
currentMethod = "read()";
int numBytes = is.read(buffer, 0, MSG_SERVER_TO_CLIENT.length);
mListener.onTestMsgReceived(
mContext.getString(R.string.aware_status_socket_server_message_from_peer,
new String(buffer, 0, numBytes)));
if (numBytes != MSG_SERVER_TO_CLIENT.length) {
setFailureReason(mContext.getString(R.string.aware_status_socket_failure));
Log.e(TAG,
"executeTestSubscriber: didn't read expected number of bytes - only "
+ "got -- " + numBytes);
return false;
}
if (!Arrays.equals(MSG_SERVER_TO_CLIENT,
Arrays.copyOf(buffer, MSG_SERVER_TO_CLIENT.length))) {
setFailureReason(mContext.getString(R.string.aware_status_socket_failure));
Log.e(TAG, "executeTestSubscriber: did not get expected message from server.");
return false;
}
// Sleep 3 second for transmit and receive.
Thread.sleep(3000);
currentMethod = "close()";
os.close();
} catch (IOException e) {
setFailureReason(mContext.getString(R.string.aware_status_socket_failure));
Log.e(TAG, "executeTestSubscriber: failure while executing " + currentMethod);
return false;
}
}
return true;
}
private boolean executeTestPublisher() throws InterruptedException {
if (DBG) Log.d(TAG, "executeTestPublisher");
if (!executePublish()) {
return false;
}
// 4. create a ServerSocket
int port = 0;
if (!mIsSecurityOpen) {
ServerSocket server;
try {
server = new ServerSocket(0);
} catch (IOException e) {
setFailureReason(
mContext.getString(R.string.aware_status_socket_failure));
Log.e(TAG, "executeTestPublisher: failure creating a ServerSocket -- " + e);
return false;
}
port = server.getLocalPort();
mListener.onTestMsgReceived(
mContext.getString(R.string.aware_status_socket_server_socket_started, port));
if (DBG) Log.d(TAG, "executeTestPublisher: server socket started on port=" + port);
// accept connections on the server socket - has to be done in a separate thread!
mClientServerThread = new Thread(() -> {
String currentMethod = "";
try {
currentMethod = "accept()";
Socket socket = server.accept();
currentMethod = "getInputStream()";
InputStream is = socket.getInputStream();
// simple interaction: read X bytes, write Y bytes
byte[] buffer = new byte[1024];
currentMethod = "read()";
int numBytes = is.read(buffer, 0, MSG_CLIENT_TO_SERVER.length);
mListener.onTestMsgReceived(mContext.getString(
R.string.aware_status_socket_server_message_from_peer,
new String(buffer, 0, numBytes)));
if (numBytes != MSG_CLIENT_TO_SERVER.length) {
setFailureReason(mContext.getString(R.string.aware_status_socket_failure));
Log.e(TAG,
"executeTestPublisher: didn't read expected number of bytes - only "
+ "got -- " + numBytes);
return;
}
if (!Arrays.equals(MSG_CLIENT_TO_SERVER,
Arrays.copyOf(buffer, MSG_CLIENT_TO_SERVER.length))) {
setFailureReason(mContext.getString(R.string.aware_status_socket_failure));
Log.e(TAG,
"executeTestPublisher: did not get expected message from client.");
return;
}
currentMethod = "getOutputStream()";
OutputStream os = socket.getOutputStream();
currentMethod = "write()";
os.write(MSG_SERVER_TO_CLIENT, 0, MSG_SERVER_TO_CLIENT.length);
// Sleep 3 second for transmit and receive.
Thread.sleep(3000);
currentMethod = "close()";
os.close();
} catch (IOException | InterruptedException e) {
setFailureReason(mContext.getString(R.string.aware_status_socket_failure));
Log.e(TAG, "executeTestPublisher: failure while executing " + currentMethod);
return;
}
});
mClientServerThread.start();
}
// 5. Request network
WifiAwareNetworkSpecifier.Builder nsBuilder;
if (mIsAcceptAny) {
nsBuilder = new WifiAwareNetworkSpecifier
.Builder((PublishDiscoverySession) mWifiAwareDiscoverySession);
} else {
nsBuilder = new WifiAwareNetworkSpecifier
.Builder(mWifiAwareDiscoverySession, mPeerHandle);
}
if (!mIsSecurityOpen) {
if (mUsePmk) {
nsBuilder.setPmk(PMK);
} else {
nsBuilder.setPskPassphrase(PASSPHRASE);
}
nsBuilder.setPort(port).setTransportProtocol(6); // 6 == TCP
}
NetworkRequest nr = new NetworkRequest.Builder().addTransportType(
NetworkCapabilities.TRANSPORT_WIFI_AWARE).setNetworkSpecifier(
nsBuilder.build()).build();
mNetworkCb = new CallbackUtils.NetworkCb();
mCm.requestNetwork(nr, mNetworkCb, CALLBACK_TIMEOUT_SEC * 1000);
mListener.onTestMsgReceived(
mContext.getString(R.string.aware_status_network_requested));
if (DBG) Log.d(TAG, "executeTestPublisher: requested network");
// 6. send message & wait for send status
mWifiAwareDiscoverySession.sendMessage(mPeerHandle, MESSAGE_ID, MSG_PUB_TO_SUB);
CallbackUtils.DiscoveryCb.CallbackData callbackData = mDiscoveryCb.waitForCallbacks(
CallbackUtils.DiscoveryCb.ON_MESSAGE_SEND_SUCCEEDED
| CallbackUtils.DiscoveryCb.ON_MESSAGE_SEND_FAILED);
switch (callbackData.callback) {
case CallbackUtils.DiscoveryCb.TIMEOUT:
setFailureReason(mContext.getString(R.string.aware_status_send_timeout));
Log.e(TAG, "executeTestPublisher: send message TIMEOUT");
return false;
case CallbackUtils.DiscoveryCb.ON_MESSAGE_SEND_FAILED:
setFailureReason(mContext.getString(R.string.aware_status_send_failed));
Log.e(TAG, "executeTestPublisher: send message ON_MESSAGE_SEND_FAILED");
return false;
}
mListener.onTestMsgReceived(mContext.getString(R.string.aware_status_send_success));
if (DBG) Log.d(TAG, "executeTestPublisher: send message succeeded");
if (callbackData.messageId != MESSAGE_ID) {
setFailureReason(mContext.getString(R.string.aware_status_send_fail_parameter));
Log.e(TAG, "executeTestPublisher: send message succeeded but message ID mismatch : "
+ callbackData.messageId);
return false;
}
// 7. wait for network
Pair<Network, NetworkCapabilities> info = mNetworkCb.waitForNetworkCapabilities();
if (info == null) {
setFailureReason(mContext.getString(R.string.aware_status_network_failed));
Log.e(TAG, "executeTestPublisher: request network rejected - ON_UNAVAILABLE");
return false;
}
if (info.first == null || info.second == null) {
setFailureReason(mContext.getString(R.string.aware_status_network_failed));
Log.e(TAG, "executeTestPublisher: received a null Network or NetworkCapabilities!?");
return false;
}
if (sSDKLevel <= android.os.Build.VERSION_CODES.P) {
if (info.second.getNetworkSpecifier() != null) {
setFailureReason(mContext.getString(R.string.aware_status_network_failed_leak));
Log.e(TAG, "executeTestSubscriber: network request accepted - but leaks NS!");
return false;
}
}
mListener.onTestMsgReceived(mContext.getString(R.string.aware_status_network_success));
if (DBG) Log.d(TAG, "executeTestPublisher: network request granted - AVAILABLE");
// 8. Send/Receive data to validate connection - happens on thread above
if (!mIsSecurityOpen) {
mClientServerThread.join(CALLBACK_TIMEOUT_SEC * 1000);
if (mClientServerThread.isAlive()) {
setFailureReason(mContext.getString(R.string.aware_status_socket_failure));
Log.e(TAG,
"executeTestPublisher: failure while waiting for client-server thread to "
+ "finish");
return false;
}
}
return true;
}
}