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