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