summaryrefslogtreecommitdiffstats
path: root/src/com
diff options
context:
space:
mode:
authorMartijn Coenen <maco@google.com>2012-01-17 12:26:44 -0800
committerMartijn Coenen <maco@google.com>2012-01-20 15:00:56 -0800
commit6f0e3b8e1de5137077127bf3144effa2016c27c6 (patch)
tree8c373061b9d77d3836681edaa4968c3513f21e10 /src/com
parente008eba3b51c5303d52bf3e9e989dfd03b18435a (diff)
downloadpackages_apps_nfc-6f0e3b8e1de5137077127bf3144effa2016c27c6.zip
packages_apps_nfc-6f0e3b8e1de5137077127bf3144effa2016c27c6.tar.gz
packages_apps_nfc-6f0e3b8e1de5137077127bf3144effa2016c27c6.tar.bz2
LLCP echo server implementation.
Can be used to test the Android LLCP stack with nfcpy. Disabled by default. Also fixed unnecessary JNI release call causing warnings in the logs. Change-Id: Iaed159e23dadc1601be2df2aadbe1d9c54c50615
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;
+ }
+ }
+ }
+}