/** * BlueCove - Java library for Bluetooth * Copyright (C) 2007-2009 Vlad Skarzhevskyy * * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you 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. * * @author vlads * @version $Id$ */ package com.intel.bluetooth.obex; import java.io.EOFException; import java.io.IOException; import javax.microedition.io.StreamConnection; import javax.obex.Authenticator; import javax.obex.ResponseCodes; import javax.obex.ServerRequestHandler; import com.intel.bluetooth.BlueCoveImpl; import com.intel.bluetooth.BluetoothServerConnection; import com.intel.bluetooth.DebugLog; import com.intel.bluetooth.UtilsJavaSE; class OBEXServerSessionImpl extends OBEXSessionBase implements Runnable, BluetoothServerConnection { private ServerRequestHandler handler; private OBEXServerOperation operation; private boolean closeRequested = false; private volatile boolean delayClose = false; private Object canCloseEvent = new Object(); private Object stackID; private Thread handlerThread; private static int threadNumber; private static synchronized int nextThreadNum() { return threadNumber++; } static int errorCount = 0; OBEXServerSessionImpl(StreamConnection connection, ServerRequestHandler handler, Authenticator authenticator, OBEXConnectionParams obexConnectionParams) throws IOException { super(connection, obexConnectionParams); this.requestSent = true; this.handler = handler; this.authenticator = authenticator; stackID = BlueCoveImpl.getCurrentThreadBluetoothStackID(); handlerThread = new Thread(this, "OBEXServerSessionThread-" + nextThreadNum()); UtilsJavaSE.threadSetDaemon(handlerThread); } void startSessionHandlerThread() { handlerThread.start(); } public void run() { // Let the acceptAndOpen return to the caller. Thread.yield(); try { if (stackID != null) { BlueCoveImpl.setThreadBluetoothStackID(stackID); } while (!isClosed() && !closeRequested) { if (!handleRequest()) { return; } } } catch (Throwable e) { synchronized (OBEXServerSessionImpl.class) { errorCount++; } if (this.isConnected) { DebugLog.error("OBEXServerSession error", e); } else { DebugLog.debug("OBEXServerSession error", e); } } finally { DebugLog.debug("OBEXServerSession ends"); try { super.close(); } catch (IOException e) { DebugLog.debug("OBEXServerSession close error", e); } } } public void close() throws IOException { closeRequested = true; while (delayClose) { synchronized (canCloseEvent) { try { if (delayClose) { canCloseEvent.wait(700); } } catch (InterruptedException e) { } delayClose = false; } } if (!isClosed()) { DebugLog.debug("OBEXServerSession close"); // (new Throwable()).printStackTrace(); if (operation != null) { operation.close(); operation = null; } } super.close(); } private boolean handleRequest() throws IOException { DebugLog.debug("OBEXServerSession handleRequest"); delayClose = false; byte[] b; try { b = readPacket(); } catch (EOFException e) { if (isConnected) { throw e; } DebugLog.debug("OBEXServerSession got EOF"); close(); return false; } delayClose = true; try { int opcode = b[0] & 0xFF; boolean finalPacket = ((opcode & OBEXOperationCodes.FINAL_BIT) != 0); if (finalPacket) { DebugLog.debug("OBEXServerSession got operation finalPacket"); } switch (opcode) { case OBEXOperationCodes.CONNECT: processConnect(b); break; case OBEXOperationCodes.DISCONNECT: processDisconnect(b); break; case OBEXOperationCodes.PUT_FINAL: case OBEXOperationCodes.PUT: processPut(b, finalPacket); break; case OBEXOperationCodes.SETPATH | OBEXOperationCodes.FINAL_BIT: case OBEXOperationCodes.SETPATH: processSetPath(b, finalPacket); break; case OBEXOperationCodes.ABORT: processAbort(); break; case OBEXOperationCodes.GET_FINAL: case OBEXOperationCodes.GET: processGet(b, finalPacket); break; default: writePacket(ResponseCodes.OBEX_HTTP_NOT_IMPLEMENTED, null); } } finally { delayClose = false; } synchronized (canCloseEvent) { canCloseEvent.notifyAll(); } return true; } private void processConnect(byte[] b) throws IOException { DebugLog.debug("Connect operation"); if (b[3] != OBEXOperationCodes.OBEX_VERSION) { throw new IOException("Unsupported client OBEX version " + b[3]); } if (b.length < 7) { throw new IOException("Corrupted OBEX data"); } int requestedMTU = OBEXUtils.bytesToShort(b[5], b[6]); if (requestedMTU < OBEXOperationCodes.OBEX_MINIMUM_MTU) { throw new IOException("Invalid MTU " + requestedMTU); } this.mtu = requestedMTU; DebugLog.debug("mtu selected", this.mtu); int rc; OBEXHeaderSetImpl replyHeaders = createOBEXHeaderSetImpl(); OBEXHeaderSetImpl requestHeaders = OBEXHeaderSetImpl.readHeaders(b, 7); if (!handleAuthenticationResponse(requestHeaders)) { rc = ResponseCodes.OBEX_HTTP_UNAUTHORIZED; } else { handleAuthenticationChallenge(requestHeaders, (OBEXHeaderSetImpl) replyHeaders); rc = ResponseCodes.OBEX_HTTP_INTERNAL_ERROR; try { rc = handler.onConnect(requestHeaders, replyHeaders); } catch (Throwable e) { DebugLog.error("onConnect", e); } } byte[] connectResponse = new byte[4]; connectResponse[0] = OBEXOperationCodes.OBEX_VERSION; connectResponse[1] = 0; /* Flags */ connectResponse[2] = OBEXUtils.hiByte(obexConnectionParams.mtu); connectResponse[3] = OBEXUtils.loByte(obexConnectionParams.mtu); writePacketWithFlags(rc, connectResponse, replyHeaders); if (rc == ResponseCodes.OBEX_HTTP_OK) { this.isConnected = true; } } boolean handleAuthenticationResponse(OBEXHeaderSetImpl incomingHeaders) throws IOException { return handleAuthenticationResponse(incomingHeaders, handler); } private boolean validateConnection() throws IOException { if (this.isConnected) { return true; } writePacket(ResponseCodes.OBEX_HTTP_BAD_REQUEST, null); return false; } private void processDisconnect(byte[] b) throws IOException { DebugLog.debug("Disconnect operation"); if (!validateConnection()) { return; } OBEXHeaderSetImpl requestHeaders = OBEXHeaderSetImpl.readHeaders(b, 3); OBEXHeaderSetImpl replyHeaders = createOBEXHeaderSetImpl(); int rc = ResponseCodes.OBEX_HTTP_OK; try { handler.onDisconnect(requestHeaders, replyHeaders); } catch (Throwable e) { rc = ResponseCodes.OBEX_HTTP_UNAVAILABLE; DebugLog.error("onDisconnect", e); } this.isConnected = false; writePacket(rc, replyHeaders); } private void processDelete(OBEXHeaderSetImpl requestHeaders) throws IOException { DebugLog.debug("Delete operation"); OBEXHeaderSetImpl replyHeaders = createOBEXHeaderSetImpl(); handleAuthenticationChallenge(requestHeaders, replyHeaders); int rc = ResponseCodes.OBEX_HTTP_OK; try { rc = handler.onDelete(requestHeaders, replyHeaders); } catch (Throwable e) { rc = ResponseCodes.OBEX_HTTP_UNAVAILABLE; DebugLog.error("onDelete", e); } writePacket(rc, replyHeaders); } private void processPut(byte[] b, boolean finalPacket) throws IOException { DebugLog.debug("Put/Delete operation"); if (!validateConnection()) { return; } OBEXHeaderSetImpl requestHeaders = OBEXHeaderSetImpl.readHeaders(b, 3); // OFF; Not tested in TCK. // while ((!finalPacket) && (!operation.isIncommingDataReceived())) { // finalPacket = operation.exchangeRequestPhasePackets(); // } // if (operation.isErrorReceived()) { // return; // } // If Client re-send the command packet with an Authenticate Response if (!handleAuthenticationResponse(requestHeaders, handler)) { writePacket(ResponseCodes.OBEX_HTTP_UNAUTHORIZED, null); return; } // A PUT operation with NO Body or End-of-Body headers whatsoever should // be treated as a delete request. if (finalPacket && (!requestHeaders.hasIncommingData())) { processDelete(requestHeaders); return; } DebugLog.debug("Put operation"); operation = new OBEXServerOperationPut(this, requestHeaders, finalPacket); try { int rc = ResponseCodes.OBEX_HTTP_OK; try { rc = handler.onPut(operation); } catch (Throwable e) { rc = ResponseCodes.OBEX_HTTP_UNAVAILABLE; DebugLog.error("onPut", e); } if (!operation.isAborted) { operation.writeResponse(rc); } } finally { operation.close(); operation = null; } } private void processGet(byte[] b, boolean finalPacket) throws IOException { DebugLog.debug("Get operation"); if (!validateConnection()) { return; } OBEXHeaderSetImpl requestHeaders = OBEXHeaderSetImpl.readHeaders(b, 3); // If Client re-send the command packet with an Authenticate Response if (!handleAuthenticationResponse(requestHeaders, handler)) { writePacket(ResponseCodes.OBEX_HTTP_UNAUTHORIZED, null); return; } operation = new OBEXServerOperationGet(this, requestHeaders, finalPacket); // OFF; Not tested in TCK // while ((!finalPacket) && (!operation.isIncommingDataReceived())) { // finalPacket = operation.exchangeRequestPhasePackets(); // } // if (operation.isErrorReceived()) { // return; // } try { int rc = ResponseCodes.OBEX_HTTP_OK; try { rc = handler.onGet(operation); } catch (Throwable e) { rc = ResponseCodes.OBEX_HTTP_UNAVAILABLE; DebugLog.error("onGet", e); } if (!operation.isAborted) { operation.writeResponse(rc); } } finally { operation.close(); operation = null; } } private void processAbort() throws IOException { DebugLog.debug("Abort operation"); if (!validateConnection()) { return; } if (operation != null) { operation.isAborted = true; operation.close(); operation = null; writePacket(OBEXOperationCodes.OBEX_RESPONSE_SUCCESS, null); } else { writePacket(ResponseCodes.OBEX_HTTP_BAD_REQUEST, null); } } private void processSetPath(byte[] b, boolean finalPacket) throws IOException { DebugLog.debug("SetPath operation"); if (!validateConnection()) { return; } if (b.length < 5) { throw new IOException("Corrupted OBEX data"); } OBEXHeaderSetImpl requestHeaders = OBEXHeaderSetImpl.readHeaders(b, 5); // DebugLog.debug("setPath b[3]", b[3]); // b[4] = (byte) ((backup?1:0) | (create?0:2)); boolean backup = ((b[3] & 1) != 0); boolean create = ((b[3] & 2) == 0); DebugLog.debug("setPath backup", backup); DebugLog.debug("setPath create", create); if (!handleAuthenticationResponse(requestHeaders, handler)) { writePacket(ResponseCodes.OBEX_HTTP_UNAUTHORIZED, null); return; } OBEXHeaderSetImpl replyHeaders = createOBEXHeaderSetImpl(); handleAuthenticationChallenge(requestHeaders, replyHeaders); int rc = ResponseCodes.OBEX_HTTP_OK; try { rc = handler.onSetPath(requestHeaders, replyHeaders, backup, create); } catch (Throwable e) { rc = ResponseCodes.OBEX_HTTP_UNAVAILABLE; DebugLog.error("onSetPath", e); } writePacket(rc, replyHeaders); } }