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