1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24 package com.intel.bluetooth.obex;
25
26 import java.io.ByteArrayOutputStream;
27 import java.io.IOException;
28 import java.io.OutputStream;
29 import java.util.Calendar;
30 import java.util.Date;
31 import java.util.Enumeration;
32 import java.util.Hashtable;
33 import java.util.TimeZone;
34 import java.util.Vector;
35
36 import javax.obex.HeaderSet;
37
38 import com.intel.bluetooth.DebugLog;
39
40 class OBEXHeaderSetImpl implements HeaderSet {
41
42
43 static final int OBEX_HDR_COUNT = HeaderSet.COUNT;
44
45
46 static final int OBEX_HDR_NAME = HeaderSet.NAME;
47
48
49 static final int OBEX_HDR_TYPE = HeaderSet.TYPE;
50
51
52 static final int OBEX_HDR_LENGTH = HeaderSet.LENGTH;
53
54
55 static final int OBEX_HDR_TIME = HeaderSet.TIME_ISO_8601;
56
57
58 static final int OBEX_HDR_TIME2 = HeaderSet.TIME_4_BYTE;
59
60
61 static final int OBEX_HDR_DESCRIPTION = HeaderSet.DESCRIPTION;
62
63
64 static final int OBEX_HDR_TARGET = HeaderSet.TARGET;
65
66
67 static final int OBEX_HDR_HTTP = HeaderSet.HTTP;
68
69
70 static final int OBEX_HDR_BODY = 0x48;
71
72
73 static final int OBEX_HDR_BODY_END = 0x49;
74
75
76 static final int OBEX_HDR_WHO = HeaderSet.WHO;
77
78
79 static final int OBEX_HDR_CONNECTION = 0xCB;
80
81
82 static final int OBEX_HDR_APP_PARAM = HeaderSet.APPLICATION_PARAMETER;
83
84
85 static final int OBEX_HDR_AUTH_CHALLENGE = 0x4D;
86
87
88 static final int OBEX_HDR_AUTH_RESPONSE = 0x4E;
89
90
91 static final int OBEX_HDR_OBJECTCLASS = HeaderSet.OBJECT_CLASS;
92
93
94 static final int OBEX_HDR_CREATOR = 0xCF;
95
96
97 static final int OBEX_HDR_WANUUID = 0x50;
98
99
100
101
102
103 static final int OBEX_HDR_SESSIONPARAM = 0x52;
104
105
106 static final int OBEX_HDR_SESSIONSEQ = 0x93;
107
108
109
110 static final int OBEX_HDR_USER = 0x30;
111
112 static final int OBEX_HDR_HI_MASK = 0xC0;
113
114 static final int OBEX_HDR_ID_MASK = 0x3F;
115
116
117
118
119 static final int OBEX_STRING = 0x00;
120
121
122 static final int OBEX_BYTE_STREAM = 0x40;
123
124
125 static final int OBEX_BYTE = 0x80;
126
127
128 static final int OBEX_INT = 0xC0;
129
130 private static final int OBEX_MAX_FIELD_LEN = 0xFF;
131
132 private int responseCode;
133
134 private Hashtable headerValues;
135
136 private Vector authResponses;
137
138 private Vector authChallenges;
139
140 private static final int NO_RESPONSE_CODE = Integer.MIN_VALUE;
141
142 OBEXHeaderSetImpl() {
143 this(NO_RESPONSE_CODE);
144 }
145
146 private OBEXHeaderSetImpl(int responseCode) {
147 this.headerValues = new Hashtable();
148 this.responseCode = responseCode;
149 this.authResponses = null;
150 this.authChallenges = null;
151 }
152
153 static void validateCreatedHeaderSet(HeaderSet headers) {
154 if (headers == null) {
155 return;
156 }
157 if (!(headers instanceof OBEXHeaderSetImpl)) {
158 throw new IllegalArgumentException("Illegal HeaderSet type");
159 }
160 if (((OBEXHeaderSetImpl) headers).responseCode != NO_RESPONSE_CODE) {
161 throw new IllegalArgumentException("Illegal HeaderSet");
162 }
163 }
164
165 private void validateHeaderID(int headerID) throws IllegalArgumentException {
166 if (headerID < 0 || headerID > 0xff) {
167 throw new IllegalArgumentException("Expected header ID in range 0 to 255");
168 }
169 int identifier = headerID & OBEX_HDR_ID_MASK;
170 if (identifier >= 0x10 && identifier < 0x2F) {
171 throw new IllegalArgumentException("Reserved header ID");
172 }
173 }
174
175 public void setHeader(int headerID, Object headerValue) {
176 validateHeaderID(headerID);
177 if (headerValue == null) {
178 headerValues.remove(new Integer(headerID));
179 } else {
180
181 if ((headerID == OBEX_HDR_TIME) || (headerID == OBEX_HDR_TIME2)) {
182 if (!(headerValue instanceof Calendar)) {
183 throw new IllegalArgumentException("Expected java.util.Calendar");
184 }
185 } else if (headerID == OBEX_HDR_TYPE) {
186 if (!(headerValue instanceof String)) {
187 throw new IllegalArgumentException("Expected java.lang.String");
188 }
189 } else {
190 switch (headerID & OBEX_HDR_HI_MASK) {
191 case OBEX_STRING:
192 if (!(headerValue instanceof String)) {
193 throw new IllegalArgumentException("Expected java.lang.String");
194 }
195 break;
196 case OBEX_BYTE_STREAM:
197 if (!(headerValue instanceof byte[])) {
198 throw new IllegalArgumentException("Expected byte[]");
199 }
200 break;
201 case OBEX_BYTE:
202 if (!(headerValue instanceof Byte)) {
203 throw new IllegalArgumentException("Expected java.lang.Byte");
204 }
205 break;
206 case OBEX_INT:
207 if (!(headerValue instanceof Long)) {
208 throw new IllegalArgumentException("Expected java.lang.Long");
209 }
210 long v = ((Long) headerValue).longValue();
211 if (v < 0 || v > 0xffffffffl) {
212 throw new IllegalArgumentException("Expected long in range 0 to 2^32-1");
213 }
214 break;
215 default:
216 throw new IllegalArgumentException("Unsupported encoding " + (headerID & OBEX_HDR_HI_MASK));
217 }
218 }
219 headerValues.put(new Integer(headerID), headerValue);
220 }
221 }
222
223 public Object getHeader(int headerID) throws IOException {
224 validateHeaderID(headerID);
225 return headerValues.get(new Integer(headerID));
226 }
227
228
229
230
231
232
233 public int[] getHeaderList() throws IOException {
234 if (headerValues.size() == 0) {
235
236 return null;
237 }
238 int[] headerIDArray = new int[headerValues.size()];
239 int i = 0;
240 for (Enumeration e = headerValues.keys(); e.hasMoreElements();) {
241 headerIDArray[i++] = ((Integer) e.nextElement()).intValue();
242 }
243 return headerIDArray;
244 }
245
246 public int getResponseCode() throws IOException {
247 if (this.responseCode == NO_RESPONSE_CODE) {
248 throw new IOException();
249 }
250 return this.responseCode;
251 }
252
253 boolean hasIncommingData() {
254 return headerValues.contains(new Integer(OBEX_HDR_BODY))
255 || headerValues.contains(new Integer(OBEX_HDR_BODY_END));
256 }
257
258 static OBEXHeaderSetImpl cloneHeaders(HeaderSet headers) throws IOException {
259 if (headers == null) {
260 return null;
261 }
262 if (!(headers instanceof OBEXHeaderSetImpl)) {
263 throw new IllegalArgumentException("Illegal HeaderSet type");
264 }
265 OBEXHeaderSetImpl hs = new OBEXHeaderSetImpl(((OBEXHeaderSetImpl) headers).responseCode);
266
267 int[] headerIDArray = headers.getHeaderList();
268 for (int i = 0; (headerIDArray != null) && (i < headerIDArray.length); i++) {
269 int headerID = headerIDArray[i];
270
271 if ((headerID == OBEX_HDR_BODY) || (headerID == OBEX_HDR_BODY_END)) {
272 continue;
273 }
274 hs.setHeader(headerID, headers.getHeader(headerID));
275 }
276 return hs;
277 }
278
279 static HeaderSet appendHeaders(HeaderSet dst, HeaderSet src) throws IOException {
280 int[] headerIDArray = src.getHeaderList();
281 for (int i = 0; (headerIDArray != null) && (i < headerIDArray.length); i++) {
282 int headerID = headerIDArray[i];
283 if ((headerID == OBEX_HDR_BODY) || (headerID == OBEX_HDR_BODY_END)) {
284 continue;
285 }
286 dst.setHeader(headerID, src.getHeader(headerID));
287 }
288 return dst;
289 }
290
291 public synchronized void createAuthenticationChallenge(String realm, boolean isUserIdRequired, boolean isFullAccess) {
292 if (authChallenges == null) {
293 authChallenges = new Vector();
294 }
295 authChallenges.addElement(OBEXAuthentication.createChallenge(realm, isUserIdRequired, isFullAccess));
296 }
297
298 synchronized void addAuthenticationResponse(byte[] authResponse) {
299 if (authResponses == null) {
300 authResponses = new Vector();
301 }
302 authResponses.addElement(authResponse);
303 }
304
305 boolean hasAuthenticationChallenge() {
306 if (authChallenges == null) {
307 return false;
308 }
309 return !authChallenges.isEmpty();
310 }
311
312 Enumeration getAuthenticationChallenges() {
313 return authChallenges.elements();
314 }
315
316 boolean hasAuthenticationResponses() {
317 if (authResponses == null) {
318 return false;
319 }
320 return !authResponses.isEmpty();
321 }
322
323 Enumeration getAuthenticationResponses() {
324 return authResponses.elements();
325 }
326
327 static long readObexInt(byte[] data, int off) throws IOException {
328 long l = 0;
329 for (int i = 0; i < 4; i++) {
330 l = l << 8;
331 l += (int) (data[off + i] & 0xFF);
332 }
333 return l;
334 }
335
336 static void writeObexInt(OutputStream out, int headerID, long data) throws IOException {
337 byte[] b = new byte[5];
338 b[0] = (byte) headerID;
339 b[1] = (byte) ((data >>> 24) & 0xFF);
340 b[2] = (byte) ((data >>> 16) & 0xFF);
341 b[3] = (byte) ((data >>> 8) & 0xFF);
342 b[4] = (byte) ((data >>> 0) & 0xFF);
343 out.write(b);
344 }
345
346 static void writeObexLen(OutputStream out, int headerID, int len) throws IOException {
347 byte[] b = new byte[3];
348 b[0] = (byte) headerID;
349 if ((len < 0) || len > 0xFFFF) {
350 throw new IOException("very large data" + len);
351 }
352 b[1] = OBEXUtils.hiByte(len);
353 b[2] = OBEXUtils.loByte(len);
354 out.write(b);
355 }
356
357 static void writeObexASCII(OutputStream out, int headerID, String value) throws IOException {
358 writeObexLen(out, headerID, 3 + value.length() + 1);
359 out.write(value.getBytes("iso-8859-1"));
360 out.write(0);
361 }
362
363 static void writeObexUnicode(OutputStream out, int headerID, String value) throws IOException {
364
365
366
367
368
369
370 if (value.length() == 0) {
371 writeObexLen(out, headerID, 3);
372 return;
373 }
374 byte[] b = OBEXUtils.getUTF16Bytes(value);
375 writeObexLen(out, headerID, 3 + b.length + 2);
376 out.write(b);
377 out.write(new byte[] { 0, 0 });
378 }
379
380 static byte[] toByteArray(HeaderSet headers) throws IOException {
381 if (headers == null) {
382 return new byte[0];
383 }
384 ByteArrayOutputStream buf = new ByteArrayOutputStream();
385 int[] headerIDArray = headers.getHeaderList();
386 for (int i = 0; (headerIDArray != null) && (i < headerIDArray.length); i++) {
387 int hi = headerIDArray[i];
388 if (hi == OBEX_HDR_TIME) {
389 Calendar c = (Calendar) headers.getHeader(hi);
390 writeObexLen(buf, hi, 19);
391 writeTimeISO8601(buf, c);
392 } else if (hi == OBEX_HDR_TIME2) {
393 Calendar c = (Calendar) headers.getHeader(hi);
394 writeObexInt(buf, hi, c.getTime().getTime() / 1000);
395 } else if (hi == OBEX_HDR_TYPE) {
396
397 writeObexASCII(buf, hi, (String) headers.getHeader(hi));
398 } else {
399 switch (hi & OBEX_HDR_HI_MASK) {
400 case OBEX_STRING:
401 writeObexUnicode(buf, hi, (String) headers.getHeader(hi));
402 break;
403 case OBEX_BYTE_STREAM:
404 byte data[] = (byte[]) headers.getHeader(hi);
405 writeObexLen(buf, hi, 3 + data.length);
406 buf.write(data);
407 break;
408 case OBEX_BYTE:
409 buf.write(hi);
410 buf.write(((Byte) headers.getHeader(hi)).byteValue());
411 break;
412 case OBEX_INT:
413 writeObexInt(buf, hi, ((Long) headers.getHeader(hi)).longValue());
414 break;
415 default:
416 throw new IOException("Unsupported encoding " + (hi & OBEX_HDR_HI_MASK));
417 }
418 }
419 }
420 if ((headerIDArray != null) && (headerIDArray.length != 0)) {
421 DebugLog.debug("written headers", headerIDArray.length);
422 }
423 if (((OBEXHeaderSetImpl) headers).hasAuthenticationChallenge()) {
424 for (Enumeration iter = ((OBEXHeaderSetImpl) headers).authChallenges.elements(); iter.hasMoreElements();) {
425 byte[] authChallenge = (byte[]) iter.nextElement();
426 writeObexLen(buf, OBEX_HDR_AUTH_CHALLENGE, 3 + authChallenge.length);
427 buf.write(authChallenge);
428 DebugLog.debug("written AUTH_CHALLENGE");
429 }
430 }
431 if (((OBEXHeaderSetImpl) headers).hasAuthenticationResponses()) {
432 for (Enumeration iter = ((OBEXHeaderSetImpl) headers).authResponses.elements(); iter.hasMoreElements();) {
433 byte[] authResponse = (byte[]) iter.nextElement();
434 writeObexLen(buf, OBEX_HDR_AUTH_RESPONSE, 3 + authResponse.length);
435 buf.write(authResponse);
436 DebugLog.debug("written AUTH_RESPONSE");
437 }
438 }
439 return buf.toByteArray();
440 }
441
442
443
444
445 static OBEXHeaderSetImpl readHeaders(byte[] buf, int off) throws IOException {
446 return readHeaders(new OBEXHeaderSetImpl(NO_RESPONSE_CODE), buf, off);
447 }
448
449 static OBEXHeaderSetImpl readHeaders(byte responseCode, byte[] buf, int off) throws IOException {
450 return readHeaders(new OBEXHeaderSetImpl(0xFF & responseCode), buf, off);
451 }
452
453 private static OBEXHeaderSetImpl readHeaders(OBEXHeaderSetImpl hs, byte[] buf, int off) throws IOException {
454 int count = 0;
455 while (off < buf.length) {
456 int hi = 0xFF & buf[off];
457 int len = 0;
458 switch (hi & OBEX_HDR_HI_MASK) {
459 case OBEX_STRING:
460 len = OBEXUtils.bytesToShort(buf[off + 1], buf[off + 2]);
461 if (len == 3) {
462 hs.setHeader(hi, "");
463 } else {
464 byte data[] = new byte[len - 5];
465 System.arraycopy(buf, off + 3, data, 0, data.length);
466 hs.setHeader(hi, OBEXUtils.newStringUTF16(data));
467 }
468 break;
469 case OBEX_BYTE_STREAM:
470 len = OBEXUtils.bytesToShort(buf[off + 1], buf[off + 2]);
471 byte data[] = new byte[len - 3];
472 System.arraycopy(buf, off + 3, data, 0, data.length);
473 if (hi == OBEX_HDR_TYPE) {
474 if (data[data.length - 1] != 0) {
475 hs.setHeader(hi, new String(data, "iso-8859-1"));
476 } else {
477 hs.setHeader(hi, new String(data, 0, data.length - 1, "iso-8859-1"));
478 }
479 } else if (hi == OBEX_HDR_TIME) {
480 hs.setHeader(hi, readTimeISO8601(data));
481 } else if (hi == OBEX_HDR_AUTH_CHALLENGE) {
482 synchronized (hs) {
483 if (hs.authChallenges == null) {
484 hs.authChallenges = new Vector();
485 }
486 }
487 hs.authChallenges.addElement(data);
488 DebugLog.debug("received AUTH_CHALLENGE");
489 } else if (hi == OBEX_HDR_AUTH_RESPONSE) {
490 synchronized (hs) {
491 if (hs.authResponses == null) {
492 hs.authResponses = new Vector();
493 }
494 }
495 hs.authResponses.addElement(data);
496 DebugLog.debug("received AUTH_RESPONSE");
497 } else {
498 hs.setHeader(hi, data);
499 }
500 break;
501 case OBEX_BYTE:
502 len = 2;
503 hs.setHeader(hi, new Byte(buf[off + 1]));
504 break;
505 case OBEX_INT:
506 len = 5;
507 long intValue = readObexInt(buf, off + 1);
508 if (hi == OBEX_HDR_TIME2) {
509 Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
510 cal.setTime(new Date(intValue * 1000));
511 hs.setHeader(hi, cal);
512 } else {
513 hs.setHeader(hi, new Long(intValue));
514 }
515 break;
516 default:
517 throw new IOException("Unsupported encoding " + (hi & OBEX_HDR_HI_MASK));
518 }
519 off += len;
520 count++;
521 }
522 if (count != 0) {
523 DebugLog.debug("read headers", count);
524 }
525 return hs;
526 }
527
528 private static byte[] d4(int i) {
529 byte[] b = new byte[4];
530 int d = 1000;
531 for (int k = 0; k < 4; k++) {
532 b[k] = (byte) (i / d + '0');
533 i %= d;
534 d /= 10;
535 }
536 return b;
537 }
538
539 private static byte[] d2(int i) {
540 byte[] b = new byte[2];
541 b[0] = (byte) (i / 10 + '0');
542 b[1] = (byte) (i % 10 + '0');
543 return b;
544 }
545
546
547
548
549 static void writeTimeISO8601(OutputStream out, Calendar c) throws IOException {
550 Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
551 cal.setTime(c.getTime());
552 out.write(d4(cal.get(Calendar.YEAR)));
553 out.write(d2(cal.get(Calendar.MONTH) + 1));
554 out.write(d2(cal.get(Calendar.DAY_OF_MONTH)));
555 out.write('T');
556 out.write(d2(cal.get(Calendar.HOUR_OF_DAY)));
557 out.write(d2(cal.get(Calendar.MINUTE)));
558 out.write(d2(cal.get(Calendar.SECOND)));
559 out.write('Z');
560 }
561
562
563
564
565 static Calendar readTimeISO8601(byte data[]) throws IOException {
566 boolean utc = false;
567 if ((data.length != 16) && (data.length != 15)) {
568 throw new IOException("Invalid ISO-8601 date length " + new String(data) + " length " + data.length);
569 } else if (data[8] != 'T') {
570 throw new IOException("Invalid ISO-8601 date " + new String(data));
571 } else if (data.length == 16) {
572 if (data[15] != 'Z') {
573 throw new IOException("Invalid ISO-8601 date " + new String(data));
574 } else {
575 utc = true;
576 }
577 }
578 Calendar cal = utc ? Calendar.getInstance(TimeZone.getTimeZone("UTC")) : Calendar.getInstance();
579 cal.set(Calendar.YEAR, Integer.valueOf(new String(data, 0, 4)).intValue());
580 cal.set(Calendar.MONTH, Integer.valueOf(new String(data, 4, 2)).intValue() - 1);
581 cal.set(Calendar.DAY_OF_MONTH, Integer.valueOf(new String(data, 6, 2)).intValue());
582 cal.set(Calendar.HOUR_OF_DAY, Integer.valueOf(new String(data, 9, 2)).intValue());
583 cal.set(Calendar.MINUTE, Integer.valueOf(new String(data, 11, 2)).intValue());
584 cal.set(Calendar.SECOND, Integer.valueOf(new String(data, 13, 2)).intValue());
585 return cal;
586 }
587
588 }