View Javadoc

1   /**
2    *  BlueCove - Java library for Bluetooth
3    *  Copyright (C) 2007-2008 Vlad Skarzhevskyy
4    *
5    *  Licensed to the Apache Software Foundation (ASF) under one
6    *  or more contributor license agreements.  See the NOTICE file
7    *  distributed with this work for additional information
8    *  regarding copyright ownership.  The ASF licenses this file
9    *  to you under the Apache License, Version 2.0 (the
10   *  "License"); you may not use this file except in compliance
11   *  with the License.  You may obtain a copy of the License at
12   *
13   *    http://www.apache.org/licenses/LICENSE-2.0
14   *
15   *  Unless required by applicable law or agreed to in writing,
16   *  software distributed under the License is distributed on an
17   *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18   *  KIND, either express or implied.  See the License for the
19   *  specific language governing permissions and limitations
20   *  under the License.
21   *
22   *  @author vlads
23   *  @version $Id: OBEXServerSessionImpl.java 2643 2008-12-23 00:24:46Z skarzhevskyy $
24   */
25  package com.intel.bluetooth.obex;
26  
27  import java.io.EOFException;
28  import java.io.IOException;
29  
30  import javax.microedition.io.StreamConnection;
31  import javax.obex.Authenticator;
32  import javax.obex.ResponseCodes;
33  import javax.obex.ServerRequestHandler;
34  
35  import com.intel.bluetooth.BlueCoveImpl;
36  import com.intel.bluetooth.BluetoothServerConnection;
37  import com.intel.bluetooth.DebugLog;
38  import com.intel.bluetooth.UtilsJavaSE;
39  
40  class OBEXServerSessionImpl extends OBEXSessionBase implements Runnable, BluetoothServerConnection {
41  
42  	private ServerRequestHandler handler;
43  
44  	private OBEXServerOperation operation;
45  
46  	private boolean closeRequested = false;
47  
48  	private volatile boolean delayClose = false;
49  
50  	private Object canCloseEvent = new Object();
51  
52  	private Object stackID;
53  
54  	private Thread handlerThread;
55  
56  	private static int threadNumber;
57  
58  	private static synchronized int nextThreadNum() {
59  		return threadNumber++;
60  	}
61  
62  	static int errorCount = 0;
63  
64  	OBEXServerSessionImpl(StreamConnection connection, ServerRequestHandler handler, Authenticator authenticator,
65  			OBEXConnectionParams obexConnectionParams) throws IOException {
66  		super(connection, obexConnectionParams);
67  		this.requestSent = true;
68  		this.handler = handler;
69  		this.authenticator = authenticator;
70  		stackID = BlueCoveImpl.getCurrentThreadBluetoothStackID();
71  		handlerThread = new Thread(this, "OBEXServerSessionThread-" + nextThreadNum());
72  		UtilsJavaSE.threadSetDaemon(handlerThread);
73  	}
74  
75  	void startSessionHandlerThread() {
76  		handlerThread.start();
77  	}
78  
79  	public void run() {
80  		// Let the acceptAndOpen return to the caller.
81  		Thread.yield();
82  		try {
83  			if (stackID != null) {
84  				BlueCoveImpl.setThreadBluetoothStackID(stackID);
85  			}
86  			while (!isClosed() && !closeRequested) {
87  				if (!handleRequest()) {
88  					return;
89  				}
90  			}
91  		} catch (Throwable e) {
92  			synchronized (OBEXServerSessionImpl.class) {
93  				errorCount++;
94  			}
95  			if (this.isConnected) {
96  				DebugLog.error("OBEXServerSession error", e);
97  			} else {
98  				DebugLog.debug("OBEXServerSession error", e);
99  			}
100 		} finally {
101 			DebugLog.debug("OBEXServerSession ends");
102 			try {
103 				super.close();
104 			} catch (IOException e) {
105 				DebugLog.debug("OBEXServerSession close error", e);
106 			}
107 		}
108 	}
109 
110 	public void close() throws IOException {
111 		closeRequested = true;
112 		while (delayClose) {
113 			synchronized (canCloseEvent) {
114 				try {
115 					if (delayClose) {
116 						canCloseEvent.wait(700);
117 					}
118 				} catch (InterruptedException e) {
119 				}
120 				delayClose = false;
121 			}
122 		}
123 		if (!isClosed()) {
124 			DebugLog.debug("OBEXServerSession close");
125 			// (new Throwable()).printStackTrace();
126 			if (operation != null) {
127 				operation.close();
128 				operation = null;
129 			}
130 		}
131 		super.close();
132 	}
133 
134 	private boolean handleRequest() throws IOException {
135 		DebugLog.debug("OBEXServerSession handleRequest");
136 		delayClose = false;
137 		byte[] b;
138 		try {
139 			b = readPacket();
140 		} catch (EOFException e) {
141 			if (isConnected) {
142 				throw e;
143 			}
144 			DebugLog.debug("OBEXServerSession got EOF");
145 			close();
146 			return false;
147 		}
148 		delayClose = true;
149 		try {
150 			int opcode = b[0] & 0xFF;
151 			boolean finalPacket = ((opcode & OBEXOperationCodes.FINAL_BIT) != 0);
152 			if (finalPacket) {
153 				DebugLog.debug("OBEXServerSession got operation finalPacket");
154 			}
155 			switch (opcode) {
156 			case OBEXOperationCodes.CONNECT:
157 				processConnect(b);
158 				break;
159 			case OBEXOperationCodes.DISCONNECT:
160 				processDisconnect(b);
161 				break;
162 			case OBEXOperationCodes.PUT_FINAL:
163 			case OBEXOperationCodes.PUT:
164 				processPut(b, finalPacket);
165 				break;
166 			case OBEXOperationCodes.SETPATH | OBEXOperationCodes.FINAL_BIT:
167 			case OBEXOperationCodes.SETPATH:
168 				processSetPath(b, finalPacket);
169 				break;
170 			case OBEXOperationCodes.ABORT:
171 				processAbort();
172 				break;
173 			case OBEXOperationCodes.GET_FINAL:
174 			case OBEXOperationCodes.GET:
175 				processGet(b, finalPacket);
176 				break;
177 			default:
178 				writePacket(ResponseCodes.OBEX_HTTP_NOT_IMPLEMENTED, null);
179 			}
180 		} finally {
181 			delayClose = false;
182 		}
183 		synchronized (canCloseEvent) {
184 			canCloseEvent.notifyAll();
185 		}
186 		return true;
187 	}
188 
189 	private void processConnect(byte[] b) throws IOException {
190 		DebugLog.debug("Connect operation");
191 		if (b[3] != OBEXOperationCodes.OBEX_VERSION) {
192 			throw new IOException("Unsupported client OBEX version " + b[3]);
193 		}
194 		if (b.length < 7) {
195 			throw new IOException("Corrupted OBEX data");
196 		}
197 		int requestedMTU = OBEXUtils.bytesToShort(b[5], b[6]);
198 		if (requestedMTU < OBEXOperationCodes.OBEX_MINIMUM_MTU) {
199 			throw new IOException("Invalid MTU " + requestedMTU);
200 		}
201 		this.mtu = requestedMTU;
202 		DebugLog.debug("mtu selected", this.mtu);
203 
204 		int rc;
205 		OBEXHeaderSetImpl replyHeaders = createOBEXHeaderSetImpl();
206 		OBEXHeaderSetImpl requestHeaders = OBEXHeaderSetImpl.readHeaders(b, 7);
207 		if (!handleAuthenticationResponse(requestHeaders)) {
208 			rc = ResponseCodes.OBEX_HTTP_UNAUTHORIZED;
209 		} else {
210 			handleAuthenticationChallenge(requestHeaders, (OBEXHeaderSetImpl) replyHeaders);
211 			rc = ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
212 			try {
213 				rc = handler.onConnect(requestHeaders, replyHeaders);
214 			} catch (Throwable e) {
215 				DebugLog.error("onConnect", e);
216 			}
217 		}
218 		byte[] connectResponse = new byte[4];
219 		connectResponse[0] = OBEXOperationCodes.OBEX_VERSION;
220 		connectResponse[1] = 0; /* Flags */
221 		connectResponse[2] = OBEXUtils.hiByte(obexConnectionParams.mtu);
222 		connectResponse[3] = OBEXUtils.loByte(obexConnectionParams.mtu);
223 		writePacketWithFlags(rc, connectResponse, replyHeaders);
224 		if (rc == ResponseCodes.OBEX_HTTP_OK) {
225 			this.isConnected = true;
226 		}
227 	}
228 
229 	boolean handleAuthenticationResponse(OBEXHeaderSetImpl incomingHeaders) throws IOException {
230 		return handleAuthenticationResponse(incomingHeaders, handler);
231 	}
232 
233 	private boolean validateConnection() throws IOException {
234 		if (this.isConnected) {
235 			return true;
236 		}
237 		writePacket(ResponseCodes.OBEX_HTTP_BAD_REQUEST, null);
238 		return false;
239 	}
240 
241 	private void processDisconnect(byte[] b) throws IOException {
242 		DebugLog.debug("Disconnect operation");
243 		if (!validateConnection()) {
244 			return;
245 		}
246 		OBEXHeaderSetImpl requestHeaders = OBEXHeaderSetImpl.readHeaders(b, 3);
247 		OBEXHeaderSetImpl replyHeaders = createOBEXHeaderSetImpl();
248 		int rc = ResponseCodes.OBEX_HTTP_OK;
249 		try {
250 			handler.onDisconnect(requestHeaders, replyHeaders);
251 		} catch (Throwable e) {
252 			rc = ResponseCodes.OBEX_HTTP_UNAVAILABLE;
253 			DebugLog.error("onDisconnect", e);
254 		}
255 		this.isConnected = false;
256 		writePacket(rc, replyHeaders);
257 	}
258 
259 	private void processDelete(OBEXHeaderSetImpl requestHeaders) throws IOException {
260 		DebugLog.debug("Delete operation");
261 		OBEXHeaderSetImpl replyHeaders = createOBEXHeaderSetImpl();
262 		handleAuthenticationChallenge(requestHeaders, replyHeaders);
263 		int rc = ResponseCodes.OBEX_HTTP_OK;
264 		try {
265 			rc = handler.onDelete(requestHeaders, replyHeaders);
266 		} catch (Throwable e) {
267 			rc = ResponseCodes.OBEX_HTTP_UNAVAILABLE;
268 			DebugLog.error("onDelete", e);
269 		}
270 		writePacket(rc, replyHeaders);
271 	}
272 
273 	private void processPut(byte[] b, boolean finalPacket) throws IOException {
274 		DebugLog.debug("Put/Delete operation");
275 		if (!validateConnection()) {
276 			return;
277 		}
278 		OBEXHeaderSetImpl requestHeaders = OBEXHeaderSetImpl.readHeaders(b, 3);
279 		// OFF; Not tested in TCK.
280 		// while ((!finalPacket) && (!operation.isIncommingDataReceived())) {
281 		// finalPacket = operation.exchangeRequestPhasePackets();
282 		// }
283 		// if (operation.isErrorReceived()) {
284 		// return;
285 		// }
286 
287 		// If Client re-send the command packet with an Authenticate Response
288 		if (!handleAuthenticationResponse(requestHeaders, handler)) {
289 			writePacket(ResponseCodes.OBEX_HTTP_UNAUTHORIZED, null);
290 			return;
291 		}
292 		// A PUT operation with NO Body or End-of-Body headers whatsoever should
293 		// be treated as a delete request.
294 		if (finalPacket && (!requestHeaders.hasIncommingData())) {
295 			processDelete(requestHeaders);
296 			return;
297 		}
298 		DebugLog.debug("Put operation");
299 		operation = new OBEXServerOperationPut(this, requestHeaders, finalPacket);
300 		try {
301 			int rc = ResponseCodes.OBEX_HTTP_OK;
302 			try {
303 				rc = handler.onPut(operation);
304 			} catch (Throwable e) {
305 				rc = ResponseCodes.OBEX_HTTP_UNAVAILABLE;
306 				DebugLog.error("onPut", e);
307 			}
308 			if (!operation.isAborted) {
309 				operation.writeResponse(rc);
310 			}
311 		} finally {
312 			operation.close();
313 			operation = null;
314 		}
315 	}
316 
317 	private void processGet(byte[] b, boolean finalPacket) throws IOException {
318 		DebugLog.debug("Get operation");
319 		if (!validateConnection()) {
320 			return;
321 		}
322 		OBEXHeaderSetImpl requestHeaders = OBEXHeaderSetImpl.readHeaders(b, 3);
323 		// If Client re-send the command packet with an Authenticate Response
324 		if (!handleAuthenticationResponse(requestHeaders, handler)) {
325 			writePacket(ResponseCodes.OBEX_HTTP_UNAUTHORIZED, null);
326 			return;
327 		}
328 
329 		operation = new OBEXServerOperationGet(this, requestHeaders, finalPacket);
330 
331 		// OFF; Not tested in TCK
332 		// while ((!finalPacket) && (!operation.isIncommingDataReceived())) {
333 		// finalPacket = operation.exchangeRequestPhasePackets();
334 		// }
335 		// if (operation.isErrorReceived()) {
336 		// return;
337 		// }
338 		try {
339 			int rc = ResponseCodes.OBEX_HTTP_OK;
340 			try {
341 				rc = handler.onGet(operation);
342 			} catch (Throwable e) {
343 				rc = ResponseCodes.OBEX_HTTP_UNAVAILABLE;
344 				DebugLog.error("onGet", e);
345 			}
346 			if (!operation.isAborted) {
347 				operation.writeResponse(rc);
348 			}
349 		} finally {
350 			operation.close();
351 			operation = null;
352 		}
353 	}
354 
355 	private void processAbort() throws IOException {
356 		DebugLog.debug("Abort operation");
357 		if (!validateConnection()) {
358 			return;
359 		}
360 		if (operation != null) {
361 			operation.isAborted = true;
362 			operation.close();
363 			operation = null;
364 			writePacket(OBEXOperationCodes.OBEX_RESPONSE_SUCCESS, null);
365 		} else {
366 			writePacket(ResponseCodes.OBEX_HTTP_BAD_REQUEST, null);
367 		}
368 	}
369 
370 	private void processSetPath(byte[] b, boolean finalPacket) throws IOException {
371 		DebugLog.debug("SetPath operation");
372 		if (!validateConnection()) {
373 			return;
374 		}
375 		if (b.length < 5) {
376 			throw new IOException("Corrupted OBEX data");
377 		}
378 		OBEXHeaderSetImpl requestHeaders = OBEXHeaderSetImpl.readHeaders(b, 5);
379 		// DebugLog.debug("setPath b[3]", b[3]);
380 		// b[4] = (byte) ((backup?1:0) | (create?0:2));
381 		boolean backup = ((b[3] & 1) != 0);
382 		boolean create = ((b[3] & 2) == 0);
383 		DebugLog.debug("setPath backup", backup);
384 		DebugLog.debug("setPath create", create);
385 
386 		if (!handleAuthenticationResponse(requestHeaders, handler)) {
387 			writePacket(ResponseCodes.OBEX_HTTP_UNAUTHORIZED, null);
388 			return;
389 		}
390 
391 		OBEXHeaderSetImpl replyHeaders = createOBEXHeaderSetImpl();
392 		handleAuthenticationChallenge(requestHeaders, replyHeaders);
393 		int rc = ResponseCodes.OBEX_HTTP_OK;
394 		try {
395 			rc = handler.onSetPath(requestHeaders, replyHeaders, backup, create);
396 		} catch (Throwable e) {
397 			rc = ResponseCodes.OBEX_HTTP_UNAVAILABLE;
398 			DebugLog.error("onSetPath", e);
399 		}
400 		writePacket(rc, replyHeaders);
401 	}
402 
403 }