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