DNSIncoming.java :  » UnTagged » android-cookbook » javax » jmdns » impl » Android Open Source

Android Open Source » UnTagged » android cookbook 
android cookbook » javax » jmdns » impl » DNSIncoming.java
///Copyright 2003-2005 Arthur van Hoff, Rick Blair
//Licensed under Apache License version 2.0
//Original license LGPL

package javax.jmdns.impl;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.InetAddress;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.jmdns.impl.constants.DNSConstants;
import javax.jmdns.impl.constants.DNSLabel;
import javax.jmdns.impl.constants.DNSOptionCode;
import javax.jmdns.impl.constants.DNSRecordClass;
import javax.jmdns.impl.constants.DNSRecordType;
import javax.jmdns.impl.constants.DNSResultCode;

/**
 * Parse an incoming DNS message into its components.
 *
 * @version %I%, %G%
 * @author Arthur van Hoff, Werner Randelshofer, Pierre Frisch, Daniel Bobbert
 */
public final class DNSIncoming extends DNSMessage
{
    private static Logger logger = Logger.getLogger(DNSIncoming.class.getName());

    // This is a hack to handle a bug in the BonjourConformanceTest
    // It is sending out target strings that don't follow the "domain name" format.
    public static boolean USE_DOMAIN_NAME_FORMAT_FOR_SRV_TARGET = true;

    public static class MessageInputStream extends ByteArrayInputStream
    {

        /**
         * @param buffer
         * @param offset
         * @param length
         */
        public MessageInputStream(byte[] buffer, int offset, int length)
        {
            super(buffer, offset, length);
            // TODO Auto-generated constructor stub
        }

    }

    private DatagramPacket _packet;

    private int _off;

    private int _len;

    private byte[] _data;

    private long _receivedTime;

    private int _senderUDPPayload;

    /**
     * Parse a message from a datagram packet.
     */
    DNSIncoming(DatagramPacket packet) throws IOException
    {
        super(0, 0, packet.getPort() == DNSConstants.MDNS_PORT);
        this._packet = packet;
        InetAddress source = packet.getAddress();
        this._data = packet.getData();
        this._len = packet.getLength();
        this._off = packet.getOffset();
        this._receivedTime = System.currentTimeMillis();
        this._senderUDPPayload = DNSConstants.MAX_MSG_TYPICAL;

        try
        {
            this.setId(this.readUnsignedShort());
            this.setFlags(this.readUnsignedShort());
            int numQuestions = readUnsignedShort();
            int numAnswers = readUnsignedShort();
            int numAuthorities = readUnsignedShort();
            int numAdditionals = readUnsignedShort();

            // parse questions
            if (numQuestions > 0)
            {
                for (int i = 0; i < numQuestions; i++)
                {
                    _questions.add(this.readQuestion());
                }
            }

            // parse answers
            if (numAnswers > 0)
            {
                for (int i = 0; i < numAnswers; i++)
                {
                    DNSRecord rec = this.readAnswer(source);
                    if (rec != null)
                    {
                        // Add a record, if we were able to create one.
                        _answers.add(rec);
                    }
                }
            }

            if (numAuthorities > 0)
            {
                for (int i = 0; i < numAuthorities; i++)
                {
                    DNSRecord rec = this.readAnswer(source);
                    if (rec != null)
                    {
                        // Add a record, if we were able to create one.
                        _authoritativeAnswers.add(rec);
                    }
                }
            }

            if (numAdditionals > 0)
            {
                for (int i = 0; i < numAdditionals; i++)
                {
                    DNSRecord rec = this.readAnswer(source);
                    if (rec != null)
                    {
                        // Add a record, if we were able to create one.
                        _additionals.add(rec);
                    }
                }
            }
        }
        catch (IOException e)
        {
            logger.log(Level.WARNING, "DNSIncoming() dump " + print(true) + "\n exception ", e);
            throw e;
        }
    }

    private DNSQuestion readQuestion() throws IOException
    {
        String domain = this.readName();
        DNSRecordType type = DNSRecordType.typeForIndex(this.readUnsignedShort());
        int recordClassIndex = this.readUnsignedShort();
        DNSRecordClass recordClass = DNSRecordClass.classForIndex(recordClassIndex);
        boolean unique = recordClass.isUnique(recordClassIndex);
        return DNSQuestion.newQuestion(domain, type, recordClass, unique);
    }

