/* * Copyright (C) 2011 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.nfc.snep; import com.android.nfc.DeviceHost.LlcpServerSocket; import com.android.nfc.DeviceHost.LlcpSocket; import com.android.nfc.LlcpException; import com.android.nfc.NfcService; import android.nfc.NdefMessage; import android.nfc.NfcAdapter; import android.util.Log; import java.io.IOException; /** * A simple server that accepts NDEF messages pushed to it over an LLCP connection. Those messages * are typically set on the client side by using {@link NfcAdapter#enableForegroundNdefPush}. */ public final class SnepServer { private static final String TAG = "SnepServer"; private static final boolean DBG = false; public static final int DEFAULT_PORT = 4; private static final int MIU = 248; public static final String DEFAULT_SERVICE_NAME = "urn:nfc:sn:snep"; final Callback mCallback; final String mServiceName; final int mServiceSap; final int mFragmentLength; /** Protected by 'this', null when stopped, non-null when running */ ServerThread mServerThread = null; boolean mServerRunning = false; public interface Callback { public SnepMessage doPut(NdefMessage msg); public SnepMessage doGet(int acceptableLength, NdefMessage msg); } public SnepServer(Callback callback) { mCallback = callback; mServiceName = DEFAULT_SERVICE_NAME; mServiceSap = DEFAULT_PORT; mFragmentLength = -1; } public SnepServer(String serviceName, int serviceSap, Callback callback) { mCallback = callback; mServiceName = serviceName; mServiceSap = serviceSap; mFragmentLength = -1; } SnepServer(String serviceName, int serviceSap, int fragmentLength, Callback callback) { mCallback = callback; mServiceName = serviceName; mServiceSap = serviceSap; mFragmentLength = fragmentLength; } /** Connection class, used to handle incoming connections */ private class ConnectionThread extends Thread { private final LlcpSocket mSock; private final SnepMessenger mMessager; ConnectionThread(LlcpSocket socket, int fragmentLength) { super(TAG); mSock = socket; mMessager = new SnepMessenger(false, socket, fragmentLength); } @Override public void run() { if (DBG) Log.d(TAG, "starting connection thread"); try { boolean running; synchronized (SnepServer.this) { running = mServerRunning; } while (running) { if (!handleRequest(mMessager, mCallback)) { break; } synchronized (SnepServer.this) { running = mServerRunning; } } } catch (IOException e) { if (DBG) Log.e(TAG, "Closing from IOException"); } finally { try { if (DBG) Log.d(TAG, "about to close"); mSock.close(); } catch (IOException e) { // ignore } } if (DBG) Log.d(TAG, "finished connection thread"); } } static boolean handleRequest(SnepMessenger messenger, Callback callback) throws IOException { SnepMessage request; try { request = messenger.getMessage(); } catch (SnepException e) { if (DBG) Log.w(TAG, "Bad snep message", e); try { messenger.sendMessage(SnepMessage.getMessage( SnepMessage.RESPONSE_BAD_REQUEST)); } catch (IOException e2) { // Ignore } return false; } if (((request.getVersion() & 0xF0) >> 4) != SnepMessage.VERSION_MAJOR) { messenger.sendMessage(SnepMessage.getMessage( SnepMessage.RESPONSE_UNSUPPORTED_VERSION)); } else if (request.getField() == SnepMessage.REQUEST_GET) { messenger.sendMessage(callback.doGet(request.getAcceptableLength(), request.getNdefMessage())); } else if (request.getField() == SnepMessage.REQUEST_PUT) { if (DBG) Log.d(TAG, "putting message " + request.toString()); messenger.sendMessage(callback.doPut(request.getNdefMessage())); } else { if (DBG) Log.d(TAG, "Unknown request (" + request.getField() +")"); messenger.sendMessage(SnepMessage.getMessage( SnepMessage.RESPONSE_BAD_REQUEST)); } return true; } /** Server class, used to listen for incoming connection request */ class ServerThread extends Thread { private boolean mThreadRunning = true; LlcpServerSocket mServerSocket; @Override public void run() { boolean threadRunning; synchronized (SnepServer.this) { threadRunning = mThreadRunning; } while (threadRunning) { if (DBG) Log.d(TAG, "about create LLCP service socket"); try { synchronized (SnepServer.this) { mServerSocket = NfcService.getInstance().createLlcpServerSocket(mServiceSap, mServiceName, MIU, 1, 1024); } if (mServerSocket == null) { if (DBG) Log.d(TAG, "failed to create LLCP service socket"); return; } if (DBG) Log.d(TAG, "created LLCP service socket"); synchronized (SnepServer.this) { threadRunning = mThreadRunning; } while (threadRunning) { LlcpServerSocket serverSocket; synchronized (SnepServer.this) { serverSocket = mServerSocket; } if (serverSocket == null) { if (DBG) Log.d(TAG, "Server socket shut down."); return; } if (DBG) Log.d(TAG, "about to accept"); LlcpSocket communicationSocket = serverSocket.accept(); if (DBG) Log.d(TAG, "accept returned " + communicationSocket); if (communicationSocket != null) { int miu = communicationSocket.getRemoteMiu(); int fragmentLength = (mFragmentLength == -1) ? miu : Math.min(miu, mFragmentLength); new ConnectionThread(communicationSocket, fragmentLength).start(); } synchronized (SnepServer.this) { threadRunning = mThreadRunning; } } if (DBG) Log.d(TAG, "stop running"); } catch (LlcpException e) { Log.e(TAG, "llcp error", e); } catch (IOException e) { Log.e(TAG, "IO error", e); } finally { synchronized (SnepServer.this) { if (mServerSocket != null) { if (DBG) Log.d(TAG, "about to close"); try { mServerSocket.close(); } catch (IOException e) { // ignore } mServerSocket = null; } } } synchronized (SnepServer.this) { threadRunning = mThreadRunning; } } } public void shutdown() { synchronized (SnepServer.this) { mThreadRunning = false; if (mServerSocket != null) { try { mServerSocket.close(); } catch (IOException e) { // ignore } mServerSocket = null; } } } } public void start() { synchronized (SnepServer.this) { if (DBG) Log.d(TAG, "start, thread = " + mServerThread); if (mServerThread == null) { if (DBG) Log.d(TAG, "starting new server thread"); mServerThread = new ServerThread(); mServerThread.start(); mServerRunning = true; } } } public void stop() { synchronized (SnepServer.this) { if (DBG) Log.d(TAG, "stop, thread = " + mServerThread); if (mServerThread != null) { if (DBG) Log.d(TAG, "shuting down server thread"); mServerThread.shutdown(); mServerThread = null; mServerRunning = false; } } } }