/** * 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.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.Enumeration; import java.util.Vector; import javax.bluetooth.RemoteDevice; import javax.bluetooth.ServiceRecord; import javax.microedition.io.Connection; import javax.microedition.io.StreamConnection; import javax.obex.Authenticator; import javax.obex.HeaderSet; import javax.obex.ServerRequestHandler; import com.intel.bluetooth.BluetoothConnectionAccess; import com.intel.bluetooth.BluetoothStack; import com.intel.bluetooth.DebugLog; import com.intel.bluetooth.obex.OBEXAuthentication.Challenge; /** * Base for Client and Server implementations. See <a * href="http://bluetooth.com/Bluetooth/Learn/Technology/Specifications/" >Bluetooth * Specification Documents</A> for details. * */ abstract class OBEXSessionBase implements Connection, BluetoothConnectionAccess { protected boolean isConnected; private StreamConnection conn; private InputStream is; private OutputStream os; protected long connectionID; protected int mtu = OBEXOperationCodes.OBEX_DEFAULT_MTU; protected Authenticator authenticator; protected final OBEXConnectionParams obexConnectionParams; protected int packetsCountWrite; protected int packetsCountRead; private Vector authChallengesSent; /** * Each request packet flowed by response. This flag is from Client point of view */ protected boolean requestSent; public OBEXSessionBase(StreamConnection conn, OBEXConnectionParams obexConnectionParams) throws IOException { if (obexConnectionParams == null) { throw new NullPointerException("obexConnectionParams is null"); } this.isConnected = false; this.conn = conn; this.obexConnectionParams = obexConnectionParams; this.mtu = obexConnectionParams.mtu; this.connectionID = -1; this.packetsCountWrite = 0; this.packetsCountRead = 0; boolean initOK = false; try { this.os = conn.openOutputStream(); this.is = conn.openInputStream(); initOK = true; } finally { if (!initOK) { try { this.close(); } catch (IOException e) { DebugLog.error("close error", e); } } } } public void close() throws IOException { StreamConnection c = this.conn; this.conn = null; try { try { if (this.is != null) { this.is.close(); this.is = null; } } finally { // Close output even if input can't be closed. if (this.os != null) { this.os.close(); this.os = null; } } } finally { // Close connection even if streams can't be closed. if (c != null) { c.close(); } } } static OBEXHeaderSetImpl createOBEXHeaderSetImpl() { return new OBEXHeaderSetImpl(); } public static HeaderSet createOBEXHeaderSet() { return createOBEXHeaderSetImpl(); } static void validateCreatedHeaderSet(HeaderSet headers) { OBEXHeaderSetImpl.validateCreatedHeaderSet(headers); } protected void writePacket(int commId, OBEXHeaderSetImpl headers) throws IOException { writePacketWithFlags(commId, null, headers); } protected synchronized void writePacketWithFlags(int commId, byte[] headerFlagsData, OBEXHeaderSetImpl headers) throws IOException { if (this.requestSent) { throw new IOException("Write packet out of order"); } this.requestSent = true; int len = 3; if (this.connectionID != -1) { len += 5; } if (headerFlagsData != null) { len += headerFlagsData.length; } byte[] data = null; if (headers != null) { data = OBEXHeaderSetImpl.toByteArray(headers); len += data.length; } if (len > mtu) { throw new IOException("Can't sent more data than in MTU, len=" + len + ", mtu=" + mtu); } this.packetsCountWrite++; ByteArrayOutputStream buf = new ByteArrayOutputStream(); OBEXHeaderSetImpl.writeObexLen(buf, commId, len); if (headerFlagsData != null) { buf.write(headerFlagsData); } if (this.connectionID != -1) { OBEXHeaderSetImpl.writeObexInt(buf, OBEXHeaderSetImpl.OBEX_HDR_CONNECTION, this.connectionID); } if (data != null) { buf.write(data); } DebugLog.debug0x("obex send (" + this.packetsCountWrite + ")", OBEXUtils.toStringObexResponseCodes(commId), commId); os.write(buf.toByteArray()); os.flush(); DebugLog.debug("obex sent (" + this.packetsCountWrite + ") len", len); if ((headers != null) && (headers.hasAuthenticationChallenge())) { if (authChallengesSent == null) { authChallengesSent = new Vector(); } for (Enumeration iter = headers.getAuthenticationChallenges(); iter.hasMoreElements();) { byte[] authChallenge = (byte[]) iter.nextElement(); Challenge challenge = new Challenge(authChallenge); authChallengesSent.addElement(challenge); } } } protected synchronized byte[] readPacket() throws IOException { if (!this.requestSent) { throw new IOException("Read packet out of order"); } this.requestSent = false; byte[] header = new byte[3]; OBEXUtils.readFully(is, obexConnectionParams, header); this.packetsCountRead++; DebugLog.debug0x("obex received (" + this.packetsCountRead + ")", OBEXUtils.toStringObexResponseCodes(header[0]), header[0] & 0xFF); int lenght = OBEXUtils.bytesToShort(header[1], header[2]); if (lenght == 3) { return header; } if ((lenght < 3) || (lenght > OBEXOperationCodes.OBEX_MAX_PACKET_LEN)) { throw new IOException("Invalid packet length " + lenght); } byte[] data = new byte[lenght]; System.arraycopy(header, 0, data, 0, header.length); OBEXUtils.readFully(is, obexConnectionParams, data, header.length, lenght - header.length); if (is.available() > 0) { DebugLog.debug("has more data after read", is.available()); } return data; } private void validateBluetoothConnection() { if ((conn != null) && !(conn instanceof BluetoothConnectionAccess)) { throw new IllegalArgumentException("Not a Bluetooth connection " + conn.getClass().getName()); } } void validateAuthenticationResponse(OBEXHeaderSetImpl requestHeaders, OBEXHeaderSetImpl incomingHeaders) throws IOException { if ((requestHeaders != null) && requestHeaders.hasAuthenticationChallenge() && (!incomingHeaders.hasAuthenticationResponses())) { // TODO verify that this appropriate Exception throw new IOException("Authentication response is missing"); } handleAuthenticationResponse(incomingHeaders, null); } boolean handleAuthenticationResponse(OBEXHeaderSetImpl incomingHeaders, ServerRequestHandler serverHandler) throws IOException { if (incomingHeaders.hasAuthenticationResponses()) { if (authenticator == null) { throw new IOException("Authenticator required for authentication"); } if ((authChallengesSent == null) && (authChallengesSent.size() == 0)) { throw new IOException("Authentication challenges had not been sent"); } boolean authenticated = false; try { authenticated = OBEXAuthentication.handleAuthenticationResponse(incomingHeaders, authenticator, serverHandler, authChallengesSent); } finally { if ((authenticated) && (authChallengesSent != null)) { authChallengesSent.removeAllElements(); } } return authenticated; } else { if ((authChallengesSent != null) && (authChallengesSent.size() > 0)) { throw new IOException("Authentication response is missing"); } return true; } } void handleAuthenticationChallenge(OBEXHeaderSetImpl incomingHeaders, OBEXHeaderSetImpl replyHeaders) throws IOException { if (incomingHeaders.hasAuthenticationChallenge()) { if (authenticator == null) { throw new IOException("Authenticator required for authentication"); } OBEXAuthentication.handleAuthenticationChallenge(incomingHeaders, replyHeaders, authenticator); } } /* * (non-Javadoc) * * @see com.intel.bluetooth.BluetoothConnectionAccess#getRemoteAddress() */ public long getRemoteAddress() throws IOException { validateBluetoothConnection(); if (conn == null) { throw new IOException("Connection closed"); } return ((BluetoothConnectionAccess) conn).getRemoteAddress(); } /* * (non-Javadoc) * * @see com.intel.bluetooth.BluetoothConnectionAccess#getRemoteDevice() */ public RemoteDevice getRemoteDevice() { validateBluetoothConnection(); if (conn == null) { return null; } return ((BluetoothConnectionAccess) conn).getRemoteDevice(); } /* * (non-Javadoc) * * @see com.intel.bluetooth.BluetoothConnectionAccess#isClosed() */ public boolean isClosed() { if (conn == null) { return true; } if (this.conn instanceof BluetoothConnectionAccess) { return ((BluetoothConnectionAccess) conn).isClosed(); } else { return false; } } /* * (non-Javadoc) * * @see com.intel.bluetooth.BluetoothConnectionAccess#shutdown() */ public void shutdown() throws IOException { if (this.conn instanceof BluetoothConnectionAccess) { ((BluetoothConnectionAccess) conn).shutdown(); } } /* * (non-Javadoc) * * @see com.intel.bluetooth.BluetoothConnectionAccess#markAuthenticated() */ public void markAuthenticated() { validateBluetoothConnection(); if (conn != null) { ((BluetoothConnectionAccess) conn).markAuthenticated(); } } /* * (non-Javadoc) * * @see com.intel.bluetooth.BluetoothConnectionAccess#getSecurityOpt() */ public int getSecurityOpt() { validateBluetoothConnection(); if (conn == null) { return ServiceRecord.NOAUTHENTICATE_NOENCRYPT; } else { return ((BluetoothConnectionAccess) conn).getSecurityOpt(); } } /* * (non-Javadoc) * * @see com.intel.bluetooth.BluetoothConnectionAccess#encrypt(boolean) */ public boolean encrypt(long address, boolean on) throws IOException { validateBluetoothConnection(); if (conn == null) { throw new IOException("Connection closed"); } return ((BluetoothConnectionAccess) conn).encrypt(address, on); } /* * (non-Javadoc) * * @see com.intel.bluetooth.BluetoothConnectionAccess#setRemoteDevice(javax.bluetooth * .RemoteDevice) */ public void setRemoteDevice(RemoteDevice remoteDevice) { validateBluetoothConnection(); if (conn != null) { ((BluetoothConnectionAccess) conn).setRemoteDevice(remoteDevice); } } /* * (non-Javadoc) * * @see com.intel.bluetooth.BluetoothConnectionAccess#getBluetoothStack() */ public BluetoothStack getBluetoothStack() { validateBluetoothConnection(); if (conn == null) { return null; } return ((BluetoothConnectionAccess) conn).getBluetoothStack(); } /** * Function used in unit tests. * * @return the packetsCountWrite */ int getPacketsCountWrite() { return this.packetsCountWrite; } /** * Function used in unit tests. * * @return the packetsCountRead */ int getPacketsCountRead() { return this.packetsCountRead; } /** * Function used in unit tests. * * @return the mtu */ int getPacketSize() { if (isConnected) { return this.mtu; } else { return obexConnectionParams.mtu; } } /** * Function used to change the connection mtu * * @param mtu * @throws IOException */ void setPacketSize(int mtu) throws IOException { if (isConnected) { throw new IOException("Session already connected"); } obexConnectionParams.mtu = mtu; } }