Cts test cases for asynchronous DNS query API

implement test cases without gtest

Test: build
      atest MultinetworkApiTest

Change-Id: Iacb0b05f449c3a848c62a402cf0fa73d54956245
diff --git a/tests/cts/net/jni/NativeMultinetworkJni.cpp b/tests/cts/net/jni/NativeMultinetworkJni.cpp
index ab486e3..332148b 100644
--- a/tests/cts/net/jni/NativeMultinetworkJni.cpp
+++ b/tests/cts/net/jni/NativeMultinetworkJni.cpp
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2010 The Android Open Source Project
+ * Copyright (C) 2019 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -19,18 +19,298 @@
 #include <utils/Log.h>
 
 #include <arpa/inet.h>
+#include <arpa/nameser.h>
 #include <errno.h>
 #include <inttypes.h>
 #include <jni.h>
 #include <netdb.h>
+#include <poll.h> /* poll */
+#include <resolv.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
 #include <sys/socket.h>
 #include <sys/time.h>
-#include <android/multinetwork.h>
 
+#include <string>
+
+#include <android/multinetwork.h>
+#include <nativehelper/JNIHelp.h>
+
+#define EXPECT_GE(env, actual, expected, msg)                        \
+    do {                                                             \
+        if (actual < expected) {                                     \
+            jniThrowExceptionFmt(env, "java/lang/AssertionError",    \
+                    "%s:%d: %s EXPECT_GE: expected %d, got %d",      \
+                    __FILE__, __LINE__, expected, actual);           \
+        }                                                            \
+    } while (0)
+
+#define EXPECT_GT(env, actual, expected, msg)                        \
+    do {                                                             \
+        if (actual <= expected) {                                    \
+            jniThrowExceptionFmt(env, "java/lang/AssertionError",    \
+                    "%s:%d: %s EXPECT_GT: expected %d, got %d",      \
+                    __FILE__, __LINE__, msg, expected, actual);      \
+        }                                                            \
+    } while (0)
+
+#define EXPECT_EQ(env, expected, actual, msg)                        \
+    do {                                                             \
+        if (actual != expected) {                                    \
+            jniThrowExceptionFmt(env, "java/lang/AssertionError",    \
+                    "%s:%d: %s EXPECT_EQ: expected %d, got %d",      \
+                    __FILE__, __LINE__, msg, expected, actual);      \
+        }                                                            \
+    } while (0)
+
+static const int MAXPACKET = 8 * 1024;
+static const int TIMEOUT_MS = 2000;
 static const char kHostname[] = "connectivitycheck.android.com";
