/*******************************************************************************
* This file is part of OpenNMS(R).
*
* Copyright (C) 2008-2011 The OpenNMS Group, Inc.
* OpenNMS(R) is Copyright (C) 1999-2011 The OpenNMS Group, Inc.
*
* OpenNMS(R) is a registered trademark of The OpenNMS Group, Inc.
*
* OpenNMS(R) is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published
* by the Free Software Foundation, either version 3 of the License,
* or (at your option) any later version.
*
* OpenNMS(R) is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with OpenNMS(R). If not, see:
* http://www.gnu.org/licenses/
*
* For more information contact:
* OpenNMS(R) Licensing <license@opennms.org>
* http://www.opennms.org/
* http://www.opennms.com/
*******************************************************************************/
package org.opennms.netmgt.provision.support.dns;
import java.io.ByteArrayInputStream;
import java.io.EOFException;
import java.io.IOException;
/**
* <P>
* DNSInputStrean extends a ByteArrayInputStream and has methods to decode the
* data of a DNS response to an address resquest.
* </P>
*/
public class DNSInputStream extends ByteArrayInputStream {
/**
* <P>
* Constructs a new input stream for decoding DNS records.
* </P>
*
* @param data
* The array of data to pass to the base class.
*/
public DNSInputStream(final byte[] data) {
super(data);
}
/**
* <P>
* Constructs a DNSInputStream object from the byte array.
* </P>
*
* @param data
* byte array containing the response data
* @param off
* offset of the data in the byte array
* @param len
* length of the byte array
*/
public DNSInputStream(final byte[] data, final int off, final int len) {
super(data, off, len);
}
/**
* <P>
* Read a byte off the input stream.
* </P>
*
* @return The integer read.
* @exception java.io.IOException
* Thrown if the end-of-file is encountered trying to read
* the next byte.
* @throws java.io.IOException if any.
*/
public int readByte() throws IOException {
final int rc = read();
if (rc == -1)
throw new EOFException("end of buffer on read");
return rc;
}
/**
* <P>
* Read a 'short' off the input stream.
* </P>
*
* @return The short from the input stream.
* @exception java.io.IOException
* Thrown if the end-of-file is encountered trying to read
* the next short.
* @throws java.io.IOException if any.
*/
public int readShort() throws IOException {
return (readByte() << 8 | readByte());
}
/**
* <P>
* Read an 'int' off the input stream.
* </P>
*
* @return The int from the input stream.
* @exception java.io.IOException
* Thrown if there is an error while read.
* @throws java.io.IOException if any.
*/
public long readInt() throws IOException {
long rc = 0;
for (int i = 0; i < 4; i++)
rc = (rc << 8) | readByte();
return rc;
}
/**
* <P>
* Read a 'string' off the input stream.
* </P>
*
* @return the string from the input stream
* @exception java.io.IOException
* Thrown if there is an error while read
* @throws java.io.IOException if any.
*/
public String readString() throws IOException {
final int len = readByte();
if (len == 0) {
return "";
}
final byte[] buffer = new byte[len];
final int rc = read(buffer);
if (rc == -1 || rc != len) throw new EOFException("end of file while reading array");
return new String(buffer);
}
/**
* <P>
* The readDomainName method is used to read an entire domain name from the
* stream. The string returned will be the concatentation of several
* substrings, each substring in the record is separated by a '.'(dot). For
* more information see the RFC for the distributed name service.
* </P>
*
* @return The domain name string.
* @exception java.io.IOException
* Thrown if an error occurs decoding the string from the
* stream.
* @throws java.io.IOException if any.
*/
public String readDomainName() throws IOException {
//
// Check to make sure that we are not going
// to index the array and generate an bounds
// exception.
//
if (pos >= count)
throw new EOFException("EOF reading domain name");
//
// check the length byte. If the upper two bits are
// not set then it is a normal string and can be
// decoded using the readString() method.
//
if ((buf[pos] & 0xc0) == 0) {
String label = readString();
if (label.length() > 0) {
//
// get the next component(s) of the
// domain name. This is a recursive call.
// If the length is not equal to null then
// append the tail and return the new domain
// name.
//
final String tail = readDomainName();
if (tail.length() > 0)
label = label + '.' + tail;
}
return label;
}
//
// If this point in the code is reached then
// compression is being used!
//
//
// If the upper two bits were set then this is a special
// encoding that points us to somewhere else in the record!
// We have to read that part of the record and to get the
// next element in the stream.
//
// Throw an I/O exception if the compression offset is
// malformed.
//
if ((buf[pos] & 0xc0) != 0xc0) throw new IOException("Invalid domain name compression offset");
//
// read the short that is the pointer to the other
// part of the stream. Note buf is a protected buffer.
//
final int offset = readShort() & 0x3fff;
final DNSInputStream dnsIn = new DNSInputStream(buf, offset, buf.length - offset);
return dnsIn.readDomainName();
}
/**
* <P>
* Reads the resource record from the input stream.
* </P>
*
* @return The DNSAddressRR that is in response to the address request.
* @exception java.io.IOException
* Thrown if data does not decode to a DNSAddressRRl.
* @throws java.io.IOException if any.
*/
public DNSAddressRR readRR() throws IOException {
final String rrName = readDomainName();
final int rrType = readShort();
final int rrClass = readShort();
final long rrTTL = readInt();
final int rrDataLen = readShort();
//
// Convert the length of data in this byte array input stream
// into a "substream" of data. The only way this could get
// complicated is if there are multiple threads using this
// stream. If that is the case then synchronization code
// should be used to wrap the next two lines -- Weave
//
final DNSInputStream rrDNSIn = new DNSInputStream(buf, pos, rrDataLen);
pos += rrDataLen;
try {
return new DNSAddressRR(rrName, rrType, rrClass, rrTTL, rrDNSIn);
} catch (Throwable ex) {
throw new IOException("Unknown DNSAddressRR (type " + " (" + rrType + "))" + "\nOriginating Exception: " + ex.getMessage());
}
}
}