• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (c) 2008-2009, Motorola, Inc.
3  *
4  * All rights reserved.
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions are met:
8  *
9  * - Redistributions of source code must retain the above copyright notice,
10  * this list of conditions and the following disclaimer.
11  *
12  * - Redistributions in binary form must reproduce the above copyright notice,
13  * this list of conditions and the following disclaimer in the documentation
14  * and/or other materials provided with the distribution.
15  *
16  * - Neither the name of the Motorola, Inc. nor the names of its contributors
17  * may be used to endorse or promote products derived from this software
18  * without specific prior written permission.
19  *
20  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23  * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
24  * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
25  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
26  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
27  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
28  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
29  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
30  * POSSIBILITY OF SUCH DAMAGE.
31  */
32 
33 package javax.obex;
34 
35 import java.io.ByteArrayOutputStream;
36 import java.io.IOException;
37 import java.io.UnsupportedEncodingException;
38 import java.security.MessageDigest;
39 import java.security.NoSuchAlgorithmException;
40 import java.util.Calendar;
41 import java.util.Date;
42 import java.util.TimeZone;
43 
44 /**
45  * This class defines a set of helper methods for the implementation of Obex.
46  * @hide
47  */
48 public final class ObexHelper {
49 
50     /**
51      * Defines the basic packet length used by OBEX. Every OBEX packet has the
52      * same basic format:<BR>
53      * Byte 0: Request or Response Code Byte 1&2: Length of the packet.
54      */
55     public static final int BASE_PACKET_LENGTH = 3;
56 
57     /** Prevent object construction of helper class */
ObexHelper()58     private ObexHelper() {
59     }
60 
61     /**
62      * The maximum packet size for OBEX packets that this client can handle. At
63      * present, this must be changed for each port. TODO: The max packet size
64      * should be the Max incoming MTU minus TODO: L2CAP package headers and
65      * RFCOMM package headers. TODO: Retrieve the max incoming MTU from TODO:
66      * LocalDevice.getProperty().
67      */
68     /*
69      * android note set as 0xFFFE to match remote MPS
70      */
71     public static final int MAX_PACKET_SIZE_INT = 0xFFFE;
72 
73     /**
74      * Temporary workaround to be able to push files to Windows 7.
75      * TODO: Should be removed as soon as Microsoft updates their driver.
76      */
77     public static final int MAX_CLIENT_PACKET_SIZE = 0xFC00;
78 
79     public static final int OBEX_OPCODE_CONNECT = 0x80;
80 
81     public static final int OBEX_OPCODE_DISCONNECT = 0x81;
82 
83     public static final int OBEX_OPCODE_PUT = 0x02;
84 
85     public static final int OBEX_OPCODE_PUT_FINAL = 0x82;
86 
87     public static final int OBEX_OPCODE_GET = 0x03;
88 
89     public static final int OBEX_OPCODE_GET_FINAL = 0x83;
90 
91     public static final int OBEX_OPCODE_RESERVED = 0x04;
92 
93     public static final int OBEX_OPCODE_RESERVED_FINAL = 0x84;
94 
95     public static final int OBEX_OPCODE_SETPATH = 0x85;
96 
97     public static final int OBEX_OPCODE_ABORT = 0xFF;
98 
99     public static final int OBEX_AUTH_REALM_CHARSET_ASCII = 0x00;
100 
101     public static final int OBEX_AUTH_REALM_CHARSET_ISO_8859_1 = 0x01;
102 
103     public static final int OBEX_AUTH_REALM_CHARSET_ISO_8859_2 = 0x02;
104 
105     public static final int OBEX_AUTH_REALM_CHARSET_ISO_8859_3 = 0x03;
106 
107     public static final int OBEX_AUTH_REALM_CHARSET_ISO_8859_4 = 0x04;
108 
109     public static final int OBEX_AUTH_REALM_CHARSET_ISO_8859_5 = 0x05;
110 
111     public static final int OBEX_AUTH_REALM_CHARSET_ISO_8859_6 = 0x06;
112 
113     public static final int OBEX_AUTH_REALM_CHARSET_ISO_8859_7 = 0x07;
114 
115     public static final int OBEX_AUTH_REALM_CHARSET_ISO_8859_8 = 0x08;
116 
117     public static final int OBEX_AUTH_REALM_CHARSET_ISO_8859_9 = 0x09;
118 
119     public static final int OBEX_AUTH_REALM_CHARSET_UNICODE = 0xFF;
120 
121     /**
122      * Updates the HeaderSet with the headers received in the byte array
123      * provided. Invalid headers are ignored.
124      * <P>
125      * The first two bits of an OBEX Header specifies the type of object that is
126      * being sent. The table below specifies the meaning of the high bits.
127      * <TABLE>
128      * <TR>
129      * <TH>Bits 8 and 7</TH>
130      * <TH>Value</TH>
131      * <TH>Description</TH>
132      * </TR>
133      * <TR>
134      * <TD>00</TD>
135      * <TD>0x00</TD>
136      * <TD>Null Terminated Unicode text, prefixed with 2 byte unsigned integer</TD>
137      * </TR>
138      * <TR>
139      * <TD>01</TD>
140      * <TD>0x40</TD>
141      * <TD>Byte Sequence, length prefixed with 2 byte unsigned integer</TD>
142      * </TR>
143      * <TR>
144      * <TD>10</TD>
145      * <TD>0x80</TD>
146      * <TD>1 byte quantity</TD>
147      * </TR>
148      * <TR>
149      * <TD>11</TD>
150      * <TD>0xC0</TD>
151      * <TD>4 byte quantity - transmitted in network byte order (high byte first</TD>
152      * </TR>
153      * </TABLE>
154      * This method uses the information in this table to determine the type of
155      * Java object to create and passes that object with the full header to
156      * setHeader() to update the HeaderSet object. Invalid headers will cause an
157      * exception to be thrown. When it is thrown, it is ignored.
158      * @param header the HeaderSet to update
159      * @param headerArray the byte array containing headers
160      * @return the result of the last start body or end body header provided;
161      *         the first byte in the result will specify if a body or end of
162      *         body is received
163      * @throws IOException if an invalid header was found
164      */
updateHeaderSet(HeaderSet header, byte[] headerArray)165     public static byte[] updateHeaderSet(HeaderSet header, byte[] headerArray) throws IOException {
166         int index = 0;
167         int length = 0;
168         int headerID;
169         byte[] value = null;
170         byte[] body = null;
171         HeaderSet headerImpl = header;
172         try {
173             while (index < headerArray.length) {
174                 headerID = 0xFF & headerArray[index];
175                 switch (headerID & (0xC0)) {
176 
177                     /*
178                      * 0x00 is a unicode null terminate string with the first
179                      * two bytes after the header identifier being the length
180                      */
181                     case 0x00:
182                         // Fall through
183                         /*
184                          * 0x40 is a byte sequence with the first
185                          * two bytes after the header identifier being the length
186                          */
187                     case 0x40:
188                         boolean trimTail = true;
189                         index++;
190                         length = 0xFF & headerArray[index];
191                         length = length << 8;
192                         index++;
193                         length += 0xFF & headerArray[index];
194                         length -= 3;
195                         index++;
196                         value = new byte[length];
197                         System.arraycopy(headerArray, index, value, 0, length);
198                         if (length == 0 || (length > 0 && (value[length - 1] != 0))) {
199                             trimTail = false;
200                         }
201                         switch (headerID) {
202                             case HeaderSet.TYPE:
203                                 try {
204                                     // Remove trailing null
205                                     if (trimTail == false) {
206                                         headerImpl.setHeader(headerID, new String(value, 0,
207                                                 value.length, "ISO8859_1"));
208                                     } else {
209                                         headerImpl.setHeader(headerID, new String(value, 0,
210                                                 value.length - 1, "ISO8859_1"));
211                                     }
212                                 } catch (UnsupportedEncodingException e) {
213                                     throw e;
214                                 }
215                                 break;
216 
217                             case HeaderSet.AUTH_CHALLENGE:
218                                 headerImpl.mAuthChall = new byte[length];
219                                 System.arraycopy(headerArray, index, headerImpl.mAuthChall, 0,
220                                         length);
221                                 break;
222 
223                             case HeaderSet.AUTH_RESPONSE:
224                                 headerImpl.mAuthResp = new byte[length];
225                                 System.arraycopy(headerArray, index, headerImpl.mAuthResp, 0,
226                                         length);
227                                 break;
228 
229                             case HeaderSet.BODY:
230                                 /* Fall Through */
231                             case HeaderSet.END_OF_BODY:
232                                 body = new byte[length + 1];
233                                 body[0] = (byte)headerID;
234                                 System.arraycopy(headerArray, index, body, 1, length);
235                                 break;
236 
237                             case HeaderSet.TIME_ISO_8601:
238                                 try {
239                                     String dateString = new String(value, "ISO8859_1");
240                                     Calendar temp = Calendar.getInstance();
241                                     if ((dateString.length() == 16)
242                                             && (dateString.charAt(15) == 'Z')) {
243                                         temp.setTimeZone(TimeZone.getTimeZone("UTC"));
244                                     }
245                                     temp.set(Calendar.YEAR, Integer.parseInt(dateString.substring(
246                                             0, 4)));
247                                     temp.set(Calendar.MONTH, Integer.parseInt(dateString.substring(
248                                             4, 6)));
249                                     temp.set(Calendar.DAY_OF_MONTH, Integer.parseInt(dateString
250                                             .substring(6, 8)));
251                                     temp.set(Calendar.HOUR_OF_DAY, Integer.parseInt(dateString
252                                             .substring(9, 11)));
253                                     temp.set(Calendar.MINUTE, Integer.parseInt(dateString
254                                             .substring(11, 13)));
255                                     temp.set(Calendar.SECOND, Integer.parseInt(dateString
256                                             .substring(13, 15)));
257                                     headerImpl.setHeader(HeaderSet.TIME_ISO_8601, temp);
258                                 } catch (UnsupportedEncodingException e) {
259                                     throw e;
260                                 }
261                                 break;
262 
263                             default:
264                                 if ((headerID & 0xC0) == 0x00) {
265                                     headerImpl.setHeader(headerID, ObexHelper.convertToUnicode(
266                                             value, true));
267                                 } else {
268                                     headerImpl.setHeader(headerID, value);
269                                 }
270                         }
271 
272                         index += length;
273                         break;
274 
275                     /*
276                      * 0x80 is a byte header.  The only valid byte headers are
277                      * the 16 user defined byte headers.
278                      */
279                     case 0x80:
280                         index++;
281                         try {
282                             headerImpl.setHeader(headerID, Byte.valueOf(headerArray[index]));
283                         } catch (Exception e) {
284                             // Not a valid header so ignore
285                         }
286                         index++;
287                         break;
288 
289                     /*
290                      * 0xC0 is a 4 byte unsigned integer header and with the
291                      * exception of TIME_4_BYTE will be converted to a Long
292                      * and added.
293                      */
294                     case 0xC0:
295                         index++;
296                         value = new byte[4];
297                         System.arraycopy(headerArray, index, value, 0, 4);
298                         try {
299                             if (headerID != HeaderSet.TIME_4_BYTE) {
300                                 // Determine if it is a connection ID.  These
301                                 // need to be handled differently
302                                 if (headerID == HeaderSet.CONNECTION_ID) {
303                                     headerImpl.mConnectionID = new byte[4];
304                                     System.arraycopy(value, 0, headerImpl.mConnectionID, 0, 4);
305                                 } else {
306                                     headerImpl.setHeader(headerID, Long
307                                             .valueOf(convertToLong(value)));
308                                 }
309                             } else {
310                                 Calendar temp = Calendar.getInstance();
311                                 temp.setTime(new Date(convertToLong(value) * 1000L));
312                                 headerImpl.setHeader(HeaderSet.TIME_4_BYTE, temp);
313                             }
314                         } catch (Exception e) {
315                             // Not a valid header so ignore
316                             throw new IOException("Header was not formatted properly");
317                         }
318                         index += 4;
319                         break;
320                 }
321 
322             }
323         } catch (IOException e) {
324             throw new IOException("Header was not formatted properly");
325         }
326 
327         return body;
328     }
329 
330     /**
331      * Creates the header part of OBEX packet based on the header provided.
332      * TODO: Could use getHeaderList() to get the array of headers to include
333      * and then use the high two bits to determine the the type of the object
334      * and construct the byte array from that. This will make the size smaller.
335      * @param head the header used to construct the byte array
336      * @param nullOut <code>true</code> if the header should be set to
337      *        <code>null</code> once it is added to the array or
338      *        <code>false</code> if it should not be nulled out
339      * @return the header of an OBEX packet
340      */
createHeader(HeaderSet head, boolean nullOut)341     public static byte[] createHeader(HeaderSet head, boolean nullOut) {
342         Long intHeader = null;
343         String stringHeader = null;
344         Calendar dateHeader = null;
345         Byte byteHeader = null;
346         StringBuffer buffer = null;
347         byte[] value = null;
348         byte[] result = null;
349         byte[] lengthArray = new byte[2];
350         int length;
351         HeaderSet headImpl = null;
352         ByteArrayOutputStream out = new ByteArrayOutputStream();
353         headImpl = head;
354 
355         try {
356             /*
357              * Determine if there is a connection ID to send.  If there is,
358              * then it should be the first header in the packet.
359              */
360             if ((headImpl.mConnectionID != null) && (headImpl.getHeader(HeaderSet.TARGET) == null)) {
361 
362                 out.write((byte)HeaderSet.CONNECTION_ID);
363                 out.write(headImpl.mConnectionID);
364             }
365 
366             // Count Header
367             intHeader = (Long)headImpl.getHeader(HeaderSet.COUNT);
368             if (intHeader != null) {
369                 out.write((byte)HeaderSet.COUNT);
370                 value = ObexHelper.convertToByteArray(intHeader.longValue());
371                 out.write(value);
372                 if (nullOut) {
373                     headImpl.setHeader(HeaderSet.COUNT, null);
374                 }
375             }
376 
377             // Name Header
378             stringHeader = (String)headImpl.getHeader(HeaderSet.NAME);
379             if (stringHeader != null) {
380                 out.write((byte)HeaderSet.NAME);
381                 value = ObexHelper.convertToUnicodeByteArray(stringHeader);
382                 length = value.length + 3;
383                 lengthArray[0] = (byte)(0xFF & (length >> 8));
384                 lengthArray[1] = (byte)(0xFF & length);
385                 out.write(lengthArray);
386                 out.write(value);
387                 if (nullOut) {
388                     headImpl.setHeader(HeaderSet.NAME, null);
389                 }
390             }
391 
392             // Type Header
393             stringHeader = (String)headImpl.getHeader(HeaderSet.TYPE);
394             if (stringHeader != null) {
395                 out.write((byte)HeaderSet.TYPE);
396                 try {
397                     value = stringHeader.getBytes("ISO8859_1");
398                 } catch (UnsupportedEncodingException e) {
399                     throw e;
400                 }
401 
402                 length = value.length + 4;
403                 lengthArray[0] = (byte)(255 & (length >> 8));
404                 lengthArray[1] = (byte)(255 & length);
405                 out.write(lengthArray);
406                 out.write(value);
407                 out.write(0x00);
408                 if (nullOut) {
409                     headImpl.setHeader(HeaderSet.TYPE, null);
410                 }
411             }
412 
413             // Length Header
414             intHeader = (Long)headImpl.getHeader(HeaderSet.LENGTH);
415             if (intHeader != null) {
416                 out.write((byte)HeaderSet.LENGTH);
417                 value = ObexHelper.convertToByteArray(intHeader.longValue());
418                 out.write(value);
419                 if (nullOut) {
420                     headImpl.setHeader(HeaderSet.LENGTH, null);
421                 }
422             }
423 
424             // Time ISO Header
425             dateHeader = (Calendar)headImpl.getHeader(HeaderSet.TIME_ISO_8601);
426             if (dateHeader != null) {
427 
428                 /*
429                  * The ISO Header should take the form YYYYMMDDTHHMMSSZ.  The
430                  * 'Z' will only be included if it is a UTC time.
431                  */
432                 buffer = new StringBuffer();
433                 int temp = dateHeader.get(Calendar.YEAR);
434                 for (int i = temp; i < 1000; i = i * 10) {
435                     buffer.append("0");
436                 }
437                 buffer.append(temp);
438                 temp = dateHeader.get(Calendar.MONTH);
439                 if (temp < 10) {
440                     buffer.append("0");
441                 }
442                 buffer.append(temp);
443                 temp = dateHeader.get(Calendar.DAY_OF_MONTH);
444                 if (temp < 10) {
445                     buffer.append("0");
446                 }
447                 buffer.append(temp);
448                 buffer.append("T");
449                 temp = dateHeader.get(Calendar.HOUR_OF_DAY);
450                 if (temp < 10) {
451                     buffer.append("0");
452                 }
453                 buffer.append(temp);
454                 temp = dateHeader.get(Calendar.MINUTE);
455                 if (temp < 10) {
456                     buffer.append("0");
457                 }
458                 buffer.append(temp);
459                 temp = dateHeader.get(Calendar.SECOND);
460                 if (temp < 10) {
461                     buffer.append("0");
462                 }
463                 buffer.append(temp);
464 
465                 if (dateHeader.getTimeZone().getID().equals("UTC")) {
466                     buffer.append("Z");
467                 }
468 
469                 try {
470                     value = buffer.toString().getBytes("ISO8859_1");
471                 } catch (UnsupportedEncodingException e) {
472                     throw e;
473                 }
474 
475                 length = value.length + 3;
476                 lengthArray[0] = (byte)(255 & (length >> 8));
477                 lengthArray[1] = (byte)(255 & length);
478                 out.write(HeaderSet.TIME_ISO_8601);
479                 out.write(lengthArray);
480                 out.write(value);
481                 if (nullOut) {
482                     headImpl.setHeader(HeaderSet.TIME_ISO_8601, null);
483                 }
484             }
485 
486             // Time 4 Byte Header
487             dateHeader = (Calendar)headImpl.getHeader(HeaderSet.TIME_4_BYTE);
488             if (dateHeader != null) {
489                 out.write(HeaderSet.TIME_4_BYTE);
490 
491                 /*
492                  * Need to call getTime() twice.  The first call will return
493                  * a java.util.Date object.  The second call returns the number
494                  * of milliseconds since January 1, 1970.  We need to convert
495                  * it to seconds since the TIME_4_BYTE expects the number of
496                  * seconds since January 1, 1970.
497                  */
498                 value = ObexHelper.convertToByteArray(dateHeader.getTime().getTime() / 1000L);
499                 out.write(value);
500                 if (nullOut) {
501                     headImpl.setHeader(HeaderSet.TIME_4_BYTE, null);
502                 }
503             }
504 
505             // Description Header
506             stringHeader = (String)headImpl.getHeader(HeaderSet.DESCRIPTION);
507             if (stringHeader != null) {
508                 out.write((byte)HeaderSet.DESCRIPTION);
509                 value = ObexHelper.convertToUnicodeByteArray(stringHeader);
510                 length = value.length + 3;
511                 lengthArray[0] = (byte)(255 & (length >> 8));
512                 lengthArray[1] = (byte)(255 & length);
513                 out.write(lengthArray);
514                 out.write(value);
515                 if (nullOut) {
516                     headImpl.setHeader(HeaderSet.DESCRIPTION, null);
517                 }
518             }
519 
520             // Target Header
521             value = (byte[])headImpl.getHeader(HeaderSet.TARGET);
522             if (value != null) {
523                 out.write((byte)HeaderSet.TARGET);
524                 length = value.length + 3;
525                 lengthArray[0] = (byte)(255 & (length >> 8));
526                 lengthArray[1] = (byte)(255 & length);
527                 out.write(lengthArray);
528                 out.write(value);
529                 if (nullOut) {
530                     headImpl.setHeader(HeaderSet.TARGET, null);
531                 }
532             }
533 
534             // HTTP Header
535             value = (byte[])headImpl.getHeader(HeaderSet.HTTP);
536             if (value != null) {
537                 out.write((byte)HeaderSet.HTTP);
538                 length = value.length + 3;
539                 lengthArray[0] = (byte)(255 & (length >> 8));
540                 lengthArray[1] = (byte)(255 & length);
541                 out.write(lengthArray);
542                 out.write(value);
543                 if (nullOut) {
544                     headImpl.setHeader(HeaderSet.HTTP, null);
545                 }
546             }
547 
548             // Who Header
549             value = (byte[])headImpl.getHeader(HeaderSet.WHO);
550             if (value != null) {
551                 out.write((byte)HeaderSet.WHO);
552                 length = value.length + 3;
553                 lengthArray[0] = (byte)(255 & (length >> 8));
554                 lengthArray[1] = (byte)(255 & length);
555                 out.write(lengthArray);
556                 out.write(value);
557                 if (nullOut) {
558                     headImpl.setHeader(HeaderSet.WHO, null);
559                 }
560             }
561 
562             // Connection ID Header
563             value = (byte[])headImpl.getHeader(HeaderSet.APPLICATION_PARAMETER);
564             if (value != null) {
565                 out.write((byte)HeaderSet.APPLICATION_PARAMETER);
566                 length = value.length + 3;
567                 lengthArray[0] = (byte)(255 & (length >> 8));
568                 lengthArray[1] = (byte)(255 & length);
569                 out.write(lengthArray);
570                 out.write(value);
571                 if (nullOut) {
572                     headImpl.setHeader(HeaderSet.APPLICATION_PARAMETER, null);
573                 }
574             }
575 
576             // Object Class Header
577             value = (byte[])headImpl.getHeader(HeaderSet.OBJECT_CLASS);
578             if (value != null) {
579                 out.write((byte)HeaderSet.OBJECT_CLASS);
580                 length = value.length + 3;
581                 lengthArray[0] = (byte)(255 & (length >> 8));
582                 lengthArray[1] = (byte)(255 & length);
583                 out.write(lengthArray);
584                 out.write(value);
585                 if (nullOut) {
586                     headImpl.setHeader(HeaderSet.OBJECT_CLASS, null);
587                 }
588             }
589 
590             // Check User Defined Headers
591             for (int i = 0; i < 16; i++) {
592 
593                 //Unicode String Header
594                 stringHeader = (String)headImpl.getHeader(i + 0x30);
595                 if (stringHeader != null) {
596                     out.write((byte)i + 0x30);
597                     value = ObexHelper.convertToUnicodeByteArray(stringHeader);
598                     length = value.length + 3;
599                     lengthArray[0] = (byte)(255 & (length >> 8));
600                     lengthArray[1] = (byte)(255 & length);
601                     out.write(lengthArray);
602                     out.write(value);
603                     if (nullOut) {
604                         headImpl.setHeader(i + 0x30, null);
605                     }
606                 }
607 
608                 // Byte Sequence Header
609                 value = (byte[])headImpl.getHeader(i + 0x70);
610                 if (value != null) {
611                     out.write((byte)i + 0x70);
612                     length = value.length + 3;
613                     lengthArray[0] = (byte)(255 & (length >> 8));
614                     lengthArray[1] = (byte)(255 & length);
615                     out.write(lengthArray);
616                     out.write(value);
617                     if (nullOut) {
618                         headImpl.setHeader(i + 0x70, null);
619                     }
620                 }
621 
622                 // Byte Header
623                 byteHeader = (Byte)headImpl.getHeader(i + 0xB0);
624                 if (byteHeader != null) {
625                     out.write((byte)i + 0xB0);
626                     out.write(byteHeader.byteValue());
627                     if (nullOut) {
628                         headImpl.setHeader(i + 0xB0, null);
629                     }
630                 }
631 
632                 // Integer header
633                 intHeader = (Long)headImpl.getHeader(i + 0xF0);
634                 if (intHeader != null) {
635                     out.write((byte)i + 0xF0);
636                     out.write(ObexHelper.convertToByteArray(intHeader.longValue()));
637                     if (nullOut) {
638                         headImpl.setHeader(i + 0xF0, null);
639                     }
640                 }
641             }
642 
643             // Add the authentication challenge header
644             if (headImpl.mAuthChall != null) {
645                 out.write((byte)HeaderSet.AUTH_CHALLENGE);
646                 length = headImpl.mAuthChall.length + 3;
647                 lengthArray[0] = (byte)(255 & (length >> 8));
648                 lengthArray[1] = (byte)(255 & length);
649                 out.write(lengthArray);
650                 out.write(headImpl.mAuthChall);
651                 if (nullOut) {
652                     headImpl.mAuthChall = null;
653                 }
654             }
655 
656             // Add the authentication response header
657             if (headImpl.mAuthResp != null) {
658                 out.write((byte)HeaderSet.AUTH_RESPONSE);
659                 length = headImpl.mAuthResp.length + 3;
660                 lengthArray[0] = (byte)(255 & (length >> 8));
661                 lengthArray[1] = (byte)(255 & length);
662                 out.write(lengthArray);
663                 out.write(headImpl.mAuthResp);
664                 if (nullOut) {
665                     headImpl.mAuthResp = null;
666                 }
667             }
668 
669         } catch (IOException e) {
670         } finally {
671             result = out.toByteArray();
672             try {
673                 out.close();
674             } catch (Exception ex) {
675             }
676         }
677 
678         return result;
679 
680     }
681 
682     /**
683      * Determines where the maximum divide is between headers. This method is
684      * used by put and get operations to separate headers to a size that meets
685      * the max packet size allowed.
686      * @param headerArray the headers to separate
687      * @param start the starting index to search
688      * @param maxSize the maximum size of a packet
689      * @return the index of the end of the header block to send or -1 if the
690      *         header could not be divided because the header is too large
691      */
findHeaderEnd(byte[] headerArray, int start, int maxSize)692     public static int findHeaderEnd(byte[] headerArray, int start, int maxSize) {
693 
694         int fullLength = 0;
695         int lastLength = -1;
696         int index = start;
697         int length = 0;
698 
699         while ((fullLength < maxSize) && (index < headerArray.length)) {
700             int headerID = (headerArray[index] < 0 ? headerArray[index] + 256 : headerArray[index]);
701             lastLength = fullLength;
702 
703             switch (headerID & (0xC0)) {
704 
705                 case 0x00:
706                     // Fall through
707                 case 0x40:
708 
709                     index++;
710                     length = (headerArray[index] < 0 ? headerArray[index] + 256
711                             : headerArray[index]);
712                     length = length << 8;
713                     index++;
714                     length += (headerArray[index] < 0 ? headerArray[index] + 256
715                             : headerArray[index]);
716                     length -= 3;
717                     index++;
718                     index += length;
719                     fullLength += length + 3;
720                     break;
721 
722                 case 0x80:
723 
724                     index++;
725                     index++;
726                     fullLength += 2;
727                     break;
728 
729                 case 0xC0:
730 
731                     index += 5;
732                     fullLength += 5;
733                     break;
734 
735             }
736 
737         }
738 
739         /*
740          * Determine if this is the last header or not
741          */
742         if (lastLength == 0) {
743             /*
744              * Since this is the last header, check to see if the size of this
745              * header is less then maxSize.  If it is, return the length of the
746              * header, otherwise return -1.  The length of the header is
747              * returned since it would be the start of the next header
748              */
749             if (fullLength < maxSize) {
750                 return headerArray.length;
751             } else {
752                 return -1;
753             }
754         } else {
755             return lastLength + start;
756         }
757     }
758 
759     /**
760      * Converts the byte array to a long.
761      * @param b the byte array to convert to a long
762      * @return the byte array as a long
763      */
convertToLong(byte[] b)764     public static long convertToLong(byte[] b) {
765         long result = 0;
766         long value = 0;
767         long power = 0;
768 
769         for (int i = (b.length - 1); i >= 0; i--) {
770             value = b[i];
771             if (value < 0) {
772                 value += 256;
773             }
774 
775             result = result | (value << power);
776             power += 8;
777         }
778 
779         return result;
780     }
781 
782     /**
783      * Converts the long to a 4 byte array. The long must be non negative.
784      * @param l the long to convert
785      * @return a byte array that is the same as the long
786      */
convertToByteArray(long l)787     public static byte[] convertToByteArray(long l) {
788         byte[] b = new byte[4];
789 
790         b[0] = (byte)(255 & (l >> 24));
791         b[1] = (byte)(255 & (l >> 16));
792         b[2] = (byte)(255 & (l >> 8));
793         b[3] = (byte)(255 & l);
794 
795         return b;
796     }
797 
798     /**
799      * Converts the String to a UNICODE byte array. It will also add the ending
800      * null characters to the end of the string.
801      * @param s the string to convert
802      * @return the unicode byte array of the string
803      */
convertToUnicodeByteArray(String s)804     public static byte[] convertToUnicodeByteArray(String s) {
805         if (s == null) {
806             return null;
807         }
808 
809         char c[] = s.toCharArray();
810         byte[] result = new byte[(c.length * 2) + 2];
811         for (int i = 0; i < c.length; i++) {
812             result[(i * 2)] = (byte)(c[i] >> 8);
813             result[((i * 2) + 1)] = (byte)c[i];
814         }
815 
816         // Add the UNICODE null character
817         result[result.length - 2] = 0;
818         result[result.length - 1] = 0;
819 
820         return result;
821     }
822 
823     /**
824      * Retrieves the value from the byte array for the tag value specified. The
825      * array should be of the form Tag - Length - Value triplet.
826      * @param tag the tag to retrieve from the byte array
827      * @param triplet the byte sequence containing the tag length value form
828      * @return the value of the specified tag
829      */
getTagValue(byte tag, byte[] triplet)830     public static byte[] getTagValue(byte tag, byte[] triplet) {
831 
832         int index = findTag(tag, triplet);
833         if (index == -1) {
834             return null;
835         }
836 
837         index++;
838         int length = triplet[index] & 0xFF;
839 
840         byte[] result = new byte[length];
841         index++;
842         System.arraycopy(triplet, index, result, 0, length);
843 
844         return result;
845     }
846 
847     /**
848      * Finds the index that starts the tag value pair in the byte array provide.
849      * @param tag the tag to look for
850      * @param value the byte array to search
851      * @return the starting index of the tag or -1 if the tag could not be found
852      */
findTag(byte tag, byte[] value)853     public static int findTag(byte tag, byte[] value) {
854         int length = 0;
855 
856         if (value == null) {
857             return -1;
858         }
859 
860         int index = 0;
861 
862         while ((index < value.length) && (value[index] != tag)) {
863             length = value[index + 1] & 0xFF;
864             index += length + 2;
865         }
866 
867         if (index >= value.length) {
868             return -1;
869         }
870 
871         return index;
872     }
873 
874     /**
875      * Converts the byte array provided to a unicode string.
876      * @param b the byte array to convert to a string
877      * @param includesNull determine if the byte string provided contains the
878      *        UNICODE null character at the end or not; if it does, it will be
879      *        removed
880      * @return a Unicode string
881      * @throws IllegalArgumentException if the byte array has an odd length
882      */
convertToUnicode(byte[] b, boolean includesNull)883     public static String convertToUnicode(byte[] b, boolean includesNull) {
884         if (b == null || b.length == 0) {
885             return null;
886         }
887         int arrayLength = b.length;
888         if (!((arrayLength % 2) == 0)) {
889             throw new IllegalArgumentException("Byte array not of a valid form");
890         }
891         arrayLength = (arrayLength >> 1);
892         if (includesNull) {
893             arrayLength -= 1;
894         }
895 
896         char[] c = new char[arrayLength];
897         for (int i = 0; i < arrayLength; i++) {
898             int upper = b[2 * i];
899             int lower = b[(2 * i) + 1];
900             if (upper < 0) {
901                 upper += 256;
902             }
903             if (lower < 0) {
904                 lower += 256;
905             }
906             // If upper and lower both equal 0, it should be the end of string.
907             // Ignore left bytes from array to avoid potential issues
908             if (upper == 0 && lower == 0) {
909                 return new String(c, 0, i);
910             }
911 
912             c[i] = (char)((upper << 8) | lower);
913         }
914 
915         return new String(c);
916     }
917 
918     /**
919      * Compute the MD5 hash of the byte array provided. Does not accumulate
920      * input.
921      * @param in the byte array to hash
922      * @return the MD5 hash of the byte array
923      */
computeMd5Hash(byte[] in)924     public static byte[] computeMd5Hash(byte[] in) {
925         try {
926             MessageDigest md5 = MessageDigest.getInstance("MD5");
927             return md5.digest(in);
928         } catch (NoSuchAlgorithmException e) {
929             throw new RuntimeException(e);
930         }
931     }
932 
933     /**
934      * Computes an authentication challenge header.
935      * @param nonce the challenge that will be provided to the peer; the
936      *        challenge must be 16 bytes long
937      * @param realm a short description that describes what password to use
938      * @param access if <code>true</code> then full access will be granted if
939      *        successful; if <code>false</code> then read only access will be
940      *        granted if successful
941      * @param userID if <code>true</code>, a user ID is required in the reply;
942      *        if <code>false</code>, no user ID is required
943      * @throws IllegalArgumentException if the challenge is not 16 bytes long;
944      *         if the realm can not be encoded in less then 255 bytes
945      * @throws IOException if the encoding scheme ISO 8859-1 is not supported
946      */
computeAuthenticationChallenge(byte[] nonce, String realm, boolean access, boolean userID)947     public static byte[] computeAuthenticationChallenge(byte[] nonce, String realm, boolean access,
948             boolean userID) throws IOException {
949         byte[] authChall = null;
950 
951         if (nonce.length != 16) {
952             throw new IllegalArgumentException("Nonce must be 16 bytes long");
953         }
954 
955         /*
956          * The authentication challenge is a byte sequence of the following form
957          * byte 0: 0x00 - the tag for the challenge
958          * byte 1: 0x10 - the length of the challenge; must be 16
959          * byte 2-17: the authentication challenge
960          * byte 18: 0x01 - the options tag; this is optional in the spec, but
961          *                 we are going to include it in every message
962          * byte 19: 0x01 - length of the options; must be 1
963          * byte 20: the value of the options; bit 0 is set if user ID is
964          *          required; bit 1 is set if access mode is read only
965          * byte 21: 0x02 - the tag for authentication realm; only included if
966          *                 an authentication realm is specified
967          * byte 22: the length of the authentication realm; only included if
968          *          the authentication realm is specified
969          * byte 23: the encoding scheme of the authentication realm; we will use
970          *          the ISO 8859-1 encoding scheme since it is part of the KVM
971          * byte 24 & up: the realm if one is specified.
972          */
973         if (realm == null) {
974             authChall = new byte[21];
975         } else {
976             if (realm.length() >= 255) {
977                 throw new IllegalArgumentException("Realm must be less then 255 bytes");
978             }
979             authChall = new byte[24 + realm.length()];
980             authChall[21] = 0x02;
981             authChall[22] = (byte)(realm.length() + 1);
982             authChall[23] = 0x01; // ISO 8859-1 Encoding
983             System.arraycopy(realm.getBytes("ISO8859_1"), 0, authChall, 24, realm.length());
984         }
985 
986         // Include the nonce field in the header
987         authChall[0] = 0x00;
988         authChall[1] = 0x10;
989         System.arraycopy(nonce, 0, authChall, 2, 16);
990 
991         // Include the options header
992         authChall[18] = 0x01;
993         authChall[19] = 0x01;
994         authChall[20] = 0x00;
995 
996         if (!access) {
997             authChall[20] = (byte)(authChall[20] | 0x02);
998         }
999         if (userID) {
1000             authChall[20] = (byte)(authChall[20] | 0x01);
1001         }
1002 
1003         return authChall;
1004     }
1005 }
1006