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