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: OBEXSessionBase.java 2641 2008-12-22 23:28:52Z skarzhevskyy $
24   */
25  package com.intel.bluetooth.obex;
26  
27  import java.io.ByteArrayOutputStream;
28  import java.io.IOException;
29  import java.io.InputStream;
30  import java.io.OutputStream;
31  import java.util.Enumeration;
32  import java.util.Vector;
33  
34  import javax.bluetooth.RemoteDevice;
35  import javax.bluetooth.ServiceRecord;
36  import javax.microedition.io.Connection;
37  import javax.microedition.io.StreamConnection;
38  import javax.obex.Authenticator;
39  import javax.obex.HeaderSet;
40  import javax.obex.ServerRequestHandler;
41  
42  import com.intel.bluetooth.BluetoothConnectionAccess;
43  import com.intel.bluetooth.BluetoothStack;
44  import com.intel.bluetooth.DebugLog;
45  import com.intel.bluetooth.obex.OBEXAuthentication.Challenge;
46  
47  /**
48   * Base for Client and Server implementations. See <a
49   * href="http://bluetooth.com/Bluetooth/Learn/Technology/Specifications/" >Bluetooth Specification Documents</A> for
50   * details.
51   * 
52   */
53  abstract class OBEXSessionBase implements Connection, BluetoothConnectionAccess {
54  
55  	protected boolean isConnected;
56  
57  	private StreamConnection conn;
58  
59  	private InputStream is;
60  
61  	private OutputStream os;
62  
63  	protected long connectionID;
64  
65  	protected int mtu = OBEXOperationCodes.OBEX_DEFAULT_MTU;
66  
67  	protected Authenticator authenticator;
68  
69  	protected OBEXConnectionParams obexConnectionParams;
70  
71  	protected int packetsCountWrite;
72  
73  	protected int packetsCountRead;
74  
75  	private Vector authChallengesSent;
76  
77  	/**
78  	 * Each request packet flowed by response. This flag is from Client point of view
79  	 */
80  	protected boolean requestSent;
81  
82  	public OBEXSessionBase(StreamConnection conn, OBEXConnectionParams obexConnectionParams) throws IOException {
83  		if (obexConnectionParams == null) {
84  			throw new NullPointerException("obexConnectionParams is null");
85  		}
86  		this.isConnected = false;
87  		this.conn = conn;
88  		this.obexConnectionParams = obexConnectionParams;
89  		this.mtu = obexConnectionParams.mtu;
90  		this.connectionID = -1;
91  		this.packetsCountWrite = 0;
92  		this.packetsCountRead = 0;
93  		boolean initOK = false;
94  		try {
95  			this.os = conn.openOutputStream();
96  			this.is = conn.openInputStream();
97  			initOK = true;
98  		} finally {
99  			if (!initOK) {
100 				try {
101 					this.close();
102 				} catch (IOException e) {
103 					DebugLog.error("close error", e);
104 				}
105 			}
106 		}
107 	}
108 
109 	public void close() throws IOException {
110 		StreamConnection c = this.conn;
111 		this.conn = null;
112 		try {
113 			if (this.is != null) {
114 				this.is.close();
115 				this.is = null;
116 			}
117 			if (this.os != null) {
118 				this.os.close();
119 				this.os = null;
120 			}
121 		} finally {
122 			if (c != null) {
123 				c.close();
124 			}
125 		}
126 
127 	}
128 
129 	static OBEXHeaderSetImpl createOBEXHeaderSetImpl() {
130 		return new OBEXHeaderSetImpl();
131 	}
132 
133 	public static HeaderSet createOBEXHeaderSet() {
134 		return createOBEXHeaderSetImpl();
135 	}
136 
137 	static void validateCreatedHeaderSet(HeaderSet headers) {
138 		OBEXHeaderSetImpl.validateCreatedHeaderSet(headers);
139 	}
140 
141 	protected void writePacket(int commId, OBEXHeaderSetImpl headers) throws IOException {
142 		writePacketWithFlags(commId, null, headers);
143 	}
144 
145 	protected synchronized void writePacketWithFlags(int commId, byte[] headerFlagsData, OBEXHeaderSetImpl headers)
146 			throws IOException {
147 		if (this.requestSent) {
148 			throw new IOException("Write packet out of order");
149 		}
150 		this.requestSent = true;
151 		int len = 3;
152 		if (this.connectionID != -1) {
153 			len += 5;
154 		}
155 		if (headerFlagsData != null) {
156 			len += headerFlagsData.length;
157 		}
158 		byte[] data = null;
159 		if (headers != null) {
160 			data = OBEXHeaderSetImpl.toByteArray(headers);
161 			len += data.length;
162 		}
163 		if (len > mtu) {
164 			throw new IOException("Can't sent more data than in MTU, len=" + len + ", mtu=" + mtu);
165 		}
166 		this.packetsCountWrite++;
167 		ByteArrayOutputStream buf = new ByteArrayOutputStream();
168 		OBEXHeaderSetImpl.writeObexLen(buf, commId, len);
169 		if (headerFlagsData != null) {
170 			buf.write(headerFlagsData);
171 		}
172 		if (this.connectionID != -1) {
173 			OBEXHeaderSetImpl.writeObexInt(buf, OBEXHeaderSetImpl.OBEX_HDR_CONNECTION, this.connectionID);
174 		}
175 		if (data != null) {
176 			buf.write(data);
177 		}
178 		DebugLog.debug0x("obex send (" + this.packetsCountWrite + ")", OBEXUtils.toStringObexResponseCodes(commId),
179 				commId);
180 		os.write(buf.toByteArray());
181 		os.flush();
182 		DebugLog.debug("obex sent (" + this.packetsCountWrite + ") len", len);
183 
184 		if ((headers != null) && (headers.hasAuthenticationChallenge())) {
185 			if (authChallengesSent == null) {
186 				authChallengesSent = new Vector();
187 			}
188 			for (Enumeration iter = headers.getAuthenticationChallenges(); iter.hasMoreElements();) {
189 				byte[] authChallenge = (byte[]) iter.nextElement();
190 				Challenge challenge = new Challenge(authChallenge);
191 				authChallengesSent.addElement(challenge);
192 			}
193 		}
194 	}
195 
196 	protected synchronized byte[] readPacket() throws IOException {
197 		if (!this.requestSent) {
198 			throw new IOException("Read packet out of order");
199 		}
200 		this.requestSent = false;
201 		byte[] header = new byte[3];
202 		OBEXUtils.readFully(is, obexConnectionParams, header);
203 		this.packetsCountRead++;
204 		DebugLog.debug0x("obex received (" + this.packetsCountRead + ")", OBEXUtils
205 				.toStringObexResponseCodes(header[0]), header[0] & 0xFF);
206 		int lenght = OBEXUtils.bytesToShort(header[1], header[2]);
207 		if (lenght == 3) {
208 			return header;
209 		}
210 		if ((lenght < 3) || (lenght > OBEXOperationCodes.OBEX_MAX_PACKET_LEN)) {
211 			throw new IOException("Invalid packet length " + lenght);
212 		}
213 		byte[] data = new byte[lenght];
214 		System.arraycopy(header, 0, data, 0, header.length);
215 		OBEXUtils.readFully(is, obexConnectionParams, data, header.length, lenght - header.length);
216 		if (is.available() > 0) {
217 			DebugLog.debug("has more data after read", is.available());
218 		}
219 		return data;
220 	}
221 
222 	private void validateBluetoothConnection() {
223 		if ((conn != null) && !(conn instanceof BluetoothConnectionAccess)) {
224 			throw new IllegalArgumentException("Not a Bluetooth connection " + conn.getClass().getName());
225 		}
226 	}
227 
228 	void validateAuthenticationResponse(OBEXHeaderSetImpl requestHeaders, OBEXHeaderSetImpl incomingHeaders)
229 			throws IOException {
230 		if ((requestHeaders != null) && requestHeaders.hasAuthenticationChallenge()
231 				&& (!incomingHeaders.hasAuthenticationResponses())) {
232 			// TODO verify that this appropriate Exception
233 			throw new IOException("Authentication response is missing");
234 		}
235 		handleAuthenticationResponse(incomingHeaders, null);
236 	}
237 
238 	boolean handleAuthenticationResponse(OBEXHeaderSetImpl incomingHeaders, ServerRequestHandler serverHandler)
239 			throws IOException {
240 		if (incomingHeaders.hasAuthenticationResponses()) {
241 			if (authenticator == null) {
242 				throw new IOException("Authenticator required for authentication");
243 			}
244 			if ((authChallengesSent == null) && (authChallengesSent.size() == 0)) {
245 				throw new IOException("Authentication challenges had not been sent");
246 			}
247 			boolean authenticated = false;
248 			try {
249 				authenticated = OBEXAuthentication.handleAuthenticationResponse(incomingHeaders, authenticator,
250 						serverHandler, authChallengesSent);
251 			} finally {
252 				if ((authenticated) && (authChallengesSent != null)) {
253 					authChallengesSent.removeAllElements();
254 				}
255 			}
256 			return authenticated;
257 		} else {
258 			if ((authChallengesSent != null) && (authChallengesSent.size() > 0)) {
259 				throw new IOException("Authentication response is missing");
260 			}
261 			return true;
262 		}
263 	}
264 
265 	void handleAuthenticationChallenge(OBEXHeaderSetImpl incomingHeaders, OBEXHeaderSetImpl replyHeaders)
266 			throws IOException {
267 		if (incomingHeaders.hasAuthenticationChallenge()) {
268 			if (authenticator == null) {
269 				throw new IOException("Authenticator required for authentication");
270 			}
271 			OBEXAuthentication.handleAuthenticationChallenge(incomingHeaders, replyHeaders, authenticator);
272 		}
273 	}
274 
275 	/*
276 	 * (non-Javadoc)
277 	 * 
278 	 * @see com.intel.bluetooth.BluetoothConnectionAccess#getRemoteAddress()
279 	 */
280 	public long getRemoteAddress() throws IOException {
281 		validateBluetoothConnection();
282 		if (conn == null) {
283 			throw new IOException("Connection closed");
284 		}
285 		return ((BluetoothConnectionAccess) conn).getRemoteAddress();
286 	}
287 
288 	/*
289 	 * (non-Javadoc)
290 	 * 
291 	 * @see com.intel.bluetooth.BluetoothConnectionAccess#getRemoteDevice()
292 	 */
293 	public RemoteDevice getRemoteDevice() {
294 		validateBluetoothConnection();
295 		if (conn == null) {
296 			return null;
297 		}
298 		return ((BluetoothConnectionAccess) conn).getRemoteDevice();
299 	}
300 
301 	/*
302 	 * (non-Javadoc)
303 	 * 
304 	 * @see com.intel.bluetooth.BluetoothConnectionAccess#isClosed()
305 	 */
306 	public boolean isClosed() {
307 		if (conn == null) {
308 			return true;
309 		}
310 		if (this.conn instanceof BluetoothConnectionAccess) {
311 			return ((BluetoothConnectionAccess) conn).isClosed();
312 		} else {
313 			return false;
314 		}
315 	}
316 
317 	/*
318 	 * (non-Javadoc)
319 	 * 
320 	 * @see com.intel.bluetooth.BluetoothConnectionAccess#shutdown()
321 	 */
322 	public void shutdown() throws IOException {
323 		if (this.conn instanceof BluetoothConnectionAccess) {
324 			((BluetoothConnectionAccess) conn).shutdown();
325 		}
326 	}
327 
328 	/*
329 	 * (non-Javadoc)
330 	 * 
331 	 * @see com.intel.bluetooth.BluetoothConnectionAccess#markAuthenticated()
332 	 */
333 	public void markAuthenticated() {
334 		validateBluetoothConnection();
335 		if (conn != null) {
336 			((BluetoothConnectionAccess) conn).markAuthenticated();
337 		}
338 	}
339 
340 	/*
341 	 * (non-Javadoc)
342 	 * 
343 	 * @see com.intel.bluetooth.BluetoothConnectionAccess#getSecurityOpt()
344 	 */
345 	public int getSecurityOpt() {
346 		validateBluetoothConnection();
347 		if (conn == null) {
348 			return ServiceRecord.NOAUTHENTICATE_NOENCRYPT;
349 		} else {
350 			return ((BluetoothConnectionAccess) conn).getSecurityOpt();
351 		}
352 	}
353 
354 	/*
355 	 * (non-Javadoc)
356 	 * 
357 	 * @see com.intel.bluetooth.BluetoothConnectionAccess#encrypt(boolean)
358 	 */
359 	public boolean encrypt(long address, boolean on) throws IOException {
360 		validateBluetoothConnection();
361 		if (conn == null) {
362 			throw new IOException("Connection closed");
363 		}
364 		return ((BluetoothConnectionAccess) conn).encrypt(address, on);
365 	}
366 
367 	/*
368 	 * (non-Javadoc)
369 	 * 
370 	 * @see com.intel.bluetooth.BluetoothConnectionAccess#setRemoteDevice(javax.bluetooth .RemoteDevice)
371 	 */
372 	public void setRemoteDevice(RemoteDevice remoteDevice) {
373 		validateBluetoothConnection();
374 		if (conn != null) {
375 			((BluetoothConnectionAccess) conn).setRemoteDevice(remoteDevice);
376 		}
377 	}
378 
379 	/*
380 	 * (non-Javadoc)
381 	 * 
382 	 * @see com.intel.bluetooth.BluetoothConnectionAccess#getBluetoothStack()
383 	 */
384 	public BluetoothStack getBluetoothStack() {
385 		validateBluetoothConnection();
386 		if (conn == null) {
387 			return null;
388 		}
389 		return ((BluetoothConnectionAccess) conn).getBluetoothStack();
390 	}
391 
392 	/**
393 	 * Function used in unit tests.
394 	 * 
395 	 * @return the packetsCountWrite
396 	 */
397 	int getPacketsCountWrite() {
398 		return this.packetsCountWrite;
399 	}
400 
401 	/**
402 	 * Function used in unit tests.
403 	 * 
404 	 * @return the packetsCountRead
405 	 */
406 	int getPacketsCountRead() {
407 		return this.packetsCountRead;
408 	}
409 
410 	/**
411 	 * Function used in unit tests.
412 	 * 
413 	 * @return the mtu
414 	 */
415 	int getPacketSize() {
416 		return this.mtu;
417 	}
418 
419 	/**
420 	 * Function used to change the connection mtu
421 	 * 
422 	 * @param mtu
423 	 * @throws IOException
424 	 */
425 	void setPacketSize(int mtu) throws IOException {
426 		if (isConnected) {
427 			throw new IOException("Session already connected");
428 		}
429 		obexConnectionParams.mtu = mtu;
430 	}
431 }