summaryrefslogtreecommitdiffstats
path: root/src/com
diff options
context:
space:
mode:
Diffstat (limited to 'src/com')
-rwxr-xr-xsrc/com/android/nfc/P2pLinkManager.java38
-rw-r--r--src/com/android/nfc/echoserver/EchoServer.java393
2 files changed, 431 insertions, 0 deletions
diff --git a/src/com/android/nfc/P2pLinkManager.java b/src/com/android/nfc/P2pLinkManager.java
index 466453d..277e739 100755
--- a/src/com/android/nfc/P2pLinkManager.java
+++ b/src/com/android/nfc/P2pLinkManager.java
@@ -16,6 +16,7 @@
package com.android.nfc;
+import com.android.nfc.echoserver.EchoServer;
import com.android.nfc.ndefpush.NdefPushClient;
import com.android.nfc.ndefpush.NdefPushServer;
import com.android.nfc.snep.SnepClient;
@@ -97,6 +98,11 @@ public class P2pLinkManager implements Handler.Callback, P2pEventListener.Callba
static final String TAG = "NfcP2pLinkManager";
static final boolean DBG = true;
+ /** Enables the LLCP EchoServer, which can be used to test the android
+ * LLCP stack against nfcpy.
+ */
+ static final boolean ECHOSERVER_ENABLED = false;
+
// TODO dynamically assign SAP values
static final int NDEFPUSH_SAP = 0x10;
@@ -105,6 +111,8 @@ public class P2pLinkManager implements Handler.Callback, P2pEventListener.Callba
static final int MSG_DEBOUNCE_TIMEOUT = 1;
static final int MSG_RECEIVE_COMPLETE = 2;
static final int MSG_SEND_COMPLETE = 3;
+ static final int MSG_START_ECHOSERVER = 4;
+ static final int MSG_STOP_ECHOSERVER = 5;
// values for mLinkState
static final int LINK_STATE_DOWN = 1;
@@ -123,6 +131,7 @@ public class P2pLinkManager implements Handler.Callback, P2pEventListener.Callba
final NdefPushServer mNdefPushServer;
final SnepServer mDefaultSnepServer;
+ final EchoServer mEchoServer;
final ActivityManager mActivityManager;
final PackageManager mPackageManager;
final Context mContext;
@@ -144,6 +153,11 @@ public class P2pLinkManager implements Handler.Callback, P2pEventListener.Callba
public P2pLinkManager(Context context) {
mNdefPushServer = new NdefPushServer(NDEFPUSH_SAP, mNppCallback);
mDefaultSnepServer = new SnepServer(mDefaultSnepCallback);
+ if (ECHOSERVER_ENABLED) {
+ mEchoServer = new EchoServer();
+ } else {
+ mEchoServer = null;
+ }
mActivityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
mPackageManager = context.getPackageManager();
mContext = context;
@@ -166,9 +180,15 @@ public class P2pLinkManager implements Handler.Callback, P2pEventListener.Callba
if (!mIsReceiveEnabled && receiveEnable) {
mDefaultSnepServer.start();
mNdefPushServer.start();
+ if (mEchoServer != null) {
+ mHandler.sendEmptyMessage(MSG_START_ECHOSERVER);
+ }
} else if (mIsReceiveEnabled && !receiveEnable) {
mDefaultSnepServer.stop();
mNdefPushServer.stop();
+ if (mEchoServer != null) {
+ mHandler.sendEmptyMessage(MSG_STOP_ECHOSERVER);
+ }
}
mIsSendEnabled = sendEnable;
mIsReceiveEnabled = receiveEnable;
@@ -196,6 +216,10 @@ public class P2pLinkManager implements Handler.Callback, P2pEventListener.Callba
Log.i(TAG, "LLCP activated");
synchronized (P2pLinkManager.this) {
+ if (mEchoServer != null) {
+ mEchoServer.onLlcpActivated();
+ }
+
switch (mLinkState) {
case LINK_STATE_DOWN:
mLinkState = LINK_STATE_UP;
@@ -278,6 +302,10 @@ public class P2pLinkManager implements Handler.Callback, P2pEventListener.Callba
public void onLlcpDeactivated() {
Log.i(TAG, "LLCP deactivated.");
synchronized (this) {
+ if (mEchoServer != null) {
+ mEchoServer.onLlcpDeactivated();
+ }
+
switch (mLinkState) {
case LINK_STATE_DOWN:
case LINK_STATE_DEBOUNCE:
@@ -410,6 +438,16 @@ public class P2pLinkManager implements Handler.Callback, P2pEventListener.Callba
@Override
public boolean handleMessage(Message msg) {
switch (msg.what) {
+ case MSG_START_ECHOSERVER:
+ synchronized (this) {
+ mEchoServer.start();
+ break;
+ }
+ case MSG_STOP_ECHOSERVER:
+ synchronized (this) {
+ mEchoServer.stop();
+ break;
+ }
case MSG_DEBOUNCE_TIMEOUT:
synchronized (this) {
if (mLinkState != LINK_STATE_DEBOUNCE) {
diff --git a/src/com/android/nfc/echoserver/EchoServer.java b/src/com/android/nfc/echoserver/EchoServer.java
new file mode 100644
index 0000000..bd060f7
--- /dev/null
+++ b/src/com/android/nfc/echoserver/EchoServer.java
@@ -0,0 +1,393 @@
+/*
+ * Copyright (C) 2012 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.nfc.echoserver;
+
+import com.android.nfc.DeviceHost.LlcpConnectionlessSocket;
+import com.android.nfc.LlcpException;
+import com.android.nfc.DeviceHost.LlcpServerSocket;
+import com.android.nfc.DeviceHost.LlcpSocket;
+import com.android.nfc.LlcpPacket;
+import com.android.nfc.NfcService;
+
+import android.os.Handler;
+import android.os.Message;
+import android.util.Log;
+
+import java.io.IOException;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingQueue;
+
+/**
+ * EchoServer is an implementation of the echo server that is used in the
+ * nfcpy LLCP test suite. Enabling the EchoServer allows to test Android
+ * NFC devices against nfcpy.
+ * It has two main features (which run simultaneously):
+ * 1) A connection-based server, which has a receive buffer of two
+ * packets. Once a packet is received, a 2-second sleep is initiated.
+ * After these 2 seconds, all packets that are in the receive buffer
+ * are echoed back on the same connection. The connection-based server
+ * does not drop packets, but simply blocks if the queue is full.
+ * 2) A connection-less mode, which has a receive buffer of two packets.
+ * On LLCP link activation, we try to receive data on a pre-determined
+ * connection-less SAP. Like the connection-based server, all data in
+ * the buffer is echoed back to the SAP from which the data originated
+ * after a sleep of two seconds.
+ * The main difference is that the connection-less SAP is supposed
+ * to drop packets when the buffer is full.
+ *
+ * To use with nfcpy:
+ * - Adapt default_miu (see ECHO_MIU below)
+ * - llcp-test-client.py --mode=target --co-echo=17 --cl-echo=18 -t 1
+ *
+ * Modify -t to execute the different tests.
+ *
+ */
+public class EchoServer {
+ static boolean DBG = true;
+
+ static final int DEFAULT_CO_SAP = 0x11;
+ static final int DEFAULT_CL_SAP = 0x12;
+
+ // Link MIU
+ static final int MIU = 128;
+
+ static final String TAG = "EchoServer";
+ static final String CONNECTION_SERVICE_NAME = "urn:nfc:sn:co-echo";
+ static final String CONNECTIONLESS_SERVICE_NAME = "urn:nfc:sn:cl-echo";
+
+ ServerThread mServerThread;
+ ConnectionlessServerThread mConnectionlessServerThread;
+ NfcService mService;
+
+ public interface WriteCallback {
+ public void write(byte[] data);
+ }
+
+ public EchoServer() {
+ mService = NfcService.getInstance();
+ }
+
+ static class EchoMachine implements Handler.Callback {
+ static final int QUEUE_SIZE = 2;
+ static final int ECHO_DELAY_IN_MS = 2000;
+
+ /**
+ * ECHO_MIU must be set equal to default_miu in nfcpy.
+ * The nfcpy echo server is expected to maintain the
+ * packet boundaries and sizes of the requests - that is,
+ * if the nfcpy client sends a service data unit of 48 bytes
+ * in a packet, the echo packet should have a payload of
+ * 48 bytes as well. The "problem" is that the current
+ * Android LLCP implementation simply pushes all received data
+ * in a single large buffer, causing us to loose the packet
+ * boundaries, not knowing how much data to put in a single
+ * response packet. The ECHO_MIU parameter determines exactly that.
+ * We use ECHO_MIU=48 because of a bug in PN544, which does not respect
+ * the target length reduction parameter of the p2p protocol.
+ */
+ static final int ECHO_MIU = 48;
+
+ final BlockingQueue<byte[]> dataQueue;
+ final Handler handler;
+ final boolean dumpWhenFull;
+ final WriteCallback callback;
+
+ // shutdown can be modified from multiple threads, protected by this
+ boolean shutdown = false;
+
+ EchoMachine(WriteCallback callback, boolean dumpWhenFull) {
+ this.callback = callback;
+ this.dumpWhenFull = dumpWhenFull;
+ dataQueue = new LinkedBlockingQueue<byte[]>(QUEUE_SIZE);
+ handler = new Handler(this);
+ }
+
+ public void pushUnit(byte[] unit, int size) {
+ if (dumpWhenFull && dataQueue.remainingCapacity() == 0) {
+ if (DBG) Log.d(TAG, "Dumping data unit");
+ } else {
+ try {
+ // Split up the packet in ECHO_MIU size packets
+ int sizeLeft = size;
+
+ if (dataQueue.isEmpty()) {
+ // First message: start echo'ing in 2 seconds
+ handler.sendMessageDelayed(handler.obtainMessage(), ECHO_DELAY_IN_MS);
+ }
+
+ if (sizeLeft == 0) {
+ // Special case: also send a zero-sized data unit
+ dataQueue.put(new byte[] {});
+ }
+ while (sizeLeft > 0) {
+ int minSize = Math.min(size, ECHO_MIU);
+ byte[] data = new byte[minSize];
+ System.arraycopy(unit, 0, data, 0, minSize);
+ dataQueue.put(data);
+ sizeLeft -= minSize;
+ }
+ } catch (InterruptedException e) {
+ // Ignore
+ }
+ }
+ }
+
+ /** Shuts down the EchoMachine. May block until callbacks
+ * in progress are completed.
+ */
+ public synchronized void shutdown() {
+ dataQueue.clear();
+ shutdown = true;
+ }
+
+ @Override
+ public synchronized boolean handleMessage(Message msg) {
+ if (shutdown) return true;
+ while (!dataQueue.isEmpty()) {
+ callback.write(dataQueue.remove());
+ }
+ return true;
+ }
+ }
+
+ public class ServerThread extends Thread implements WriteCallback {
+ final EchoMachine echoMachine;
+
+ boolean running = true;
+ LlcpServerSocket serverSocket;
+ LlcpSocket clientSocket;
+
+ public ServerThread() {
+ super();
+ echoMachine = new EchoMachine(this, false);
+ }
+
+ private void handleClient(LlcpSocket socket) {
+ boolean connectionBroken = false;
+ byte[] dataUnit = new byte[1024];
+
+ // Get raw data from remote server
+ while (!connectionBroken) {
+ try {
+ int size = socket.receive(dataUnit);
+ if (DBG) Log.d(TAG, "read " + size + " bytes");
+ if (size < 0) {
+ connectionBroken = true;
+ break;
+ } else {
+ echoMachine.pushUnit(dataUnit, size);
+ }
+ } catch (IOException e) {
+ // Connection broken
+ connectionBroken = true;
+ if (DBG) Log.d(TAG, "connection broken by IOException", e);
+ }
+ }
+ }
+
+ @Override
+ public void run() {
+ if (DBG) Log.d(TAG, "about create LLCP service socket");
+ try {
+ serverSocket = mService.createLlcpServerSocket(DEFAULT_CO_SAP,
+ CONNECTION_SERVICE_NAME, MIU, 1, 1024);
+ } catch (LlcpException e) {
+ return;
+ }
+ if (serverSocket == null) {
+ if (DBG) Log.d(TAG, "failed to create LLCP service socket");
+ return;
+ }
+ if (DBG) Log.d(TAG, "created LLCP service socket");
+
+ while (running) {
+
+ try {
+ if (DBG) Log.d(TAG, "about to accept");
+ clientSocket = serverSocket.accept();
+ if (DBG) Log.d(TAG, "accept returned " + clientSocket);
+ handleClient(clientSocket);
+ } catch (LlcpException e) {
+ Log.e(TAG, "llcp error", e);
+ running = false;
+ } catch (IOException e) {
+ Log.e(TAG, "IO error", e);
+ running = false;
+ }
+ }
+
+ echoMachine.shutdown();
+
+ try {
+ clientSocket.close();
+ } catch (IOException e) {
+ // Ignore
+ }
+ clientSocket = null;
+
+ try {
+ serverSocket.close();
+ } catch (IOException e) {
+ // Ignore
+ }
+ serverSocket = null;
+ }
+
+ @Override
+ public void write(byte[] data) {
+ if (clientSocket != null) {
+ try {
+ clientSocket.send(data);
+ } catch (IOException e) {
+ // Ignore
+ }
+ }
+ }
+
+ public void shutdown() {
+ running = false;
+ if (serverSocket != null) {
+ try {
+ serverSocket.close();
+ } catch (IOException e) {
+ // ignore
+ }
+ serverSocket = null;
+ }
+ }
+ }
+
+ public class ConnectionlessServerThread extends Thread implements WriteCallback {
+ final EchoMachine echoMachine;
+
+ LlcpConnectionlessSocket socket;
+ int mRemoteSap;
+ boolean mRunning = true;
+
+ public ConnectionlessServerThread() {
+ super();
+ echoMachine = new EchoMachine(this, true);
+ }
+
+ @Override
+ public void run() {
+ boolean connectionBroken = false;
+ LlcpPacket packet;
+ if (DBG) Log.d(TAG, "about create LLCP connectionless socket");
+ try {
+ socket = mService.createLlcpConnectionLessSocket(
+ DEFAULT_CL_SAP);
+ if (socket == null) {
+ if (DBG) Log.d(TAG, "failed to create LLCP connectionless socket");
+ return;
+ }
+
+ while (mRunning && !connectionBroken) {
+ try {
+ packet = socket.receive();
+ if (packet == null || packet.getDataBuffer() == null) {
+ break;
+ }
+ byte[] dataUnit = packet.getDataBuffer();
+ int size = dataUnit.length;
+
+ if (DBG) Log.d(TAG, "read " + packet.getDataBuffer().length + " bytes");
+ if (size < 0) {
+ connectionBroken = true;
+ break;
+ } else {
+ mRemoteSap = packet.getRemoteSap();
+ echoMachine.pushUnit(dataUnit, size);
+ }
+ } catch (IOException e) {
+ // Connection broken
+ connectionBroken = true;
+ if (DBG) Log.d(TAG, "connection broken by IOException", e);
+ }
+ }
+ } catch (LlcpException e) {
+ Log.e(TAG, "llcp error", e);
+ } finally {
+ echoMachine.shutdown();
+
+ if (socket != null) {
+ try {
+ socket.close();
+ } catch (IOException e) {
+ }
+ }
+ }
+
+ }
+
+ public void shutdown() {
+ mRunning = false;
+ }
+
+ @Override
+ public void write(byte[] data) {
+ try {
+ socket.send(mRemoteSap, data);
+ } catch (IOException e) {
+ if (DBG) Log.d(TAG, "Error writing data.");
+ }
+ }
+ }
+
+ public void onLlcpActivated() {
+ synchronized (this) {
+ // Connectionless server can only be started once the link is up
+ // - otherwise, all calls to receive() on the connectionless socket
+ // will fail immediately.
+ if (mConnectionlessServerThread == null) {
+ mConnectionlessServerThread = new ConnectionlessServerThread();
+ mConnectionlessServerThread.start();
+ }
+ }
+ }
+
+ public void onLlcpDeactivated() {
+ synchronized (this) {
+ if (mConnectionlessServerThread != null) {
+ mConnectionlessServerThread.shutdown();
+ mConnectionlessServerThread = null;
+ }
+ }
+ }
+
+ /**
+ * Needs to be called on the UI thread
+ */
+ public void start() {
+ synchronized (this) {
+ if (mServerThread == null) {
+ mServerThread = new ServerThread();
+ mServerThread.start();
+ }
+ }
+
+ }
+
+ public void stop() {
+ synchronized (this) {
+ if (mServerThread != null) {
+ mServerThread.shutdown();
+ mServerThread = null;
+ }
+ }
+ }
+}