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