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