    private DNSRecord readAnswer(InetAddress source) throws IOException
    {
        String domain = this.readName();
        DNSRecordType type = DNSRecordType.typeForIndex(this.readUnsignedShort());
        int recordClassIndex = this.readUnsignedShort();
        DNSRecordClass recordClass = (type == DNSRecordType.TYPE_OPT ? DNSRecordClass.CLASS_UNKNOWN : DNSRecordClass.classForIndex(recordClassIndex));
        boolean unique = recordClass.isUnique(recordClassIndex);
        int ttl = this.readInt();
        int len = this.readUnsignedShort();
        int end = _off + len;
        DNSRecord rec = null;

        switch (type)
        {
            case TYPE_A: // IPv4
                rec = new DNSRecord.IPv4Address(domain, recordClass, unique, ttl, readBytes(_off, len));
                _off = _off + len;
                break;
            case TYPE_AAAA: // IPv6
                rec = new DNSRecord.IPv6Address(domain, recordClass, unique, ttl, readBytes(_off, len));
                _off = _off + len;
                break;
            case TYPE_CNAME:
            case TYPE_PTR:
                String service = "";
                try
                {
                    service = this.readName();
                }
                catch (IOException e)
                {
                    // there was a problem reading the service name
                    logger.log(Level.WARNING, "There was a problem reading the service name of the answer for domain:" + domain, e);
                }
                if (service.length() > 0)
                {
                    rec = new DNSRecord.Pointer(domain, recordClass, unique, ttl, service);
                }
                else
                {
                    logger.log(Level.WARNING, "There was a problem reading the service name of the answer for domain:" + domain);
                }
                break;
            case TYPE_TXT:
                rec = new DNSRecord.Text(domain, recordClass, unique, ttl, readBytes(_off, len));
                _off = _off + len;
                break;
            case TYPE_SRV:
                int priority = readUnsignedShort();
                int weight = readUnsignedShort();
                int port = readUnsignedShort();
                String target = "";
                try
                {
                    // This is a hack to handle a bug in the BonjourConformanceTest
                    // It is sending out target strings that don't follow the "domain name" format.

                    if (USE_DOMAIN_NAME_FORMAT_FOR_SRV_TARGET)
                    {
                        target = readName();
                    }
                    else
                    {
                        target = readNonNameString();
                    }
                }
                catch (IOException e)
                {
                    // this can happen if the type of the label cannot be handled.
                    // down below the offset gets advanced to the end of the record
                    logger.log(Level.WARNING, "There was a problem reading the label of the answer. This can happen if the type of the label cannot be handled." + this, e);
                }
                rec = new DNSRecord.Service(domain, recordClass, unique, ttl, priority, weight, port, target);
                break;
            case TYPE_HINFO:
                StringBuffer buf = new StringBuffer();
                this.readUTF(buf, _off, len);
                int index = buf.indexOf(" ");
                String cpu = (index > 0 ? buf.substring(0, index) : buf.toString()).trim();
                String os = (index > 0 ? buf.substring(index + 1) : "").trim();
                rec = new DNSRecord.HostInformation(domain, recordClass, unique, ttl, cpu, os);
                break;
            case TYPE_OPT:
                DNSResultCode extendedResultCode = DNSResultCode.resultCodeForFlags(this.getFlags(), ttl);
                int version = (ttl & 0x00ff0000) >> 16;
                if (version == 0)
                {
                    _senderUDPPayload = recordClassIndex;
                    while (_off < end)
                    {
                        // Read RDData
                        int optionCodeInt = 0;
                        DNSOptionCode optionCode = null;
                        if (end - _off >= 2)
                        {
                            optionCodeInt = this.readUnsignedShort();
                            optionCode = DNSOptionCode.resultCodeForFlags(optionCodeInt);
                        }
                        else
                        {
                            logger.log(Level.WARNING, "There was a problem reading the OPT record. Ignoring.");
                            break;
                        }
                        int optionLength = 0;
                        if (end - _off >= 2)
                        {
                            optionLength = readUnsignedShort();
                        }
                        else
                        {
                            logger.log(Level.WARNING, "There was a problem reading the OPT record. Ignoring.");
                            break;
                        }
                        byte[] optiondata = new byte[0];
                        if (end - _off >= optionLength)
                        {
                            optiondata = this.readBytes(_off, optionLength);
                            _off = _off + optionLength;
                        }
                        //
                        if (DNSOptionCode.Unknown == optionCode)
                        {
                            logger.log(Level.WARNING, "There was an OPT answer. Not currently handled. Option code: " + optionCodeInt + " data: " + this._hexString(optiondata));
                        }
                        else
                        {
                            // We should really do something with those options.
                            logger.log(Level.INFO, "There was an OPT answer. Option code: " + optionCode + " data: " + this._hexString(optiondata));
                        }
                    }
                }
                else
                {
                    logger.log(Level.WARNING, "There was an OPT answer. Wrong version number: " + version + " result code: " + extendedResultCode);
                }
                break;
            default:
                if (logger.isLoggable(Level.FINER))
                {
                    logger.finer("DNSIncoming() unknown type:" + type);
                }
                _off = end;
                break;
        }
        if (rec != null)
        {
            rec.setRecordSource(source);
        }
        _off = end;
        return rec;
    }

