Merge "Refactor answer callback for async DNS query JAVA API"

This commit is contained in:
Luke Huang
2019-03-19 05:35:18 +00:00
committed by Gerrit Code Review
5 changed files with 168 additions and 141 deletions

View File

@@ -27227,10 +27227,9 @@ package android.net {
}
public final class DnsResolver {
method public static android.net.DnsResolver getInstance();
method public void query(@Nullable android.net.Network, @NonNull byte[], int, @NonNull android.os.Handler, @NonNull android.net.DnsResolver.RawAnswerListener) throws android.system.ErrnoException;
method public void query(@Nullable android.net.Network, @NonNull String, int, int, int, @NonNull android.os.Handler, @NonNull android.net.DnsResolver.RawAnswerListener) throws android.system.ErrnoException;
method public void query(@Nullable android.net.Network, @NonNull String, int, @NonNull android.os.Handler, @NonNull android.net.DnsResolver.InetAddressAnswerListener) throws android.system.ErrnoException;
method @NonNull public static android.net.DnsResolver getInstance();
method public <T> void query(@Nullable android.net.Network, @NonNull byte[], int, @NonNull android.os.Handler, @NonNull android.net.DnsResolver.AnswerCallback<T>);
method public <T> void query(@Nullable android.net.Network, @NonNull String, int, int, int, @NonNull android.os.Handler, @NonNull android.net.DnsResolver.AnswerCallback<T>);
field public static final int CLASS_IN = 1; // 0x1
field public static final int FLAG_EMPTY = 0; // 0x0
field public static final int FLAG_NO_CACHE_LOOKUP = 4; // 0x4
@@ -27240,12 +27239,23 @@ package android.net {
field public static final int TYPE_AAAA = 28; // 0x1c
}
public static interface DnsResolver.InetAddressAnswerListener {
method public void onAnswer(@NonNull java.util.List<java.net.InetAddress>);
public abstract static class DnsResolver.AnswerCallback<T> {
ctor public DnsResolver.AnswerCallback(@NonNull android.net.DnsResolver.AnswerParser<T>);
method public abstract void onAnswer(@NonNull T);
method public abstract void onParseException(@NonNull android.net.ParseException);
method public abstract void onQueryException(@NonNull android.system.ErrnoException);
}
public static interface DnsResolver.RawAnswerListener {
method public void onAnswer(@Nullable byte[]);
public static interface DnsResolver.AnswerParser<T> {
method @NonNull public T parse(@NonNull byte[]) throws android.net.ParseException;
}
public abstract static class DnsResolver.InetAddressAnswerCallback extends android.net.DnsResolver.AnswerCallback<java.util.List<java.net.InetAddress>> {
ctor public DnsResolver.InetAddressAnswerCallback();
}
public abstract static class DnsResolver.RawAnswerCallback extends android.net.DnsResolver.AnswerCallback<byte[]> {
ctor public DnsResolver.RawAnswerCallback();
}
public class InetAddresses {
@@ -27559,6 +27569,8 @@ package android.net {
}
public class ParseException extends java.lang.RuntimeException {
ctor public ParseException(@NonNull String);
ctor public ParseException(@NonNull String, @NonNull Throwable);
field public String response;
}

View File

@@ -71,7 +71,7 @@ public abstract class DnsPacket {
}
/**
* It's used both for DNS questions and DNS resource records.
* Superclass for DNS questions and DNS resource records.
*
* DNS questions (No TTL/RDATA)
* DNS resource records (With TTL/RDATA)
@@ -96,12 +96,13 @@ public abstract class DnsPacket {
/**
* Create a new DnsRecord from a positioned ByteBuffer.
*
* @param ByteBuffer input of record, must be in network byte order
* (which is the default).
* Reads the passed ByteBuffer from its current position and decodes a DNS record.
* When this constructor returns, the reading position of the ByteBuffer has been
* advanced to the end of the DNS header record.
* This is meant to chain with other methods reading a DNS response in sequence.
*
* @param ByteBuffer input of record, must be in network byte order
* (which is the default).
*/
DnsRecord(int recordType, @NonNull ByteBuffer buf)
throws BufferUnderflowException, ParseException {
@@ -205,16 +206,6 @@ public abstract class DnsPacket {
protected final DnsHeader mHeader;
protected final List<DnsRecord>[] mRecords;
public static class ParseException extends Exception {
public ParseException(String msg) {
super(msg);
}
public ParseException(String msg, Throwable cause) {
super(msg, cause);
}
}
protected DnsPacket(@NonNull byte[] data) throws ParseException {
if (null == data) throw new ParseException("Parse header failed, null input data");
final ByteBuffer buffer;

View File

@@ -37,8 +37,6 @@ import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
/**
* Dns resolver class for asynchronous dns querying
@@ -81,66 +79,138 @@ public final class DnsResolver {
public static final int FLAG_NO_CACHE_STORE = 1 << 1;
public static final int FLAG_NO_CACHE_LOOKUP = 1 << 2;
private static final int DNS_RAW_RESPONSE = 1;
private static final int NETID_UNSET = 0;
private static final DnsResolver sInstance = new DnsResolver();
/**
* listener for receiving raw answers
*/
public interface RawAnswerListener {
/**
* {@code byte[]} is {@code null} if query timed out
*/
void onAnswer(@Nullable byte[] answer);
}
/**
* listener for receiving parsed answers
*/
public interface InetAddressAnswerListener {
/**
* Will be called exactly once with all the answers to the query.
* size of addresses will be zero if no available answer could be parsed.
*/
void onAnswer(@NonNull List<InetAddress> addresses);
}
/**
* Get instance for DnsResolver
*/
public static DnsResolver getInstance() {
public static @NonNull DnsResolver getInstance() {
return sInstance;
}
private DnsResolver() {}
/**
* Pass in a blob and corresponding setting,
* get a blob back asynchronously with the entire raw answer.
* Answer parser for parsing raw answers
*
* @param <T> The type of the parsed answer
*/
public interface AnswerParser<T> {
/**
* Creates a <T> answer by parsing the given raw answer.
*
* @param rawAnswer the raw answer to be parsed
* @return a parsed <T> answer
* @throws ParseException if parsing failed
*/
@NonNull T parse(@NonNull byte[] rawAnswer) throws ParseException;
}
/**
* Base class for answer callbacks
*
* @param <T> The type of the parsed answer
*/
public abstract static class AnswerCallback<T> {
/** @hide */
public final AnswerParser<T> parser;
public AnswerCallback(@NonNull AnswerParser<T> parser) {
this.parser = parser;
};
/**
* Success response to
* {@link android.net.DnsResolver#query query()}.
*
* Invoked when the answer to a query was successfully parsed.
*
* @param answer parsed answer to the query.
*
* {@see android.net.DnsResolver#query query()}
*/
public abstract void onAnswer(@NonNull T answer);
/**
* Error response to
* {@link android.net.DnsResolver#query query()}.
*
* Invoked when there is no valid answer to
* {@link android.net.DnsResolver#query query()}
*
* @param exception a {@link ParseException} object with additional
* detail regarding the failure
*/
public abstract void onParseException(@NonNull ParseException exception);
/**
* Error response to
* {@link android.net.DnsResolver#query query()}.
*
* Invoked if an error happens when
* issuing the DNS query or receiving the result.
* {@link android.net.DnsResolver#query query()}
*
* @param exception an {@link ErrnoException} object with additional detail
* regarding the failure
*/
public abstract void onQueryException(@NonNull ErrnoException exception);
}
/**
* Callback for receiving raw answers
*/
public abstract static class RawAnswerCallback extends AnswerCallback<byte[]> {
public RawAnswerCallback() {
super(rawAnswer -> rawAnswer);
}
}
/**
* Callback for receiving parsed {@link InetAddress} answers
*
* Note that if the answer does not contain any IP addresses,
* onAnswer will be called with an empty list.
*/
public abstract static class InetAddressAnswerCallback
extends AnswerCallback<List<InetAddress>> {
public InetAddressAnswerCallback() {
super(rawAnswer -> new DnsAddressAnswer(rawAnswer).getAddresses());
}
}
/**
* Pass in a blob and corresponding flags, get an answer back asynchronously
* through {@link AnswerCallback}.
*
* @param network {@link Network} specifying which network for querying.
* {@code null} for query on default network.
* @param query blob message
* @param flags flags as a combination of the FLAGS_* constants
* @param handler {@link Handler} to specify the thread
* upon which the {@link RawAnswerListener} will be invoked.
* @param listener a {@link RawAnswerListener} which will be called to notify the caller
* upon which the {@link AnswerCallback} will be invoked.
* @param callback an {@link AnswerCallback} which will be called to notify the caller
* of the result of dns query.
*/
public void query(@Nullable Network network, @NonNull byte[] query, @QueryFlag int flags,
@NonNull Handler handler, @NonNull RawAnswerListener listener) throws ErrnoException {
final FileDescriptor queryfd = resNetworkSend((network != null
public <T> void query(@Nullable Network network, @NonNull byte[] query, @QueryFlag int flags,
@NonNull Handler handler, @NonNull AnswerCallback<T> callback) {
final FileDescriptor queryfd;
try {
queryfd = resNetworkSend((network != null
? network.netId : NETID_UNSET), query, query.length, flags);
registerFDListener(handler.getLooper().getQueue(), queryfd,
answerbuf -> listener.onAnswer(answerbuf));
} catch (ErrnoException e) {
callback.onQueryException(e);
return;
}
registerFDListener(handler.getLooper().getQueue(), queryfd, callback);
}
/**
* Pass in a domain name and corresponding setting,
* get a blob back asynchronously with the entire raw answer.
* Pass in a domain name and corresponding setting, get an answer back asynchronously
* through {@link AnswerCallback}.
*
* @param network {@link Network} specifying which network for querying.
* {@code null} for query on default network.
@@ -149,52 +219,26 @@ public final class DnsResolver {
* @param nsType dns resource record (RR) type as one of the TYPE_* constants
* @param flags flags as a combination of the FLAGS_* constants
* @param handler {@link Handler} to specify the thread
* upon which the {@link RawAnswerListener} will be invoked.
* @param listener a {@link RawAnswerListener} which will be called to notify the caller
* upon which the {@link AnswerCallback} will be invoked.
* @param callback an {@link AnswerCallback} which will be called to notify the caller
* of the result of dns query.
*/
public void query(@Nullable Network network, @NonNull String domain, @QueryClass int nsClass,
@QueryType int nsType, @QueryFlag int flags,
@NonNull Handler handler, @NonNull RawAnswerListener listener) throws ErrnoException {
final FileDescriptor queryfd = resNetworkQuery((network != null
? network.netId : NETID_UNSET), domain, nsClass, nsType, flags);
registerFDListener(handler.getLooper().getQueue(), queryfd,
answerbuf -> listener.onAnswer(answerbuf));
public <T> void query(@Nullable Network network, @NonNull String domain,
@QueryClass int nsClass, @QueryType int nsType, @QueryFlag int flags,
@NonNull Handler handler, @NonNull AnswerCallback<T> callback) {
final FileDescriptor queryfd;
try {
queryfd = resNetworkQuery((network != null
? network.netId : NETID_UNSET), domain, nsClass, nsType, flags);
} catch (ErrnoException e) {
callback.onQueryException(e);
return;
}
registerFDListener(handler.getLooper().getQueue(), queryfd, callback);
}
/**
* Pass in a domain name and corresponding setting,
* get back a set of InetAddresses asynchronously.
*
* @param network {@link Network} specifying which network for querying.
* {@code null} for query on default network.
* @param domain domain name for querying
* @param flags flags as a combination of the FLAGS_* constants
* @param handler {@link Handler} to specify the thread
* upon which the {@link InetAddressAnswerListener} will be invoked.
* @param listener an {@link InetAddressAnswerListener} which will be called to
* notify the caller of the result of dns query.
*
*/
public void query(@Nullable Network network, @NonNull String domain, @QueryFlag int flags,
@NonNull Handler handler, @NonNull InetAddressAnswerListener listener)
throws ErrnoException {
final FileDescriptor v4fd = resNetworkQuery((network != null
? network.netId : NETID_UNSET), domain, CLASS_IN, TYPE_A, flags);
final FileDescriptor v6fd = resNetworkQuery((network != null
? network.netId : NETID_UNSET), domain, CLASS_IN, TYPE_AAAA, flags);
final InetAddressAnswerAccumulator accmulator =
new InetAddressAnswerAccumulator(2, listener);
final Consumer<byte[]> consumer = answerbuf ->
accmulator.accumulate(parseAnswers(answerbuf));
registerFDListener(handler.getLooper().getQueue(), v4fd, consumer);
registerFDListener(handler.getLooper().getQueue(), v6fd, consumer);
}
private void registerFDListener(@NonNull MessageQueue queue,
@NonNull FileDescriptor queryfd, @NonNull Consumer<byte[]> answerConsumer) {
private <T> void registerFDListener(@NonNull MessageQueue queue,
@NonNull FileDescriptor queryfd, @NonNull AnswerCallback<T> answerCallback) {
queue.addOnFileDescriptorEventListener(
queryfd,
FD_EVENTS,
@@ -207,15 +251,22 @@ public final class DnsResolver {
answerbuf = resNetworkResult(fd);
} catch (ErrnoException e) {
Log.e(TAG, "resNetworkResult:" + e.toString());
answerCallback.onQueryException(e);
return 0;
}
try {
answerCallback.onAnswer(answerCallback.parser.parse(answerbuf));
} catch (ParseException e) {
answerCallback.onParseException(e);
}
answerConsumer.accept(answerbuf);
// Unregister this fd listener
return 0;
});
}
private class DnsAddressAnswer extends DnsPacket {
private static class DnsAddressAnswer extends DnsPacket {
private static final String TAG = "DnsResolver.DnsAddressAnswer";
private static final boolean DBG = false;
@@ -226,12 +277,6 @@ public final class DnsResolver {
if ((mHeader.flags & (1 << 15)) == 0) {
throw new ParseException("Not an answer packet");
}
if (mHeader.rcode != 0) {
throw new ParseException("Response error, rcode:" + mHeader.rcode);
}
if (mHeader.getRecordCount(ANSECTION) == 0) {
throw new ParseException("No available answer");
}
if (mHeader.getRecordCount(QDSECTION) == 0) {
throw new ParseException("No question found");
}
@@ -241,6 +286,8 @@ public final class DnsResolver {
public @NonNull List<InetAddress> getAddresses() {
final List<InetAddress> results = new ArrayList<InetAddress>();
if (mHeader.getRecordCount(ANSECTION) == 0) return results;
for (final DnsRecord ansSec : mRecords[ANSECTION]) {
// Only support A and AAAA, also ignore answers if query type != answer type.
int nsType = ansSec.nsType;
@@ -259,34 +306,4 @@ public final class DnsResolver {
}
}
private @Nullable List<InetAddress> parseAnswers(@Nullable byte[] data) {
try {
return (data == null) ? null : new DnsAddressAnswer(data).getAddresses();
} catch (DnsPacket.ParseException e) {
Log.e(TAG, "Parse answer fail " + e.getMessage());
return null;
}
}
private class InetAddressAnswerAccumulator {
private final List<InetAddress> mAllAnswers;
private final InetAddressAnswerListener mAnswerListener;
private final int mTargetAnswerCount;
private int mReceivedAnswerCount = 0;
InetAddressAnswerAccumulator(int size, @NonNull InetAddressAnswerListener listener) {
mTargetAnswerCount = size;
mAllAnswers = new ArrayList<>();
mAnswerListener = listener;
}
public void accumulate(@Nullable List<InetAddress> answer) {
if (null != answer) {
mAllAnswers.addAll(answer);
}
if (++mReceivedAnswerCount == mTargetAnswerCount) {
mAnswerListener.onAnswer(mAllAnswers);
}
}
}
}

View File

@@ -16,15 +16,22 @@
package android.net;
import android.annotation.NonNull;
/**
* Thrown when parsing a URL fails.
* Thrown when parsing failed.
*/
// See non-public class {@link WebAddress}.
public class ParseException extends RuntimeException {
public String response;
ParseException(String response) {
public ParseException(@NonNull String response) {
super(response);
this.response = response;
}
public ParseException(@NonNull String response, @NonNull Throwable cause) {
super(response, cause);
this.response = response;
}
}

View File

@@ -69,7 +69,7 @@ public class DnsPacketTest {
try {
new TestDnsPacket(null);
fail("Exception not thrown for null byte array");
} catch (DnsPacket.ParseException e) {
} catch (ParseException e) {
}
}