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