View Javadoc

1   /**
2    *  BlueCove - Java library for Bluetooth
3    *  Copyright (C) 2007-2008 Vlad Skarzhevskyy
4    *
5    *  This library is free software; you can redistribute it and/or
6    *  modify it under the terms of the GNU Lesser General Public
7    *  License as published by the Free Software Foundation; either
8    *  version 2.1 of the License, or (at your option) any later version.
9    *
10   *  This library is distributed in the hope that it will be useful,
11   *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12   *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13   *  Lesser General Public License for more details.
14   *
15   *  You should have received a copy of the GNU Lesser General Public
16   *  License along with this library; if not, write to the Free Software
17   *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18   *
19   *  @version $Id: OBEXHeaderSetImpl.java 2326 2008-07-04 15:10:14Z skarzhevskyy $
20   */
21  package com.intel.bluetooth.obex;
22  
23  import java.io.ByteArrayOutputStream;
24  import java.io.IOException;
25  import java.io.OutputStream;
26  import java.util.Calendar;
27  import java.util.Date;
28  import java.util.Enumeration;
29  import java.util.Hashtable;
30  import java.util.TimeZone;
31  import java.util.Vector;
32  
33  import javax.obex.HeaderSet;
34  
35  import com.intel.bluetooth.DebugLog;
36  
37  class OBEXHeaderSetImpl implements HeaderSet {
38  
39  	/** Number of objects (used by connect) (0xC0) */
40  	static final int OBEX_HDR_COUNT = HeaderSet.COUNT;
41  
42  	/** Name of the object (0x01) */
43  	static final int OBEX_HDR_NAME = HeaderSet.NAME;
44  
45  	/** Type of the object (0x42) */
46  	static final int OBEX_HDR_TYPE = HeaderSet.TYPE;
47  
48  	/** Total lenght of object (0xC3) */
49  	static final int OBEX_HDR_LENGTH = HeaderSet.LENGTH;
50  
51  	/** Last modification time of (ISO8601) (0x44) */
52  	static final int OBEX_HDR_TIME = HeaderSet.TIME_ISO_8601;
53  
54  	/** Deprecated use HDR_TIME instead (0xC4) */
55  	static final int OBEX_HDR_TIME2 = HeaderSet.TIME_4_BYTE;
56  
57  	/** Description of object (0x05) */
58  	static final int OBEX_HDR_DESCRIPTION = HeaderSet.DESCRIPTION;
59  
60  	/** name of service that operation is targeted to (0x46) */
61  	static final int OBEX_HDR_TARGET = HeaderSet.TARGET;
62  
63  	/** An HTTP 1.x header (0x47) */
64  	static final int OBEX_HDR_HTTP = HeaderSet.HTTP;
65  
66  	/** Data part of the object (0x48) */
67  	static final int OBEX_HDR_BODY = 0x48;
68  
69  	/** Last data part of the object (0x49) */
70  	static final int OBEX_HDR_BODY_END = 0x49;
71  
72  	/** Identifies the sender of the object (0x4A) */
73  	static final int OBEX_HDR_WHO = HeaderSet.WHO;
74  
75  	/** Connection identifier used for OBEX connection multiplexing (0xCB) */
76  	static final int OBEX_HDR_CONNECTION = 0xCB;
77  
78  	/** Application parameters (0x4C) */
79  	static final int OBEX_HDR_APP_PARAM = HeaderSet.APPLICATION_PARAMETER;
80  
81  	/** Authentication digest-challenge (0x4D) */
82  	static final int OBEX_HDR_AUTH_CHALLENGE = 0x4D;
83  
84  	/** Authentication digest-response (0x4E) */
85  	static final int OBEX_HDR_AUTH_RESPONSE = 0x4E;
86  
87  	/** OBEX Object class of object (0x51) */
88  	static final int OBEX_HDR_OBJECTCLASS = HeaderSet.OBJECT_CLASS;
89  
90  	/** indicates the creator of an object (0xCF) */
91  	static final int OBEX_HDR_CREATOR = 0xCF;
92  
93  	/** uniquely identifies the network client (OBEX server) (0x50) */
94  	static final int OBEX_HDR_WANUUID = 0x50;
95  
96  	// /** OBEX Object class of object (0x51)*/
97  	// static final int OBEX_HDR_OBJECTCLASS = 0x51;
98  
99  	/** Parameters used in sessioncommands/responses (0x52) */
100 	static final int OBEX_HDR_SESSIONPARAM = 0x52;
101 
102 	/** Sequence number used in each OBEX packet for reliability (0x93) */
103 	static final int OBEX_HDR_SESSIONSEQ = 0x93;
104 
105 	// 0x30 to 0x3F user defined - this range includes all combinations of the
106 	// upper 2 bits
107 	static final int OBEX_HDR_USER = 0x30;
108 
109 	static final int OBEX_HDR_HI_MASK = 0xC0;
110 
111 	static final int OBEX_HDR_ID_MASK = 0x3F;
112 
113 	/**
114 	 * null terminated Unicode text, length prefixed with 2 byte unsigned
115 	 * integer
116 	 */
117 	static final int OBEX_STRING = 0x00;
118 
119 	/** byte sequence, length prefixed with 2 byte unsigned integer */
120 	static final int OBEX_BYTE_STREAM = 0x40;
121 
122 	/** 1 byte quantity */
123 	static final int OBEX_BYTE = 0x80;
124 
125 	/** 4 byte quantity – transmitted in network byte order (high byte first) */
126 	static final int OBEX_INT = 0xC0;
127 
128 	private static final int OBEX_MAX_FIELD_LEN = 0xFF;
129 
130 	private int responseCode;
131 
132 	private Hashtable headerValues;
133 
134 	private Vector authResponses;
135 
136 	private Vector authChallenges;
137 
138 	private static final int NO_RESPONSE_CODE = Integer.MIN_VALUE;
139 
140 	OBEXHeaderSetImpl() {
141 		this(NO_RESPONSE_CODE);
142 	}
143 
144 	private OBEXHeaderSetImpl(int responseCode) {
145 		this.headerValues = new Hashtable();
146 		this.responseCode = responseCode;
147 		this.authResponses = new Vector();
148 		this.authChallenges = new Vector();
149 	}
150 
151 	static void validateCreatedHeaderSet(HeaderSet headers) {
152 		if (headers == null) {
153 			return;
154 		}
155 		if (!(headers instanceof OBEXHeaderSetImpl)) {
156 			throw new IllegalArgumentException("Illegal HeaderSet type");
157 		}
158 		if (((OBEXHeaderSetImpl) headers).responseCode != NO_RESPONSE_CODE) {
159 			throw new IllegalArgumentException("Illegal HeaderSet");
160 		}
161 	}
162 
163 	private void validateHeaderID(int headerID) throws IllegalArgumentException {
164 		if (headerID < 0 || headerID > 0xff) {
165 			throw new IllegalArgumentException("Expected header ID in range 0 to 255");
166 		}
167 		int identifier = headerID & OBEX_HDR_ID_MASK;
168 		if (identifier >= 0x10 && identifier < 0x2F) {
169 			throw new IllegalArgumentException("Reserved header ID");
170 		}
171 	}
172 
173 	public void setHeader(int headerID, Object headerValue) {
174 		validateHeaderID(headerID);
175 		if (headerValue == null) {
176 			headerValues.remove(new Integer(headerID));
177 		} else {
178 			// Validate Java value Type
179 			if ((headerID == OBEX_HDR_TIME) || (headerID == OBEX_HDR_TIME2)) {
180 				if (!(headerValue instanceof Calendar)) {
181 					throw new IllegalArgumentException("Expected java.util.Calendar");
182 				}
183 			} else if (headerID == OBEX_HDR_TYPE) {
184 				if (!(headerValue instanceof String)) {
185 					throw new IllegalArgumentException("Expected java.lang.String");
186 				}
187 			} else {
188 				switch (headerID & OBEX_HDR_HI_MASK) {
189 				case OBEX_STRING:
190 					if (!(headerValue instanceof String)) {
191 						throw new IllegalArgumentException("Expected java.lang.String");
192 					}
193 					break;
194 				case OBEX_BYTE_STREAM:
195 					if (!(headerValue instanceof byte[])) {
196 						throw new IllegalArgumentException("Expected byte[]");
197 					}
198 					break;
199 				case OBEX_BYTE:
200 					if (!(headerValue instanceof Byte)) {
201 						throw new IllegalArgumentException("Expected java.lang.Byte");
202 					}
203 					break;
204 				case OBEX_INT:
205 					if (!(headerValue instanceof Long)) {
206 						throw new IllegalArgumentException("Expected java.lang.Long");
207 					}
208 					long v = ((Long) headerValue).longValue();
209 					if (v < 0 || v > 0xffffffffl) {
210 						throw new IllegalArgumentException("Expected long in range 0 to 2^32-1");
211 					}
212 					break;
213 				default:
214 					throw new IllegalArgumentException("Unsupported encoding " + (headerID & OBEX_HDR_HI_MASK));
215 				}
216 			}
217 			headerValues.put(new Integer(headerID), headerValue);
218 		}
219 	}
220 
221 	public Object getHeader(int headerID) throws IOException {
222 		validateHeaderID(headerID);
223 		return headerValues.get(new Integer(headerID));
224 	}
225 
226 	/*
227 	 * (non-Javadoc)
228 	 * 
229 	 * @see javax.obex.HeaderSet#getHeaderList()
230 	 */
231 	public int[] getHeaderList() throws IOException {
232 		if (headerValues.size() == 0) {
233 			// Spec: null if no headers are available
234 			return null;
235 		}
236 		int[] headerIDArray = new int[headerValues.size()];
237 		int i = 0;
238 		for (Enumeration e = headerValues.keys(); e.hasMoreElements();) {
239 			headerIDArray[i++] = ((Integer) e.nextElement()).intValue();
240 		}
241 		return headerIDArray;
242 	}
243 
244 	public int getResponseCode() throws IOException {
245 		if (this.responseCode == NO_RESPONSE_CODE) {
246 			throw new IOException();
247 		}
248 		return this.responseCode;
249 	}
250 
251 	static HeaderSet cloneHeaders(HeaderSet headers) throws IOException {
252 		if (headers == null) {
253 			return null;
254 		}
255 		if (!(headers instanceof OBEXHeaderSetImpl)) {
256 			throw new IllegalArgumentException("Illegal HeaderSet type");
257 		}
258 		HeaderSet hs = new OBEXHeaderSetImpl(((OBEXHeaderSetImpl) headers).responseCode);
259 
260 		int[] headerIDArray = headers.getHeaderList();
261 		for (int i = 0; (headerIDArray != null) && (i < headerIDArray.length); i++) {
262 			int headerID = headerIDArray[i];
263 			// Body is not accessible by the client
264 			if ((headerID == OBEX_HDR_BODY) || (headerID == OBEX_HDR_BODY_END)) {
265 				continue;
266 			}
267 			hs.setHeader(headerID, headers.getHeader(headerID));
268 		}
269 		return hs;
270 	}
271 
272 	static HeaderSet appendHeaders(HeaderSet dst, HeaderSet src) throws IOException {
273 		int[] headerIDArray = src.getHeaderList();
274 		for (int i = 0; (headerIDArray != null) && (i < headerIDArray.length); i++) {
275 			int headerID = headerIDArray[i];
276 			if ((headerID == OBEX_HDR_BODY) || (headerID == OBEX_HDR_BODY_END)) {
277 				continue;
278 			}
279 			dst.setHeader(headerID, src.getHeader(headerID));
280 		}
281 		return dst;
282 	}
283 
284 	public void createAuthenticationChallenge(String realm, boolean isUserIdRequired, boolean isFullAccess) {
285 		authChallenges.addElement(OBEXAuthentication.createChallenge(realm, isUserIdRequired, isFullAccess));
286 	}
287 
288 	void addAuthenticationResponse(byte[] authResponse) {
289 		authResponses.addElement(authResponse);
290 	}
291 
292 	boolean hasAuthenticationChallenge() {
293 		return !authChallenges.isEmpty();
294 	}
295 
296 	Enumeration getAuthenticationChallenges() {
297 		return authChallenges.elements();
298 	}
299 
300 	boolean hasAuthenticationResponse() {
301 		return !authResponses.isEmpty();
302 	}
303 
304 	Enumeration getAuthenticationResponses() {
305 		return authResponses.elements();
306 	}
307 
308 	static long readObexInt(byte[] data, int off) throws IOException {
309 		long l = 0;
310 		for (int i = 0; i < 4; i++) {
311 			l = l << 8;
312 			l += (int) (data[off + i] & 0xFF);
313 		}
314 		return l;
315 	}
316 
317 	static void writeObexInt(OutputStream out, int headerID, long data) throws IOException {
318 		byte[] b = new byte[5];
319 		b[0] = (byte) headerID;
320 		b[1] = (byte) ((data >>> 24) & 0xFF);
321 		b[2] = (byte) ((data >>> 16) & 0xFF);
322 		b[3] = (byte) ((data >>> 8) & 0xFF);
323 		b[4] = (byte) ((data >>> 0) & 0xFF);
324 		out.write(b);
325 	}
326 
327 	static void writeObexLen(OutputStream out, int headerID, int len) throws IOException {
328 		byte[] b = new byte[3];
329 		b[0] = (byte) headerID;
330 		if ((len < 0) || len > 0xFFFF) {
331 			throw new IOException("very large data" + len);
332 		}
333 		b[1] = OBEXUtils.hiByte(len);
334 		b[2] = OBEXUtils.loByte(len);
335 		out.write(b);
336 	}
337 
338 	static void writeObexASCII(OutputStream out, int headerID, String value) throws IOException {
339 		writeObexLen(out, headerID, 3 + value.length() + 1);
340 		out.write(value.getBytes("iso-8859-1"));
341 		out.write(0);
342 	}
343 
344 	static void writeObexUnicode(OutputStream out, int headerID, String value) throws IOException {
345 		// null terminated Unicode text, length prefixed with 2 byte unsigned
346 		// integer
347 		// the length field includes the 2 bytes of the null
348 		// terminator (0x00, 0x00). Therefore the length of the string ”Jumar”
349 		// would be 12 bytes; 5 visible
350 		// characters plus the null terminator, each two bytes in length.
351 		if (value.length() == 0) {
352 			writeObexLen(out, headerID, 3);
353 			return;
354 		}
355 		byte[] b = OBEXUtils.getUTF16Bytes(value);
356 		writeObexLen(out, headerID, 3 + b.length + 2);
357 		out.write(b);
358 		out.write(new byte[] { 0, 0 });
359 	}
360 
361 	static byte[] toByteArray(HeaderSet headers) throws IOException {
362 		if (headers == null) {
363 			return new byte[0];
364 		}
365 		ByteArrayOutputStream buf = new ByteArrayOutputStream();
366 		int[] headerIDArray = headers.getHeaderList();
367 		for (int i = 0; (headerIDArray != null) && (i < headerIDArray.length); i++) {
368 			int hi = headerIDArray[i];
369 			if (hi == OBEX_HDR_TIME) {
370 				Calendar c = (Calendar) headers.getHeader(hi);
371 				writeObexLen(buf, hi, 19);
372 				writeTimeISO8601(buf, c);
373 			} else if (hi == OBEX_HDR_TIME2) {
374 				Calendar c = (Calendar) headers.getHeader(hi);
375 				writeObexInt(buf, hi, c.getTime().getTime() / 1000);
376 			} else if (hi == OBEX_HDR_TYPE) {
377 				// ASCII string
378 				writeObexASCII(buf, hi, (String) headers.getHeader(hi));
379 			} else {
380 				switch (hi & OBEX_HDR_HI_MASK) {
381 				case OBEX_STRING:
382 					writeObexUnicode(buf, hi, (String) headers.getHeader(hi));
383 					break;
384 				case OBEX_BYTE_STREAM:
385 					byte data[] = (byte[]) headers.getHeader(hi);
386 					writeObexLen(buf, hi, 3 + data.length);
387 					buf.write(data);
388 					break;
389 				case OBEX_BYTE:
390 					buf.write(hi);
391 					buf.write(((Byte) headers.getHeader(hi)).byteValue());
392 					break;
393 				case OBEX_INT:
394 					writeObexInt(buf, hi, ((Long) headers.getHeader(hi)).longValue());
395 					break;
396 				default:
397 					throw new IOException("Unsupported encoding " + (hi & OBEX_HDR_HI_MASK));
398 				}
399 			}
400 		}
401 		if ((headerIDArray != null) && (headerIDArray.length != 0)) {
402 			DebugLog.debug("written headers", headerIDArray.length);
403 		}
404 		for (Enumeration iter = ((OBEXHeaderSetImpl) headers).authChallenges.elements(); iter.hasMoreElements();) {
405 			byte[] authChallenge = (byte[]) iter.nextElement();
406 			writeObexLen(buf, OBEX_HDR_AUTH_CHALLENGE, 3 + authChallenge.length);
407 			buf.write(authChallenge);
408 			DebugLog.debug("written AUTH_CHALLENGE");
409 		}
410 		for (Enumeration iter = ((OBEXHeaderSetImpl) headers).authResponses.elements(); iter.hasMoreElements();) {
411 			byte[] authResponse = (byte[]) iter.nextElement();
412 			writeObexLen(buf, OBEX_HDR_AUTH_RESPONSE, 3 + authResponse.length);
413 			buf.write(authResponse);
414 			DebugLog.debug("written AUTH_RESPONSE");
415 		}
416 		return buf.toByteArray();
417 	}
418 
419 	/*
420 	 * Read by server
421 	 */
422 	static OBEXHeaderSetImpl readHeaders(byte[] buf, int off) throws IOException {
423 		return readHeaders(new OBEXHeaderSetImpl(NO_RESPONSE_CODE), buf, off);
424 	}
425 
426 	static OBEXHeaderSetImpl readHeaders(byte responseCode, byte[] buf, int off) throws IOException {
427 		return readHeaders(new OBEXHeaderSetImpl(0xFF & responseCode), buf, off);
428 	}
429 
430 	private static OBEXHeaderSetImpl readHeaders(OBEXHeaderSetImpl hs, byte[] buf, int off) throws IOException {
431 		int count = 0;
432 		while (off < buf.length) {
433 			int hi = 0xFF & buf[off];
434 			int len = 0;
435 			switch (hi & OBEX_HDR_HI_MASK) {
436 			case OBEX_STRING:
437 				len = OBEXUtils.bytesToShort(buf[off + 1], buf[off + 2]);
438 				if (len == 3) {
439 					hs.setHeader(hi, "");
440 				} else {
441 					byte data[] = new byte[len - 5];
442 					System.arraycopy(buf, off + 3, data, 0, data.length);
443 					hs.setHeader(hi, OBEXUtils.newStringUTF16(data));
444 				}
445 				break;
446 			case OBEX_BYTE_STREAM:
447 				len = OBEXUtils.bytesToShort(buf[off + 1], buf[off + 2]);
448 				byte data[] = new byte[len - 3];
449 				System.arraycopy(buf, off + 3, data, 0, data.length);
450 				if (hi == OBEX_HDR_TYPE) {
451 					if (data[data.length - 1] != 0) {
452 						hs.setHeader(hi, new String(data, "iso-8859-1"));
453 					} else {
454 						hs.setHeader(hi, new String(data, 0, data.length - 1, "iso-8859-1"));
455 					}
456 				} else if (hi == OBEX_HDR_TIME) {
457 					hs.setHeader(hi, readTimeISO8601(data));
458 				} else if (hi == OBEX_HDR_AUTH_CHALLENGE) {
459 					((OBEXHeaderSetImpl) hs).authChallenges.addElement(data);
460 					DebugLog.debug("received AUTH_CHALLENGE");
461 				} else if (hi == OBEX_HDR_AUTH_RESPONSE) {
462 					((OBEXHeaderSetImpl) hs).authResponses.addElement(data);
463 					DebugLog.debug("received AUTH_RESPONSE");
464 				} else {
465 					hs.setHeader(hi, data);
466 				}
467 				break;
468 			case OBEX_BYTE:
469 				len = 2;
470 				hs.setHeader(hi, new Byte(buf[off + 1]));
471 				break;
472 			case OBEX_INT:
473 				len = 5;
474 				long intValue = readObexInt(buf, off + 1);
475 				if (hi == OBEX_HDR_TIME2) {
476 					Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
477 					cal.setTime(new Date(intValue * 1000));
478 					hs.setHeader(hi, cal);
479 				} else {
480 					hs.setHeader(hi, new Long(intValue));
481 				}
482 				break;
483 			default:
484 				throw new IOException("Unsupported encoding " + (hi & OBEX_HDR_HI_MASK));
485 			}
486 			off += len;
487 			count++;
488 		}
489 		if (count != 0) {
490 			DebugLog.debug("read headers", count);
491 		}
492 		return hs;
493 	}
494 
495 	private static byte[] d4(int i) {
496 		byte[] b = new byte[4];
497 		int d = 1000;
498 		for (int k = 0; k < 4; k++) {
499 			b[k] = (byte) (i / d + '0');
500 			i %= d;
501 			d /= 10;
502 		}
503 		return b;
504 	}
505 
506 	private static byte[] d2(int i) {
507 		byte[] b = new byte[2];
508 		b[0] = (byte) (i / 10 + '0');
509 		b[1] = (byte) (i % 10 + '0');
510 		return b;
511 	}
512 
513 	/**
514 	 * ISO-8601 UTC YYYYMMDDTHHMMSSZ
515 	 */
516 	static void writeTimeISO8601(OutputStream out, Calendar c) throws IOException {
517 		Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
518 		cal.setTime(c.getTime());
519 		out.write(d4(cal.get(Calendar.YEAR)));
520 		out.write(d2(cal.get(Calendar.MONTH) + 1));
521 		out.write(d2(cal.get(Calendar.DAY_OF_MONTH)));
522 		out.write('T');
523 		out.write(d2(cal.get(Calendar.HOUR_OF_DAY)));
524 		out.write(d2(cal.get(Calendar.MINUTE)));
525 		out.write(d2(cal.get(Calendar.SECOND)));
526 		out.write('Z');
527 	}
528 
529 	/**
530 	 * ISO-8601 UTC YYYYMMDDTHHMMSS(Z) Z for UTC time
531 	 */
532 	static Calendar readTimeISO8601(byte data[]) throws IOException {
533 		boolean utc = false;
534 		if ((data.length != 16) && (data.length != 15)) {
535 			throw new IOException("Invalid ISO-8601 date length " + new String(data) + " length " + data.length);
536 		} else if (data[8] != 'T') {
537 			throw new IOException("Invalid ISO-8601 date " + new String(data));
538 		} else if (data.length == 16) {
539 			if (data[15] != 'Z') {
540 				throw new IOException("Invalid ISO-8601 date " + new String(data));
541 			} else {
542 				utc = true;
543 			}
544 		}
545 		Calendar cal = utc ? Calendar.getInstance(TimeZone.getTimeZone("UTC")) : Calendar.getInstance();
546 		cal.set(Calendar.YEAR, Integer.valueOf(new String(data, 0, 4)).intValue());
547 		cal.set(Calendar.MONTH, Integer.valueOf(new String(data, 4, 2)).intValue() - 1);
548 		cal.set(Calendar.DAY_OF_MONTH, Integer.valueOf(new String(data, 6, 2)).intValue());
549 		cal.set(Calendar.HOUR_OF_DAY, Integer.valueOf(new String(data, 9, 2)).intValue());
550 		cal.set(Calendar.MINUTE, Integer.valueOf(new String(data, 11, 2)).intValue());
551 		cal.set(Calendar.SECOND, Integer.valueOf(new String(data, 13, 2)).intValue());
552 		return cal;
553 	}
554 
555 }