    private int get(int off) throws IOException
    {
        if ((off < 0) || (off >= _len))
        {
            throw new IOException("parser error: offset=" + off);
        }
        return _data[off] & 0xFF;
    }

    private int readUnsignedShort() throws IOException
    {
        return (this.get(_off++) << 8) | this.get(_off++);
    }

    private int readInt() throws IOException
    {
        return (this.readUnsignedShort() << 16) | this.readUnsignedShort();
    }

    /**
     * @param off
     * @param len
     * @return
     * @throws IOException
     */
    private byte[] readBytes(int off, int len) throws IOException
    {
        byte bytes[] = new byte[len];
        if (len > 0)
            System.arraycopy(_data, off, bytes, 0, len);
        return bytes;
    }

    private void readUTF(StringBuffer buf, int off, int len) throws IOException
    {
        int offset = off;
        for (int end = offset + len; offset < end;)
        {
            int ch = get(offset++);
            switch (ch >> 4)
            {
                case 0:
                case 1:
                case 2:
                case 3:
                case 4:
                case 5:
                case 6:
                case 7:
                    // 0xxxxxxx
                    break;
                case 12:
                case 13:
                    // 110x xxxx 10xx xxxx
                    ch = ((ch & 0x1F) << 6) | (get(offset++) & 0x3F);
                    break;
                case 14:
                    // 1110 xxxx 10xx xxxx 10xx xxxx
                    ch = ((ch & 0x0f) << 12) | ((get(offset++) & 0x3F) << 6) | (get(offset++) & 0x3F);
                    break;
                default:
                    // 10xx xxxx, 1111 xxxx
                    ch = ((ch & 0x3F) << 4) | (get(offset++) & 0x0f);
                    break;
            }
            buf.append((char) ch);
        }
    }

    private String readNonNameString() throws IOException
    {
        StringBuffer buf = new StringBuffer();
        int off = this._off;
        int len = get(off++);
        readUTF(buf, off, len);

        this._off = this._off + len + 1;
        return buf.toString();
    }

    private String readName() throws IOException
    {
        StringBuffer buf = new StringBuffer();
        int off = this._off;
        int next = -1;
        int first = off;

        while (true)
        {
            int len = get(off++);
            if (len == 0)
            {
                break;
            }
            switch (DNSLabel.labelForByte(len))
            {
                case Standard:
                    // buf.append("[" + off + "]");
                    this.readUTF(buf, off, len);
                    off += len;
                    buf.append('.');
                    break;
                case Compressed:
                    // buf.append("<" + (off - 1) + ">");
                    if (next < 0)
                    {
                        next = off + 1;
                    }
                    off = (DNSLabel.labelValue(len) << 8) | this.get(off++);
                    if (off >= first)
                    {
                        throw new IOException("bad domain name: possible circular name detected." + " name start: " + first + " bad offset: 0x" + Integer.toHexString(off));
                    }
                    first = off;
                    break;
                case Extended:
                    // int extendedLabelClass = DNSLabel.labelValue(len);
                    logger.severe("Extended label are not currently supported.");
                    break;
                case Unknown:
                default:
                    throw new IOException("unsupported dns label type: '" + Integer.toHexString(len & 0xC0) + "' at " + (off - 1));
            }
        }
        this._off = (next >= 0) ? next : off;
        return buf.toString();
    }

    /**
     * Debugging.
     */
    String print(boolean dump)
    {
        StringBuilder buf = new StringBuilder();
        buf.append(this.print());
        if (dump)
        {
            for (int off = 0, len = _packet.getLength(); off < len; off += 32)
            {
                int n = Math.min(32, len - off);
                if (off < 0x10)
                {
                    buf.append(' ');
                }
                if (off < 0x100)
                {
                    buf.append(' ');
                }
                if (off < 0x1000)
                {
                    buf.append(' ');
                }
                buf.append(Integer.toHexString(off));
                buf.append(':');
                int index = 0;
                for (index = 0; index < n; index++)
                {
                    if ((index % 8) == 0)
                    {
                        buf.append(' ');
                    }
                    buf.append(Integer.toHexString((_data[off + index] & 0xF0) >> 4));
                    buf.append(Integer.toHexString((_data[off + index] & 0x0F) >> 0));
                }
                // for incomplete lines
                if (index < 32)
                {
                    for (int i = index; i < 32; i++)
                    {
                        if ((i % 8) == 0)
                        {
                            buf.append(' ');
                        }
                        buf.append("  ");
                    }
                }
                buf.append("    ");
                for (index = 0; index < n; index++)
                {
                    if ((index % 8) == 0)
                    {
                        buf.append(' ');
                    }
                    int ch = _data[off + index] & 0xFF;
                    buf.append(((ch > ' ') && (ch < 127)) ? (char) ch : '.');
                }
                buf.append("\n");

                // limit message size
                if (off + 32 >= 2048)
                {
                    buf.append("....\n");
                    break;
                }
            }
        }
        return buf.toString();
    }

