nxpandroid | 64fd68c | 2015-09-23 16:45:15 +0530 | [diff] [blame] | 1 | /* |
| 2 | * Copyright (C) 2012 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.nfc.echoserver; |
| 18 | |
| 19 | import com.android.nfc.DeviceHost.LlcpConnectionlessSocket; |
| 20 | import com.android.nfc.LlcpException; |
| 21 | import com.android.nfc.DeviceHost.LlcpServerSocket; |
| 22 | import com.android.nfc.DeviceHost.LlcpSocket; |
| 23 | import com.android.nfc.LlcpPacket; |
| 24 | import com.android.nfc.NfcService; |
| 25 | |
| 26 | import android.os.Handler; |
| 27 | import android.os.Message; |
| 28 | import android.util.Log; |
| 29 | |
| 30 | import java.io.IOException; |
| 31 | import java.util.concurrent.BlockingQueue; |
| 32 | import java.util.concurrent.LinkedBlockingQueue; |
| 33 | |
| 34 | /** |
| 35 | * EchoServer is an implementation of the echo server that is used in the |
| 36 | * nfcpy LLCP test suite. Enabling the EchoServer allows to test Android |
| 37 | * NFC devices against nfcpy. |
| 38 | * It has two main features (which run simultaneously): |
| 39 | * 1) A connection-based server, which has a receive buffer of two |
| 40 | * packets. Once a packet is received, a 2-second sleep is initiated. |
| 41 | * After these 2 seconds, all packets that are in the receive buffer |
| 42 | * are echoed back on the same connection. The connection-based server |
| 43 | * does not drop packets, but simply blocks if the queue is full. |
| 44 | * 2) A connection-less mode, which has a receive buffer of two packets. |
| 45 | * On LLCP link activation, we try to receive data on a pre-determined |
| 46 | * connection-less SAP. Like the connection-based server, all data in |
| 47 | * the buffer is echoed back to the SAP from which the data originated |
| 48 | * after a sleep of two seconds. |
| 49 | * The main difference is that the connection-less SAP is supposed |
| 50 | * to drop packets when the buffer is full. |
| 51 | * |
| 52 | * To use with nfcpy: |
| 53 | * - Adapt default_miu (see ECHO_MIU below) |
| 54 | * - llcp-test-client.py --mode=target --co-echo=17 --cl-echo=18 -t 1 |
| 55 | * |
| 56 | * Modify -t to execute the different tests. |
| 57 | * |
| 58 | */ |
| 59 | public class EchoServer { |
| 60 | static boolean DBG = true; |
| 61 | |
| 62 | static final int DEFAULT_CO_SAP = 0x11; |
| 63 | static final int DEFAULT_CL_SAP = 0x12; |
| 64 | |
| 65 | // Link MIU |
| 66 | static final int MIU = 128; |
| 67 | |
| 68 | static final String TAG = "EchoServer"; |
| 69 | static final String CONNECTION_SERVICE_NAME = "urn:nfc:sn:co-echo"; |
| 70 | static final String CONNECTIONLESS_SERVICE_NAME = "urn:nfc:sn:cl-echo"; |
| 71 | |
| 72 | ServerThread mServerThread; |
| 73 | ConnectionlessServerThread mConnectionlessServerThread; |
| 74 | NfcService mService; |
| 75 | |
| 76 | public interface WriteCallback { |
| 77 | public void write(byte[] data); |
| 78 | } |
| 79 | |
| 80 | public EchoServer() { |
| 81 | mService = NfcService.getInstance(); |
| 82 | } |
| 83 | |
| 84 | static class EchoMachine implements Handler.Callback { |
| 85 | static final int QUEUE_SIZE = 2; |
| 86 | static final int ECHO_DELAY_IN_MS = 2000; |
| 87 | |
| 88 | /** |
| 89 | * ECHO_MIU must be set equal to default_miu in nfcpy. |
| 90 | * The nfcpy echo server is expected to maintain the |
| 91 | * packet boundaries and sizes of the requests - that is, |
| 92 | * if the nfcpy client sends a service data unit of 48 bytes |
| 93 | * in a packet, the echo packet should have a payload of |
| 94 | * 48 bytes as well. The "problem" is that the current |
| 95 | * Android LLCP implementation simply pushes all received data |
| 96 | * in a single large buffer, causing us to loose the packet |
| 97 | * boundaries, not knowing how much data to put in a single |
| 98 | * response packet. The ECHO_MIU parameter determines exactly that. |
| 99 | * We use ECHO_MIU=48 because of a bug in PN544, which does not respect |
| 100 | * the target length reduction parameter of the p2p protocol. |
| 101 | */ |
| 102 | static final int ECHO_MIU = 128; |
| 103 | |
| 104 | final BlockingQueue<byte[]> dataQueue; |
| 105 | final Handler handler; |
| 106 | final boolean dumpWhenFull; |
| 107 | final WriteCallback callback; |
| 108 | |
| 109 | // shutdown can be modified from multiple threads, protected by this |
| 110 | boolean shutdown = false; |
| 111 | |
| 112 | EchoMachine(WriteCallback callback, boolean dumpWhenFull) { |
| 113 | this.callback = callback; |
| 114 | this.dumpWhenFull = dumpWhenFull; |
| 115 | dataQueue = new LinkedBlockingQueue<byte[]>(QUEUE_SIZE); |
| 116 | handler = new Handler(this); |
| 117 | } |
| 118 | |
| 119 | public void pushUnit(byte[] unit, int size) { |
| 120 | if (dumpWhenFull && dataQueue.remainingCapacity() == 0) { |
| 121 | if (DBG) Log.d(TAG, "Dumping data unit"); |
| 122 | } else { |
| 123 | try { |
| 124 | // Split up the packet in ECHO_MIU size packets |
| 125 | int sizeLeft = size; |
| 126 | int offset = 0; |
| 127 | if (dataQueue.isEmpty()) { |
| 128 | // First message: start echo'ing in 2 seconds |
| 129 | handler.sendMessageDelayed(handler.obtainMessage(), ECHO_DELAY_IN_MS); |
| 130 | } |
| 131 | |
| 132 | if (sizeLeft == 0) { |
| 133 | // Special case: also send a zero-sized data unit |
| 134 | dataQueue.put(new byte[] {}); |
| 135 | } |
| 136 | while (sizeLeft > 0) { |
| 137 | int minSize = Math.min(size, ECHO_MIU); |
| 138 | byte[] data = new byte[minSize]; |
| 139 | System.arraycopy(unit, offset, data, 0, minSize); |
| 140 | dataQueue.put(data); |
| 141 | sizeLeft -= minSize; |
| 142 | offset += minSize; |
| 143 | } |
| 144 | } catch (InterruptedException e) { |
| 145 | // Ignore |
| 146 | } |
| 147 | } |
| 148 | } |
| 149 | |
| 150 | /** Shuts down the EchoMachine. May block until callbacks |
| 151 | * in progress are completed. |
| 152 | */ |
| 153 | public synchronized void shutdown() { |
| 154 | dataQueue.clear(); |
| 155 | shutdown = true; |
| 156 | } |
| 157 | |
| 158 | @Override |
| 159 | public synchronized boolean handleMessage(Message msg) { |
| 160 | if (shutdown) return true; |
| 161 | while (!dataQueue.isEmpty()) { |
| 162 | callback.write(dataQueue.remove()); |
| 163 | } |
| 164 | return true; |
| 165 | } |
| 166 | } |
| 167 | |
| 168 | public class ServerThread extends Thread implements WriteCallback { |
| 169 | final EchoMachine echoMachine; |
| 170 | |
| 171 | boolean running = true; |
| 172 | LlcpServerSocket serverSocket; |
| 173 | LlcpSocket clientSocket; |
| 174 | |
| 175 | public ServerThread() { |
| 176 | super(); |
| 177 | echoMachine = new EchoMachine(this, false); |
| 178 | } |
| 179 | |
| 180 | private void handleClient(LlcpSocket socket) { |
| 181 | boolean connectionBroken = false; |
| 182 | byte[] dataUnit = new byte[1024]; |
| 183 | |
| 184 | // Get raw data from remote server |
| 185 | while (!connectionBroken) { |
| 186 | try { |
| 187 | int size = socket.receive(dataUnit); |
| 188 | if (DBG) Log.d(TAG, "read " + size + " bytes"); |
| 189 | if (size < 0) { |
| 190 | connectionBroken = true; |
| 191 | break; |
| 192 | } else { |
| 193 | echoMachine.pushUnit(dataUnit, size); |
| 194 | } |
| 195 | } catch (IOException e) { |
| 196 | // Connection broken |
| 197 | connectionBroken = true; |
| 198 | if (DBG) Log.d(TAG, "connection broken by IOException", e); |
| 199 | } |
| 200 | } |
| 201 | } |
| 202 | |
| 203 | @Override |
| 204 | public void run() { |
| 205 | if (DBG) Log.d(TAG, "about create LLCP service socket"); |
| 206 | try { |
| 207 | serverSocket = mService.createLlcpServerSocket(DEFAULT_CO_SAP, |
| 208 | CONNECTION_SERVICE_NAME, MIU, 1, 1024); |
| 209 | } catch (LlcpException e) { |
| 210 | return; |
| 211 | } |
| 212 | if (serverSocket == null) { |
| 213 | if (DBG) Log.d(TAG, "failed to create LLCP service socket"); |
| 214 | return; |
| 215 | } |
| 216 | if (DBG) Log.d(TAG, "created LLCP service socket"); |
| 217 | |
| 218 | while (running) { |
| 219 | |
| 220 | try { |
| 221 | if (DBG) Log.d(TAG, "about to accept"); |
| 222 | clientSocket = serverSocket.accept(); |
| 223 | if (DBG) Log.d(TAG, "accept returned " + clientSocket); |
| 224 | handleClient(clientSocket); |
| 225 | } catch (LlcpException e) { |
| 226 | Log.e(TAG, "llcp error", e); |
| 227 | running = false; |
| 228 | } catch (IOException e) { |
| 229 | Log.e(TAG, "IO error", e); |
| 230 | running = false; |
| 231 | } |
| 232 | } |
| 233 | |
| 234 | echoMachine.shutdown(); |
| 235 | |
| 236 | try { |
| 237 | clientSocket.close(); |
| 238 | } catch (IOException e) { |
| 239 | // Ignore |
| 240 | } |
| 241 | clientSocket = null; |
| 242 | |
| 243 | try { |
| 244 | serverSocket.close(); |
| 245 | } catch (IOException e) { |
| 246 | // Ignore |
| 247 | } |
| 248 | serverSocket = null; |
| 249 | } |
| 250 | |
| 251 | @Override |
| 252 | public void write(byte[] data) { |
| 253 | if (clientSocket != null) { |
| 254 | try { |
| 255 | clientSocket.send(data); |
| 256 | Log.e(TAG, "Send success!"); |
| 257 | } catch (IOException e) { |
| 258 | Log.e(TAG, "Send failed."); |
| 259 | } |
| 260 | } |
| 261 | } |
| 262 | |
| 263 | public void shutdown() { |
| 264 | running = false; |
| 265 | if (serverSocket != null) { |
| 266 | try { |
| 267 | serverSocket.close(); |
| 268 | } catch (IOException e) { |
| 269 | // ignore |
| 270 | } |
| 271 | serverSocket = null; |
| 272 | } |
| 273 | } |
| 274 | } |
| 275 | |
| 276 | public class ConnectionlessServerThread extends Thread implements WriteCallback { |
| 277 | final EchoMachine echoMachine; |
| 278 | |
| 279 | LlcpConnectionlessSocket socket; |
| 280 | int mRemoteSap; |
| 281 | boolean mRunning = true; |
| 282 | |
| 283 | public ConnectionlessServerThread() { |
| 284 | super(); |
| 285 | echoMachine = new EchoMachine(this, true); |
| 286 | } |
| 287 | |
| 288 | @Override |
| 289 | public void run() { |
| 290 | boolean connectionBroken = false; |
| 291 | LlcpPacket packet; |
| 292 | if (DBG) Log.d(TAG, "about create LLCP connectionless socket"); |
| 293 | try { |
| 294 | socket = mService.createLlcpConnectionLessSocket( |
| 295 | DEFAULT_CL_SAP, CONNECTIONLESS_SERVICE_NAME); |
| 296 | if (socket == null) { |
| 297 | if (DBG) Log.d(TAG, "failed to create LLCP connectionless socket"); |
| 298 | return; |
| 299 | } |
| 300 | |
| 301 | while (mRunning && !connectionBroken) { |
| 302 | try { |
| 303 | packet = socket.receive(); |
| 304 | if (packet == null || packet.getDataBuffer() == null) { |
| 305 | break; |
| 306 | } |
| 307 | byte[] dataUnit = packet.getDataBuffer(); |
| 308 | int size = dataUnit.length; |
| 309 | |
| 310 | if (DBG) Log.d(TAG, "read " + packet.getDataBuffer().length + " bytes"); |
| 311 | if (size < 0) { |
| 312 | connectionBroken = true; |
| 313 | break; |
| 314 | } else { |
| 315 | mRemoteSap = packet.getRemoteSap(); |
| 316 | echoMachine.pushUnit(dataUnit, size); |
| 317 | } |
| 318 | } catch (IOException e) { |
| 319 | // Connection broken |
| 320 | connectionBroken = true; |
| 321 | if (DBG) Log.d(TAG, "connection broken by IOException", e); |
| 322 | } |
| 323 | } |
| 324 | } catch (LlcpException e) { |
| 325 | Log.e(TAG, "llcp error", e); |
| 326 | } finally { |
| 327 | echoMachine.shutdown(); |
| 328 | |
| 329 | if (socket != null) { |
| 330 | try { |
| 331 | socket.close(); |
| 332 | } catch (IOException e) { |
| 333 | } |
| 334 | } |
| 335 | } |
| 336 | |
| 337 | } |
| 338 | |
| 339 | public void shutdown() { |
| 340 | mRunning = false; |
| 341 | } |
| 342 | |
| 343 | @Override |
| 344 | public void write(byte[] data) { |
| 345 | try { |
| 346 | socket.send(mRemoteSap, data); |
| 347 | } catch (IOException e) { |
| 348 | if (DBG) Log.d(TAG, "Error writing data."); |
| 349 | } |
| 350 | } |
| 351 | } |
| 352 | |
| 353 | public void onLlcpActivated() { |
| 354 | synchronized (this) { |
| 355 | // Connectionless server can only be started once the link is up |
| 356 | // - otherwise, all calls to receive() on the connectionless socket |
| 357 | // will fail immediately. |
| 358 | if (mConnectionlessServerThread == null) { |
| 359 | mConnectionlessServerThread = new ConnectionlessServerThread(); |
| 360 | mConnectionlessServerThread.start(); |
| 361 | } |
| 362 | } |
| 363 | } |
| 364 | |
| 365 | public void onLlcpDeactivated() { |
| 366 | synchronized (this) { |
| 367 | if (mConnectionlessServerThread != null) { |
| 368 | mConnectionlessServerThread.shutdown(); |
| 369 | mConnectionlessServerThread = null; |
| 370 | } |
| 371 | } |
| 372 | } |
| 373 | |
| 374 | /** |
| 375 | * Needs to be called on the UI thread |
| 376 | */ |
| 377 | public void start() { |
| 378 | synchronized (this) { |
| 379 | if (mServerThread == null) { |
| 380 | mServerThread = new ServerThread(); |
| 381 | mServerThread.start(); |
| 382 | } |
| 383 | } |
| 384 | |
| 385 | } |
| 386 | |
| 387 | public void stop() { |
| 388 | synchronized (this) { |
| 389 | if (mServerThread != null) { |
| 390 | mServerThread.shutdown(); |
| 391 | mServerThread = null; |
| 392 | } |
| 393 | } |
| 394 | } |
| 395 | } |