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 { write(byte[] data)77 public void write(byte[] data); 78 } 79 EchoServer()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 EchoMachine(WriteCallback callback, boolean dumpWhenFull)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 pushUnit(byte[] unit, int size)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 */ shutdown()153 public synchronized void shutdown() { 154 dataQueue.clear(); 155 shutdown = true; 156 } 157 158 @Override handleMessage(Message msg)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 ServerThread()175 public ServerThread() { 176 super(); 177 echoMachine = new EchoMachine(this, false); 178 } 179 handleClient(LlcpSocket socket)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 run()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 write(byte[] data)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 shutdown()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 ConnectionlessServerThread()283 public ConnectionlessServerThread() { 284 super(); 285 echoMachine = new EchoMachine(this, true); 286 } 287 288 @Override run()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 shutdown()339 public void shutdown() { 340 mRunning = false; 341 } 342 343 @Override write(byte[] data)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 onLlcpActivated()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 onLlcpDeactivated()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 */ start()377 public void start() { 378 synchronized (this) { 379 if (mServerThread == null) { 380 mServerThread = new ServerThread(); 381 mServerThread.start(); 382 } 383 } 384 385 } 386 stop()387 public void stop() { 388 synchronized (this) { 389 if (mServerThread != null) { 390 mServerThread.shutdown(); 391 mServerThread = null; 392 } 393 } 394 } 395 } 396