1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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
40 static final int OBEX_HDR_COUNT = HeaderSet.COUNT;
41
42
43 static final int OBEX_HDR_NAME = HeaderSet.NAME;
44
45
46 static final int OBEX_HDR_TYPE = HeaderSet.TYPE;
47
48
49 static final int OBEX_HDR_LENGTH = HeaderSet.LENGTH;
50
51
52 static final int OBEX_HDR_TIME = HeaderSet.TIME_ISO_8601;
53
54
55 static final int OBEX_HDR_TIME2 = HeaderSet.TIME_4_BYTE;
56
57
58 static final int OBEX_HDR_DESCRIPTION = HeaderSet.DESCRIPTION;
59
60
61 static final int OBEX_HDR_TARGET = HeaderSet.TARGET;
62
63
64 static final int OBEX_HDR_HTTP = HeaderSet.HTTP;
65
66
67 static final int OBEX_HDR_BODY = 0x48;
68
69
70 static final int OBEX_HDR_BODY_END = 0x49;
71
72
73 static final int OBEX_HDR_WHO = HeaderSet.WHO;
74
75
76 static final int OBEX_HDR_CONNECTION = 0xCB;
77
78
79 static final int OBEX_HDR_APP_PARAM = HeaderSet.APPLICATION_PARAMETER;
80
81
82 static final int OBEX_HDR_AUTH_CHALLENGE = 0x4D;
83
84
85 static final int OBEX_HDR_AUTH_RESPONSE = 0x4E;
86
87
88 static final int OBEX_HDR_OBJECTCLASS = HeaderSet.OBJECT_CLASS;
89
90
91 static final int OBEX_HDR_CREATOR = 0xCF;
92
93
94 static final int OBEX_HDR_WANUUID = 0x50;
95
96
97
98
99
100 static final int OBEX_HDR_SESSIONPARAM = 0x52;
101
102
103 static final int OBEX_HDR_SESSIONSEQ = 0x93;
104
105
106
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
115
116
117 static final int OBEX_STRING = 0x00;
118
119
120 static final int OBEX_BYTE_STREAM = 0x40;
121
122
123 static final int OBEX_BYTE = 0x80;
124
125
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
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
228
229
230
231 public int[] getHeaderList() throws IOException {
232 if (headerValues.size() == 0) {
233
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
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
346
347
348
349
350
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
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
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
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
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 }