aboutsummaryrefslogtreecommitdiff
path: root/src/org/xbill/DNS/Record.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/org/xbill/DNS/Record.java')
-rw-r--r--src/org/xbill/DNS/Record.java736
1 files changed, 736 insertions, 0 deletions
diff --git a/src/org/xbill/DNS/Record.java b/src/org/xbill/DNS/Record.java
new file mode 100644
index 0000000..8da7015
--- /dev/null
+++ b/src/org/xbill/DNS/Record.java
@@ -0,0 +1,736 @@
+// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org)
+
+package org.xbill.DNS;
+
+import java.io.*;
+import java.text.*;
+import java.util.*;
+import org.xbill.DNS.utils.*;
+
+/**
+ * A generic DNS resource record. The specific record types extend this class.
+ * A record contains a name, type, class, ttl, and rdata.
+ *
+ * @author Brian Wellington
+ */
+
+public abstract class Record implements Cloneable, Comparable, Serializable {
+
+private static final long serialVersionUID = 2694906050116005466L;
+
+protected Name name;
+protected int type, dclass;
+protected long ttl;
+
+private static final DecimalFormat byteFormat = new DecimalFormat();
+
+static {
+ byteFormat.setMinimumIntegerDigits(3);
+}
+
+protected
+Record() {}
+
+Record(Name name, int type, int dclass, long ttl) {
+ if (!name.isAbsolute())
+ throw new RelativeNameException(name);
+ Type.check(type);
+ DClass.check(dclass);
+ TTL.check(ttl);
+ this.name = name;
+ this.type = type;
+ this.dclass = dclass;
+ this.ttl = ttl;
+}
+
+/**
+ * Creates an empty record of the correct type; must be overriden
+ */
+abstract Record
+getObject();
+
+private static final Record
+getEmptyRecord(Name name, int type, int dclass, long ttl, boolean hasData) {
+ Record proto, rec;
+
+ if (hasData) {
+ proto = Type.getProto(type);
+ if (proto != null)
+ rec = proto.getObject();
+ else
+ rec = new UNKRecord();
+ } else
+ rec = new EmptyRecord();
+ rec.name = name;
+ rec.type = type;
+ rec.dclass = dclass;
+ rec.ttl = ttl;
+ return rec;
+}
+
+/**
+ * Converts the type-specific RR to wire format - must be overriden
+ */
+abstract void
+rrFromWire(DNSInput in) throws IOException;
+
+private static Record
+newRecord(Name name, int type, int dclass, long ttl, int length, DNSInput in)
+throws IOException
+{
+ Record rec;
+ rec = getEmptyRecord(name, type, dclass, ttl, in != null);
+ if (in != null) {
+ if (in.remaining() < length)
+ throw new WireParseException("truncated record");
+ in.setActive(length);
+
+ rec.rrFromWire(in);
+
+ if (in.remaining() > 0)
+ throw new WireParseException("invalid record length");
+ in.clearActive();
+ }
+ return rec;
+}
+
+/**
+ * Creates a new record, with the given parameters.
+ * @param name The owner name of the record.
+ * @param type The record's type.
+ * @param dclass The record's class.
+ * @param ttl The record's time to live.
+ * @param length The length of the record's data.
+ * @param data The rdata of the record, in uncompressed DNS wire format. Only
+ * the first length bytes are used.
+ */
+public static Record
+newRecord(Name name, int type, int dclass, long ttl, int length, byte [] data) {
+ if (!name.isAbsolute())
+ throw new RelativeNameException(name);
+ Type.check(type);
+ DClass.check(dclass);
+ TTL.check(ttl);
+
+ DNSInput in;
+ if (data != null)
+ in = new DNSInput(data);
+ else
+ in = null;
+ try {
+ return newRecord(name, type, dclass, ttl, length, in);
+ }
+ catch (IOException e) {
+ return null;
+ }
+}
+
+/**
+ * Creates a new record, with the given parameters.
+ * @param name The owner name of the record.
+ * @param type The record's type.
+ * @param dclass The record's class.
+ * @param ttl The record's time to live.
+ * @param data The complete rdata of the record, in uncompressed DNS wire
+ * format.
+ */
+public static Record
+newRecord(Name name, int type, int dclass, long ttl, byte [] data) {
+ return newRecord(name, type, dclass, ttl, data.length, data);
+}
+
+/**
+ * Creates a new empty record, with the given parameters.
+ * @param name The owner name of the record.
+ * @param type The record's type.
+ * @param dclass The record's class.
+ * @param ttl The record's time to live.
+ * @return An object of a subclass of Record
+ */
+public static Record
+newRecord(Name name, int type, int dclass, long ttl) {
+ if (!name.isAbsolute())
+ throw new RelativeNameException(name);
+ Type.check(type);
+ DClass.check(dclass);
+ TTL.check(ttl);
+
+ return getEmptyRecord(name, type, dclass, ttl, false);
+}
+
+/**
+ * Creates a new empty record, with the given parameters. This method is
+ * designed to create records that will be added to the QUERY section
+ * of a message.
+ * @param name The owner name of the record.
+ * @param type The record's type.
+ * @param dclass The record's class.
+ * @return An object of a subclass of Record
+ */
+public static Record
+newRecord(Name name, int type, int dclass) {
+ return newRecord(name, type, dclass, 0);
+}
+
+static Record
+fromWire(DNSInput in, int section, boolean isUpdate) throws IOException {
+ int type, dclass;
+ long ttl;
+ int length;
+ Name name;
+ Record rec;
+
+ name = new Name(in);
+ type = in.readU16();
+ dclass = in.readU16();
+
+ if (section == Section.QUESTION)
+ return newRecord(name, type, dclass);
+
+ ttl = in.readU32();
+ length = in.readU16();
+ if (length == 0 && isUpdate &&
+ (section == Section.PREREQ || section == Section.UPDATE))
+ return newRecord(name, type, dclass, ttl);
+ rec = newRecord(name, type, dclass, ttl, length, in);
+ return rec;
+}
+
+static Record
+fromWire(DNSInput in, int section) throws IOException {
+ return fromWire(in, section, false);
+}
+
+/**
+ * Builds a Record from DNS uncompressed wire format.
+ */
+public static Record
+fromWire(byte [] b, int section) throws IOException {
+ return fromWire(new DNSInput(b), section, false);
+}
+
+void
+toWire(DNSOutput out, int section, Compression c) {
+ name.toWire(out, c);
+ out.writeU16(type);
+ out.writeU16(dclass);
+ if (section == Section.QUESTION)
+ return;
+ out.writeU32(ttl);
+ int lengthPosition = out.current();
+ out.writeU16(0); /* until we know better */
+ rrToWire(out, c, false);
+ int rrlength = out.current() - lengthPosition - 2;
+ out.writeU16At(rrlength, lengthPosition);
+}
+
+/**
+ * Converts a Record into DNS uncompressed wire format.
+ */
+public byte []
+toWire(int section) {
+ DNSOutput out = new DNSOutput();
+ toWire(out, section, null);
+ return out.toByteArray();
+}
+
+private void
+toWireCanonical(DNSOutput out, boolean noTTL) {
+ name.toWireCanonical(out);
+ out.writeU16(type);
+ out.writeU16(dclass);
+ if (noTTL) {
+ out.writeU32(0);
+ } else {
+ out.writeU32(ttl);
+ }
+ int lengthPosition = out.current();
+ out.writeU16(0); /* until we know better */
+ rrToWire(out, null, true);
+ int rrlength = out.current() - lengthPosition - 2;
+ out.writeU16At(rrlength, lengthPosition);
+}
+
+/*
+ * Converts a Record into canonical DNS uncompressed wire format (all names are
+ * converted to lowercase), optionally ignoring the TTL.
+ */
+private byte []
+toWireCanonical(boolean noTTL) {
+ DNSOutput out = new DNSOutput();
+ toWireCanonical(out, noTTL);
+ return out.toByteArray();
+}
+
+/**
+ * Converts a Record into canonical DNS uncompressed wire format (all names are
+ * converted to lowercase).
+ */
+public byte []
+toWireCanonical() {
+ return toWireCanonical(false);
+}
+
+/**
+ * Converts the rdata in a Record into canonical DNS uncompressed wire format
+ * (all names are converted to lowercase).
+ */
+public byte []
+rdataToWireCanonical() {
+ DNSOutput out = new DNSOutput();
+ rrToWire(out, null, true);
+ return out.toByteArray();
+}
+
+/**
+ * Converts the type-specific RR to text format - must be overriden
+ */
+abstract String rrToString();
+
+/**
+ * Converts the rdata portion of a Record into a String representation
+ */
+public String
+rdataToString() {
+ return rrToString();
+}
+
+/**
+ * Converts a Record into a String representation
+ */
+public String
+toString() {
+ StringBuffer sb = new StringBuffer();
+ sb.append(name);
+ if (sb.length() < 8)
+ sb.append("\t");
+ if (sb.length() < 16)
+ sb.append("\t");
+ sb.append("\t");
+ if (Options.check("BINDTTL"))
+ sb.append(TTL.format(ttl));
+ else
+ sb.append(ttl);
+ sb.append("\t");
+ if (dclass != DClass.IN || !Options.check("noPrintIN")) {
+ sb.append(DClass.string(dclass));
+ sb.append("\t");
+ }
+ sb.append(Type.string(type));
+ String rdata = rrToString();
+ if (!rdata.equals("")) {
+ sb.append("\t");
+ sb.append(rdata);
+ }
+ return sb.toString();
+}
+
+/**
+ * Converts the text format of an RR to the internal format - must be overriden
+ */
+abstract void
+rdataFromString(Tokenizer st, Name origin) throws IOException;
+
+/**
+ * Converts a String into a byte array.
+ */
+protected static byte []
+byteArrayFromString(String s) throws TextParseException {
+ byte [] array = s.getBytes();
+ boolean escaped = false;
+ boolean hasEscapes = false;
+
+ for (int i = 0; i < array.length; i++) {
+ if (array[i] == '\\') {
+ hasEscapes = true;
+ break;
+ }
+ }
+ if (!hasEscapes) {
+ if (array.length > 255) {
+ throw new TextParseException("text string too long");
+ }
+ return array;
+ }
+
+ ByteArrayOutputStream os = new ByteArrayOutputStream();
+
+ int digits = 0;
+ int intval = 0;
+ for (int i = 0; i < array.length; i++) {
+ byte b = array[i];
+ if (escaped) {
+ if (b >= '0' && b <= '9' && digits < 3) {
+ digits++;
+ intval *= 10;
+ intval += (b - '0');
+ if (intval > 255)
+ throw new TextParseException
+ ("bad escape");
+ if (digits < 3)
+ continue;
+ b = (byte) intval;
+ }
+ else if (digits > 0 && digits < 3)
+ throw new TextParseException("bad escape");
+ os.write(b);
+ escaped = false;
+ }
+ else if (array[i] == '\\') {
+ escaped = true;
+ digits = 0;
+ intval = 0;
+ }
+ else
+ os.write(array[i]);
+ }
+ if (digits > 0 && digits < 3)
+ throw new TextParseException("bad escape");
+ array = os.toByteArray();
+ if (array.length > 255) {
+ throw new TextParseException("text string too long");
+ }
+
+ return os.toByteArray();
+}
+
+/**
+ * Converts a byte array into a String.
+ */
+protected static String
+byteArrayToString(byte [] array, boolean quote) {
+ StringBuffer sb = new StringBuffer();
+ if (quote)
+ sb.append('"');
+ for (int i = 0; i < array.length; i++) {
+ int b = array[i] & 0xFF;
+ if (b < 0x20 || b >= 0x7f) {
+ sb.append('\\');
+ sb.append(byteFormat.format(b));
+ } else if (b == '"' || b == '\\') {
+ sb.append('\\');
+ sb.append((char)b);
+ } else
+ sb.append((char)b);
+ }
+ if (quote)
+ sb.append('"');
+ return sb.toString();
+}
+
+/**
+ * Converts a byte array into the unknown RR format.
+ */
+protected static String
+unknownToString(byte [] data) {
+ StringBuffer sb = new StringBuffer();
+ sb.append("\\# ");
+ sb.append(data.length);
+ sb.append(" ");
+ sb.append(base16.toString(data));
+ return sb.toString();
+}
+
+/**
+ * Builds a new Record from its textual representation
+ * @param name The owner name of the record.
+ * @param type The record's type.
+ * @param dclass The record's class.
+ * @param ttl The record's time to live.
+ * @param st A tokenizer containing the textual representation of the rdata.
+ * @param origin The default origin to be appended to relative domain names.
+ * @return The new record
+ * @throws IOException The text format was invalid.
+ */
+public static Record
+fromString(Name name, int type, int dclass, long ttl, Tokenizer st, Name origin)
+throws IOException
+{
+ Record rec;
+
+ if (!name.isAbsolute())
+ throw new RelativeNameException(name);
+ Type.check(type);
+ DClass.check(dclass);
+ TTL.check(ttl);
+
+ Tokenizer.Token t = st.get();
+ if (t.type == Tokenizer.IDENTIFIER && t.value.equals("\\#")) {
+ int length = st.getUInt16();
+ byte [] data = st.getHex();
+ if (data == null) {
+ data = new byte[0];
+ }
+ if (length != data.length)
+ throw st.exception("invalid unknown RR encoding: " +
+ "length mismatch");
+ DNSInput in = new DNSInput(data);
+ return newRecord(name, type, dclass, ttl, length, in);
+ }
+ st.unget();
+ rec = getEmptyRecord(name, type, dclass, ttl, true);
+ rec.rdataFromString(st, origin);
+ t = st.get();
+ if (t.type != Tokenizer.EOL && t.type != Tokenizer.EOF) {
+ throw st.exception("unexpected tokens at end of record");
+ }
+ return rec;
+}
+
+/**
+ * Builds a new Record from its textual representation
+ * @param name The owner name of the record.
+ * @param type The record's type.
+ * @param dclass The record's class.
+ * @param ttl The record's time to live.
+ * @param s The textual representation of the rdata.
+ * @param origin The default origin to be appended to relative domain names.
+ * @return The new record
+ * @throws IOException The text format was invalid.
+ */
+public static Record
+fromString(Name name, int type, int dclass, long ttl, String s, Name origin)
+throws IOException
+{
+ return fromString(name, type, dclass, ttl, new Tokenizer(s), origin);
+}
+
+/**
+ * Returns the record's name
+ * @see Name
+ */
+public Name
+getName() {
+ return name;
+}
+
+/**
+ * Returns the record's type
+ * @see Type
+ */
+public int
+getType() {
+ return type;
+}
+
+/**
+ * Returns the type of RRset that this record would belong to. For all types
+ * except RRSIG, this is equivalent to getType().
+ * @return The type of record, if not RRSIG. If the type is RRSIG,
+ * the type covered is returned.
+ * @see Type
+ * @see RRset
+ * @see SIGRecord
+ */
+public int
+getRRsetType() {
+ if (type == Type.RRSIG) {
+ RRSIGRecord sig = (RRSIGRecord) this;
+ return sig.getTypeCovered();
+ }
+ return type;
+}
+
+/**
+ * Returns the record's class
+ */
+public int
+getDClass() {
+ return dclass;
+}
+
+/**
+ * Returns the record's TTL
+ */
+public long
+getTTL() {
+ return ttl;
+}
+
+/**
+ * Converts the type-specific RR to wire format - must be overriden
+ */
+abstract void
+rrToWire(DNSOutput out, Compression c, boolean canonical);
+
+/**
+ * Determines if two Records could be part of the same RRset.
+ * This compares the name, type, and class of the Records; the ttl and
+ * rdata are not compared.
+ */
+public boolean
+sameRRset(Record rec) {
+ return (getRRsetType() == rec.getRRsetType() &&
+ dclass == rec.dclass &&
+ name.equals(rec.name));
+}
+
+/**
+ * Determines if two Records are identical. This compares the name, type,
+ * class, and rdata (with names canonicalized). The TTLs are not compared.
+ * @param arg The record to compare to
+ * @return true if the records are equal, false otherwise.
+ */
+public boolean
+equals(Object arg) {
+ if (arg == null || !(arg instanceof Record))
+ return false;
+ Record r = (Record) arg;
+ if (type != r.type || dclass != r.dclass || !name.equals(r.name))
+ return false;
+ byte [] array1 = rdataToWireCanonical();
+ byte [] array2 = r.rdataToWireCanonical();
+ return Arrays.equals(array1, array2);
+}
+
+/**
+ * Generates a hash code based on the Record's data.
+ */
+public int
+hashCode() {
+ byte [] array = toWireCanonical(true);
+ int code = 0;
+ for (int i = 0; i < array.length; i++)
+ code += ((code << 3) + (array[i] & 0xFF));
+ return code;
+}
+
+Record
+cloneRecord() {
+ try {
+ return (Record) clone();
+ }
+ catch (CloneNotSupportedException e) {
+ throw new IllegalStateException();
+ }
+}
+
+/**
+ * Creates a new record identical to the current record, but with a different
+ * name. This is most useful for replacing the name of a wildcard record.
+ */
+public Record
+withName(Name name) {
+ if (!name.isAbsolute())
+ throw new RelativeNameException(name);
+ Record rec = cloneRecord();
+ rec.name = name;
+ return rec;
+}
+
+/**
+ * Creates a new record identical to the current record, but with a different
+ * class and ttl. This is most useful for dynamic update.
+ */
+Record
+withDClass(int dclass, long ttl) {
+ Record rec = cloneRecord();
+ rec.dclass = dclass;
+ rec.ttl = ttl;
+ return rec;
+}
+
+/* Sets the TTL to the specified value. This is intentionally not public. */
+void
+setTTL(long ttl) {
+ this.ttl = ttl;
+}
+
+/**
+ * Compares this Record to another Object.
+ * @param o The Object to be compared.
+ * @return The value 0 if the argument is a record equivalent to this record;
+ * a value less than 0 if the argument is less than this record in the
+ * canonical ordering, and a value greater than 0 if the argument is greater
+ * than this record in the canonical ordering. The canonical ordering
+ * is defined to compare by name, class, type, and rdata.
+ * @throws ClassCastException if the argument is not a Record.
+ */
+public int
+compareTo(Object o) {
+ Record arg = (Record) o;
+
+ if (this == arg)
+ return (0);
+
+ int n = name.compareTo(arg.name);
+ if (n != 0)
+ return (n);
+ n = dclass - arg.dclass;
+ if (n != 0)
+ return (n);
+ n = type - arg.type;
+ if (n != 0)
+ return (n);
+ byte [] rdata1 = rdataToWireCanonical();
+ byte [] rdata2 = arg.rdataToWireCanonical();
+ for (int i = 0; i < rdata1.length && i < rdata2.length; i++) {
+ n = (rdata1[i] & 0xFF) - (rdata2[i] & 0xFF);
+ if (n != 0)
+ return (n);
+ }
+ return (rdata1.length - rdata2.length);
+}
+
+/**
+ * Returns the name for which additional data processing should be done
+ * for this record. This can be used both for building responses and
+ * parsing responses.
+ * @return The name to used for additional data processing, or null if this
+ * record type does not require additional data processing.
+ */
+public Name
+getAdditionalName() {
+ return null;
+}
+
+/* Checks that an int contains an unsigned 8 bit value */
+static int
+checkU8(String field, int val) {
+ if (val < 0 || val > 0xFF)
+ throw new IllegalArgumentException("\"" + field + "\" " + val +
+ " must be an unsigned 8 " +
+ "bit value");
+ return val;
+}
+
+/* Checks that an int contains an unsigned 16 bit value */
+static int
+checkU16(String field, int val) {
+ if (val < 0 || val > 0xFFFF)
+ throw new IllegalArgumentException("\"" + field + "\" " + val +
+ " must be an unsigned 16 " +
+ "bit value");
+ return val;
+}
+
+/* Checks that a long contains an unsigned 32 bit value */
+static long
+checkU32(String field, long val) {
+ if (val < 0 || val > 0xFFFFFFFFL)
+ throw new IllegalArgumentException("\"" + field + "\" " + val +
+ " must be an unsigned 32 " +
+ "bit value");
+ return val;
+}
+
+/* Checks that a name is absolute */
+static Name
+checkName(String field, Name name) {
+ if (!name.isAbsolute())
+ throw new RelativeNameException(name);
+ return name;
+}
+
+static byte []
+checkByteArrayLength(String field, byte [] array, int maxLength) {
+ if (array.length > 0xFFFF)
+ throw new IllegalArgumentException("\"" + field + "\" array " +
+ "must have no more than " +
+ maxLength + " elements");
+ byte [] out = new byte[array.length];
+ System.arraycopy(array, 0, out, 0, array.length);
+ return out;
+}
+
+}