Home | History | Annotate | Download | only in echoserver
      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 }
    396