    @Override
    public String toString()
    {
        StringBuffer buf = new StringBuffer();
        buf.append(isQuery() ? "dns[query," : "dns[response,");
        if (_packet.getAddress() != null)
        {
            buf.append(_packet.getAddress().getHostAddress());
        }
        buf.append(':');
        buf.append(_packet.getPort());
        buf.append(", length=");
        buf.append(_packet.getLength());
        buf.append(", id=0x");
        buf.append(Integer.toHexString(this.getId()));
        if (this.getFlags() != 0)
        {
            buf.append(", flags=0x");
            buf.append(Integer.toHexString(this.getFlags()));
            if ((this.getFlags() & DNSConstants.FLAGS_QR_RESPONSE) != 0)
            {
                buf.append(":r");
            }
            if ((this.getFlags() & DNSConstants.FLAGS_AA) != 0)
            {
                buf.append(":aa");
            }
            if ((this.getFlags() & DNSConstants.FLAGS_TC) != 0)
            {
                buf.append(":tc");
            }
        }
        if (this.getNumberOfQuestions() > 0)
        {
            buf.append(", questions=");
            buf.append(this.getNumberOfQuestions());
        }
        if (this.getNumberOfAnswers() > 0)
        {
            buf.append(", answers=");
            buf.append(this.getNumberOfAnswers());
        }
        if (this.getNumberOfAuthorities() > 0)
        {
            buf.append(", authorities=");
            buf.append(this.getNumberOfAuthorities());
        }
        if (this.getNumberOfAdditionals() > 0)
        {
            buf.append(", additionals=");
            buf.append(this.getNumberOfAdditionals());
        }
        if (this.getNumberOfQuestions() > 0)
        {
            buf.append("\nquestions:");
            for (DNSQuestion question : _questions)
            {
                buf.append("\n\t" + question);
            }
        }
        if (this.getNumberOfAnswers() > 0)
        {
            buf.append("\nanswers:");
            for (DNSRecord record : _answers)
            {
                buf.append("\n\t" + record);
            }
        }
        if (this.getNumberOfAuthorities() > 0)
        {
            buf.append("\nauthorities:");
            for (DNSRecord record : _authoritativeAnswers)
            {
                buf.append("\n\t" + record);
            }
        }
        if (this.getNumberOfAdditionals() > 0)
        {
            buf.append("\nadditionals:");
            for (DNSRecord record : _additionals)
            {
                buf.append("\n\t" + record);
            }
        }
        buf.append("]");
        return buf.toString();
    }

    /**
     * Appends answers to this Incoming.
     *
     * @throws IllegalArgumentException
     *             If not a query or if Truncated.
     */
    void append(DNSIncoming that)
    {
        if (this.isQuery() && this.isTruncated() && that.isQuery())
        {
            this._questions.addAll(that.getQuestions());
            this._answers.addAll(that.getAnswers());
            this._authoritativeAnswers.addAll(that.getAuthorities());
            this._additionals.addAll(that.getAdditionals());
        }
        else
        {
            throw new IllegalArgumentException();
        }
    }

    public int elapseSinceArrival()
    {
        return (int) (System.currentTimeMillis() - _receivedTime);
    }

    /**
     * This will return the default UDP payload except if an OPT record was found with a different size.
     *
     * @return the senderUDPPayload
     */
    public int getSenderUDPPayload()
    {
        return this._senderUDPPayload;
    }

    private static final char[] _nibbleToHex = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };

    /**
     * Returns a hex-string for printing
     *
     * @param bytes
     *
     * @return Returns a hex-string which can be used within a SQL expression
     */
    private String _hexString(byte[] bytes)
    {

        StringBuilder result = new StringBuilder(2 * bytes.length);

        for (int i = 0; i < bytes.length; i++)
        {
            int b = bytes[i] & 0xFF;
            result.append(_nibbleToHex[b / 16]);
            result.append(_nibbleToHex[b % 16]);
        }

        return result.toString();
    }

}
java2s.com  | Contact Us | Privacy Policy
Copyright 2009 - 12 Demo Source and Support. All rights reserved.
All other trademarks are property of their respective owners.