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