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