Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit f1d0fd26 authored by Luke Huang's avatar Luke Huang Committed by Gerrit Code Review
Browse files

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

parents 09e3d8c3 304491db
Loading
Loading
Loading
Loading
+20 −8
Original line number Diff line number Diff line
@@ -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;
  }
+4 −13
Original line number Diff line number Diff line
@@ -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;
+125 −108
Original line number Diff line number Diff line
@@ -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
     * Get instance for DnsResolver
     */
    public static @NonNull DnsResolver getInstance() {
        return sInstance;
    }

    private DnsResolver() {}

    /**
     * Answer parser for parsing raw answers
     *
     * @param <T> The type of the parsed answer
     */
    public interface RawAnswerListener {
    public interface AnswerParser<T> {
        /**
         * {@code byte[]} is {@code null} if query timed out
         * 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
         */
        void onAnswer(@Nullable byte[] answer);
        @NonNull T parse(@NonNull byte[] rawAnswer) throws ParseException;
    }

    /**
     * listener for receiving parsed answers
     * 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 interface InetAddressAnswerListener {
        public abstract void onParseException(@NonNull ParseException exception);

        /**
         * 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.
         * 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
         */
        void onAnswer(@NonNull List<InetAddress> addresses);
        public abstract void onQueryException(@NonNull ErrnoException exception);
    }

    /**
     * Get instance for DnsResolver
     * Callback for receiving raw answers
     */
    public static DnsResolver getInstance() {
        return sInstance;
    public abstract static class RawAnswerCallback extends AnswerCallback<byte[]> {
        public RawAnswerCallback() {
            super(rawAnswer -> rawAnswer);
        }
    }

    private DnsResolver() {}
    /**
     * 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 setting,
     * get a blob back asynchronously with the entire raw answer.
     * 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
    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);
        registerFDListener(handler.getLooper().getQueue(), queryfd,
                answerbuf -> listener.onAnswer(answerbuf));
        } catch (ErrnoException e) {
            callback.onQueryException(e);
            return;
        }

    /**
     * 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);
        registerFDListener(handler.getLooper().getQueue(), queryfd, callback);
    }

    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);
            }
        }
    }
}
+9 −2
Original line number Diff line number Diff line
@@ -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;
    }
}
+1 −1
Original line number Diff line number Diff line
@@ -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) {
        }
    }