blob: 71d0bf8e7b82113dab72b225f6bd2aea0913c8f5 [file] [log] [blame]
nxpandroid64fd68c2015-09-23 16:45:15 +05301/*
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
17package com.android.nfc.echoserver;
18
19import com.android.nfc.DeviceHost.LlcpConnectionlessSocket;
20import com.android.nfc.LlcpException;
21import com.android.nfc.DeviceHost.LlcpServerSocket;
22import com.android.nfc.DeviceHost.LlcpSocket;
23import com.android.nfc.LlcpPacket;
24import com.android.nfc.NfcService;
25
26import android.os.Handler;
27import android.os.Message;
28import android.util.Log;
29
30import java.io.IOException;
31import java.util.concurrent.BlockingQueue;
32import 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 */
59public 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}