415 lines
9.8 KiB
Java
Executable File
415 lines
9.8 KiB
Java
Executable File
package org.handwerkszeug.dns;
|
|
|
|
import static org.handwerkszeug.util.Validation.notNull;
|
|
|
|
import java.text.DecimalFormat;
|
|
import java.util.ArrayList;
|
|
import java.util.Arrays;
|
|
import java.util.Collections;
|
|
import java.util.Iterator;
|
|
import java.util.List;
|
|
|
|
import org.handwerkszeug.dns.nls.Messages;
|
|
|
|
import io.netty.buffer.ByteBuf;
|
|
import io.netty.buffer.Unpooled;
|
|
|
|
public class Name implements Comparable<Name> {
|
|
|
|
public static final Name NULL_NAME;
|
|
public static final Name WILDCARD;
|
|
|
|
static final byte[] NULL_ARRAY = new byte[0];
|
|
static final byte[] WILDCARD_ARRAY = new byte[] { '*' };
|
|
|
|
static {
|
|
NULL_NAME = create(NULL_ARRAY);
|
|
WILDCARD = create(WILDCARD_ARRAY);
|
|
}
|
|
|
|
static Name create(byte[] array) {
|
|
List<byte[]> l = new ArrayList<byte[]>(1);
|
|
l.add(array);
|
|
return new Name(Collections.unmodifiableList(l));
|
|
}
|
|
|
|
/**
|
|
* 4.1.4. DnsMessage compression
|
|
*/
|
|
public static final int MASK_POINTER = 0xC0; // 1100 0000
|
|
|
|
/**
|
|
* 2.3.4. Size limits
|
|
*/
|
|
public static final int MAX_LABEL_SIZE = 63; // 0011 1111
|
|
|
|
/**
|
|
* 2.3.4. Size limits
|
|
*/
|
|
public static final int MAX_NAME_SIZE = 255;
|
|
|
|
protected final List<byte[]> name;
|
|
|
|
public Name(ByteBuf buffer) {
|
|
this.name = this.parse(buffer);
|
|
}
|
|
|
|
public Name(String name) {
|
|
this.name = this.parse(name);
|
|
}
|
|
|
|
protected Name(List<byte[]> rawdata) {
|
|
this.name = rawdata;
|
|
}
|
|
|
|
protected List<byte[]> parse(ByteBuf buffer) {
|
|
List<byte[]> list = new ArrayList<byte[]>();
|
|
boolean jumped = false;
|
|
|
|
int namesize = 0;
|
|
for (int length = buffer.readUnsignedByte(); -1 < length; length = buffer
|
|
.readUnsignedByte()) {
|
|
if (length == 0) {
|
|
list.add(NULL_ARRAY);
|
|
break;
|
|
} else if ((length & MASK_POINTER) != 0) {
|
|
int p = ((length ^ MASK_POINTER) << 8)
|
|
+ buffer.readUnsignedByte();
|
|
if (jumped == false) {
|
|
buffer.markReaderIndex();
|
|
jumped = true;
|
|
}
|
|
buffer.readerIndex(p);
|
|
} else if (length <= MAX_LABEL_SIZE) {
|
|
namesize += length;
|
|
if (MAX_NAME_SIZE < namesize) {
|
|
throw new IllegalArgumentException(String.format(
|
|
Messages.NamesMustBe255orLess, namesize));
|
|
}
|
|
byte[] ary = new byte[length];
|
|
buffer.readBytes(ary);
|
|
list.add(ary);
|
|
} else {
|
|
throw new IllegalStateException(String.format(
|
|
Messages.InvalidCompressionMask, length));
|
|
}
|
|
}
|
|
|
|
if (jumped) {
|
|
buffer.resetReaderIndex();
|
|
}
|
|
|
|
return Collections.unmodifiableList(list);
|
|
}
|
|
|
|
/**
|
|
* 5.1. Format
|
|
*
|
|
* <pre>
|
|
* \X where X is any character other than a digit (0-9), is
|
|
* used to quote that character so that its special meaning
|
|
* does not apply. For example, "\." can be used to place
|
|
* a dot character in a label.
|
|
*
|
|
* \DDD where each D is a digit is the octet corresponding to
|
|
* the decimal number described by DDD. The resulting
|
|
* octet is assumed to be text and is not checked for
|
|
* special meaning.
|
|
* </pre>
|
|
*
|
|
* @param namedata
|
|
* @return
|
|
*/
|
|
protected List<byte[]> parse(String namedata) {
|
|
if (".".equals(namedata)) {
|
|
return NULL_NAME.name;
|
|
}
|
|
// TODO IDN support from RFC3490 RFC3491 RFC3492 RFC3454
|
|
List<byte[]> result = new ArrayList<byte[]>();
|
|
byte[] bytes = namedata.getBytes();
|
|
int namesize = 0;
|
|
ByteBuf buffer = Unpooled.buffer(MAX_LABEL_SIZE);
|
|
int current = 0;
|
|
int length = bytes.length;
|
|
|
|
boolean escape = false;
|
|
int digits = 0;
|
|
int value = 0;
|
|
for (; current < length; current++) {
|
|
byte b = bytes[current];
|
|
if (escape) {
|
|
if ((('0' <= b) && (b <= '9')) && (digits++ < 3)) {
|
|
value *= 10;
|
|
value += (b - '0');
|
|
if (255 < value) {
|
|
throw new IllegalArgumentException(String.format(
|
|
Messages.EscapedDecimalIsInvalid, value));
|
|
}
|
|
if (2 < digits) {
|
|
appendByte(namedata, buffer, (byte) value);
|
|
escape = false;
|
|
}
|
|
} else if (0 < digits) {
|
|
throw new IllegalArgumentException(
|
|
String.format(
|
|
Messages.MixtureOfEscapedDigitAndNonDigit,
|
|
namedata));
|
|
} else {
|
|
appendByte(namedata, buffer, b);
|
|
escape = false;
|
|
}
|
|
} else if (b == '\\') {
|
|
escape = true;
|
|
digits = 0;
|
|
value = 0;
|
|
} else if (b == '.') {
|
|
namesize = namesize + addBytes(result, buffer) + 1;
|
|
} else {
|
|
appendByte(namedata, buffer, b);
|
|
}
|
|
}
|
|
|
|
if (escape) {
|
|
throw new IllegalArgumentException(String.format(
|
|
Messages.InvalidEscapeSequence, namedata));
|
|
}
|
|
|
|
if (buffer.isReadable()) {
|
|
// relative domain name
|
|
namesize = namesize + addBytes(result, buffer);
|
|
} else {
|
|
// absolute domain name
|
|
result.add(NULL_ARRAY);
|
|
}
|
|
|
|
buffer.release();
|
|
|
|
namesize += 1;
|
|
|
|
if (MAX_NAME_SIZE < namesize) {
|
|
throw new IllegalArgumentException(String.format(
|
|
Messages.NamesMustBe255orLess, namesize));
|
|
}
|
|
return result;
|
|
}
|
|
|
|
protected void appendByte(String namedata, ByteBuf buffer, byte b) {
|
|
if (buffer.isWritable()) {
|
|
buffer.writeByte(b);
|
|
} else {
|
|
throw new IllegalArgumentException(String.format(Messages.LabelsMustBe63orLess, namedata));
|
|
}
|
|
}
|
|
|
|
protected int addBytes(List<byte[]> result, ByteBuf buffer) {
|
|
int size = buffer.readableBytes();
|
|
if (size < 1) {
|
|
throw new IllegalArgumentException(Messages.NullLabelIsNotValid);
|
|
}
|
|
byte[] newone = new byte[size];
|
|
buffer.readBytes(newone);
|
|
buffer.clear();
|
|
result.add(newone);
|
|
return size;
|
|
}
|
|
|
|
public void write(ByteBuf buffer, NameCompressor compressor) {
|
|
// TODO DNAME and other non compress RR
|
|
// TODO need writing cache?
|
|
if (writePointer(buffer, compressor, this) == false) {
|
|
compressor.put(this, buffer.writerIndex());
|
|
for (int i = 0, size = this.name.size(); i < size; i++) {
|
|
byte[] current = this.name.get(i);
|
|
int cl = current.length;
|
|
buffer.writeByte(cl);
|
|
if (0 < cl) {
|
|
buffer.writeBytes(current);
|
|
if (i + 1 < size) {
|
|
Name n = new Name(this.name.subList(i + 1, size));
|
|
if (writePointer(buffer, compressor, n)) {
|
|
break;
|
|
} else {
|
|
compressor.put(n, buffer.writerIndex());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
protected boolean writePointer(ByteBuf buffer,
|
|
NameCompressor compressor, Name n) {
|
|
int position = compressor.get(n);
|
|
if (-1 < position) {
|
|
int pointer = (MASK_POINTER << 8) | position;
|
|
buffer.writeShort(pointer);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
public Name toParent() {
|
|
int size = this.name.size();
|
|
if (1 < size) {
|
|
List<byte[]> newone = this.name.subList(1, size);
|
|
return new Name(newone);
|
|
}
|
|
return NULL_NAME;
|
|
}
|
|
|
|
public Name toWildcard() {
|
|
int size = this.name.size();
|
|
if (1 < size) {
|
|
List<byte[]> newone = new ArrayList<byte[]>(size);
|
|
newone.add(WILDCARD_ARRAY);
|
|
newone.addAll(this.name.subList(1, size));
|
|
return new Name(Collections.unmodifiableList(newone));
|
|
}
|
|
return WILDCARD;
|
|
}
|
|
|
|
public boolean contains(Name other) {
|
|
notNull(other, "other");
|
|
int mySize = this.name.size();
|
|
int otherSize = other.name.size();
|
|
if (mySize < otherSize) {
|
|
return false;
|
|
}
|
|
int diff = mySize - otherSize;
|
|
for (int i = 0; i < otherSize; i++) {
|
|
byte[] me = this.name.get(i + diff);
|
|
byte[] yu = other.name.get(i);
|
|
if (Arrays.equals(me, yu) == false) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
public Name replace(Name from, Name to) {
|
|
notNull(from, "from");
|
|
notNull(to, "to");
|
|
if (contains(from)) {
|
|
int diff = this.name.size() - from.name.size();
|
|
int newsize = diff + to.name.size();
|
|
List<byte[]> newone = new ArrayList<byte[]>(newsize);
|
|
newone.addAll(this.name.subList(0, diff));
|
|
newone.addAll(to.name);
|
|
int size = 0;
|
|
for (byte[] ary : newone) {
|
|
size += ary.length;
|
|
}
|
|
if (size < MAX_NAME_SIZE) {
|
|
return new Name(Collections.unmodifiableList(newone));
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
@Override
|
|
public int hashCode() {
|
|
final int prime = 31;
|
|
int result = 1;
|
|
for (byte[] b : this.name) {
|
|
result = prime * result + Arrays.hashCode(b);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
@Override
|
|
public boolean equals(Object obj) {
|
|
if (this == obj) {
|
|
return true;
|
|
}
|
|
if (obj instanceof Name) {
|
|
return equals(Name.class.cast(obj));
|
|
}
|
|
return false;
|
|
}
|
|
|
|
public boolean equals(Name other) {
|
|
if (other == null) {
|
|
return false;
|
|
}
|
|
int mySize = this.name.size();
|
|
int yrSize = other.name.size();
|
|
if (mySize != yrSize) {
|
|
return false;
|
|
}
|
|
for (int i = 0; i < mySize; i++) {
|
|
byte[] me = this.name.get(i);
|
|
byte[] yu = other.name.get(i);
|
|
if (Arrays.equals(me, yu) == false) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
@Override
|
|
public int compareTo(Name o) {
|
|
// TODO use more effective algorithm for red black tree.
|
|
int mySize = this.name.size();
|
|
int yrSize = o.name.size();
|
|
if (mySize != yrSize) {
|
|
return mySize - yrSize;
|
|
}
|
|
int minSize = Math.min(mySize, yrSize);
|
|
for (int i = minSize - 1; (-1 < i); i--) {
|
|
byte[] mine = this.name.get(i);
|
|
byte[] other = o.name.get(i);
|
|
if (Arrays.equals(mine, other) == false) {
|
|
int size = Math.min(mine.length, other.length);
|
|
for (int ii = size - 1; -1 < ii; i--) {
|
|
byte mb = mine[ii];
|
|
byte yb = other[ii];
|
|
if (mb != yb) {
|
|
if (mb < yb) {
|
|
return -1;
|
|
} else {
|
|
return 1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
@Override
|
|
public String toString() {
|
|
StringBuilder stb = new StringBuilder();
|
|
DecimalFormat fmt = new DecimalFormat();
|
|
fmt.setMinimumIntegerDigits(3);
|
|
for (Iterator<byte[]> cursor = this.name.iterator(); cursor.hasNext();) {
|
|
byte[] ary = cursor.next();
|
|
for (int i = 0, l = ary.length; i < l; i++) {
|
|
int b = ary[i] & 0xFF;
|
|
if ((b < 0x21) || (0x7F < b)) {
|
|
stb.append('\\');
|
|
stb.append(fmt.format(b));
|
|
} else {
|
|
switch (b) {
|
|
case '"':
|
|
case '(':
|
|
case ')':
|
|
case '.':
|
|
case ';':
|
|
case '\\':
|
|
case '@':
|
|
case '$':
|
|
stb.append('\\');
|
|
default:
|
|
stb.append((char) b);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (cursor.hasNext()) {
|
|
stb.append('.');
|
|
}
|
|
}
|
|
return stb.toString();
|
|
}
|
|
}
|