1 /* 2 * Copyright (C) 2011 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.snep; 18 19 import com.android.nfc.DeviceHost.LlcpServerSocket; 20 import com.android.nfc.DeviceHost.LlcpSocket; 21 import com.android.nfc.LlcpException; 22 import com.android.nfc.NfcService; 23 24 import android.nfc.NdefMessage; 25 import android.nfc.NfcAdapter; 26 import android.util.Log; 27 28 import java.io.IOException; 29 30 /** 31 * A simple server that accepts NDEF messages pushed to it over an LLCP connection. Those messages 32 * are typically set on the client side by using {@link NfcAdapter#enableForegroundNdefPush}. 33 */ 34 public final class SnepServer { 35 private static final String TAG = "SnepServer"; 36 private static final boolean DBG = false; 37 private static final int DEFAULT_MIU = 248; 38 private static final int DEFAULT_RW_SIZE = 1; 39 40 public static final int DEFAULT_PORT = 4; 41 42 public static final String DEFAULT_SERVICE_NAME = "urn:nfc:sn:snep"; 43 44 final Callback mCallback; 45 final String mServiceName; 46 final int mServiceSap; 47 final int mFragmentLength; 48 final int mMiu; 49 final int mRwSize; 50 51 /** Protected by 'this', null when stopped, non-null when running */ 52 ServerThread mServerThread = null; 53 boolean mServerRunning = false; 54 55 public interface Callback { doPut(NdefMessage msg)56 public SnepMessage doPut(NdefMessage msg); doGet(int acceptableLength, NdefMessage msg)57 public SnepMessage doGet(int acceptableLength, NdefMessage msg); 58 } 59 SnepServer(Callback callback)60 public SnepServer(Callback callback) { 61 mCallback = callback; 62 mServiceName = DEFAULT_SERVICE_NAME; 63 mServiceSap = DEFAULT_PORT; 64 mFragmentLength = -1; 65 mMiu = DEFAULT_MIU; 66 mRwSize = DEFAULT_RW_SIZE; 67 } 68 SnepServer(String serviceName, int serviceSap, Callback callback)69 public SnepServer(String serviceName, int serviceSap, Callback callback) { 70 mCallback = callback; 71 mServiceName = serviceName; 72 mServiceSap = serviceSap; 73 mFragmentLength = -1; 74 mMiu = DEFAULT_MIU; 75 mRwSize = DEFAULT_RW_SIZE; 76 } 77 SnepServer(Callback callback, int miu, int rwSize)78 public SnepServer(Callback callback, int miu, int rwSize) { 79 mCallback = callback; 80 mServiceName = DEFAULT_SERVICE_NAME; 81 mServiceSap = DEFAULT_PORT; 82 mFragmentLength = -1; 83 mMiu = miu; 84 mRwSize = rwSize; 85 } 86 SnepServer(String serviceName, int serviceSap, int fragmentLength, Callback callback)87 SnepServer(String serviceName, int serviceSap, int fragmentLength, Callback callback) { 88 mCallback = callback; 89 mServiceName = serviceName; 90 mServiceSap = serviceSap; 91 mFragmentLength = fragmentLength; 92 mMiu = DEFAULT_MIU; 93 mRwSize = DEFAULT_RW_SIZE; 94 } 95 96 /** Connection class, used to handle incoming connections */ 97 private class ConnectionThread extends Thread { 98 private final LlcpSocket mSock; 99 private final SnepMessenger mMessager; 100 ConnectionThread(LlcpSocket socket, int fragmentLength)101 ConnectionThread(LlcpSocket socket, int fragmentLength) { 102 super(TAG); 103 mSock = socket; 104 mMessager = new SnepMessenger(false, socket, fragmentLength); 105 } 106 107 @Override run()108 public void run() { 109 if (DBG) Log.d(TAG, "starting connection thread"); 110 try { 111 boolean running; 112 synchronized (SnepServer.this) { 113 running = mServerRunning; 114 } 115 116 while (running) { 117 if (!handleRequest(mMessager, mCallback)) { 118 break; 119 } 120 121 synchronized (SnepServer.this) { 122 running = mServerRunning; 123 } 124 } 125 } catch (IOException e) { 126 if (DBG) Log.e(TAG, "Closing from IOException"); 127 } finally { 128 try { 129 if (DBG) Log.d(TAG, "about to close"); 130 mSock.close(); 131 } catch (IOException e) { 132 // ignore 133 } 134 } 135 136 if (DBG) Log.d(TAG, "finished connection thread"); 137 } 138 } 139 handleRequest(SnepMessenger messenger, Callback callback)140 static boolean handleRequest(SnepMessenger messenger, Callback callback) throws IOException { 141 SnepMessage request; 142 try { 143 request = messenger.getMessage(); 144 } catch (SnepException e) { 145 if (DBG) Log.w(TAG, "Bad snep message", e); 146 try { 147 messenger.sendMessage(SnepMessage.getMessage( 148 SnepMessage.RESPONSE_BAD_REQUEST)); 149 } catch (IOException e2) { 150 // Ignore 151 } 152 return false; 153 } 154 155 if (((request.getVersion() & 0xF0) >> 4) != SnepMessage.VERSION_MAJOR) { 156 messenger.sendMessage(SnepMessage.getMessage( 157 SnepMessage.RESPONSE_UNSUPPORTED_VERSION)); 158 } else if (request.getField() == SnepMessage.REQUEST_GET) { 159 messenger.sendMessage(callback.doGet(request.getAcceptableLength(), 160 request.getNdefMessage())); 161 } else if (request.getField() == SnepMessage.REQUEST_PUT) { 162 if (DBG) Log.d(TAG, "putting message " + request.toString()); 163 messenger.sendMessage(callback.doPut(request.getNdefMessage())); 164 } else { 165 if (DBG) Log.d(TAG, "Unknown request (" + request.getField() +")"); 166 messenger.sendMessage(SnepMessage.getMessage( 167 SnepMessage.RESPONSE_BAD_REQUEST)); 168 } 169 return true; 170 } 171 172 /** Server class, used to listen for incoming connection request */ 173 class ServerThread extends Thread { 174 private boolean mThreadRunning = true; 175 LlcpServerSocket mServerSocket; 176 177 @Override run()178 public void run() { 179 boolean threadRunning; 180 synchronized (SnepServer.this) { 181 threadRunning = mThreadRunning; 182 } 183 184 while (threadRunning) { 185 if (DBG) Log.d(TAG, "about create LLCP service socket"); 186 try { 187 synchronized (SnepServer.this) { 188 mServerSocket = NfcService.getInstance().createLlcpServerSocket(mServiceSap, 189 mServiceName, mMiu, mRwSize, 1024); 190 } 191 if (mServerSocket == null) { 192 if (DBG) Log.d(TAG, "failed to create LLCP service socket"); 193 return; 194 } 195 if (DBG) Log.d(TAG, "created LLCP service socket"); 196 synchronized (SnepServer.this) { 197 threadRunning = mThreadRunning; 198 } 199 200 while (threadRunning) { 201 LlcpServerSocket serverSocket; 202 synchronized (SnepServer.this) { 203 serverSocket = mServerSocket; 204 } 205 206 if (serverSocket == null) { 207 if (DBG) Log.d(TAG, "Server socket shut down."); 208 return; 209 } 210 if (DBG) Log.d(TAG, "about to accept"); 211 LlcpSocket communicationSocket = serverSocket.accept(); 212 if (DBG) Log.d(TAG, "accept returned " + communicationSocket); 213 if (communicationSocket != null) { 214 int fragmentLength = (mFragmentLength == -1) ? 215 mMiu : Math.min(mMiu, mFragmentLength); 216 new ConnectionThread(communicationSocket, fragmentLength).start(); 217 } 218 219 synchronized (SnepServer.this) { 220 threadRunning = mThreadRunning; 221 } 222 } 223 if (DBG) Log.d(TAG, "stop running"); 224 } catch (LlcpException e) { 225 Log.e(TAG, "llcp error", e); 226 } catch (IOException e) { 227 Log.e(TAG, "IO error", e); 228 } finally { 229 synchronized (SnepServer.this) { 230 if (mServerSocket != null) { 231 if (DBG) Log.d(TAG, "about to close"); 232 try { 233 mServerSocket.close(); 234 } catch (IOException e) { 235 // ignore 236 } 237 mServerSocket = null; 238 } 239 } 240 } 241 242 synchronized (SnepServer.this) { 243 threadRunning = mThreadRunning; 244 } 245 } 246 } 247 shutdown()248 public void shutdown() { 249 synchronized (SnepServer.this) { 250 mThreadRunning = false; 251 if (mServerSocket != null) { 252 try { 253 mServerSocket.close(); 254 } catch (IOException e) { 255 // ignore 256 } 257 mServerSocket = null; 258 } 259 } 260 } 261 } 262 start()263 public void start() { 264 synchronized (SnepServer.this) { 265 if (DBG) Log.d(TAG, "start, thread = " + mServerThread); 266 if (mServerThread == null) { 267 if (DBG) Log.d(TAG, "starting new server thread"); 268 mServerThread = new ServerThread(); 269 mServerThread.start(); 270 mServerRunning = true; 271 } 272 } 273 } 274 stop()275 public void stop() { 276 synchronized (SnepServer.this) { 277 if (DBG) Log.d(TAG, "stop, thread = " + mServerThread); 278 if (mServerThread != null) { 279 if (DBG) Log.d(TAG, "shuting down server thread"); 280 mServerThread.shutdown(); 281 mServerThread = null; 282 mServerRunning = false; 283 } 284 } 285 } 286 } 287