blob: c9f931c5bbb7f794e95237ac2309f26c03ca894f [file] [log] [blame]
///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.IOException;
import java.net.DatagramPacket;
import java.net.InetAddress;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
//import java.util.logging.Level;
//import java.util.logging.Logger;
/**
* 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
{
// 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;
// Implementation note: This vector should be immutable.
// If a client of DNSIncoming changes the contents of this vector,
// we get undesired results. To fix this, we have to migrate to
// the Collections API of Java 1.2. i.e we replace Vector by List.
// final static Vector EMPTY = new Vector();
private DatagramPacket packet;
private int off;
private int len;
private byte data[];
int id;
private int flags;
private int numQuestions;
int numAnswers;
private int numAuthorities;
private int numAdditionals;
private long receivedTime;
private List questions;
List answers;
/**
* Parse a message from a datagram packet.
*/
DNSIncoming(DatagramPacket packet) throws IOException
{
this.packet = packet;
InetAddress source = packet.getAddress();
this.data = packet.getData();
this.len = packet.getLength();
this.off = packet.getOffset();
this.questions = Collections.EMPTY_LIST;
this.answers = Collections.EMPTY_LIST;
this.receivedTime = System.currentTimeMillis();
try
{
id = readUnsignedShort();
flags = readUnsignedShort();
numQuestions = readUnsignedShort();
numAnswers = readUnsignedShort();
numAuthorities = readUnsignedShort();
numAdditionals = readUnsignedShort();
// parse questions
if (numQuestions > 0)
{
questions = Collections.synchronizedList(new ArrayList(numQuestions));
for (int i = 0; i < numQuestions; i++)
{
DNSQuestion question = new DNSQuestion(readName(), readUnsignedShort(), readUnsignedShort());
questions.add(question);
}
}
// parse answers
int n = numAnswers + numAuthorities + numAdditionals;
if (n > 0)
{
answers = Collections.synchronizedList(new ArrayList(n));
for (int i = 0; i < n; i++)
{
String domain = readName();
int type = readUnsignedShort();
int clazz = readUnsignedShort();
int ttl = readInt();
int len = readUnsignedShort();
int end = off + len;
DNSRecord rec = null;
switch (type)
{
case DNSConstants.TYPE_A: // IPv4
case DNSConstants.TYPE_AAAA: // IPv6 FIXME [PJYF Oct 14 2004] This has not been tested
rec = new DNSRecord.Address(domain, type, clazz, ttl, readBytes(off, len));
break;
case DNSConstants.TYPE_CNAME:
case DNSConstants.TYPE_PTR:
String service = "";
try {
service = readName();
} catch (IOException e){
// there was a problem reading the service name
e.printStackTrace();
}
rec = new DNSRecord.Pointer(domain, type, clazz, ttl, service);
break;
case DNSConstants.TYPE_TXT:
rec = new DNSRecord.Text(domain, type, clazz, ttl, readBytes(off, len));
break;
case DNSConstants.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
e.printStackTrace();
}
rec = new DNSRecord.Service(domain, type, clazz, ttl,
priority, weight, port, target);
break;
case DNSConstants.TYPE_HINFO:
// Maybe we should do something with those
break;
default :
// logger.finer("DNSIncoming() unknown type:" + type);
break;
}
if (rec != null)
{
rec.setRecordSource(source);
// Add a record, if we were able to create one.
answers.add(rec);
}
else
{
// Addjust the numbers for the skipped record
if (answers.size() < numAnswers)
{
numAnswers--;
}
else
{
if (answers.size() < numAnswers + numAuthorities)
{
numAuthorities--;
}
else
{
if (answers.size() < numAnswers + numAuthorities + numAdditionals)
{
numAdditionals--;
}
}
}
}
off = end;
}
}
}
catch (IOException e)
{
// logger.log(Level.WARNING, "DNSIncoming() dump " + print(true) + "\n exception ", e);
throw e;
}
}
/**
* Check if the message is a query.
*/
boolean isQuery()
{
return (flags & DNSConstants.FLAGS_QR_MASK) == DNSConstants.FLAGS_QR_QUERY;
}
/**
* Check if the message is truncated.
*/
public boolean isTruncated()
{
return (flags & DNSConstants.FLAGS_TC) != 0;
}
/**
* Check if the message is a response.
*/
boolean isResponse()
{
return (flags & DNSConstants.FLAGS_QR_MASK) == DNSConstants.FLAGS_QR_RESPONSE;
}
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 (get(off++) << 8) + get(off++);
}
private int readInt() throws IOException
{
return (readUnsignedShort() << 16) + readUnsignedShort();
}
private byte[] readBytes(int off, int len) throws IOException
{
byte bytes[] = new byte[len];
System.arraycopy(data, off, bytes, 0, len);
return bytes;
}
private void readUTF(StringBuffer buf, int off, int len) throws IOException
{
for (int end = off + len; off < end;)
{
int ch = get(off++);
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(off++) & 0x3F);
break;
case 14:
// 1110 xxxx 10xx xxxx 10xx xxxx
ch = ((ch & 0x0f) << 12) | ((get(off++) & 0x3F) << 6) | (get(off++) & 0x3F);
break;
default:
// 10xx xxxx, 1111 xxxx
ch = ((ch & 0x3F) << 4) | (get(off++) & 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);
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 (len & 0xC0)
{
case 0x00:
//buf.append("[" + off + "]");
readUTF(buf, off, len);
off += len;
buf.append('.');
break;
case 0xC0:
//buf.append("<" + (off - 1) + ">");
if (next < 0)
{
next = off + 1;
}
off = ((len & 0x3F) << 8) | 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;
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)
{
StringBuffer buf = new StringBuffer();
buf.append(toString() + "\n");
for (Iterator iterator = questions.iterator(); iterator.hasNext();)
{
buf.append(" ques:" + iterator.next() + "\n");
}
int count = 0;
for (Iterator iterator = answers.iterator(); iterator.hasNext(); count++)
{
if (count < numAnswers)
{
buf.append(" answ:");
}
else
{
if (count < numAnswers + numAuthorities)
{
buf.append(" auth:");
}
else
{
buf.append(" addi:");
}
}
buf.append(iterator.next() + "\n");
}
if (dump)
{
for (int off = 0, len = packet.getLength(); off < len; off += 32)
{
int n = Math.min(32, len - off);
if (off < 10)
{
buf.append(' ');
}
if (off < 100)
{
buf.append(' ');
}
buf.append(off);
buf.append(':');
for (int i = 0; i < n; i++)
{
if ((i % 8) == 0)
{
buf.append(' ');
}
buf.append(Integer.toHexString((data[off + i] & 0xF0) >> 4));
buf.append(Integer.toHexString((data[off + i] & 0x0F) >> 0));
}
buf.append("\n");
buf.append(" ");
for (int i = 0; i < n; i++)
{
if ((i % 8) == 0)
{
buf.append(' ');
}
buf.append(' ');
int ch = data[off + i] & 0xFF;
buf.append(((ch > ' ') && (ch < 127)) ? (char) ch : '.');
}
buf.append("\n");
// limit message size
if (off + 32 >= 256)
{
buf.append("....\n");
break;
}
}
}
return buf.toString();
}
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(",len=");
buf.append(packet.getLength());
buf.append(",id=0x");
buf.append(Integer.toHexString(id));
if (flags != 0)
{
buf.append(",flags=0x");
buf.append(Integer.toHexString(flags));
if ((flags & DNSConstants.FLAGS_QR_RESPONSE) != 0)
{
buf.append(":r");
}
if ((flags & DNSConstants.FLAGS_AA) != 0)
{
buf.append(":aa");
}
if ((flags & DNSConstants.FLAGS_TC) != 0)
{
buf.append(":tc");
}
}
if (numQuestions > 0)
{
buf.append(",questions=");
buf.append(numQuestions);
}
if (numAnswers > 0)
{
buf.append(",answers=");
buf.append(numAnswers);
}
if (numAuthorities > 0)
{
buf.append(",authorities=");
buf.append(numAuthorities);
}
if (numAdditionals > 0)
{
buf.append(",additionals=");
buf.append(numAdditionals);
}
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())
{
if (that.numQuestions > 0) {
if (Collections.EMPTY_LIST.equals(this.questions))
this.questions = Collections.synchronizedList(new ArrayList(that.numQuestions));
this.questions.addAll(that.questions);
this.numQuestions += that.numQuestions;
}
if (Collections.EMPTY_LIST.equals(answers))
{
answers = Collections.synchronizedList(new ArrayList());
}
if (that.numAnswers > 0)
{
this.answers.addAll(this.numAnswers, that.answers.subList(0, that.numAnswers));
this.numAnswers += that.numAnswers;
}
if (that.numAuthorities > 0)
{
this.answers.addAll(this.numAnswers + this.numAuthorities, that.answers.subList(that.numAnswers, that.numAnswers + that.numAuthorities));
this.numAuthorities += that.numAuthorities;
}
if (that.numAdditionals > 0)
{
this.answers.addAll(that.answers.subList(that.numAnswers + that.numAuthorities, that.numAnswers + that.numAuthorities + that.numAdditionals));
this.numAdditionals += that.numAdditionals;
}
}
else
{
throw new IllegalArgumentException();
}
}
public int elapseSinceArrival()
{
return (int) (System.currentTimeMillis() - receivedTime);
}
public List getQuestions()
{
return questions;
}
public List getAnswers()
{
return answers;
}
}