/*
* Copyright (c) 2013 The Netty Project
* ------------------------------------
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* and Apache License v2.0 which accompanies this distribution.
*
* The Eclipse Public License is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* The Apache License v2.0 is available at
* http://www.opensource.org/licenses/apache2.0.php
*
* You may elect to redistribute this code under either of these licenses.
*/
package io.vertx.core.dns.impl.decoder;
import io.netty.buffer.ByteBuf;
import io.netty.handler.codec.DecoderException;
import io.netty.handler.codec.dns.DnsPtrRecord;
import io.netty.handler.codec.dns.DnsRawRecord;
import io.netty.handler.codec.dns.DnsRecord;
import io.netty.handler.codec.dns.DnsRecordType;
import io.netty.util.CharsetUtil;
import io.vertx.core.dns.impl.MxRecordImpl;
import io.vertx.core.dns.impl.SrvRecordImpl;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
/**
* Handles the decoding of resource records. Some default decoders are mapped to
* their resource types in the map {@code decoders}.
*/
public class RecordDecoder {
/**
* Decodes MX (mail exchanger) resource records.
*/
public static final Function<DnsRecord, MxRecordImpl> MX = record -> {
ByteBuf packet = ((DnsRawRecord)record).content();
int priority = packet.readShort();
String name = RecordDecoder.readName(packet);
return new MxRecordImpl(priority, name);
};
/**
* Decodes any record that simply returns a domain name, such as NS (name
* server) and CNAME (canonical name) resource records.
*/
public static final Function<DnsRecord, String> DOMAIN = record -> {
if (record instanceof DnsPtrRecord) {
String val = ((DnsPtrRecord)record).hostname();
if (val.endsWith(".")) {
val = val.substring(0, val.length() - 1);
}
return val;
} else {
ByteBuf content = ((DnsRawRecord) record).content();
return RecordDecoder.getName(content, content.readerIndex());
}
};
/**
* Decodes A resource records into IPv4 addresses.
*/
public static final Function<DnsRecord, String> A = address(4);
/**
* Decodes AAAA resource records into IPv6 addresses.
*/
public static final Function<DnsRecord, String> AAAA = address(16);
/**
* Decodes SRV (service) resource records.
*/
public static final Function<DnsRecord, SrvRecordImpl> SRV = record -> {
ByteBuf packet = ((DnsRawRecord)record).content();
int priority = packet.readShort();
int weight = packet.readShort();
int port = packet.readUnsignedShort();
String target = RecordDecoder.readName(packet);
String[] parts = record.name().split("\\.", 3);
String service = parts[0];
String protocol = parts[1];
String name = parts[2];
return new SrvRecordImpl(priority, weight, port, name, protocol, service, target);
};
/**
* Decodes SOA (start of authority) resource records.
*/
public static final Function<DnsRecord, StartOfAuthorityRecord> SOA = record -> {
ByteBuf packet = ((DnsRawRecord)record).content();
String mName = RecordDecoder.readName(packet);
String rName = RecordDecoder.readName(packet);
long serial = packet.readUnsignedInt();
int refresh = packet.readInt();
int retry = packet.readInt();
int expire = packet.readInt();
long minimum = packet.readUnsignedInt();
return new StartOfAuthorityRecord(mName, rName, serial, refresh, retry, expire, minimum);
};
public static final Function<DnsRecord, List<String>> TXT = record -> {
List<String> list = new ArrayList<>();
ByteBuf data = ((DnsRawRecord)record).content();
int index = data.readerIndex();
while (index < data.writerIndex()) {
int len = data.getUnsignedByte(index++);
list.add(data.toString(index, len, CharsetUtil.UTF_8));
index += len;
}
return list;
};
static Function<DnsRecord, String> address(int octets) {
return record -> {
ByteBuf data = ((DnsRawRecord)record).content();
int size = data.readableBytes();
if (size != octets) {
throw new DecoderException("Invalid content length, or reader index when decoding address [index: "
+ data.readerIndex() + ", expected length: " + octets + ", actual: " + size + "].");
}
byte[] address = new byte[octets];
data.getBytes(data.readerIndex(), address);
try {
return InetAddress.getByAddress(address).getHostAddress();
} catch (UnknownHostException e) {
throw new DecoderException("Could not convert address "
+ data.toString(data.readerIndex(), size, CharsetUtil.UTF_8) + " to InetAddress.");
}
};
}
/**
* Retrieves a domain name given a buffer containing a DNS packet. If the
* name contains a pointer, the position of the buffer will be set to
* directly after the pointer's index after the name has been read.
*
* @param buf the byte buffer containing the DNS packet
* @return the domain name for an entry
*/
static String readName(ByteBuf buf) {
int position = -1;
StringBuilder name = new StringBuilder();
for (int len = buf.readUnsignedByte(); buf.isReadable() && len != 0; len = buf.readUnsignedByte()) {
boolean pointer = (len & 0xc0) == 0xc0;
if (pointer) {
if (position == -1) {
position = buf.readerIndex() + 1;
}
buf.readerIndex((len & 0x3f) << 8 | buf.readUnsignedByte());
} else {
name.append(buf.toString(buf.readerIndex(), len, CharsetUtil.UTF_8)).append(".");
buf.skipBytes(len);
}
}
if (position != -1) {
buf.readerIndex(position);
}
if (name.length() == 0) {
return null;
}
return name.substring(0, name.length() - 1);
}
/**
* Retrieves a domain name given a buffer containing a DNS packet without
* advancing the readerIndex for the buffer.
*
* @param buf the byte buffer containing the DNS packet
* @param offset the position at which the name begins
* @return the domain name for an entry
*/
static String getName(ByteBuf buf, int offset) {
StringBuilder name = new StringBuilder();
for (int len = buf.getUnsignedByte(offset++); buf.writerIndex() > offset && len != 0; len = buf
.getUnsignedByte(offset++)) {
boolean pointer = (len & 0xc0) == 0xc0;
if (pointer) {
offset = (len & 0x3f) << 8 | buf.getUnsignedByte(offset++);
} else {
name.append(buf.toString(offset, len, CharsetUtil.UTF_8)).append(".");
offset += len;
}
}
if (name.length() == 0) {
return null;
}
return name.substring(0, name.length() - 1);
}
private static final Map<DnsRecordType, Function<DnsRecord, ?>> decoders = new HashMap<>();
static {
decoders.put(DnsRecordType.A, RecordDecoder.A);
decoders.put(DnsRecordType.AAAA, RecordDecoder.AAAA);
decoders.put(DnsRecordType.MX, RecordDecoder.MX);
decoders.put(DnsRecordType.TXT, RecordDecoder.TXT);
decoders.put(DnsRecordType.SRV, RecordDecoder.SRV);
decoders.put(DnsRecordType.NS, RecordDecoder.DOMAIN);
decoders.put(DnsRecordType.CNAME, RecordDecoder.DOMAIN);
decoders.put(DnsRecordType.PTR, RecordDecoder.DOMAIN);
decoders.put(DnsRecordType.SOA, RecordDecoder.SOA);
}
/**
* Decodes a resource record and returns the result.
*
* @param record
* @return the decoded resource record
*/
@SuppressWarnings("unchecked")
public static <T> T decode(DnsRecord record) {
DnsRecordType type = record.type();
Function<DnsRecord, ?> decoder = decoders.get(type);
if (decoder == null) {
throw new IllegalStateException("Unsupported resource record type [id: " + type + "].");
}
T result = null;
try {
result = (T) decoder.apply(record);
} catch (Exception e) {
e.printStackTrace();
}
return result;
}
}