io.milton.dns.record.Record Maven / Gradle / Ivy
/*
* Copied from the DnsJava project
*
* Copyright (c) 1998-2011, Brian Wellington.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
package io.milton.dns.record;
import io.milton.dns.Name;
import io.milton.dns.TextParseException;
import io.milton.dns.utils.base16;
import java.io.*;
import java.text.*;
import java.util.*;
/**
* 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;
public Name name;
public int type;
public int dclass;
public 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 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);
}
public 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);
}
public 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() {
StringBuilder sb = new StringBuilder();
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.isEmpty()) {
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 (byte item : array) {
if (item == '\\') {
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 (byte value : array) {
byte b = value;
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 (value == '\\') {
escaped = true;
digits = 0;
intval = 0;
} else {
os.write(value);
}
}
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) {
StringBuilder sb = new StringBuilder();
if (quote) {
sb.append('"');
}
for (byte value : array) {
int b = value & 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) {
StringBuilder sb = new StringBuilder();
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 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 (byte b : array) {
code += ((code << 3) + (b & 0xFF));
}
return code;
}
public 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.
*/
public 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. */
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 */
public 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 */
public 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 == null ) {
throw new NullPointerException("Name is null");
}
if (!name.isAbsolute()) {
throw new RelativeNameException(name);
}
return name;
}
public 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;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy