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

Commit 304491db authored by Luke Huang's avatar Luke Huang
Browse files

Refactor answer callback for async DNS query JAVA API

1. refactor AnswerCallback with a generic type
2. support onError in AnswerCallback
3. Fix minor problem reported from API Review

Bug: 124882626
Test: built, flashed, booted
      atest DnsResolverTest DnsPacketTest

Change-Id: I685c9989f8401acb63d2e83f552b2d5b20c41af0
parent 1276a178
Loading
Loading
Loading
Loading
+20 −8
Original line number Original line Diff line number Diff line
@@ -27227,10 +27227,9 @@ package android.net {
  }
  }
  public final class DnsResolver {
  public final class DnsResolver {
    method public static android.net.DnsResolver getInstance();
    method @NonNull 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 <T> void query(@Nullable android.net.Network, @NonNull byte[], int, @NonNull android.os.Handler, @NonNull android.net.DnsResolver.AnswerCallback<T>);
    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 <T> void query(@Nullable android.net.Network, @NonNull String, int, int, int, @NonNull android.os.Handler, @NonNull android.net.DnsResolver.AnswerCallback<T>);
    method public void query(@Nullable android.net.Network, @NonNull String, int, @NonNull android.os.Handler, @NonNull android.net.DnsResolver.InetAddressAnswerListener) throws android.system.ErrnoException;
    field public static final int CLASS_IN = 1; // 0x1
    field public static final int CLASS_IN = 1; // 0x1
    field public static final int FLAG_EMPTY = 0; // 0x0
    field public static final int FLAG_EMPTY = 0; // 0x0
    field public static final int FLAG_NO_CACHE_LOOKUP = 4; // 0x4
    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
    field public static final int TYPE_AAAA = 28; // 0x1c
  }
  }
  public static interface DnsResolver.InetAddressAnswerListener {
  public abstract static class DnsResolver.AnswerCallback<T> {
    method public void onAnswer(@NonNull java.util.List<java.net.InetAddress>);
    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 {
  public static interface DnsResolver.AnswerParser<T> {
    method public void onAnswer(@Nullable byte[]);
    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 {
  public class InetAddresses {
@@ -27559,6 +27569,8 @@ package android.net {
  }
  }
  public class ParseException extends java.lang.RuntimeException {
  public class ParseException extends java.lang.RuntimeException {
    ctor public ParseException(@NonNull String);
    ctor public ParseException(@NonNull String, @NonNull Throwable);
    field public String response;
    field public String response;
  }
  }
+4 −13
Original line number Original line 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 questions (No TTL/RDATA)
     * DNS resource records (With TTL/RDATA)
     * DNS resource records (With TTL/RDATA)
@@ -96,12 +96,13 @@ public abstract class DnsPacket {
        /**
        /**
         * Create a new DnsRecord from a positioned ByteBuffer.
         * 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.
         * 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
         * When this constructor returns, the reading position of the ByteBuffer has been
         * advanced to the end of the DNS header record.
         * advanced to the end of the DNS header record.
         * This is meant to chain with other methods reading a DNS response in sequence.
         * 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)
        DnsRecord(int recordType, @NonNull ByteBuffer buf)
                throws BufferUnderflowException, ParseException {
                throws BufferUnderflowException, ParseException {
@@ -205,16 +206,6 @@ public abstract class DnsPacket {
    protected final DnsHeader mHeader;
    protected final DnsHeader mHeader;
    protected final List<DnsRecord>[] mRecords;
    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 {
    protected DnsPacket(@NonNull byte[] data) throws ParseException {
        if (null == data) throw new ParseException("Parse header failed, null input data");
        if (null == data) throw new ParseException("Parse header failed, null input data");
        final ByteBuffer buffer;
        final ByteBuffer buffer;
+125 −108
Original line number Original line Diff line number Diff line
@@ -37,8 +37,6 @@ import java.net.InetAddress;
import java.net.UnknownHostException;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.ArrayList;
import java.util.List;
import java.util.List;
import java.util.function.Consumer;



/**
/**
 * Dns resolver class for asynchronous dns querying
 * 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_STORE = 1 << 1;
    public static final int FLAG_NO_CACHE_LOOKUP = 1 << 2;
    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 int NETID_UNSET = 0;


    private static final DnsResolver sInstance = new DnsResolver();
    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.
         * Error response to
         * size of addresses will be zero if no available answer could be parsed.
         * {@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() {
    public abstract static class RawAnswerCallback extends AnswerCallback<byte[]> {
        return sInstance;
        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,
     * Pass in a blob and corresponding flags, get an answer back asynchronously
     * get a blob back asynchronously with the entire raw answer.
     * through {@link AnswerCallback}.
     *
     *
     * @param network {@link Network} specifying which network for querying.
     * @param network {@link Network} specifying which network for querying.
     *         {@code null} for query on default network.
     *         {@code null} for query on default network.
     * @param query blob message
     * @param query blob message
     * @param flags flags as a combination of the FLAGS_* constants
     * @param flags flags as a combination of the FLAGS_* constants
     * @param handler {@link Handler} to specify the thread
     * @param handler {@link Handler} to specify the thread
     *         upon which the {@link RawAnswerListener} will be invoked.
     *         upon which the {@link AnswerCallback} will be invoked.
     * @param listener a {@link RawAnswerListener} which will be called to notify the caller
     * @param callback an {@link AnswerCallback} which will be called to notify the caller
     *         of the result of dns query.
     *         of the result of dns query.
     */
     */
    public void query(@Nullable Network network, @NonNull byte[] query, @QueryFlag int flags,
    public <T> void query(@Nullable Network network, @NonNull byte[] query, @QueryFlag int flags,
            @NonNull Handler handler, @NonNull RawAnswerListener listener) throws ErrnoException {
            @NonNull Handler handler, @NonNull AnswerCallback<T> callback) {
        final FileDescriptor queryfd = resNetworkSend((network != null
        final FileDescriptor queryfd;
        try {
            queryfd = resNetworkSend((network != null
                ? network.netId : NETID_UNSET), query, query.length, flags);
                ? network.netId : NETID_UNSET), query, query.length, flags);
        registerFDListener(handler.getLooper().getQueue(), queryfd,
        } catch (ErrnoException e) {
                answerbuf -> listener.onAnswer(answerbuf));
            callback.onQueryException(e);
            return;
        }

        registerFDListener(handler.getLooper().getQueue(), queryfd, callback);
    }
    }


    /**
    /**
     * Pass in a domain name and corresponding setting,
     * Pass in a domain name and corresponding setting, get an answer back asynchronously
     * get a blob back asynchronously with the entire raw answer.
     * through {@link AnswerCallback}.
     *
     *
     * @param network {@link Network} specifying which network for querying.
     * @param network {@link Network} specifying which network for querying.
     *         {@code null} for query on default network.
     *         {@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 nsType dns resource record (RR) type as one of the TYPE_* constants
     * @param flags flags as a combination of the FLAGS_* constants
     * @param flags flags as a combination of the FLAGS_* constants
     * @param handler {@link Handler} to specify the thread
     * @param handler {@link Handler} to specify the thread
     *         upon which the {@link RawAnswerListener} will be invoked.
     *         upon which the {@link AnswerCallback} will be invoked.
     * @param listener a {@link RawAnswerListener} which will be called to notify the caller
     * @param callback an {@link AnswerCallback} which will be called to notify the caller
     *         of the result of dns query.
     *         of the result of dns query.
     */
     */
    public void query(@Nullable Network network, @NonNull String domain, @QueryClass int nsClass,
    public <T> void query(@Nullable Network network, @NonNull String domain,
            @QueryType int nsType, @QueryFlag int flags,
            @QueryClass int nsClass, @QueryType int nsType, @QueryFlag int flags,
            @NonNull Handler handler, @NonNull RawAnswerListener listener) throws ErrnoException {
            @NonNull Handler handler, @NonNull AnswerCallback<T> callback) {
        final FileDescriptor queryfd = resNetworkQuery((network != null
        final FileDescriptor queryfd;
        try {
            queryfd = resNetworkQuery((network != null
                    ? network.netId : NETID_UNSET), domain, nsClass, nsType, flags);
                    ? network.netId : NETID_UNSET), domain, nsClass, nsType, flags);
        registerFDListener(handler.getLooper().getQueue(), queryfd,
        } catch (ErrnoException e) {
                answerbuf -> listener.onAnswer(answerbuf));
            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,
    private <T> void registerFDListener(@NonNull MessageQueue queue,
            @NonNull FileDescriptor queryfd, @NonNull Consumer<byte[]> answerConsumer) {
            @NonNull FileDescriptor queryfd, @NonNull AnswerCallback<T> answerCallback) {
        queue.addOnFileDescriptorEventListener(
        queue.addOnFileDescriptorEventListener(
                queryfd,
                queryfd,
                FD_EVENTS,
                FD_EVENTS,
@@ -207,15 +251,22 @@ public final class DnsResolver {
                        answerbuf = resNetworkResult(fd);
                        answerbuf = resNetworkResult(fd);
                    } catch (ErrnoException e) {
                    } catch (ErrnoException e) {
                        Log.e(TAG, "resNetworkResult:" + e.toString());
                        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
                    // Unregister this fd listener
                    return 0;
                    return 0;
                });
                });
    }
    }


    private class DnsAddressAnswer extends DnsPacket {
    private static class DnsAddressAnswer extends DnsPacket {
        private static final String TAG = "DnsResolver.DnsAddressAnswer";
        private static final String TAG = "DnsResolver.DnsAddressAnswer";
        private static final boolean DBG = false;
        private static final boolean DBG = false;


@@ -226,12 +277,6 @@ public final class DnsResolver {
            if ((mHeader.flags & (1 << 15)) == 0) {
            if ((mHeader.flags & (1 << 15)) == 0) {
                throw new ParseException("Not an answer packet");
                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) {
            if (mHeader.getRecordCount(QDSECTION) == 0) {
                throw new ParseException("No question found");
                throw new ParseException("No question found");
            }
            }
@@ -241,6 +286,8 @@ public final class DnsResolver {


        public @NonNull List<InetAddress> getAddresses() {
        public @NonNull List<InetAddress> getAddresses() {
            final List<InetAddress> results = new ArrayList<InetAddress>();
            final List<InetAddress> results = new ArrayList<InetAddress>();
            if (mHeader.getRecordCount(ANSECTION) == 0) return results;

            for (final DnsRecord ansSec : mRecords[ANSECTION]) {
            for (final DnsRecord ansSec : mRecords[ANSECTION]) {
                // Only support A and AAAA, also ignore answers if query type != answer type.
                // Only support A and AAAA, also ignore answers if query type != answer type.
                int nsType = ansSec.nsType;
                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 Original line Diff line number Diff line
@@ -16,15 +16,22 @@


package android.net;
package android.net;


import android.annotation.NonNull;

/**
/**
 * Thrown when parsing a URL fails.
 * Thrown when parsing failed.
 */
 */
// See non-public class {@link WebAddress}.
// See non-public class {@link WebAddress}.
public class ParseException extends RuntimeException {
public class ParseException extends RuntimeException {
    public String response;
    public String response;


    ParseException(String response) {
    public ParseException(@NonNull String response) {
        super(response);
        super(response);
        this.response = response;
        this.response = response;
    }
    }

    public ParseException(@NonNull String response, @NonNull Throwable cause) {
        super(response, cause);
        this.response = response;
    }
}
}
+1 −1
Original line number Original line Diff line number Diff line
@@ -69,7 +69,7 @@ public class DnsPacketTest {
        try {
        try {
            new TestDnsPacket(null);
            new TestDnsPacket(null);
            fail("Exception not thrown for null byte array");
            fail("Exception not thrown for null byte array");
        } catch (DnsPacket.ParseException e) {
        } catch (ParseException e) {
        }
        }
    }
    }