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