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