+static const char kNxDomainName[] = "test1-nx.metric.gstatic.com";
+static const char kGoogleName[] = "www.google.com";
+
+int makeQuery(const char* name, int qtype, uint8_t* buf, size_t buflen) {
+    return res_mkquery(ns_o_query, name, ns_c_in, qtype, NULL, 0, NULL, buf, buflen);
+}
+
+int getAsyncResponse(JNIEnv* env, int fd, int timeoutMs, int* rcode, uint8_t* buf, size_t bufLen) {
+    struct pollfd wait_fd = { .fd = fd, .events = POLLIN };
+
+    poll(&wait_fd, 1, timeoutMs);
+    if (wait_fd.revents & POLLIN) {
+        int n = android_res_nresult(fd, rcode, buf, bufLen);
+        // Verify that android_res_nresult() closed the fd
+        char dummy;
+        EXPECT_EQ(env, -1, read(fd, &dummy, sizeof dummy), "res_nresult check for closing fd");
+        EXPECT_EQ(env, EBADF, errno, "res_nresult check for errno");
+        return n;
+    }
+
+    return -EREMOTEIO;
+}
+
+int extractIpAddressAnswers(uint8_t* buf, size_t bufLen, int family) {
+    ns_msg handle;
+    if (ns_initparse((const uint8_t*) buf, bufLen, &handle) < 0) {
+        return -EREMOTEIO;
+    }
+    const int ancount = ns_msg_count(handle, ns_s_an);
+    ns_rr rr;
+    for (int i = 0; i < ancount; i++) {
+        if (ns_parserr(&handle, ns_s_an, i, &rr) < 0) {
+            continue;
+        }
+        const uint8_t* rdata = ns_rr_rdata(rr);
+        char buffer[INET6_ADDRSTRLEN];
+        if (inet_ntop(family, (const char*) rdata, buffer, sizeof(buffer)) == NULL) {
+            return -errno;
+        }
+    }
+    return 0;
+}
+
+int expectAnswersValid(JNIEnv* env, int fd, int family, int expectedRcode) {
+    int rcode = -1;
+    uint8_t buf[MAXPACKET] = {};
+    int res = getAsyncResponse(env, fd, TIMEOUT_MS, &rcode, buf, MAXPACKET);
+    if (res < 0) {
+        return res;
+    }
+    if(rcode != expectedRcode) {
+        ALOGD("rcode:%d, expectedRcode = %d", rcode, expectedRcode);
+        return -EREMOTEIO;
+    }
+
+    if (expectedRcode == ns_r_noerror && res > 0) {
+        return extractIpAddressAnswers(buf, res, family);
+    }
+    return 0;
+}
+
+int expectAnswersNotValid(JNIEnv* env, int fd, int expectedErrno) {
+    int rcode = -1;
+    uint8_t buf[MAXPACKET] = {};
+    int res = getAsyncResponse(env, fd, TIMEOUT_MS, &rcode, buf, MAXPACKET);
+    if (res != expectedErrno) {
+        ALOGD("res:%d, expectedErrno = %d", res, expectedErrno);
+        return (res > 0) ? -EREMOTEIO : res;
+    }
+    return 0;
+}
+
+extern "C"
+JNIEXPORT jint Java_android_net_cts_MultinetworkApiTest_runResNqueryCheck(
+        JNIEnv* env, jclass, jlong nethandle) {
+    net_handle_t handle = (net_handle_t) nethandle;
+
+    // V4
+    int fd = android_res_nquery(handle, kHostname, ns_c_in, ns_t_a, 0);
+    EXPECT_GE(env, fd, 0, "v4 res_nquery");
+    EXPECT_EQ(env, 0, expectAnswersValid(env, fd, AF_INET, ns_r_noerror),
+            "v4 res_nquery check answers");
+
+    // V4 NXDOMAIN
+    fd = android_res_nquery(handle, kNxDomainName, ns_c_in, ns_t_a,
+            ANDROID_RESOLV_NO_CACHE_LOOKUP);
+    EXPECT_GE(env, fd, 0, "v4 res_nquery NXDOMAIN");
+    EXPECT_EQ(env, 0, expectAnswersValid(env, fd, AF_INET, ns_r_nxdomain),
+            "v4 res_nquery NXDOMAIN check answers");
+
+    // V6
+    fd = android_res_nquery(handle, kHostname, ns_c_in, ns_t_aaaa, 0);
+    EXPECT_GE(env, fd, 0, "v6 res_nquery");
+    EXPECT_EQ(env, 0, expectAnswersValid(env, fd, AF_INET, ns_r_noerror),
+            "v6 res_nquery check answers");
+
+    // V6 NXDOMAIN
+    fd = android_res_nquery(handle, kNxDomainName, ns_c_in, ns_t_aaaa,
+            ANDROID_RESOLV_NO_CACHE_LOOKUP);
+    EXPECT_GE(env, fd, 0, "v6 res_nquery NXDOMAIN");
+    EXPECT_EQ(env, 0, expectAnswersValid(env, fd, AF_INET, ns_r_nxdomain),
+            "v6 res_nquery NXDOMAIN check answers");
+
+    return 0;
+}
+
+extern "C"
+JNIEXPORT jint Java_android_net_cts_MultinetworkApiTest_runResNsendCheck(
+        JNIEnv* env, jclass, jlong nethandle) {
+    net_handle_t handle = (net_handle_t) nethandle;
+    // V4
+    uint8_t buf1[MAXPACKET] = {};
+
+    int len1 = makeQuery(kGoogleName, ns_t_a, buf1, sizeof(buf1));
+    EXPECT_GT(env, len1, 0, "v4 res_mkquery 1st");
+
+    uint8_t buf2[MAXPACKET] = {};
+    int len2 = makeQuery(kHostname, ns_t_a, buf2, sizeof(buf2));
+    EXPECT_GT(env, len2, 0, "v4 res_mkquery 2nd");
+
+    int fd1 = android_res_nsend(handle, buf1, len1, 0);
+    EXPECT_GE(env, fd1, 0, "v4 res_nsend 1st");
+    int fd2 = android_res_nsend(handle, buf2, len2, 0);
+    EXPECT_GE(env, fd2, 0, "v4 res_nsend 2nd");
+
+    EXPECT_EQ(env, 0, expectAnswersValid(env, fd2, AF_INET, ns_r_noerror),
+            "v4 res_nsend 2nd check answers");
+    EXPECT_EQ(env, 0, expectAnswersValid(env, fd1, AF_INET, ns_r_noerror),
+            "v4 res_nsend 1st check answers");
+
+    // V4 NXDOMAIN
+    memset(buf1, 0, sizeof(buf1));
+    len1 = makeQuery(kNxDomainName, ns_t_a, buf1, sizeof(buf1));
+    EXPECT_GT(env, len1, 0, "v4 res_mkquery NXDOMAIN");
+    fd1 = android_res_nsend(handle, buf1, len1, ANDROID_RESOLV_NO_CACHE_LOOKUP);
+    EXPECT_GE(env, fd1, 0, "v4 res_nsend NXDOMAIN");
+    EXPECT_EQ(env, 0, expectAnswersValid(env, fd1, AF_INET, ns_r_nxdomain),
+            "v4 res_nsend NXDOMAIN check answers");
+
+    // V6
+    memset(buf1, 0, sizeof(buf1));
+    memset(buf2, 0, sizeof(buf2));
+    len1 = makeQuery(kGoogleName, ns_t_aaaa, buf1, sizeof(buf1));
+    EXPECT_GT(env, len1, 0, "v6 res_mkquery 1st");
+    len2 = makeQuery(kHostname, ns_t_aaaa, buf2, sizeof(buf2));
+    EXPECT_GT(env, len2, 0, "v6 res_mkquery 2nd");
+
+    fd1 = android_res_nsend(handle, buf1, len1, 0);
+    EXPECT_GE(env, fd1, 0, "v6 res_nsend 1st");
+    fd2 = android_res_nsend(handle, buf2, len2, 0);
+    EXPECT_GE(env, fd2, 0, "v6 res_nsend 2nd");
+
+    EXPECT_EQ(env, 0, expectAnswersValid(env, fd2, AF_INET6, ns_r_noerror),
+            "v6 res_nsend 2nd check answers");
+    EXPECT_EQ(env, 0, expectAnswersValid(env, fd1, AF_INET6, ns_r_noerror),
+            "v6 res_nsend 1st check answers");
+
+    // // V6 NXDOMAIN
+    memset(buf1, 0, sizeof(buf1));
+    len1 = makeQuery(kNxDomainName, ns_t_aaaa, buf1, sizeof(buf1));
+    EXPECT_GT(env, len1, 0, "v6 res_mkquery NXDOMAIN");
+    fd1 = android_res_nsend(handle, buf1, len1, ANDROID_RESOLV_NO_CACHE_LOOKUP);
+    EXPECT_GE(env, fd1, 0, "v6 res_nsend NXDOMAIN");
+    EXPECT_EQ(env, 0, expectAnswersValid(env, fd1, AF_INET6, ns_r_nxdomain),
+            "v6 res_nsend NXDOMAIN check answers");
+
+    return 0;
+}
+
+extern "C"
+JNIEXPORT jint Java_android_net_cts_MultinetworkApiTest_runResNcancelCheck(
+        JNIEnv* env, jclass, jlong nethandle) {
+    net_handle_t handle = (net_handle_t) nethandle;
+
+    int fd = android_res_nquery(handle, kGoogleName, ns_c_in, ns_t_a, 0);
+    int rcode = -1;
+    uint8_t buf[MAXPACKET] = {};
+    android_res_cancel(fd);
+    android_res_cancel(fd);
+
+    EXPECT_EQ(env, -EBADF, android_res_nresult(fd, &rcode, buf, MAXPACKET), "res_cancel");
+    return 0;
+}
+
+extern "C"
+JNIEXPORT jint Java_android_net_cts_MultinetworkApiTest_runResNapiMalformedCheck(
+        JNIEnv* env, jclass, jlong nethandle) {
+    net_handle_t handle = (net_handle_t) nethandle;
+
+    // It is the equivalent of "dig . a", Query with an empty name.
+    int fd = android_res_nquery(handle, "", ns_c_in, ns_t_a, 0);
+    EXPECT_GE(env, fd, 0, "res_nquery root");
+    EXPECT_EQ(env, 0, expectAnswersValid(env, fd, AF_INET, ns_r_noerror),
+            "res_nquery root check answers");
+
+    // Label limit 63
+    std::string exceedingLabelQuery = "www." + std::string(70, 'g') + ".com";
+    // Name limit 255
+    std::string exceedingDomainQuery = "www." + std::string(255, 'g') + ".com";
+
+    fd = android_res_nquery(handle, exceedingLabelQuery.c_str(), ns_c_in, ns_t_a, 0);
+    EXPECT_EQ(env, -EMSGSIZE, fd, "res_nquery exceedingLabelQuery");
+    fd = android_res_nquery(handle, exceedingDomainQuery.c_str(), ns_c_in, ns_t_a, 0);
+    EXPECT_EQ(env, -EMSGSIZE, fd, "res_nquery exceedingDomainQuery");
+
+    uint8_t buf[10] = {};
+    // empty BLOB
+    fd = android_res_nsend(handle, buf, 10, 0);
+    EXPECT_GE(env, fd, 0, "res_nsend empty BLOB");
+    EXPECT_EQ(env, 0, expectAnswersNotValid(env, fd, -EINVAL),
+            "res_nsend empty BLOB check answers");
+
+    uint8_t largeBuf[2 * MAXPACKET] = {};
+    // A buffer larger than 8KB
+    fd = android_res_nsend(handle, largeBuf, sizeof(largeBuf), 0);
+    EXPECT_EQ(env, -EMSGSIZE, fd, "res_nsend buffer larger than 8KB");
+
+    // 1000 bytes filled with 0. This returns EMSGSIZE because FrameworkListener limits the size of
+    // commands to 1024 bytes. TODO: b/126307309
+    fd = android_res_nsend(handle, largeBuf, 1000, 0);
+    EXPECT_EQ(env, -EMSGSIZE, fd, "res_nsend 1000 bytes filled with 0");
+
+    // 500 bytes filled with 0
+    fd = android_res_nsend(handle, largeBuf, 500, 0);
+    EXPECT_GE(env, fd, 0, "res_nsend 500 bytes filled with 0");
+    EXPECT_EQ(env, 0, expectAnswersNotValid(env, fd, -EINVAL),
+            "res_nsend 500 bytes filled with 0 check answers");
+
+    // 1000 bytes filled with 0xFF
+    uint8_t ffBuf[1001] = {};
+    memset(ffBuf, 0xFF, sizeof(ffBuf));
+    ffBuf[1000] = '\0';
+    fd = android_res_nsend(handle, ffBuf, sizeof(ffBuf), 0);
+    EXPECT_EQ(env, -EMSGSIZE, fd, "res_nsend 1000 bytes filled with 0xFF");
+
+    // 500 bytes filled with 0xFF
+    ffBuf[501] = '\0';
+    fd = android_res_nsend(handle, ffBuf, 500, 0);
+    EXPECT_GE(env, fd, 0, "res_nsend 500 bytes filled with 0xFF");
+    EXPECT_EQ(env, 0, expectAnswersNotValid(env, fd, -EINVAL),
+            "res_nsend 500 bytes filled with 0xFF check answers");
+
+    return 0;
+}
 
 extern "C"
 JNIEXPORT jint Java_android_net_cts_MultinetworkApiTest_runGetaddrinfoCheck(
diff --git a/tests/cts/net/src/android/net/cts/MultinetworkApiTest.java b/tests/cts/net/src/android/net/cts/MultinetworkApiTest.java
index b2c9d9b..c3e65b7 100644
--- a/tests/cts/net/src/android/net/cts/MultinetworkApiTest.java
+++ b/tests/cts/net/src/android/net/cts/MultinetworkApiTest.java
@@ -43,6 +43,12 @@
     private static native int runSetprocnetwork(long networkHandle);
     private static native int runSetsocknetwork(long networkHandle);
     private static native int runDatagramCheck(long networkHandle);
+    private static native int runResNapiMalformedCheck(long networkHandle);
+    private static native int runResNcancelCheck(long networkHandle);
+    private static native int runResNqueryCheck(long networkHandle);
+    private static native int runResNsendCheck(long networkHandle);
+
+
 
     private ConnectivityManager mCM;
 
@@ -175,4 +181,14 @@
             fail();
         } catch (IllegalArgumentException e) {}
     }
+
+    public void testResNApi() {
+        for (Network network : getTestableNetworks()) {
+            // Throws AssertionError directly in jni function if test fail.
+            runResNqueryCheck(network.getNetworkHandle());
+            runResNsendCheck(network.getNetworkHandle());
+            runResNcancelCheck(network.getNetworkHandle());
+            runResNapiMalformedCheck(network.getNetworkHandle());
+        }
+    }
 }