Add support for setsockoptBytes

Test: atest FrameworksNetTests:android.net.connectivity.android.net.NetworkUtilsTest
Change-Id: I3d94172fa8a42e2ab8ba4caf80d5caaa11fac703
diff --git a/framework/jni/android_net_NetworkUtils.cpp b/framework/jni/android_net_NetworkUtils.cpp
index ca297e5..5403be7 100644
--- a/framework/jni/android_net_NetworkUtils.cpp
+++ b/framework/jni/android_net_NetworkUtils.cpp
@@ -26,6 +26,7 @@
 #include <bpf/BpfClassic.h>
 #include <DnsProxydProtocol.h> // NETID_USE_LOCAL_NAMESERVERS
 #include <nativehelper/JNIPlatformHelp.h>
+#include <nativehelper/ScopedPrimitiveArray.h>
 #include <utils/Log.h>
 
 #include "jni.h"
@@ -240,6 +241,15 @@
             trw.rcv_wnd, trw.rcv_wup, tcpinfo.tcpi_rcv_wscale);
 }
 
+static void android_net_utils_setsockoptBytes(JNIEnv *env, jclass clazz, jobject javaFd,
+        jint level, jint option, jbyteArray javaBytes) {
+    int sock = AFileDescriptor_getFd(env, javaFd);
+    ScopedByteArrayRO value(env, javaBytes);
+    if (setsockopt(sock, level, option, value.get(), value.size()) != 0) {
+        jniThrowErrnoException(env, "setsockoptBytes", errno);
+    }
+}
+
 // ----------------------------------------------------------------------------
 
 /*
@@ -260,6 +270,8 @@
     { "resNetworkResult", "(Ljava/io/FileDescriptor;)Landroid/net/DnsResolver$DnsResponse;", (void*) android_net_utils_resNetworkResult },
     { "resNetworkCancel", "(Ljava/io/FileDescriptor;)V", (void*) android_net_utils_resNetworkCancel },
     { "getDnsNetwork", "()Landroid/net/Network;", (void*) android_net_utils_getDnsNetwork },
+    { "setsockoptBytes", "(Ljava/io/FileDescriptor;II[B)V",
+    (void*) android_net_utils_setsockoptBytes},
 };
 // clang-format on
 
diff --git a/framework/src/android/net/NetworkUtils.java b/framework/src/android/net/NetworkUtils.java
index 2679b62..fbdc024 100644
--- a/framework/src/android/net/NetworkUtils.java
+++ b/framework/src/android/net/NetworkUtils.java
@@ -426,4 +426,16 @@
         return routedIPCount;
     }
 
+    /**
+     * Sets a socket option with byte array
+     *
+     * @param fd The socket file descriptor
+     * @param level The level at which the option is defined
+     * @param option The socket option for which the value is to be set
+     * @param value The option value to be set in byte array
+     * @throws ErrnoException if setsockopt fails
+     */
+    public static native void setsockoptBytes(FileDescriptor fd, int level, int option,
+            byte[] value) throws ErrnoException;
+
 }
diff --git a/tests/unit/java/android/net/NetworkUtilsTest.java b/tests/unit/java/android/net/NetworkUtilsTest.java
index a28245d..5d789b4 100644
--- a/tests/unit/java/android/net/NetworkUtilsTest.java
+++ b/tests/unit/java/android/net/NetworkUtilsTest.java
@@ -16,19 +16,32 @@
 
 package android.net;
 
+import static android.system.OsConstants.AF_INET6;
+import static android.system.OsConstants.IPPROTO_ICMPV6;
+import static android.system.OsConstants.SOCK_DGRAM;
+import static android.system.OsConstants.SOL_SOCKET;
+import static android.system.OsConstants.SO_RCVTIMEO;
 import static junit.framework.Assert.assertEquals;
 
 import android.os.Build;
+import android.system.ErrnoException;
+import android.system.Os;
+import android.system.StructTimeval;
 
 import androidx.test.filters.SmallTest;
 
+import com.android.net.module.util.SocketUtils;
 import com.android.testutils.DevSdkIgnoreRule;
 import com.android.testutils.DevSdkIgnoreRunner;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.io.FileDescriptor;
+import java.io.IOException;
 import java.math.BigInteger;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
 import java.util.TreeSet;
 
 @RunWith(DevSdkIgnoreRunner.class)
@@ -131,4 +144,27 @@
         assertEquals(BigInteger.valueOf(7l - 4 + 4 + 16 + 65536),
                 NetworkUtils.routedIPv6AddressCount(set));
     }
+
+    private byte[] getTimevalBytes(StructTimeval tv) {
+        byte[] timeval = new byte[16];
+        ByteBuffer buf = ByteBuffer.wrap(timeval);
+        buf.order(ByteOrder.nativeOrder());
+        buf.putLong(tv.tv_sec);
+        buf.putLong(tv.tv_usec);
+        return timeval;
+    }
+
+    @Test
+    public void testSetSockOptBytes() throws ErrnoException {
+        final FileDescriptor sock = Os.socket(AF_INET6, SOCK_DGRAM, IPPROTO_ICMPV6);
+        final StructTimeval writeTimeval = StructTimeval.fromMillis(1200);
+        byte[] timeval = getTimevalBytes(writeTimeval);
+        final StructTimeval readTimeval;
+
+        NetworkUtils.setsockoptBytes(sock, SOL_SOCKET, SO_RCVTIMEO, timeval);
+        readTimeval = Os.getsockoptTimeval(sock, SOL_SOCKET, SO_RCVTIMEO);
+
+        assertEquals(writeTimeval, readTimeval);
+        SocketUtils.closeSocketQuietly(sock);
+    }
 }