summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndroid Build Coastguard Worker <android-build-coastguard-worker@google.com>2023-05-05 23:01:01 +0000
committerAndroid Build Coastguard Worker <android-build-coastguard-worker@google.com>2023-05-05 23:01:01 +0000
commit46114d87a2069b4b3559b4884906ee4c76245dc7 (patch)
treeecc2ab48b8d86907f891142420b62934f6bbb9a1
parent02f87bd1e30a05a60f5c746cfe0af205bc5c800e (diff)
parent9989ddf8959a187657b051901109524eaae83619 (diff)
downloadnet-46114d87a2069b4b3559b4884906ee4c76245dc7.tar.gz
Snap for 10078357 from 9989ddf8959a187657b051901109524eaae83619 to mainline-adservices-releaseaml_ads_331920180
Change-Id: I93dd26136b760b94798b01c45f51f82cad59c25b
-rw-r--r--common/Android.bp57
-rw-r--r--common/device/com/android/net/module/util/async/BufferedFile.java292
-rw-r--r--common/device/com/android/net/module/util/netlink/InetDiagMessage.java47
-rw-r--r--common/device/com/android/net/module/util/netlink/NetlinkConstants.java1
-rw-r--r--common/device/com/android/net/module/util/netlink/NetlinkUtils.java75
-rw-r--r--common/device/com/android/net/module/util/netlink/StructInetDiagMsg.java48
-rw-r--r--common/device/com/android/net/module/util/netlink/StructInetDiagReqV2.java19
-rw-r--r--common/device/com/android/net/module/util/netlink/StructInetDiagSockId.java49
-rw-r--r--common/device/com/android/net/module/util/wear/NetPacketHelpers.java41
-rw-r--r--common/device/com/android/net/module/util/wear/PacketFile.java85
-rw-r--r--common/device/com/android/net/module/util/wear/StreamingPacketFile.java221
-rw-r--r--common/framework/com/android/net/module/util/InetAddressUtils.java22
-rw-r--r--common/framework/com/android/net/module/util/IpUtils.java16
-rw-r--r--common/framework/com/android/net/module/util/LinkPropertiesUtils.java18
-rw-r--r--common/framework/com/android/net/module/util/PermissionUtils.java14
-rw-r--r--common/native/bpf_headers/include/bpf/bpf_helpers.h1
-rw-r--r--common/tests/unit/Android.bp1
-rw-r--r--common/tests/unit/src/com/android/net/module/util/InetAddressUtilsTest.java25
-rw-r--r--common/tests/unit/src/com/android/net/module/util/IpUtilsTest.java14
-rw-r--r--common/tests/unit/src/com/android/net/module/util/LinkPropertiesUtilsTest.java15
-rw-r--r--common/tests/unit/src/com/android/net/module/util/async/BufferedFileTest.java376
-rw-r--r--common/tests/unit/src/com/android/net/module/util/netlink/InetDiagSocketTest.java232
-rw-r--r--common/tests/unit/src/com/android/net/module/util/netlink/NetlinkUtilsTest.java7
-rw-r--r--common/tests/unit/src/com/android/net/module/util/wear/NetPacketHelpersTest.java60
-rw-r--r--common/tests/unit/src/com/android/net/module/util/wear/StreamingPacketFileTest.java291
-rw-r--r--common/tests/unit/src/com/android/testutils/TestableNetworkCallbackTest.kt13
-rw-r--r--common/testutils/Android.bp1
-rw-r--r--common/testutils/devicetests/com/android/testutils/TestableNetworkCallback.kt53
-rw-r--r--common/testutils/devicetests/com/android/testutils/async/FakeOsAccess.java2
-rw-r--r--common/testutils/devicetests/com/android/testutils/async/RateLimiter.java2
-rw-r--r--common/testutils/devicetests/com/android/testutils/async/ReadableDataAnswer.java76
31 files changed, 2016 insertions, 158 deletions
diff --git a/common/Android.bp b/common/Android.bp
index 1fe6c2cc..b1c653d8 100644
--- a/common/Android.bp
+++ b/common/Android.bp
@@ -328,6 +328,63 @@ java_library {
lint: { strict_updatability_linting: true },
}
+java_library {
+ name: "net-utils-device-common-wear",
+ srcs: [
+ "device/com/android/net/module/util/wear/*.java",
+ ],
+ sdk_version: "module_current",
+ min_sdk_version: "29",
+ visibility: [
+ "//frameworks/libs/net/common/tests:__subpackages__",
+ "//frameworks/libs/net/common/testutils:__subpackages__",
+ "//packages/modules/Connectivity:__subpackages__",
+ ],
+ libs: [
+ "framework-annotations-lib",
+ ],
+ static_libs: [
+ "net-utils-device-common-async",
+ ],
+ apex_available: [
+ "com.android.tethering",
+ "//apex_available:platform",
+ ],
+ lint: { strict_updatability_linting: true },
+}
+
+// Limited set of utilities for use by service-connectivity-mdns-standalone-build-test, to make sure
+// the mDNS code can build with only system APIs.
+// The mDNS code is platform code so it should use framework-annotations-lib, contrary to apps that
+// should use sdk_version: "system_current" and only androidx.annotation_annotation. But this build
+// rule verifies that the mDNS code can be built into apps, if code transformations are applied to
+// the annotations.
+// When using "system_current", framework annotations are not available; they would appear as
+// package-private as they are marked as such in the system_current stubs. So build against
+// core_platform and add the stubs manually in "libs". See http://b/147773144#comment7.
+java_library {
+ name: "net-utils-device-common-mdns-standalone-build-test",
+ // Build against core_platform and add the stub libraries manually in "libs", as annotations
+ // are already included in android_system_stubs_current but package-private, so
+ // "framework-annotations-lib" needs to be manually included before
+ // "android_system_stubs_current" (b/272392042)
+ sdk_version: "core_platform",
+ srcs: [
+ "device/com/android/net/module/util/FdEventsReader.java",
+ "device/com/android/net/module/util/HexDump.java",
+ "device/com/android/net/module/util/SharedLog.java",
+ "framework/com/android/net/module/util/ByteUtils.java",
+ "framework/com/android/net/module/util/CollectionUtils.java",
+ "framework/com/android/net/module/util/LinkPropertiesUtils.java",
+ ],
+ libs: [
+ "framework-annotations-lib",
+ "android_system_stubs_current",
+ "androidx.annotation_annotation",
+ ],
+ visibility: ["//packages/modules/Connectivity/service-t"],
+}
+
// Use a filegroup and not a library for telephony sources, as framework-annotations cannot be
// included either (some annotations would be duplicated on the bootclasspath).
filegroup {
diff --git a/common/device/com/android/net/module/util/async/BufferedFile.java b/common/device/com/android/net/module/util/async/BufferedFile.java
new file mode 100644
index 00000000..bb5736b0
--- /dev/null
+++ b/common/device/com/android/net/module/util/async/BufferedFile.java
@@ -0,0 +1,292 @@
+/*
+ * Copyright (C) 2023 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.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.net.module.util.async;
+
+import java.io.IOException;
+import java.util.concurrent.atomic.AtomicLong;
+
+/**
+ * Buffers inbound and outbound file data within given strict limits.
+ *
+ * Automatically manages all readability and writeability events in EventManager:
+ * - When read buffer has more space - asks EventManager to notify on more data
+ * - When write buffer has more space - asks the user to provide more data
+ * - When underlying file cannot accept more data - registers EventManager callback
+ *
+ * @hide
+ */
+public final class BufferedFile implements AsyncFile.Listener {
+ /**
+ * Receives notifications when new data or output space is available.
+ * @hide
+ */
+ public interface Listener {
+ /** Invoked after the underlying file has been closed. */
+ void onBufferedFileClosed();
+
+ /** Invoked when there's new data in the inbound buffer. */
+ void onBufferedFileInboundData(int readByteCount);
+
+ /** Notifies on data being flushed from output buffer. */
+ void onBufferedFileOutboundSpace();
+
+ /** Notifies on unrecoverable error in file access. */
+ void onBufferedFileIoError(String message);
+ }
+
+ private final Listener mListener;
+ private final EventManager mEventManager;
+ private AsyncFile mFile;
+
+ private final CircularByteBuffer mInboundBuffer;
+ private final AtomicLong mTotalBytesRead = new AtomicLong();
+ private boolean mIsReadingShutdown;
+
+ private final CircularByteBuffer mOutboundBuffer;
+ private final AtomicLong mTotalBytesWritten = new AtomicLong();
+
+ /** Creates BufferedFile based on the given file descriptor. */
+ public static BufferedFile create(
+ EventManager eventManager,
+ FileHandle fileHandle,
+ Listener listener,
+ int inboundBufferSize,
+ int outboundBufferSize) throws IOException {
+ if (fileHandle == null) {
+ throw new NullPointerException();
+ }
+ BufferedFile file = new BufferedFile(
+ eventManager, listener, inboundBufferSize, outboundBufferSize);
+ file.mFile = eventManager.registerFile(fileHandle, file);
+ return file;
+ }
+
+ private BufferedFile(
+ EventManager eventManager,
+ Listener listener,
+ int inboundBufferSize,
+ int outboundBufferSize) {
+ if (eventManager == null || listener == null) {
+ throw new NullPointerException();
+ }
+ mEventManager = eventManager;
+ mListener = listener;
+
+ mInboundBuffer = new CircularByteBuffer(inboundBufferSize);
+ mOutboundBuffer = new CircularByteBuffer(outboundBufferSize);
+ }
+
+ /** Requests this file to be closed. */
+ public void close() {
+ mFile.close();
+ }
+
+ @Override
+ public void onClosed(AsyncFile file) {
+ mListener.onBufferedFileClosed();
+ }
+
+ ///////////////////////////////////////////////////////////////////////////
+ // READ PATH
+ ///////////////////////////////////////////////////////////////////////////
+
+ /** Returns buffer that is automatically filled with inbound data. */
+ public ReadableByteBuffer getInboundBuffer() {
+ return mInboundBuffer;
+ }
+
+ public int getInboundBufferFreeSizeForTest() {
+ return mInboundBuffer.freeSize();
+ }
+
+ /** Permanently disables reading of this file, and clears all buffered data. */
+ public void shutdownReading() {
+ mIsReadingShutdown = true;
+ mInboundBuffer.clear();
+ mFile.enableReadEvents(false);
+ }
+
+ /** Returns true after shutdownReading() has been called. */
+ public boolean isReadingShutdown() {
+ return mIsReadingShutdown;
+ }
+
+ /** Starts or resumes async read operations on this file. */
+ public void continueReading() {
+ if (!mIsReadingShutdown && mInboundBuffer.freeSize() > 0) {
+ mFile.enableReadEvents(true);
+ }
+ }
+
+ @Override
+ public void onReadReady(AsyncFile file) {
+ if (mIsReadingShutdown) {
+ return;
+ }
+
+ int readByteCount;
+ try {
+ readByteCount = bufferInputData();
+ } catch (IOException e) {
+ mListener.onBufferedFileIoError("IOException while reading: " + e.toString());
+ return;
+ }
+
+ if (readByteCount > 0) {
+ mListener.onBufferedFileInboundData(readByteCount);
+ }
+
+ continueReading();
+ }
+
+ private int bufferInputData() throws IOException {
+ int totalReadCount = 0;
+ while (true) {
+ final int maxReadCount = mInboundBuffer.getDirectWriteSize();
+ if (maxReadCount == 0) {
+ mFile.enableReadEvents(false);
+ break;
+ }
+
+ final int bufferOffset = mInboundBuffer.getDirectWritePos();
+ final byte[] buffer = mInboundBuffer.getDirectWriteBuffer();
+
+ final int readCount = mFile.read(buffer, bufferOffset, maxReadCount);
+ if (readCount <= 0) {
+ break;
+ }
+
+ mInboundBuffer.accountForDirectWrite(readCount);
+ totalReadCount += readCount;
+ }
+
+ mTotalBytesRead.addAndGet(totalReadCount);
+ return totalReadCount;
+ }
+
+ ///////////////////////////////////////////////////////////////////////////
+ // WRITE PATH
+ ///////////////////////////////////////////////////////////////////////////
+
+ /** Returns the number of bytes currently buffered for output. */
+ public int getOutboundBufferSize() {
+ return mOutboundBuffer.size();
+ }
+
+ /** Returns the number of bytes currently available for buffering for output. */
+ public int getOutboundBufferFreeSize() {
+ return mOutboundBuffer.freeSize();
+ }
+
+ /**
+ * Queues the given data for output.
+ * Throws runtime exception if there is not enough space.
+ */
+ public boolean enqueueOutboundData(byte[] data, int pos, int len) {
+ return enqueueOutboundData(data, pos, len, null, 0, 0);
+ }
+
+ /**
+ * Queues data1, then data2 for output.
+ * Throws runtime exception if there is not enough space.
+ */
+ public boolean enqueueOutboundData(
+ byte[] data1, int pos1, int len1,
+ byte[] buffer2, int pos2, int len2) {
+ Assertions.throwsIfOutOfBounds(data1, pos1, len1);
+ Assertions.throwsIfOutOfBounds(buffer2, pos2, len2);
+
+ final int totalLen = len1 + len2;
+
+ if (totalLen > mOutboundBuffer.freeSize()) {
+ flushOutboundBuffer();
+
+ if (totalLen > mOutboundBuffer.freeSize()) {
+ return false;
+ }
+ }
+
+ mOutboundBuffer.writeBytes(data1, pos1, len1);
+
+ if (buffer2 != null) {
+ mOutboundBuffer.writeBytes(buffer2, pos2, len2);
+ }
+
+ flushOutboundBuffer();
+
+ return true;
+ }
+
+ private void flushOutboundBuffer() {
+ try {
+ while (mOutboundBuffer.getDirectReadSize() > 0) {
+ final int maxReadSize = mOutboundBuffer.getDirectReadSize();
+ final int writeCount = mFile.write(
+ mOutboundBuffer.getDirectReadBuffer(),
+ mOutboundBuffer.getDirectReadPos(),
+ maxReadSize);
+
+ if (writeCount == 0) {
+ mFile.enableWriteEvents(true);
+ break;
+ }
+
+ if (writeCount > maxReadSize) {
+ throw new IllegalArgumentException(
+ "Write count " + writeCount + " above max " + maxReadSize);
+ }
+
+ mOutboundBuffer.accountForDirectRead(writeCount);
+ }
+ } catch (IOException e) {
+ scheduleOnIoError("IOException while writing: " + e.toString());
+ }
+ }
+
+ private void scheduleOnIoError(String message) {
+ mEventManager.execute(() -> {
+ mListener.onBufferedFileIoError(message);
+ });
+ }
+
+ @Override
+ public void onWriteReady(AsyncFile file) {
+ mFile.enableWriteEvents(false);
+ flushOutboundBuffer();
+ mListener.onBufferedFileOutboundSpace();
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("file={");
+ sb.append(mFile);
+ sb.append("}");
+ if (mIsReadingShutdown) {
+ sb.append(", readingShutdown");
+ }
+ sb.append("}, inboundBuffer={");
+ sb.append(mInboundBuffer);
+ sb.append("}, outboundBuffer={");
+ sb.append(mOutboundBuffer);
+ sb.append("}, totalBytesRead=");
+ sb.append(mTotalBytesRead);
+ sb.append(", totalBytesWritten=");
+ sb.append(mTotalBytesWritten);
+ return sb.toString();
+ }
+}
diff --git a/common/device/com/android/net/module/util/netlink/InetDiagMessage.java b/common/device/com/android/net/module/util/netlink/InetDiagMessage.java
index 5a180e7b..0a2f50d8 100644
--- a/common/device/com/android/net/module/util/netlink/InetDiagMessage.java
+++ b/common/device/com/android/net/module/util/netlink/InetDiagMessage.java
@@ -58,8 +58,8 @@ public class InetDiagMessage extends NetlinkMessage {
private static final int TIMEOUT_MS = 500;
/**
- * Construct an inet_diag_req_v2 message. This method will throw {@code NullPointerException}
- * if local and remote are not both null or both non-null.
+ * Construct an inet_diag_req_v2 message. This method will throw
+ * {@link IllegalArgumentException} if local and remote are not both null or both non-null.
*/
public static byte[] inetDiagReqV2(int protocol, InetSocketAddress local,
InetSocketAddress remote, int family, short flags) {
@@ -68,16 +68,16 @@ public class InetDiagMessage extends NetlinkMessage {
}
/**
- * Construct an inet_diag_req_v2 message. This method will throw {@code NullPointerException}
- * if local and remote are not both null or both non-null.
+ * Construct an inet_diag_req_v2 message. This method will throw
+ * {@code IllegalArgumentException} if local and remote are not both null or both non-null.
*
* @param protocol the request protocol type. This should be set to one of IPPROTO_TCP,
* IPPROTO_UDP, or IPPROTO_UDPLITE.
* @param local local socket address of the target socket. This will be packed into a
- * {@Code StructInetDiagSockId}. Request to diagnose for all sockets if both of
+ * {@link StructInetDiagSockId}. Request to diagnose for all sockets if both of
* local or remote address is null.
* @param remote remote socket address of the target socket. This will be packed into a
- * {@Code StructInetDiagSockId}. Request to diagnose for all sockets if both of
+ * {@link StructInetDiagSockId}. Request to diagnose for all sockets if both of
* local or remote address is null.
* @param family the ip family of the request message. This should be set to either AF_INET or
* AF_INET6 for IPv4 or IPv6 sockets respectively.
@@ -90,18 +90,47 @@ public class InetDiagMessage extends NetlinkMessage {
*/
public static byte[] inetDiagReqV2(int protocol, @Nullable InetSocketAddress local,
@Nullable InetSocketAddress remote, int family, short flags, int pad, int idiagExt,
- int state) throws NullPointerException {
+ int state) throws IllegalArgumentException {
+ // Request for all sockets if no specific socket is requested. Specify the local and remote
+ // socket address information for target request socket.
+ if ((local == null) != (remote == null)) {
+ throw new IllegalArgumentException(
+ "Local and remote must be both null or both non-null");
+ }
+ final StructInetDiagSockId id = ((local != null && remote != null)
+ ? new StructInetDiagSockId(local, remote) : null);
+ return inetDiagReqV2(protocol, id, family,
+ SOCK_DIAG_BY_FAMILY, flags, pad, idiagExt, state);
+ }
+
+ /**
+ * Construct an inet_diag_req_v2 message.
+ *
+ * @param protocol the request protocol type. This should be set to one of IPPROTO_TCP,
+ * IPPROTO_UDP, or IPPROTO_UDPLITE.
+ * @param id inet_diag_sockid. See {@link StructInetDiagSockId}
+ * @param family the ip family of the request message. This should be set to either AF_INET or
+ * AF_INET6 for IPv4 or IPv6 sockets respectively.
+ * @param type message types.
+ * @param flags message flags. See &lt;linux_src&gt;/include/uapi/linux/netlink.h.
+ * @param pad for raw socket protocol specification.
+ * @param idiagExt a set of flags defining what kind of extended information to report.
+ * @param state a bit mask that defines a filter of socket states.
+ * @return bytes array representation of the message
+ */
+ public static byte[] inetDiagReqV2(int protocol, @Nullable StructInetDiagSockId id, int family,
+ short type, short flags, int pad, int idiagExt, int state) {
final byte[] bytes = new byte[StructNlMsgHdr.STRUCT_SIZE + StructInetDiagReqV2.STRUCT_SIZE];
final ByteBuffer byteBuffer = ByteBuffer.wrap(bytes);
byteBuffer.order(ByteOrder.nativeOrder());
final StructNlMsgHdr nlMsgHdr = new StructNlMsgHdr();
nlMsgHdr.nlmsg_len = bytes.length;
- nlMsgHdr.nlmsg_type = SOCK_DIAG_BY_FAMILY;
+ nlMsgHdr.nlmsg_type = type;
nlMsgHdr.nlmsg_flags = flags;
nlMsgHdr.pack(byteBuffer);
final StructInetDiagReqV2 inetDiagReqV2 =
- new StructInetDiagReqV2(protocol, local, remote, family, pad, idiagExt, state);
+ new StructInetDiagReqV2(protocol, id, family, pad, idiagExt, state);
inetDiagReqV2.pack(byteBuffer);
return bytes;
diff --git a/common/device/com/android/net/module/util/netlink/NetlinkConstants.java b/common/device/com/android/net/module/util/netlink/NetlinkConstants.java
index c44a5b48..44c51d8a 100644
--- a/common/device/com/android/net/module/util/netlink/NetlinkConstants.java
+++ b/common/device/com/android/net/module/util/netlink/NetlinkConstants.java
@@ -141,6 +141,7 @@ public class NetlinkConstants {
/* see include/uapi/linux/sock_diag.h */
public static final short SOCK_DIAG_BY_FAMILY = 20;
+ public static final short SOCK_DESTROY = 21;
// Netlink groups.
public static final int RTMGRP_LINK = 1;
diff --git a/common/device/com/android/net/module/util/netlink/NetlinkUtils.java b/common/device/com/android/net/module/util/netlink/NetlinkUtils.java
index ae16cf8d..d4bf14a4 100644
--- a/common/device/com/android/net/module/util/netlink/NetlinkUtils.java
+++ b/common/device/com/android/net/module/util/netlink/NetlinkUtils.java
@@ -36,6 +36,7 @@ import android.system.StructTimeval;
import android.util.Log;
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import java.io.FileDescriptor;
import java.io.IOException;
@@ -81,6 +82,54 @@ public class NetlinkUtils {
}
/**
+ * Parse netlink error message
+ *
+ * @param bytes byteBuffer to parse netlink error message
+ * @return NetlinkErrorMessage if bytes contains valid NetlinkErrorMessage, else {@code null}
+ */
+ @Nullable
+ private static NetlinkErrorMessage parseNetlinkErrorMessage(ByteBuffer bytes) {
+ final StructNlMsgHdr nlmsghdr = StructNlMsgHdr.parse(bytes);
+ if (nlmsghdr == null || nlmsghdr.nlmsg_type != NetlinkConstants.NLMSG_ERROR) {
+ return null;
+ }
+ return NetlinkErrorMessage.parse(nlmsghdr, bytes);
+ }
+
+ /**
+ * Receive netlink ack message and check error
+ *
+ * @param fd fd to read netlink message
+ */
+ public static void receiveNetlinkAck(final FileDescriptor fd)
+ throws InterruptedIOException, ErrnoException {
+ final ByteBuffer bytes = recvMessage(fd, DEFAULT_RECV_BUFSIZE, IO_TIMEOUT_MS);
+ // recvMessage() guaranteed to not return null if it did not throw.
+ final NetlinkErrorMessage response = parseNetlinkErrorMessage(bytes);
+ if (response != null && response.getNlMsgError() != null) {
+ final int errno = response.getNlMsgError().error;
+ if (errno != 0) {
+ // TODO: consider ignoring EINVAL (-22), which appears to be
+ // normal when probing a neighbor for which the kernel does
+ // not already have / no longer has a link layer address.
+ Log.e(TAG, "receiveNetlinkAck, errmsg=" + response.toString());
+ // Note: convert kernel errnos (negative) into userspace errnos (positive).
+ throw new ErrnoException(response.toString(), Math.abs(errno));
+ }
+ } else {
+ final String errmsg;
+ if (response == null) {
+ bytes.position(0);
+ errmsg = "raw bytes: " + NetlinkConstants.hexify(bytes);
+ } else {
+ errmsg = response.toString();
+ }
+ Log.e(TAG, "receiveNetlinkAck, errmsg=" + errmsg);
+ throw new ErrnoException(errmsg, EPROTO);
+ }
+ }
+
+ /**
* Send one netlink message to kernel via netlink socket.
*
* @param nlProto netlink protocol type.
@@ -93,31 +142,7 @@ public class NetlinkUtils {
try {
connectSocketToNetlink(fd);
sendMessage(fd, msg, 0, msg.length, IO_TIMEOUT_MS);
- final ByteBuffer bytes = recvMessage(fd, DEFAULT_RECV_BUFSIZE, IO_TIMEOUT_MS);
- // recvMessage() guaranteed to not return null if it did not throw.
- final NetlinkMessage response = NetlinkMessage.parse(bytes, nlProto);
- if (response != null && response instanceof NetlinkErrorMessage
- && (((NetlinkErrorMessage) response).getNlMsgError() != null)) {
- final int errno = ((NetlinkErrorMessage) response).getNlMsgError().error;
- if (errno != 0) {
- // TODO: consider ignoring EINVAL (-22), which appears to be
- // normal when probing a neighbor for which the kernel does
- // not already have / no longer has a link layer address.
- Log.e(TAG, errPrefix + ", errmsg=" + response.toString());
- // Note: convert kernel errnos (negative) into userspace errnos (positive).
- throw new ErrnoException(response.toString(), Math.abs(errno));
- }
- } else {
- final String errmsg;
- if (response == null) {
- bytes.position(0);
- errmsg = "raw bytes: " + NetlinkConstants.hexify(bytes);
- } else {
- errmsg = response.toString();
- }
- Log.e(TAG, errPrefix + ", errmsg=" + errmsg);
- throw new ErrnoException(errmsg, EPROTO);
- }
+ receiveNetlinkAck(fd);
} catch (InterruptedIOException e) {
Log.e(TAG, errPrefix, e);
throw new ErrnoException(errPrefix, ETIMEDOUT, e);
diff --git a/common/device/com/android/net/module/util/netlink/StructInetDiagMsg.java b/common/device/com/android/net/module/util/netlink/StructInetDiagMsg.java
index e7fc02f6..cbd895d6 100644
--- a/common/device/com/android/net/module/util/netlink/StructInetDiagMsg.java
+++ b/common/device/com/android/net/module/util/netlink/StructInetDiagMsg.java
@@ -43,14 +43,22 @@ import java.nio.ByteBuffer;
*/
public class StructInetDiagMsg {
public static final int STRUCT_SIZE = 4 + StructInetDiagSockId.STRUCT_SIZE + 20;
- // Offset to the id field from the beginning of inet_diag_msg struct
- private static final int IDIAG_SOCK_ID_OFFSET = 4;
- // Offset to the idiag_uid field from the beginning of inet_diag_msg struct
- private static final int IDIAG_UID_OFFSET =
- IDIAG_SOCK_ID_OFFSET + StructInetDiagSockId.STRUCT_SIZE + 12;
- public int idiag_uid;
+ public short idiag_family;
+ public short idiag_state;
+ public short idiag_timer;
+ public short idiag_retrans;
@NonNull
public StructInetDiagSockId id;
+ public long idiag_expires;
+ public long idiag_rqueue;
+ public long idiag_wqueue;
+ // Use int for uid since other code use int for uid and uid fits to int
+ public int idiag_uid;
+ public long idiag_inode;
+
+ private static short unsignedByte(byte b) {
+ return (short) (b & 0xFF);
+ }
/**
* Parse inet diag netlink message from buffer.
@@ -60,26 +68,36 @@ public class StructInetDiagMsg {
if (byteBuffer.remaining() < STRUCT_SIZE) {
return null;
}
- final int baseOffset = byteBuffer.position();
StructInetDiagMsg struct = new StructInetDiagMsg();
- final byte family = byteBuffer.get();
- byteBuffer.position(baseOffset + IDIAG_SOCK_ID_OFFSET);
- struct.id = StructInetDiagSockId.parse(byteBuffer, family);
+ struct.idiag_family = unsignedByte(byteBuffer.get());
+ struct.idiag_state = unsignedByte(byteBuffer.get());
+ struct.idiag_timer = unsignedByte(byteBuffer.get());
+ struct.idiag_retrans = unsignedByte(byteBuffer.get());
+ struct.id = StructInetDiagSockId.parse(byteBuffer, struct.idiag_family);
if (struct.id == null) {
return null;
}
- struct.idiag_uid = byteBuffer.getInt(baseOffset + IDIAG_UID_OFFSET);
-
- // Move position to the end of the inet_diag_msg
- byteBuffer.position(baseOffset + STRUCT_SIZE);
+ struct.idiag_expires = Integer.toUnsignedLong(byteBuffer.getInt());
+ struct.idiag_rqueue = Integer.toUnsignedLong(byteBuffer.getInt());
+ struct.idiag_wqueue = Integer.toUnsignedLong(byteBuffer.getInt());
+ struct.idiag_uid = byteBuffer.getInt();
+ struct.idiag_inode = Integer.toUnsignedLong(byteBuffer.getInt());
return struct;
}
@Override
public String toString() {
return "StructInetDiagMsg{ "
- + "idiag_uid{" + idiag_uid + "}, "
+ + "idiag_family{" + idiag_family + "}, "
+ + "idiag_state{" + idiag_state + "}, "
+ + "idiag_timer{" + idiag_timer + "}, "
+ + "idiag_retrans{" + idiag_retrans + "}, "
+ "id{" + id + "}, "
+ + "idiag_expires{" + idiag_expires + "}, "
+ + "idiag_rqueue{" + idiag_rqueue + "}, "
+ + "idiag_wqueue{" + idiag_wqueue + "}, "
+ + "idiag_uid{" + idiag_uid + "}, "
+ + "idiag_inode{" + idiag_inode + "}, "
+ "}";
}
}
diff --git a/common/device/com/android/net/module/util/netlink/StructInetDiagReqV2.java b/common/device/com/android/net/module/util/netlink/StructInetDiagReqV2.java
index 6eef8653..3b47008a 100644
--- a/common/device/com/android/net/module/util/netlink/StructInetDiagReqV2.java
+++ b/common/device/com/android/net/module/util/netlink/StructInetDiagReqV2.java
@@ -18,7 +18,6 @@ package com.android.net.module.util.netlink;
import androidx.annotation.Nullable;
-import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
/**
@@ -48,23 +47,11 @@ public class StructInetDiagReqV2 {
private final int mState;
public static final int INET_DIAG_REQ_V2_ALL_STATES = (int) 0xffffffff;
- public StructInetDiagReqV2(int protocol, InetSocketAddress local, InetSocketAddress remote,
- int family) {
- this(protocol, local, remote, family, 0 /* pad */, 0 /* extension */,
- INET_DIAG_REQ_V2_ALL_STATES);
- }
-
- public StructInetDiagReqV2(int protocol, @Nullable InetSocketAddress local,
- @Nullable InetSocketAddress remote, int family, int pad, int extension, int state)
- throws NullPointerException {
+ public StructInetDiagReqV2(int protocol, @Nullable StructInetDiagSockId id, int family, int pad,
+ int extension, int state) {
mSdiagFamily = (byte) family;
mSdiagProtocol = (byte) protocol;
- // Request for all sockets if no specific socket is requested. Specify the local and remote
- // socket address information for target request socket.
- if ((local == null) != (remote == null)) {
- throw new NullPointerException("Local and remote must be both null or both non-null");
- }
- mId = ((local != null && remote != null) ? new StructInetDiagSockId(local, remote) : null);
+ mId = id;
mPad = (byte) pad;
mIdiagExt = (byte) extension;
mState = state;
diff --git a/common/device/com/android/net/module/util/netlink/StructInetDiagSockId.java b/common/device/com/android/net/module/util/netlink/StructInetDiagSockId.java
index 648a0201..dd85934c 100644
--- a/common/device/com/android/net/module/util/netlink/StructInetDiagSockId.java
+++ b/common/device/com/android/net/module/util/netlink/StructInetDiagSockId.java
@@ -29,6 +29,7 @@ import android.util.Log;
import androidx.annotation.Nullable;
import java.net.Inet4Address;
+import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.UnknownHostException;
@@ -80,7 +81,7 @@ public class StructInetDiagSockId {
* Parse inet diag socket id from buffer.
*/
@Nullable
- public static StructInetDiagSockId parse(final ByteBuffer byteBuffer, final byte family) {
+ public static StructInetDiagSockId parse(final ByteBuffer byteBuffer, final short family) {
if (byteBuffer.remaining() < STRUCT_SIZE) {
return null;
}
@@ -89,43 +90,53 @@ public class StructInetDiagSockId {
final int srcPort = Short.toUnsignedInt(byteBuffer.getShort());
final int dstPort = Short.toUnsignedInt(byteBuffer.getShort());
- final byte[] srcAddrByte;
- final byte[] dstAddrByte;
+ final InetAddress srcAddr;
+ final InetAddress dstAddr;
if (family == AF_INET) {
- srcAddrByte = new byte[IPV4_ADDR_LEN];
- dstAddrByte = new byte[IPV4_ADDR_LEN];
+ final byte[] srcAddrByte = new byte[IPV4_ADDR_LEN];
+ final byte[] dstAddrByte = new byte[IPV4_ADDR_LEN];
byteBuffer.get(srcAddrByte);
// Address always uses IPV6_ADDR_LEN in the buffer. So if the address is IPv4, position
// needs to be advanced to the next field.
byteBuffer.position(byteBuffer.position() + (IPV6_ADDR_LEN - IPV4_ADDR_LEN));
byteBuffer.get(dstAddrByte);
byteBuffer.position(byteBuffer.position() + (IPV6_ADDR_LEN - IPV4_ADDR_LEN));
+ try {
+ srcAddr = Inet4Address.getByAddress(srcAddrByte);
+ dstAddr = Inet4Address.getByAddress(dstAddrByte);
+ } catch (UnknownHostException e) {
+ Log.wtf(TAG, "Failed to parse address: " + e);
+ return null;
+ }
} else if (family == AF_INET6) {
- srcAddrByte = new byte[IPV6_ADDR_LEN];
- dstAddrByte = new byte[IPV6_ADDR_LEN];
+ final byte[] srcAddrByte = new byte[IPV6_ADDR_LEN];
+ final byte[] dstAddrByte = new byte[IPV6_ADDR_LEN];
byteBuffer.get(srcAddrByte);
byteBuffer.get(dstAddrByte);
+ try {
+ // Using Inet6Address.getByAddress to be consistent with idiag_family field since
+ // InetAddress.getByAddress returns Inet4Address if the address is v4-mapped v6
+ // address.
+ srcAddr = Inet6Address.getByAddress(
+ null /* host */, srcAddrByte, -1 /* scope_id */);
+ dstAddr = Inet6Address.getByAddress(
+ null /* host */, dstAddrByte, -1 /* scope_id */);
+ } catch (UnknownHostException e) {
+ Log.wtf(TAG, "Failed to parse address: " + e);
+ return null;
+ }
} else {
Log.wtf(TAG, "Invalid address family: " + family);
return null;
}
- final InetSocketAddress srcAddr;
- final InetSocketAddress dstAddr;
- try {
- srcAddr = new InetSocketAddress(InetAddress.getByAddress(srcAddrByte), srcPort);
- dstAddr = new InetSocketAddress(InetAddress.getByAddress(dstAddrByte), dstPort);
- } catch (UnknownHostException e) {
- // Should not happen. UnknownHostException is thrown only if addr byte array is of
- // illegal length.
- Log.wtf(TAG, "Failed to parse address: " + e);
- return null;
- }
+ final InetSocketAddress srcSocketAddr = new InetSocketAddress(srcAddr, srcPort);
+ final InetSocketAddress dstSocketAddr = new InetSocketAddress(dstAddr, dstPort);
byteBuffer.order(ByteOrder.nativeOrder());
final int ifIndex = byteBuffer.getInt();
final long cookie = byteBuffer.getLong();
- return new StructInetDiagSockId(srcAddr, dstAddr, ifIndex, cookie);
+ return new StructInetDiagSockId(srcSocketAddr, dstSocketAddr, ifIndex, cookie);
}
/**
diff --git a/common/device/com/android/net/module/util/wear/NetPacketHelpers.java b/common/device/com/android/net/module/util/wear/NetPacketHelpers.java
new file mode 100644
index 00000000..341c44be
--- /dev/null
+++ b/common/device/com/android/net/module/util/wear/NetPacketHelpers.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2023 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.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.net.module.util.wear;
+
+import com.android.net.module.util.async.ReadableByteBuffer;
+
+/**
+ * Implements utilities for decoding parts of TCP/UDP/IP headers.
+ *
+ * @hide
+ */
+final class NetPacketHelpers {
+ static void encodeNetworkUnsignedInt16(int value, byte[] dst, final int dstPos) {
+ dst[dstPos] = (byte) ((value >> 8) & 0xFF);
+ dst[dstPos + 1] = (byte) (value & 0xFF);
+ }
+
+ static int decodeNetworkUnsignedInt16(byte[] data, final int pos) {
+ return ((data[pos] & 0xFF) << 8) | (data[pos + 1] & 0xFF);
+ }
+
+ static int decodeNetworkUnsignedInt16(ReadableByteBuffer data, final int pos) {
+ return ((data.peek(pos) & 0xFF) << 8) | (data.peek(pos + 1) & 0xFF);
+ }
+
+ private NetPacketHelpers() {}
+}
diff --git a/common/device/com/android/net/module/util/wear/PacketFile.java b/common/device/com/android/net/module/util/wear/PacketFile.java
new file mode 100644
index 00000000..7f5ed785
--- /dev/null
+++ b/common/device/com/android/net/module/util/wear/PacketFile.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2023 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.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.net.module.util.wear;
+
+/**
+ * Defines bidirectional file where all transmissions are made as complete packets.
+ *
+ * Automatically manages all readability and writeability events in EventManager:
+ * - When read buffer has more space - asks EventManager to notify on more data
+ * - When write buffer has more space - asks the user to provide more data
+ * - When underlying file cannot accept more data - registers EventManager callback
+ *
+ * @hide
+ */
+public interface PacketFile {
+ /** @hide */
+ public enum ErrorCode {
+ UNEXPECTED_ERROR,
+ IO_ERROR,
+ INBOUND_PACKET_TOO_LARGE,
+ OUTBOUND_PACKET_TOO_LARGE,
+ }
+
+ /**
+ * Receives notifications when new data or output space is available.
+ *
+ * @hide
+ */
+ public interface Listener {
+ /**
+ * Handles the initial part of the stream, which on some systems provides lower-level
+ * configuration data.
+ *
+ * Returns the number of bytes consumed, or zero if the preamble has been fully read.
+ */
+ int onPreambleData(byte[] data, int pos, int len);
+
+ /** Handles one extracted packet. */
+ void onInboundPacket(byte[] data, int pos, int len);
+
+ /** Notifies on new data being added to the buffer. */
+ void onInboundBuffered(int newByteCount, int totalBufferedSize);
+
+ /** Notifies on data being flushed from output buffer. */
+ void onOutboundPacketSpace();
+
+ /** Notifies on unrecoverable error in the packet processing. */
+ void onPacketFileError(ErrorCode error, String message);
+ }
+
+ /** Requests this file to be closed. */
+ void close();
+
+ /** Permanently disables reading of this file, and clears all buffered data. */
+ void shutdownReading();
+
+ /** Starts or resumes async read operations on this file. */
+ void continueReading();
+
+ /** Returns the number of bytes currently buffered as input. */
+ int getInboundBufferSize();
+
+ /** Returns the number of bytes currently available for buffering for output. */
+ int getOutboundFreeSize();
+
+ /**
+ * Queues the given data for output.
+ * Throws runtime exception if there is not enough space.
+ */
+ boolean enqueueOutboundPacket(byte[] data, int pos, int len);
+}
diff --git a/common/device/com/android/net/module/util/wear/StreamingPacketFile.java b/common/device/com/android/net/module/util/wear/StreamingPacketFile.java
new file mode 100644
index 00000000..52dbee47
--- /dev/null
+++ b/common/device/com/android/net/module/util/wear/StreamingPacketFile.java
@@ -0,0 +1,221 @@
+/*
+ * Copyright (C) 2023 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.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.net.module.util.wear;
+
+import com.android.net.module.util.async.BufferedFile;
+import com.android.net.module.util.async.EventManager;
+import com.android.net.module.util.async.FileHandle;
+import com.android.net.module.util.async.Assertions;
+import com.android.net.module.util.async.ReadableByteBuffer;
+
+import java.io.IOException;
+
+/**
+ * Implements PacketFile based on a streaming file descriptor.
+ *
+ * Packets are delineated using network-order 2-byte length indicators.
+ *
+ * @hide
+ */
+public final class StreamingPacketFile implements PacketFile, BufferedFile.Listener {
+ private static final int HEADER_SIZE = 2;
+
+ private final EventManager mEventManager;
+ private final Listener mListener;
+ private final BufferedFile mFile;
+ private final int mMaxPacketSize;
+ private final ReadableByteBuffer mInboundBuffer;
+ private boolean mIsInPreamble = true;
+
+ private final byte[] mTempPacketReadBuffer;
+ private final byte[] mTempHeaderWriteBuffer;
+
+ public StreamingPacketFile(
+ EventManager eventManager,
+ FileHandle fileHandle,
+ Listener listener,
+ int maxPacketSize,
+ int maxBufferedInboundPackets,
+ int maxBufferedOutboundPackets) throws IOException {
+ if (eventManager == null || fileHandle == null || listener == null) {
+ throw new NullPointerException();
+ }
+
+ mEventManager = eventManager;
+ mListener = listener;
+ mMaxPacketSize = maxPacketSize;
+
+ final int maxTotalLength = HEADER_SIZE + maxPacketSize;
+
+ mFile = BufferedFile.create(eventManager, fileHandle, this,
+ maxTotalLength * maxBufferedInboundPackets,
+ maxTotalLength * maxBufferedOutboundPackets);
+ mInboundBuffer = mFile.getInboundBuffer();
+
+ mTempPacketReadBuffer = new byte[maxTotalLength];
+ mTempHeaderWriteBuffer = new byte[HEADER_SIZE];
+ }
+
+ @Override
+ public void close() {
+ mFile.close();
+ }
+
+ public BufferedFile getUnderlyingFileForTest() {
+ return mFile;
+ }
+
+ @Override
+ public void shutdownReading() {
+ mFile.shutdownReading();
+ }
+
+ @Override
+ public void continueReading() {
+ mFile.continueReading();
+ }
+
+ @Override
+ public int getInboundBufferSize() {
+ return mInboundBuffer.size();
+ }
+
+ @Override
+ public void onBufferedFileClosed() {
+ }
+
+ @Override
+ public void onBufferedFileInboundData(int readByteCount) {
+ if (mFile.isReadingShutdown()) {
+ return;
+ }
+
+ if (readByteCount > 0) {
+ mListener.onInboundBuffered(readByteCount, mInboundBuffer.size());
+ }
+
+ if (extractOnePacket() && !mFile.isReadingShutdown()) {
+ // There could be more packets already buffered, continue parsing next
+ // packet even before another read event comes
+ mEventManager.execute(() -> {
+ onBufferedFileInboundData(0);
+ });
+ } else {
+ continueReading();
+ }
+ }
+
+ private boolean extractOnePacket() {
+ while (mIsInPreamble) {
+ final int directReadSize = Math.min(
+ mInboundBuffer.getDirectReadSize(), mTempPacketReadBuffer.length);
+ if (directReadSize == 0) {
+ return false;
+ }
+
+ // Copy for safety, so higher-level callback cannot modify the data.
+ System.arraycopy(mInboundBuffer.getDirectReadBuffer(),
+ mInboundBuffer.getDirectReadPos(), mTempPacketReadBuffer, 0, directReadSize);
+
+ final int preambleConsumedBytes = mListener.onPreambleData(
+ mTempPacketReadBuffer, 0, directReadSize);
+ if (mFile.isReadingShutdown()) {
+ return false; // The callback has called shutdownReading().
+ }
+
+ if (preambleConsumedBytes == 0) {
+ mIsInPreamble = false;
+ break;
+ }
+
+ mInboundBuffer.accountForDirectRead(preambleConsumedBytes);
+ }
+
+ final int bufferedSize = mInboundBuffer.size();
+ if (bufferedSize < HEADER_SIZE) {
+ return false;
+ }
+
+ final int dataLength = NetPacketHelpers.decodeNetworkUnsignedInt16(mInboundBuffer, 0);
+ if (dataLength > mMaxPacketSize) {
+ mListener.onPacketFileError(
+ PacketFile.ErrorCode.INBOUND_PACKET_TOO_LARGE,
+ "Inbound packet length: " + dataLength);
+ return false;
+ }
+
+ final int totalLength = HEADER_SIZE + dataLength;
+ if (bufferedSize < totalLength) {
+ return false;
+ }
+
+ mInboundBuffer.readBytes(mTempPacketReadBuffer, 0, totalLength);
+
+ mListener.onInboundPacket(mTempPacketReadBuffer, HEADER_SIZE, dataLength);
+ return true;
+ }
+
+ @Override
+ public int getOutboundFreeSize() {
+ final int freeSize = mFile.getOutboundBufferFreeSize();
+ return (freeSize > HEADER_SIZE ? freeSize - HEADER_SIZE : 0);
+ }
+
+ @Override
+ public boolean enqueueOutboundPacket(byte[] buffer, int pos, int len) {
+ Assertions.throwsIfOutOfBounds(buffer, pos, len);
+
+ if (len == 0) {
+ return true;
+ }
+
+ if (len > mMaxPacketSize) {
+ mListener.onPacketFileError(
+ PacketFile.ErrorCode.OUTBOUND_PACKET_TOO_LARGE,
+ "Outbound packet length: " + len);
+ return false;
+ }
+
+ NetPacketHelpers.encodeNetworkUnsignedInt16(len, mTempHeaderWriteBuffer, 0);
+
+ mFile.enqueueOutboundData(
+ mTempHeaderWriteBuffer, 0, mTempHeaderWriteBuffer.length,
+ buffer, pos, len);
+ return true;
+ }
+
+ @Override
+ public void onBufferedFileOutboundSpace() {
+ mListener.onOutboundPacketSpace();
+ }
+
+ @Override
+ public void onBufferedFileIoError(String message) {
+ mListener.onPacketFileError(PacketFile.ErrorCode.IO_ERROR, message);
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("maxPacket=");
+ sb.append(mMaxPacketSize);
+ sb.append(", file={");
+ sb.append(mFile);
+ sb.append("}");
+ return sb.toString();
+ }
+}
diff --git a/common/framework/com/android/net/module/util/InetAddressUtils.java b/common/framework/com/android/net/module/util/InetAddressUtils.java
index 31d07297..40fc59fb 100644
--- a/common/framework/com/android/net/module/util/InetAddressUtils.java
+++ b/common/framework/com/android/net/module/util/InetAddressUtils.java
@@ -16,7 +16,10 @@
package com.android.net.module.util;
+import android.annotation.NonNull;
import android.os.Parcel;
+import android.util.Log;
+
import java.net.Inet6Address;
import java.net.InetAddress;
@@ -28,6 +31,7 @@ import java.net.UnknownHostException;
*/
public class InetAddressUtils {
+ private static final String TAG = InetAddressUtils.class.getSimpleName();
private static final int INET6_ADDR_LENGTH = 16;
/**
@@ -71,5 +75,23 @@ public class InetAddressUtils {
}
}
+ /**
+ * Create a Inet6Address with scope id if it is a link local address. Otherwise, returns the
+ * original address.
+ */
+ public static Inet6Address withScopeId(@NonNull final Inet6Address addr, int scopeid) {
+ if (!addr.isLinkLocalAddress()) {
+ return addr;
+ }
+ try {
+ return Inet6Address.getByAddress(null /* host */, addr.getAddress(),
+ scopeid);
+ } catch (UnknownHostException impossible) {
+ Log.wtf(TAG, "Cannot construct scoped Inet6Address with Inet6Address.getAddress("
+ + addr.getHostAddress() + "): ", impossible);
+ return null;
+ }
+ }
+
private InetAddressUtils() {}
}
diff --git a/common/framework/com/android/net/module/util/IpUtils.java b/common/framework/com/android/net/module/util/IpUtils.java
index 569733ed..18d96f30 100644
--- a/common/framework/com/android/net/module/util/IpUtils.java
+++ b/common/framework/com/android/net/module/util/IpUtils.java
@@ -16,6 +16,8 @@
package com.android.net.module.util;
+import com.android.internal.annotations.VisibleForTesting;
+
import static android.system.OsConstants.IPPROTO_ICMPV6;
import static android.system.OsConstants.IPPROTO_TCP;
import static android.system.OsConstants.IPPROTO_UDP;
@@ -42,8 +44,9 @@ public class IpUtils {
* payload) or ICMP checksum on the specified portion of a ByteBuffer. The seed
* allows the checksum to commence with a specified value.
*/
- private static int checksum(ByteBuffer buf, int seed, int start, int end) {
- int sum = seed;
+ @VisibleForTesting
+ public static int checksum(ByteBuffer buf, int seed, int start, int end) {
+ int sum = seed + 0xFFFF; // to make things work with empty / zero-filled buffer
final int bufPosition = buf.position();
// set position of original ByteBuffer, so that the ShortBuffer
@@ -69,13 +72,12 @@ public class IpUtils {
b += 256;
}
- sum += b * 256;
+ sum += b * 256; // assumes bytebuffer is network order (ie. big endian)
}
- sum = ((sum >> 16) & 0xFFFF) + (sum & 0xFFFF);
- sum = ((sum + ((sum >> 16) & 0xFFFF)) & 0xFFFF);
- int negated = ~sum;
- return intAbs((short) negated);
+ sum = ((sum >> 16) & 0xFFFF) + (sum & 0xFFFF); // max sum is 0x1FFFE
+ sum = ((sum >> 16) & 0xFFFF) + (sum & 0xFFFF); // max sum is 0xFFFF
+ return sum ^ 0xFFFF; // u16 bitwise negation
}
private static int pseudoChecksumIPv4(
diff --git a/common/framework/com/android/net/module/util/LinkPropertiesUtils.java b/common/framework/com/android/net/module/util/LinkPropertiesUtils.java
index 1565f2bc..e271f649 100644
--- a/common/framework/com/android/net/module/util/LinkPropertiesUtils.java
+++ b/common/framework/com/android/net/module/util/LinkPropertiesUtils.java
@@ -146,6 +146,24 @@ public final class LinkPropertiesUtils {
right != null ? right.getLinkAddresses() : null);
}
+ /**
+ * Compares {@code left} {@code LinkProperties} allLinkAddresses against the {@code right}.
+ *
+ * @param left A LinkProperties or null
+ * @param right A LinkProperties or null
+ * @return {@code true} if both are identical, {@code false} otherwise.
+ * @see LinkProperties#getAllLinkAddresses()
+ */
+ public static boolean isIdenticalAllLinkAddresses(@Nullable LinkProperties left,
+ @Nullable LinkProperties right) {
+ if (left == right) return true;
+ if (left == null || right == null) return false;
+ final List<LinkAddress> leftAddresses = left.getAllLinkAddresses();
+ final List<LinkAddress> rightAddresses = right.getAllLinkAddresses();
+ if (leftAddresses.size() != rightAddresses.size()) return false;
+ return leftAddresses.containsAll(rightAddresses);
+ }
+
/**
* Compares {@code left} {@code LinkProperties} interface addresses against the {@code right}.
*
diff --git a/common/framework/com/android/net/module/util/PermissionUtils.java b/common/framework/com/android/net/module/util/PermissionUtils.java
index be5b0cde..8315b8f6 100644
--- a/common/framework/com/android/net/module/util/PermissionUtils.java
+++ b/common/framework/com/android/net/module/util/PermissionUtils.java
@@ -54,6 +54,20 @@ public final class PermissionUtils {
}
/**
+ * Return true if the context has one of give permission that is allowed
+ * for a particular process and user ID running in the system.
+ */
+ public static boolean checkAnyPermissionOf(@NonNull Context context,
+ int pid, int uid, @NonNull String... permissions) {
+ for (String permission : permissions) {
+ if (context.checkPermission(permission, pid, uid) == PERMISSION_GRANTED) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
* Enforce permission check on the context that should have one of given permission.
*/
public static void enforceAnyPermissionOf(@NonNull Context context,
diff --git a/common/native/bpf_headers/include/bpf/bpf_helpers.h b/common/native/bpf_headers/include/bpf/bpf_helpers.h
index 0300b5ec..4939483a 100644
--- a/common/native/bpf_headers/include/bpf/bpf_helpers.h
+++ b/common/native/bpf_headers/include/bpf/bpf_helpers.h
@@ -305,6 +305,7 @@ unsigned long long load_word(void* skb, unsigned long long off) asm("llvm.bpf.lo
static int (*bpf_probe_read)(void* dst, int size, void* unsafe_ptr) = (void*) BPF_FUNC_probe_read;
static int (*bpf_probe_read_str)(void* dst, int size, void* unsafe_ptr) = (void*) BPF_FUNC_probe_read_str;
+static int (*bpf_probe_read_user)(void* dst, int size, const void* unsafe_ptr) = (void*)BPF_FUNC_probe_read_user;
static int (*bpf_probe_read_user_str)(void* dst, int size, const void* unsafe_ptr) = (void*) BPF_FUNC_probe_read_user_str;
static unsigned long long (*bpf_ktime_get_ns)(void) = (void*) BPF_FUNC_ktime_get_ns;
static unsigned long long (*bpf_ktime_get_boot_ns)(void) = (void*)BPF_FUNC_ktime_get_boot_ns;
diff --git a/common/tests/unit/Android.bp b/common/tests/unit/Android.bp
index 6e223bd4..40371e63 100644
--- a/common/tests/unit/Android.bp
+++ b/common/tests/unit/Android.bp
@@ -21,6 +21,7 @@ android_library {
"net-utils-device-common-async",
"net-utils-device-common-bpf",
"net-utils-device-common-ip",
+ "net-utils-device-common-wear",
],
libs: [
"android.test.runner",
diff --git a/common/tests/unit/src/com/android/net/module/util/InetAddressUtilsTest.java b/common/tests/unit/src/com/android/net/module/util/InetAddressUtilsTest.java
index 2736c53c..bb2b9332 100644
--- a/common/tests/unit/src/com/android/net/module/util/InetAddressUtilsTest.java
+++ b/common/tests/unit/src/com/android/net/module/util/InetAddressUtilsTest.java
@@ -18,6 +18,10 @@ package com.android.net.module.util;
import static junit.framework.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.net.InetAddresses;
import android.os.Parcel;
import androidx.test.filters.SmallTest;
@@ -67,4 +71,25 @@ public class InetAddressUtilsTest {
assertEquals(ipv6, out);
assertEquals(42, out.getScopeId());
}
+
+ @Test
+ public void testWithScopeId() {
+ final int scopeId = 999;
+
+ final String globalAddrStr = "2401:fa00:49c:484:dc41:e6ff:fefd:f180";
+ final Inet6Address globalAddr = (Inet6Address) InetAddresses
+ .parseNumericAddress(globalAddrStr);
+ final Inet6Address updatedGlobalAddr = InetAddressUtils.withScopeId(globalAddr, scopeId);
+ assertFalse(updatedGlobalAddr.isLinkLocalAddress());
+ assertEquals(globalAddrStr, updatedGlobalAddr.getHostAddress());
+ assertEquals(0, updatedGlobalAddr.getScopeId());
+
+ final String localAddrStr = "fe80::4735:9628:d038:2087";
+ final Inet6Address localAddr = (Inet6Address) InetAddresses
+ .parseNumericAddress(localAddrStr);
+ final Inet6Address updatedLocalAddr = InetAddressUtils.withScopeId(localAddr, scopeId);
+ assertTrue(updatedLocalAddr.isLinkLocalAddress());
+ assertEquals(localAddrStr + "%" + scopeId, updatedLocalAddr.getHostAddress());
+ assertEquals(scopeId, updatedLocalAddr.getScopeId());
+ }
}
diff --git a/common/tests/unit/src/com/android/net/module/util/IpUtilsTest.java b/common/tests/unit/src/com/android/net/module/util/IpUtilsTest.java
index 20555b35..d57023ca 100644
--- a/common/tests/unit/src/com/android/net/module/util/IpUtilsTest.java
+++ b/common/tests/unit/src/com/android/net/module/util/IpUtilsTest.java
@@ -74,6 +74,20 @@ public class IpUtilsTest {
// print JavaPacketDefinition(str(packet))
@Test
+ public void testEmptyAndZeroBufferChecksum() throws Exception {
+ ByteBuffer packet = ByteBuffer.wrap(new byte[] { (byte) 0x00, (byte) 0x00, });
+ // the following should *not* return 0xFFFF
+ assertEquals(0, IpUtils.checksum(packet, 0, 0, 0));
+ assertEquals(0, IpUtils.checksum(packet, 0, 0, 1));
+ assertEquals(0, IpUtils.checksum(packet, 0, 0, 2));
+ assertEquals(0, IpUtils.checksum(packet, 0, 1, 2));
+ assertEquals(0, IpUtils.checksum(packet, 0xFFFF, 0, 0));
+ assertEquals(0, IpUtils.checksum(packet, 0xFFFF, 0, 1));
+ assertEquals(0, IpUtils.checksum(packet, 0xFFFF, 0, 2));
+ assertEquals(0, IpUtils.checksum(packet, 0xFFFF, 1, 2));
+ }
+
+ @Test
public void testIpv6TcpChecksum() throws Exception {
// packet = (scapy.IPv6(src="2001:db8::1", dst="2001:db8::2", tc=0x80) /
// scapy.TCP(sport=12345, dport=7,
diff --git a/common/tests/unit/src/com/android/net/module/util/LinkPropertiesUtilsTest.java b/common/tests/unit/src/com/android/net/module/util/LinkPropertiesUtilsTest.java
index 09f04902..80ab6185 100644
--- a/common/tests/unit/src/com/android/net/module/util/LinkPropertiesUtilsTest.java
+++ b/common/tests/unit/src/com/android/net/module/util/LinkPropertiesUtilsTest.java
@@ -94,6 +94,9 @@ public final class LinkPropertiesUtilsTest {
assertTrue(LinkPropertiesUtils.isIdenticalAddresses(source, target));
assertTrue(LinkPropertiesUtils.isIdenticalAddresses(target, source));
+ assertTrue(LinkPropertiesUtils.isIdenticalAllLinkAddresses(source, target));
+ assertTrue(LinkPropertiesUtils.isIdenticalAllLinkAddresses(target, source));
+
assertTrue(LinkPropertiesUtils.isIdenticalDnses(source, target));
assertTrue(LinkPropertiesUtils.isIdenticalDnses(target, source));
@@ -116,12 +119,17 @@ public final class LinkPropertiesUtilsTest {
assertFalse(LinkPropertiesUtils.isIdenticalAddresses(source, target));
assertFalse(LinkPropertiesUtils.isIdenticalAddresses(target, source));
+ assertFalse(LinkPropertiesUtils.isIdenticalAllLinkAddresses(source, target));
+ assertFalse(LinkPropertiesUtils.isIdenticalAllLinkAddresses(target, source));
+
// Currently, target contains V4_LINKADDR, V6_LINKADDR and testLinkAddr.
// Compare addresses.size() equals but contains different address.
target.removeLinkAddress(V4_LINKADDR);
assertEquals(source.getAddresses().size(), target.getAddresses().size());
assertFalse(LinkPropertiesUtils.isIdenticalAddresses(source, target));
assertFalse(LinkPropertiesUtils.isIdenticalAddresses(target, source));
+ assertFalse(LinkPropertiesUtils.isIdenticalAllLinkAddresses(source, target));
+ assertFalse(LinkPropertiesUtils.isIdenticalAllLinkAddresses(target, source));
// Restore link address
target.addLinkAddress(V4_LINKADDR);
target.removeLinkAddress(testLinkAddr);
@@ -169,6 +177,13 @@ public final class LinkPropertiesUtilsTest {
target.setHttpProxy(null);
assertFalse(LinkPropertiesUtils.isIdenticalHttpProxy(source, target));
assertFalse(LinkPropertiesUtils.isIdenticalHttpProxy(target, source));
+
+ final LinkProperties stacked = new LinkProperties();
+ stacked.setInterfaceName("v4-" + target.getInterfaceName());
+ stacked.addLinkAddress(testLinkAddr);
+ target.addStackedLink(stacked);
+ assertFalse(LinkPropertiesUtils.isIdenticalAllLinkAddresses(source, target));
+ assertFalse(LinkPropertiesUtils.isIdenticalAllLinkAddresses(target, source));
}
private <T> void compareResult(List<T> oldItems, List<T> newItems, List<T> expectRemoved,
diff --git a/common/tests/unit/src/com/android/net/module/util/async/BufferedFileTest.java b/common/tests/unit/src/com/android/net/module/util/async/BufferedFileTest.java
new file mode 100644
index 00000000..11a74f2c
--- /dev/null
+++ b/common/tests/unit/src/com/android/net/module/util/async/BufferedFileTest.java
@@ -0,0 +1,376 @@
+/*
+ * Copyright (C) 2023 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.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.net.module.util.async;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.ignoreStubs;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+import android.os.ParcelFileDescriptor;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.testutils.async.ReadableDataAnswer;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class BufferedFileTest {
+ @Mock EventManager mockEventManager;
+ @Mock BufferedFile.Listener mockFileListener;
+ @Mock AsyncFile mockAsyncFile;
+ @Mock ParcelFileDescriptor mockParcelFileDescriptor;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ verifyNoMoreInteractions(ignoreStubs(mockFileListener, mockAsyncFile, mockEventManager));
+ }
+
+ @Test
+ public void onClosed() throws Exception {
+ final int inboundBufferSize = 1024;
+ final int outboundBufferSize = 768;
+
+ final BufferedFile file = createFile(inboundBufferSize, outboundBufferSize);
+
+ file.onClosed(mockAsyncFile);
+
+ verify(mockFileListener).onBufferedFileClosed();
+ }
+
+ @Test
+ public void continueReadingAndClose() throws Exception {
+ final int inboundBufferSize = 1024;
+ final int outboundBufferSize = 768;
+
+ final BufferedFile file = createFile(inboundBufferSize, outboundBufferSize);
+
+ assertEquals(inboundBufferSize, file.getInboundBufferFreeSizeForTest());
+ assertEquals(outboundBufferSize, file.getOutboundBufferFreeSize());
+
+ file.continueReading();
+ verify(mockAsyncFile).enableReadEvents(true);
+
+ file.close();
+ verify(mockAsyncFile).close();
+ }
+
+ @Test
+ public void enqueueOutboundData() throws Exception {
+ final int inboundBufferSize = 10;
+ final int outboundBufferSize = 250;
+
+ final BufferedFile file = createFile(inboundBufferSize, outboundBufferSize);
+
+ final byte[] data1 = new byte[101];
+ final byte[] data2 = new byte[102];
+ data1[0] = (byte) 1;
+ data2[0] = (byte) 2;
+
+ assertEquals(0, file.getOutboundBufferSize());
+
+ final int totalLen = data1.length + data2.length;
+
+ when(mockAsyncFile.write(any(), anyInt(), anyInt())).thenReturn(0);
+ assertTrue(file.enqueueOutboundData(data1, 0, data1.length, null, 0, 0));
+ verify(mockAsyncFile).enableWriteEvents(true);
+
+ assertEquals(data1.length, file.getOutboundBufferSize());
+
+ checkAndResetMocks();
+
+ final ArgumentCaptor<byte[]> arrayCaptor = ArgumentCaptor.forClass(byte[].class);
+ final ArgumentCaptor<Integer> posCaptor = ArgumentCaptor.forClass(Integer.class);
+ final ArgumentCaptor<Integer> lenCaptor = ArgumentCaptor.forClass(Integer.class);
+ when(mockAsyncFile.write(
+ arrayCaptor.capture(), posCaptor.capture(), lenCaptor.capture())).thenReturn(totalLen);
+
+ assertTrue(file.enqueueOutboundData(data2, 0, data2.length, null, 0, 0));
+
+ assertEquals(0, file.getInboundBuffer().size());
+ assertEquals(0, file.getOutboundBufferSize());
+
+ assertEquals(0, posCaptor.getValue().intValue());
+ assertEquals(totalLen, lenCaptor.getValue().intValue());
+ assertEquals(data1[0], arrayCaptor.getValue()[0]);
+ assertEquals(data2[0], arrayCaptor.getValue()[data1.length]);
+ }
+
+ @Test
+ public void enqueueOutboundData_combined() throws Exception {
+ final int inboundBufferSize = 10;
+ final int outboundBufferSize = 250;
+
+ final BufferedFile file = createFile(inboundBufferSize, outboundBufferSize);
+
+ final byte[] data1 = new byte[101];
+ final byte[] data2 = new byte[102];
+ data1[0] = (byte) 1;
+ data2[0] = (byte) 2;
+
+ assertEquals(0, file.getOutboundBufferSize());
+
+ final int totalLen = data1.length + data2.length;
+
+ final ArgumentCaptor<byte[]> arrayCaptor = ArgumentCaptor.forClass(byte[].class);
+ final ArgumentCaptor<Integer> posCaptor = ArgumentCaptor.forClass(Integer.class);
+ final ArgumentCaptor<Integer> lenCaptor = ArgumentCaptor.forClass(Integer.class);
+ when(mockAsyncFile.write(
+ arrayCaptor.capture(), posCaptor.capture(), lenCaptor.capture())).thenReturn(totalLen);
+
+ assertTrue(file.enqueueOutboundData(data1, 0, data1.length, data2, 0, data2.length));
+
+ assertEquals(0, file.getInboundBuffer().size());
+ assertEquals(0, file.getOutboundBufferSize());
+
+ assertEquals(0, posCaptor.getValue().intValue());
+ assertEquals(totalLen, lenCaptor.getValue().intValue());
+ assertEquals(data1[0], arrayCaptor.getValue()[0]);
+ assertEquals(data2[0], arrayCaptor.getValue()[data1.length]);
+ }
+
+ @Test
+ public void enableWriteEvents() throws Exception {
+ final int inboundBufferSize = 10;
+ final int outboundBufferSize = 250;
+
+ final BufferedFile file = createFile(inboundBufferSize, outboundBufferSize);
+
+ final byte[] data1 = new byte[101];
+ final byte[] data2 = new byte[102];
+ final byte[] data3 = new byte[103];
+ data1[0] = (byte) 1;
+ data2[0] = (byte) 2;
+ data3[0] = (byte) 3;
+
+ assertEquals(0, file.getOutboundBufferSize());
+
+ // Write first 2 buffers, but fail to flush them, causing async write request.
+ final int data1And2Len = data1.length + data2.length;
+ when(mockAsyncFile.write(any(), eq(0), eq(data1And2Len))).thenReturn(0);
+ assertTrue(file.enqueueOutboundData(data1, 0, data1.length, data2, 0, data2.length));
+ assertEquals(0, file.getInboundBuffer().size());
+ assertEquals(data1And2Len, file.getOutboundBufferSize());
+ verify(mockAsyncFile).enableWriteEvents(true);
+
+ // Try to write 3rd buffers, which won't fit, then fail to flush.
+ when(mockAsyncFile.write(any(), eq(0), eq(data1And2Len))).thenReturn(0);
+ assertFalse(file.enqueueOutboundData(data3, 0, data3.length, null, 0, 0));
+ assertEquals(0, file.getInboundBuffer().size());
+ assertEquals(data1And2Len, file.getOutboundBufferSize());
+ verify(mockAsyncFile, times(2)).enableWriteEvents(true);
+
+ checkAndResetMocks();
+
+ // Simulate writeability event, and successfully flush.
+ final ArgumentCaptor<byte[]> arrayCaptor = ArgumentCaptor.forClass(byte[].class);
+ final ArgumentCaptor<Integer> posCaptor = ArgumentCaptor.forClass(Integer.class);
+ final ArgumentCaptor<Integer> lenCaptor = ArgumentCaptor.forClass(Integer.class);
+ when(mockAsyncFile.write(arrayCaptor.capture(),
+ posCaptor.capture(), lenCaptor.capture())).thenReturn(data1And2Len);
+ file.onWriteReady(mockAsyncFile);
+ verify(mockAsyncFile).enableWriteEvents(false);
+ verify(mockFileListener).onBufferedFileOutboundSpace();
+ assertEquals(0, file.getOutboundBufferSize());
+
+ assertEquals(0, posCaptor.getValue().intValue());
+ assertEquals(data1And2Len, lenCaptor.getValue().intValue());
+ assertEquals(data1[0], arrayCaptor.getValue()[0]);
+ assertEquals(data2[0], arrayCaptor.getValue()[data1.length]);
+
+ checkAndResetMocks();
+
+ // Now write, but fail to flush the third buffer.
+ when(mockAsyncFile.write(arrayCaptor.capture(),
+ posCaptor.capture(), lenCaptor.capture())).thenReturn(0);
+ assertTrue(file.enqueueOutboundData(data3, 0, data3.length, null, 0, 0));
+ verify(mockAsyncFile).enableWriteEvents(true);
+ assertEquals(data3.length, file.getOutboundBufferSize());
+
+ assertEquals(data1And2Len, posCaptor.getValue().intValue());
+ assertEquals(outboundBufferSize - data1And2Len, lenCaptor.getValue().intValue());
+ assertEquals(data3[0], arrayCaptor.getValue()[data1And2Len]);
+ }
+
+ @Test
+ public void read() throws Exception {
+ final int inboundBufferSize = 250;
+ final int outboundBufferSize = 10;
+
+ final BufferedFile file = createFile(inboundBufferSize, outboundBufferSize);
+
+ final byte[] data1 = new byte[101];
+ final byte[] data2 = new byte[102];
+ data1[0] = (byte) 1;
+ data2[0] = (byte) 2;
+
+ final ReadableDataAnswer dataAnswer = new ReadableDataAnswer(data1, data2);
+ final ReadableByteBuffer inboundBuffer = file.getInboundBuffer();
+
+ when(mockAsyncFile.read(any(), anyInt(), anyInt())).thenAnswer(dataAnswer);
+ file.onReadReady(mockAsyncFile);
+ verify(mockAsyncFile).enableReadEvents(true);
+ verify(mockFileListener).onBufferedFileInboundData(eq(data1.length + data2.length));
+
+ assertEquals(0, file.getOutboundBufferSize());
+ assertEquals(data1.length + data2.length, inboundBuffer.size());
+ assertEquals((byte) 1, inboundBuffer.peek(0));
+ assertEquals((byte) 2, inboundBuffer.peek(data1.length));
+ }
+
+ @Test
+ public void enableReadEvents() throws Exception {
+ final int inboundBufferSize = 250;
+ final int outboundBufferSize = 10;
+
+ final BufferedFile file = createFile(inboundBufferSize, outboundBufferSize);
+
+ final byte[] data1 = new byte[101];
+ final byte[] data2 = new byte[102];
+ final byte[] data3 = new byte[103];
+ data1[0] = (byte) 1;
+ data2[0] = (byte) 2;
+ data3[0] = (byte) 3;
+
+ final ReadableDataAnswer dataAnswer = new ReadableDataAnswer(data1, data2, data3);
+ final ReadableByteBuffer inboundBuffer = file.getInboundBuffer();
+
+ when(mockAsyncFile.read(any(), anyInt(), anyInt())).thenAnswer(dataAnswer);
+ file.onReadReady(mockAsyncFile);
+ verify(mockAsyncFile).enableReadEvents(false);
+ verify(mockFileListener).onBufferedFileInboundData(eq(inboundBufferSize));
+
+ assertEquals(0, file.getOutboundBufferSize());
+ assertEquals(inboundBufferSize, inboundBuffer.size());
+ assertEquals((byte) 1, inboundBuffer.peek(0));
+ assertEquals((byte) 2, inboundBuffer.peek(data1.length));
+ assertEquals((byte) 3, inboundBuffer.peek(data1.length + data2.length));
+
+ checkAndResetMocks();
+
+ // Cannot enable read events since the buffer is full.
+ file.continueReading();
+
+ checkAndResetMocks();
+
+ final byte[] tmp = new byte[inboundBufferSize];
+ inboundBuffer.readBytes(tmp, 0, data1.length);
+ assertEquals(inboundBufferSize - data1.length, inboundBuffer.size());
+
+ file.continueReading();
+
+ inboundBuffer.readBytes(tmp, 0, data2.length);
+ assertEquals(inboundBufferSize - data1.length - data2.length, inboundBuffer.size());
+
+ when(mockAsyncFile.read(any(), anyInt(), anyInt())).thenAnswer(dataAnswer);
+ file.onReadReady(mockAsyncFile);
+ verify(mockAsyncFile, times(2)).enableReadEvents(true);
+ verify(mockFileListener).onBufferedFileInboundData(
+ eq(data1.length + data2.length + data3.length - inboundBufferSize));
+
+ assertEquals(data3.length, inboundBuffer.size());
+ assertEquals((byte) 3, inboundBuffer.peek(0));
+ }
+
+ @Test
+ public void shutdownReading() throws Exception {
+ final int inboundBufferSize = 250;
+ final int outboundBufferSize = 10;
+
+ final BufferedFile file = createFile(inboundBufferSize, outboundBufferSize);
+
+ final byte[] data = new byte[100];
+ final ReadableDataAnswer dataAnswer = new ReadableDataAnswer(data);
+ when(mockAsyncFile.read(any(), anyInt(), anyInt())).thenAnswer(dataAnswer);
+
+ file.shutdownReading();
+ file.onReadReady(mockAsyncFile);
+
+ verify(mockAsyncFile).enableReadEvents(false);
+
+ assertEquals(0, file.getInboundBuffer().size());
+ assertEquals(data.length, dataAnswer.getRemainingSize());
+ }
+
+ @Test
+ public void shutdownReading_inCallback() throws Exception {
+ final int inboundBufferSize = 250;
+ final int outboundBufferSize = 10;
+
+ final BufferedFile file = createFile(inboundBufferSize, outboundBufferSize);
+
+ final byte[] data = new byte[100];
+ final ReadableDataAnswer dataAnswer = new ReadableDataAnswer(data);
+ when(mockAsyncFile.read(any(), anyInt(), anyInt())).thenAnswer(dataAnswer);
+
+ doAnswer(new Answer() {
+ @Override public Object answer(InvocationOnMock invocation) {
+ file.shutdownReading();
+ return null;
+ }}).when(mockFileListener).onBufferedFileInboundData(anyInt());
+
+ file.onReadReady(mockAsyncFile);
+
+ verify(mockAsyncFile).enableReadEvents(false);
+
+ assertEquals(0, file.getInboundBuffer().size());
+ assertEquals(0, dataAnswer.getRemainingSize());
+ }
+
+ private void checkAndResetMocks() {
+ verifyNoMoreInteractions(ignoreStubs(mockFileListener, mockAsyncFile, mockEventManager,
+ mockParcelFileDescriptor));
+ reset(mockFileListener, mockAsyncFile, mockEventManager);
+ }
+
+ private BufferedFile createFile(
+ int inboundBufferSize, int outboundBufferSize) throws Exception {
+ when(mockEventManager.registerFile(any(), any())).thenReturn(mockAsyncFile);
+ return BufferedFile.create(
+ mockEventManager,
+ FileHandle.fromFileDescriptor(mockParcelFileDescriptor),
+ mockFileListener,
+ inboundBufferSize,
+ outboundBufferSize);
+ }
+}
diff --git a/common/tests/unit/src/com/android/net/module/util/netlink/InetDiagSocketTest.java b/common/tests/unit/src/com/android/net/module/util/netlink/InetDiagSocketTest.java
index d81422fb..30796d24 100644
--- a/common/tests/unit/src/com/android/net/module/util/netlink/InetDiagSocketTest.java
+++ b/common/tests/unit/src/com/android/net/module/util/netlink/InetDiagSocketTest.java
@@ -22,6 +22,8 @@ import static android.system.OsConstants.IPPROTO_TCP;
import static android.system.OsConstants.IPPROTO_UDP;
import static android.system.OsConstants.NETLINK_INET_DIAG;
+import static com.android.net.module.util.netlink.NetlinkConstants.SOCK_DESTROY;
+import static com.android.net.module.util.netlink.StructNlMsgHdr.NLM_F_ACK;
import static com.android.net.module.util.netlink.StructNlMsgHdr.NLM_F_DUMP;
import static com.android.net.module.util.netlink.StructNlMsgHdr.NLM_F_REQUEST;
@@ -41,6 +43,7 @@ import libcore.util.HexEncoding;
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
@@ -49,6 +52,21 @@ import java.nio.ByteOrder;
@RunWith(AndroidJUnit4.class)
@SmallTest
public class InetDiagSocketTest {
+ // ::FFFF:192.0.2.1
+ private static final byte[] SRC_V4_MAPPED_V6_ADDRESS_BYTES = {
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0xff, (byte) 0xff,
+ (byte) 0xc0, (byte) 0x00, (byte) 0x02, (byte) 0x01,
+ };
+ // ::FFFF:192.0.2.2
+ private static final byte[] DST_V4_MAPPED_V6_ADDRESS_BYTES = {
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0xff, (byte) 0xff,
+ (byte) 0xc0, (byte) 0x00, (byte) 0x02, (byte) 0x02,
+ };
+
// Hexadecimal representation of InetDiagReqV2 request.
private static final String INET_DIAG_REQ_V2_UDP_INET4_HEX =
// struct nlmsghdr
@@ -205,14 +223,14 @@ public class InetDiagSocketTest {
msg = InetDiagMessage.inetDiagReqV2(IPPROTO_TCP, null, remote, AF_INET6,
NLM_F_REQUEST);
fail("Both remote and local should be null, expected UnknownHostException");
- } catch (NullPointerException e) {
+ } catch (IllegalArgumentException e) {
}
try {
msg = InetDiagMessage.inetDiagReqV2(IPPROTO_TCP, local, null, AF_INET6,
NLM_F_REQUEST, 0 /* pad */, 0 /* idiagExt */, TCP_ALL_STATES);
fail("Both remote and local should be null, expected UnknownHostException");
- } catch (NullPointerException e) {
+ } catch (IllegalArgumentException e) {
}
msg = InetDiagMessage.inetDiagReqV2(IPPROTO_TCP, null, null, AF_INET6,
@@ -221,6 +239,85 @@ public class InetDiagSocketTest {
assertArrayEquals(INET_DIAG_REQ_V2_TCP_INET6_NO_ID_SPECIFIED_BYTES, msgExt);
}
+ // Hexadecimal representation of InetDiagReqV2 request with v4-mapped v6 address
+ private static final String INET_DIAG_REQ_V2_TCP_INET6_V4_MAPPED_HEX =
+ // struct nlmsghdr
+ "48000000" + // length = 72
+ "1400" + // type = SOCK_DIAG_BY_FAMILY
+ "0100" + // flags = NLM_F_REQUEST
+ "00000000" + // seqno
+ "00000000" + // pid (0 == kernel)
+ // struct inet_diag_req_v2
+ "0a" + // family = AF_INET6
+ "06" + // protcol = IPPROTO_TCP
+ "00" + // idiag_ext
+ "00" + // pad
+ "ffffffff" + // idiag_states
+ // inet_diag_sockid
+ "a817" + // idiag_sport = 43031
+ "960f" + // idiag_dport = 38415
+ "00000000000000000000ffffc0000201" + // idiag_src = ::FFFF:192.0.2.1
+ "00000000000000000000ffffc0000202" + // idiag_dst = ::FFFF:192.0.2.2
+ "00000000" + // idiag_if
+ "ffffffffffffffff"; // idiag_cookie = INET_DIAG_NOCOOKIE
+
+ private static final byte[] INET_DIAG_REQ_V2_TCP_INET6_V4_MAPPED_BYTES =
+ HexEncoding.decode(INET_DIAG_REQ_V2_TCP_INET6_V4_MAPPED_HEX.toCharArray(), false);
+
+ @Test
+ public void testInetDiagReqV2TcpInet6V4Mapped() throws Exception {
+ final Inet6Address srcAddr = Inet6Address.getByAddress(
+ null /* host */, SRC_V4_MAPPED_V6_ADDRESS_BYTES, -1 /* scope_id */);
+ final Inet6Address dstAddr = Inet6Address.getByAddress(
+ null /* host */, DST_V4_MAPPED_V6_ADDRESS_BYTES, -1 /* scope_id */);
+ final byte[] msg = InetDiagMessage.inetDiagReqV2(
+ IPPROTO_TCP,
+ new InetSocketAddress(srcAddr, 43031),
+ new InetSocketAddress(dstAddr, 38415),
+ AF_INET6,
+ NLM_F_REQUEST);
+ assertArrayEquals(INET_DIAG_REQ_V2_TCP_INET6_V4_MAPPED_BYTES, msg);
+ }
+
+ // Hexadecimal representation of InetDiagReqV2 request with SOCK_DESTROY
+ private static final String INET_DIAG_REQ_V2_TCP_INET6_DESTROY_HEX =
+ // struct nlmsghdr
+ "48000000" + // length = 72
+ "1500" + // type = SOCK_DESTROY
+ "0500" + // flags = NLM_F_REQUEST | NLM_F_ACK
+ "00000000" + // seqno
+ "00000000" + // pid (0 == kernel)
+ // struct inet_diag_req_v2
+ "0a" + // family = AF_INET6
+ "06" + // protcol = IPPROTO_TCP
+ "00" + // idiag_ext
+ "00" + // pad
+ "ffffffff" + // idiag_states = TCP_ALL_STATES
+ // inet_diag_sockid
+ "a817" + // idiag_sport = 43031
+ "960f" + // idiag_dport = 38415
+ "20010db8000000000000000000000001" + // idiag_src = 2001:db8::1
+ "20010db8000000000000000000000002" + // idiag_dst = 2001:db8::2
+ "07000000" + // idiag_if = 7
+ "5800000000000000"; // idiag_cookie = 88
+
+ private static final byte[] INET_DIAG_REQ_V2_TCP_INET6_DESTROY_BYTES =
+ HexEncoding.decode(INET_DIAG_REQ_V2_TCP_INET6_DESTROY_HEX.toCharArray(), false);
+
+ @Test
+ public void testInetDiagReqV2TcpInet6Destroy() throws Exception {
+ final StructInetDiagSockId sockId = new StructInetDiagSockId(
+ new InetSocketAddress(InetAddresses.parseNumericAddress("2001:db8::1"), 43031),
+ new InetSocketAddress(InetAddresses.parseNumericAddress("2001:db8::2"), 38415),
+ 7 /* ifIndex */,
+ 88 /* cookie */);
+ final byte[] msg = InetDiagMessage.inetDiagReqV2(IPPROTO_TCP, sockId, AF_INET6,
+ SOCK_DESTROY, (short) (NLM_F_REQUEST | NLM_F_ACK), 0 /* pad */, 0 /* idiagExt */,
+ TCP_ALL_STATES);
+
+ assertArrayEquals(INET_DIAG_REQ_V2_TCP_INET6_DESTROY_BYTES, msg);
+ }
+
private void assertNlMsgHdr(StructNlMsgHdr hdr, short type, short flags, int seq, int pid) {
assertNotNull(hdr);
assertEquals(type, hdr.nlmsg_type);
@@ -248,9 +345,9 @@ public class InetDiagSocketTest {
"f5220000" + // pid
// struct inet_diag_msg
"0a" + // family = AF_INET6
- "01" + // idiag_state
- "00" + // idiag_timer
- "00" + // idiag_retrans
+ "01" + // idiag_state = 1
+ "02" + // idiag_timer = 2
+ "ff" + // idiag_retrans = 255
// inet_diag_sockid
"a817" + // idiag_sport = 43031
"960f" + // idiag_dport = 38415
@@ -258,11 +355,11 @@ public class InetDiagSocketTest {
"20010db8000000000000000000000002" + // idiag_dst = 2001:db8::2
"07000000" + // idiag_if = 7
"5800000000000000" + // idiag_cookie = 88
- "00000000" + // idiag_expires
- "00000000" + // idiag_rqueue
- "00000000" + // idiag_wqueue
+ "04000000" + // idiag_expires = 4
+ "05000000" + // idiag_rqueue = 5
+ "06000000" + // idiag_wqueue = 6
"a3270000" + // idiag_uid = 10147
- "A57E1900"; // idiag_inode
+ "a57e19f0"; // idiag_inode = 4028202661
private void assertInetDiagMsg1(final NetlinkMessage msg) {
assertNotNull(msg);
@@ -276,12 +373,20 @@ public class InetDiagSocketTest {
0 /* seq */,
8949 /* pid */);
- assertEquals(10147, inetDiagMsg.inetDiagMsg.idiag_uid);
+ assertEquals(AF_INET6, inetDiagMsg.inetDiagMsg.idiag_family);
+ assertEquals(1, inetDiagMsg.inetDiagMsg.idiag_state);
+ assertEquals(2, inetDiagMsg.inetDiagMsg.idiag_timer);
+ assertEquals(255, inetDiagMsg.inetDiagMsg.idiag_retrans);
assertInetDiagSockId(inetDiagMsg.inetDiagMsg.id,
new InetSocketAddress(InetAddresses.parseNumericAddress("2001:db8::1"), 43031),
new InetSocketAddress(InetAddresses.parseNumericAddress("2001:db8::2"), 38415),
7 /* ifIndex */,
88 /* cookie */);
+ assertEquals(4, inetDiagMsg.inetDiagMsg.idiag_expires);
+ assertEquals(5, inetDiagMsg.inetDiagMsg.idiag_rqueue);
+ assertEquals(6, inetDiagMsg.inetDiagMsg.idiag_wqueue);
+ assertEquals(10147, inetDiagMsg.inetDiagMsg.idiag_uid);
+ assertEquals(4028202661L, inetDiagMsg.inetDiagMsg.idiag_inode);
}
// Hexadecimal representation of InetDiagMessage
@@ -294,9 +399,9 @@ public class InetDiagSocketTest {
"f5220000" + // pid
// struct inet_diag_msg
"0a" + // family = AF_INET6
- "01" + // idiag_state
- "00" + // idiag_timer
- "00" + // idiag_retrans
+ "02" + // idiag_state = 2
+ "10" + // idiag_timer = 16
+ "20" + // idiag_retrans = 32
// inet_diag_sockid
"a845" + // idiag_sport = 43077
"01bb" + // idiag_dport = 443
@@ -304,11 +409,11 @@ public class InetDiagSocketTest {
"20010db8000000000000000000000004" + // idiag_dst = 2001:db8::4
"08000000" + // idiag_if = 8
"6300000000000000" + // idiag_cookie = 99
- "00000000" + // idiag_expires
- "00000000" + // idiag_rqueue
- "00000000" + // idiag_wqueue
+ "30000000" + // idiag_expires = 48
+ "40000000" + // idiag_rqueue = 64
+ "50000000" + // idiag_wqueue = 80
"39300000" + // idiag_uid = 12345
- "A57E1900"; // idiag_inode
+ "851a0000"; // idiag_inode = 6789
private void assertInetDiagMsg2(final NetlinkMessage msg) {
assertNotNull(msg);
@@ -322,12 +427,20 @@ public class InetDiagSocketTest {
0 /* seq */,
8949 /* pid */);
- assertEquals(12345, inetDiagMsg.inetDiagMsg.idiag_uid);
+ assertEquals(AF_INET6, inetDiagMsg.inetDiagMsg.idiag_family);
+ assertEquals(2, inetDiagMsg.inetDiagMsg.idiag_state);
+ assertEquals(16, inetDiagMsg.inetDiagMsg.idiag_timer);
+ assertEquals(32, inetDiagMsg.inetDiagMsg.idiag_retrans);
assertInetDiagSockId(inetDiagMsg.inetDiagMsg.id,
new InetSocketAddress(InetAddresses.parseNumericAddress("2001:db8::3"), 43077),
new InetSocketAddress(InetAddresses.parseNumericAddress("2001:db8::4"), 443),
8 /* ifIndex */,
99 /* cookie */);
+ assertEquals(48, inetDiagMsg.inetDiagMsg.idiag_expires);
+ assertEquals(64, inetDiagMsg.inetDiagMsg.idiag_rqueue);
+ assertEquals(80, inetDiagMsg.inetDiagMsg.idiag_wqueue);
+ assertEquals(12345, inetDiagMsg.inetDiagMsg.idiag_uid);
+ assertEquals(6789, inetDiagMsg.inetDiagMsg.idiag_inode);
}
private static final byte[] INET_DIAG_MSG_BYTES =
@@ -336,7 +449,7 @@ public class InetDiagSocketTest {
@Test
public void testParseInetDiagResponse() throws Exception {
final ByteBuffer byteBuffer = ByteBuffer.wrap(INET_DIAG_MSG_BYTES);
- byteBuffer.order(ByteOrder.LITTLE_ENDIAN);
+ byteBuffer.order(ByteOrder.nativeOrder());
assertInetDiagMsg1(NetlinkMessage.parse(byteBuffer, NETLINK_INET_DIAG));
}
@@ -347,8 +460,87 @@ public class InetDiagSocketTest {
@Test
public void testParseInetDiagResponseMultiple() {
final ByteBuffer byteBuffer = ByteBuffer.wrap(INET_DIAG_MSG_BYTES_MULTIPLE);
- byteBuffer.order(ByteOrder.LITTLE_ENDIAN);
+ byteBuffer.order(ByteOrder.nativeOrder());
assertInetDiagMsg1(NetlinkMessage.parse(byteBuffer, NETLINK_INET_DIAG));
assertInetDiagMsg2(NetlinkMessage.parse(byteBuffer, NETLINK_INET_DIAG));
}
+
+ private static final String INET_DIAG_SOCK_ID_V4_MAPPED_V6_HEX =
+ "a845" + // idiag_sport = 43077
+ "01bb" + // idiag_dport = 443
+ "00000000000000000000ffffc0000201" + // idiag_src = ::FFFF:192.0.2.1
+ "00000000000000000000ffffc0000202" + // idiag_dst = ::FFFF:192.0.2.2
+ "08000000" + // idiag_if = 8
+ "6300000000000000"; // idiag_cookie = 99
+
+ private static final byte[] INET_DIAG_SOCK_ID_V4_MAPPED_V6_BYTES =
+ HexEncoding.decode(INET_DIAG_SOCK_ID_V4_MAPPED_V6_HEX.toCharArray(), false);
+
+ @Test
+ public void testParseAndPackInetDiagSockIdV4MappedV6() {
+ final ByteBuffer parseByteBuffer = ByteBuffer.wrap(INET_DIAG_SOCK_ID_V4_MAPPED_V6_BYTES);
+ parseByteBuffer.order(ByteOrder.nativeOrder());
+ final StructInetDiagSockId diagSockId =
+ StructInetDiagSockId.parse(parseByteBuffer, (short) AF_INET6);
+ assertNotNull(diagSockId);
+
+ final ByteBuffer packByteBuffer =
+ ByteBuffer.allocate(INET_DIAG_SOCK_ID_V4_MAPPED_V6_BYTES.length);
+ diagSockId.pack(packByteBuffer);
+
+ // Move position to the head since ByteBuffer#equals compares the values from the current
+ // position.
+ parseByteBuffer.position(0);
+ packByteBuffer.position(0);
+ assertEquals(parseByteBuffer, packByteBuffer);
+ }
+
+ // Hexadecimal representation of InetDiagMessage with v4-mapped v6 address
+ private static final String INET_DIAG_MSG_V4_MAPPED_V6_HEX =
+ // struct nlmsghdr
+ "58000000" + // length = 88
+ "1400" + // type = SOCK_DIAG_BY_FAMILY
+ "0200" + // flags = NLM_F_MULTI
+ "00000000" + // seqno
+ "f5220000" + // pid
+ // struct inet_diag_msg
+ "0a" + // family = AF_INET6
+ "01" + // idiag_state = 1
+ "02" + // idiag_timer = 2
+ "03" + // idiag_retrans = 3
+ // inet_diag_sockid
+ "a817" + // idiag_sport = 43031
+ "960f" + // idiag_dport = 38415
+ "00000000000000000000ffffc0000201" + // idiag_src = ::FFFF:192.0.2.1
+ "00000000000000000000ffffc0000202" + // idiag_dst = ::FFFF:192.0.2.2
+ "07000000" + // idiag_if = 7
+ "5800000000000000" + // idiag_cookie = 88
+ "04000000" + // idiag_expires = 4
+ "05000000" + // idiag_rqueue = 5
+ "06000000" + // idiag_wqueue = 6
+ "a3270000" + // idiag_uid = 10147
+ "A57E1900"; // idiag_inode = 1670821
+
+ private static final byte[] INET_DIAG_MSG_V4_MAPPED_V6_BYTES =
+ HexEncoding.decode(INET_DIAG_MSG_V4_MAPPED_V6_HEX.toCharArray(), false);
+
+ @Test
+ public void testParseInetDiagResponseV4MappedV6() throws Exception {
+ final ByteBuffer byteBuffer = ByteBuffer.wrap(INET_DIAG_MSG_V4_MAPPED_V6_BYTES);
+ byteBuffer.order(ByteOrder.nativeOrder());
+ final NetlinkMessage msg = NetlinkMessage.parse(byteBuffer, NETLINK_INET_DIAG);
+
+ assertNotNull(msg);
+ assertTrue(msg instanceof InetDiagMessage);
+ final InetDiagMessage inetDiagMsg = (InetDiagMessage) msg;
+ final Inet6Address srcAddr = Inet6Address.getByAddress(
+ null /* host */, SRC_V4_MAPPED_V6_ADDRESS_BYTES, -1 /* scope_id */);
+ final Inet6Address dstAddr = Inet6Address.getByAddress(
+ null /* host */, DST_V4_MAPPED_V6_ADDRESS_BYTES, -1 /* scope_id */);
+ assertInetDiagSockId(inetDiagMsg.inetDiagMsg.id,
+ new InetSocketAddress(srcAddr, 43031),
+ new InetSocketAddress(dstAddr, 38415),
+ 7 /* ifIndex */,
+ 88 /* cookie */);
+ }
}
diff --git a/common/tests/unit/src/com/android/net/module/util/netlink/NetlinkUtilsTest.java b/common/tests/unit/src/com/android/net/module/util/netlink/NetlinkUtilsTest.java
index 7a1639ae..6fbfbf91 100644
--- a/common/tests/unit/src/com/android/net/module/util/netlink/NetlinkUtilsTest.java
+++ b/common/tests/unit/src/com/android/net/module/util/netlink/NetlinkUtilsTest.java
@@ -30,6 +30,7 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeFalse;
import android.content.Context;
import android.system.ErrnoException;
@@ -51,11 +52,13 @@ import org.junit.runner.RunWith;
import java.io.FileDescriptor;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
+import java.nio.file.Files;
+import java.nio.file.Paths;
@RunWith(AndroidJUnit4.class)
@SmallTest
public class NetlinkUtilsTest {
- private static final String TAG = "NetlinkUitlsTest";
+ private static final String TAG = "NetlinkUtilsTest";
private static final int TEST_SEQNO = 5;
private static final int TEST_TIMEOUT_MS = 500;
@@ -82,6 +85,8 @@ public class NetlinkUtilsTest {
// Apps targeting an SDK version > S are not allowed to send RTM_GETNEIGH{TBL} messages
if (SdkLevel.isAtLeastT() && targetSdk > 31) {
+ var ctxt = new String(Files.readAllBytes(Paths.get("/proc/thread-self/attr/current")));
+ assumeFalse("must not be platform app", ctxt.startsWith("u:r:platform_app:s0:"));
try {
NetlinkUtils.sendMessage(fd, req, 0, req.length, TEST_TIMEOUT_MS);
fail("RTM_GETNEIGH is not allowed for apps targeting SDK > 31 on T+ platforms,"
diff --git a/common/tests/unit/src/com/android/net/module/util/wear/NetPacketHelpersTest.java b/common/tests/unit/src/com/android/net/module/util/wear/NetPacketHelpersTest.java
new file mode 100644
index 00000000..23e7b15e
--- /dev/null
+++ b/common/tests/unit/src/com/android/net/module/util/wear/NetPacketHelpersTest.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2023 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.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.net.module.util.wear;
+
+import static org.junit.Assert.assertEquals;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import com.android.net.module.util.async.CircularByteBuffer;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class NetPacketHelpersTest {
+ @Test
+ public void decodeNetworkUnsignedInt16() {
+ final byte[] data = new byte[4];
+ data[0] = (byte) 0xFF;
+ data[1] = (byte) 1;
+ data[2] = (byte) 2;
+ data[3] = (byte) 0xFF;
+
+ assertEquals(0x0102, NetPacketHelpers.decodeNetworkUnsignedInt16(data, 1));
+
+ CircularByteBuffer buffer = new CircularByteBuffer(100);
+ buffer.writeBytes(data, 0, data.length);
+
+ assertEquals(0x0102, NetPacketHelpers.decodeNetworkUnsignedInt16(buffer, 1));
+ }
+
+ @Test
+ public void encodeNetworkUnsignedInt16() {
+ final byte[] data = new byte[4];
+ data[0] = (byte) 0xFF;
+ data[3] = (byte) 0xFF;
+ NetPacketHelpers.encodeNetworkUnsignedInt16(0x0102, data, 1);
+
+ assertEquals((byte) 0xFF, data[0]);
+ assertEquals((byte) 1, data[1]);
+ assertEquals((byte) 2, data[2]);
+ assertEquals((byte) 0xFF, data[3]);
+ }
+}
diff --git a/common/tests/unit/src/com/android/net/module/util/wear/StreamingPacketFileTest.java b/common/tests/unit/src/com/android/net/module/util/wear/StreamingPacketFileTest.java
new file mode 100644
index 00000000..1fcca704
--- /dev/null
+++ b/common/tests/unit/src/com/android/net/module/util/wear/StreamingPacketFileTest.java
@@ -0,0 +1,291 @@
+/*
+ * Copyright (C) 2023 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.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.net.module.util.wear;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.ignoreStubs;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+import android.os.ParcelFileDescriptor;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.net.module.util.async.AsyncFile;
+import com.android.net.module.util.async.BufferedFile;
+import com.android.net.module.util.async.EventManager;
+import com.android.net.module.util.async.FileHandle;
+import com.android.net.module.util.async.ReadableByteBuffer;
+import com.android.testutils.async.ReadableDataAnswer;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class StreamingPacketFileTest {
+ private static final int MAX_PACKET_SIZE = 100;
+
+ @Mock EventManager mockEventManager;
+ @Mock PacketFile.Listener mockFileListener;
+ @Mock AsyncFile mockAsyncFile;
+ @Mock ParcelFileDescriptor mockParcelFileDescriptor;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ verifyNoMoreInteractions(ignoreStubs(mockFileListener, mockAsyncFile, mockEventManager));
+ }
+
+ @Test
+ public void continueReadingAndClose() throws Exception {
+ final int maxBufferedInboundPackets = 3;
+ final int maxBufferedOutboundPackets = 5;
+
+ final StreamingPacketFile file =
+ createFile(maxBufferedInboundPackets, maxBufferedOutboundPackets);
+ final BufferedFile bufferedFile = file.getUnderlyingFileForTest();
+
+ assertEquals(maxBufferedInboundPackets * (MAX_PACKET_SIZE + 2),
+ bufferedFile.getInboundBufferFreeSizeForTest());
+ assertEquals(maxBufferedOutboundPackets * (MAX_PACKET_SIZE + 2),
+ bufferedFile.getOutboundBufferFreeSize());
+ assertEquals(bufferedFile.getOutboundBufferFreeSize() - 2,
+ file.getOutboundFreeSize());
+
+ file.continueReading();
+ verify(mockAsyncFile).enableReadEvents(true);
+
+ file.close();
+ verify(mockAsyncFile).close();
+ }
+
+ @Test
+ public void enqueueOutboundPacket() throws Exception {
+ final int maxBufferedInboundPackets = 10;
+ final int maxBufferedOutboundPackets = 20;
+
+ final StreamingPacketFile file =
+ createFile(maxBufferedInboundPackets, maxBufferedOutboundPackets);
+ final BufferedFile bufferedFile = file.getUnderlyingFileForTest();
+
+ final byte[] packet1 = new byte[11];
+ final byte[] packet2 = new byte[12];
+ packet1[0] = (byte) 1;
+ packet2[0] = (byte) 2;
+
+ assertEquals(0, bufferedFile.getOutboundBufferSize());
+
+ when(mockAsyncFile.write(any(), anyInt(), anyInt())).thenReturn(0);
+ assertTrue(file.enqueueOutboundPacket(packet1, 0, packet1.length));
+ verify(mockAsyncFile).enableWriteEvents(true);
+
+ assertEquals(packet1.length + 2, bufferedFile.getOutboundBufferSize());
+
+ checkAndResetMocks();
+
+ final int totalLen = packet1.length + packet2.length + 4;
+
+ final ArgumentCaptor<byte[]> arrayCaptor = ArgumentCaptor.forClass(byte[].class);
+ final ArgumentCaptor<Integer> posCaptor = ArgumentCaptor.forClass(Integer.class);
+ final ArgumentCaptor<Integer> lenCaptor = ArgumentCaptor.forClass(Integer.class);
+ when(mockAsyncFile.write(
+ arrayCaptor.capture(), posCaptor.capture(), lenCaptor.capture())).thenReturn(totalLen);
+
+ assertTrue(file.enqueueOutboundPacket(packet2, 0, packet2.length));
+
+ assertEquals(0, bufferedFile.getInboundBuffer().size());
+ assertEquals(0, bufferedFile.getOutboundBufferSize());
+
+ assertEquals(0, posCaptor.getValue().intValue());
+ assertEquals(totalLen, lenCaptor.getValue().intValue());
+
+ final byte[] capturedData = arrayCaptor.getValue();
+ assertEquals(packet1.length, NetPacketHelpers.decodeNetworkUnsignedInt16(capturedData, 0));
+ assertEquals(packet2.length,
+ NetPacketHelpers.decodeNetworkUnsignedInt16(capturedData, packet1.length + 2));
+ assertEquals(packet1[0], capturedData[2]);
+ assertEquals(packet2[0], capturedData[packet1.length + 4]);
+ }
+
+ @Test
+ public void onInboundPacket() throws Exception {
+ final int maxBufferedInboundPackets = 10;
+ final int maxBufferedOutboundPackets = 20;
+
+ final StreamingPacketFile file =
+ createFile(maxBufferedInboundPackets, maxBufferedOutboundPackets);
+ final BufferedFile bufferedFile = file.getUnderlyingFileForTest();
+ final ReadableByteBuffer inboundBuffer = bufferedFile.getInboundBuffer();
+
+ final int len1 = 11;
+ final int len2 = 12;
+ final byte[] data = new byte[len1 + len2 + 4];
+ NetPacketHelpers.encodeNetworkUnsignedInt16(len1, data, 0);
+ NetPacketHelpers.encodeNetworkUnsignedInt16(len2, data, 11 + 2);
+ data[2] = (byte) 1;
+ data[len1 + 4] = (byte) 2;
+
+ final ReadableDataAnswer dataAnswer = new ReadableDataAnswer(data);
+
+ final ArgumentCaptor<byte[]> arrayCaptor = ArgumentCaptor.forClass(byte[].class);
+ final ArgumentCaptor<Integer> posCaptor = ArgumentCaptor.forClass(Integer.class);
+ final ArgumentCaptor<Integer> lenCaptor = ArgumentCaptor.forClass(Integer.class);
+
+ when(mockAsyncFile.read(any(), anyInt(), anyInt())).thenAnswer(dataAnswer);
+ when(mockFileListener.onPreambleData(any(), eq(0), eq(data.length))).thenReturn(0);
+ bufferedFile.onReadReady(mockAsyncFile);
+ verify(mockAsyncFile).enableReadEvents(true);
+ verify(mockFileListener).onInboundBuffered(data.length, data.length);
+ verify(mockFileListener).onInboundPacket(
+ arrayCaptor.capture(), posCaptor.capture(), lenCaptor.capture());
+ verify(mockEventManager).execute(any());
+
+ byte[] capturedData = arrayCaptor.getValue();
+ assertEquals(2, posCaptor.getValue().intValue());
+ assertEquals(len1, lenCaptor.getValue().intValue());
+ assertEquals((byte) 1, capturedData[2]);
+
+ checkAndResetMocks();
+
+ when(mockAsyncFile.read(any(), anyInt(), anyInt())).thenAnswer(dataAnswer);
+ file.onBufferedFileInboundData(0);
+ verify(mockFileListener).onInboundPacket(
+ arrayCaptor.capture(), posCaptor.capture(), lenCaptor.capture());
+ verify(mockEventManager).execute(any());
+
+ capturedData = arrayCaptor.getValue();
+ assertEquals(2, posCaptor.getValue().intValue());
+ assertEquals(len2, lenCaptor.getValue().intValue());
+ assertEquals((byte) 2, capturedData[2]);
+
+ assertEquals(0, bufferedFile.getOutboundBufferSize());
+ assertEquals(0, inboundBuffer.size());
+ }
+
+ @Test
+ public void onReadReady_preambleData() throws Exception {
+ final int maxBufferedInboundPackets = 10;
+ final int maxBufferedOutboundPackets = 20;
+
+ final StreamingPacketFile file =
+ createFile(maxBufferedInboundPackets, maxBufferedOutboundPackets);
+ final BufferedFile bufferedFile = file.getUnderlyingFileForTest();
+ final ReadableByteBuffer inboundBuffer = bufferedFile.getInboundBuffer();
+
+ final int preambleLen = 23;
+ final int len1 = 11;
+ final byte[] data = new byte[preambleLen + 2 + len1];
+ NetPacketHelpers.encodeNetworkUnsignedInt16(len1, data, preambleLen);
+ data[preambleLen + 2] = (byte) 1;
+
+ final ReadableDataAnswer dataAnswer = new ReadableDataAnswer(data);
+
+ when(mockAsyncFile.read(any(), anyInt(), anyInt())).thenAnswer(dataAnswer);
+ when(mockFileListener.onPreambleData(any(), eq(0), eq(data.length))).thenReturn(5);
+ when(mockFileListener.onPreambleData(
+ any(), eq(0), eq(data.length - 5))).thenReturn(preambleLen - 5);
+ when(mockFileListener.onPreambleData(
+ any(), eq(0), eq(data.length - preambleLen))).thenReturn(0);
+
+ bufferedFile.onReadReady(mockAsyncFile);
+
+ final ArgumentCaptor<byte[]> arrayCaptor = ArgumentCaptor.forClass(byte[].class);
+ final ArgumentCaptor<Integer> posCaptor = ArgumentCaptor.forClass(Integer.class);
+ final ArgumentCaptor<Integer> lenCaptor = ArgumentCaptor.forClass(Integer.class);
+
+ verify(mockFileListener).onInboundBuffered(data.length, data.length);
+ verify(mockFileListener).onInboundPacket(
+ arrayCaptor.capture(), posCaptor.capture(), lenCaptor.capture());
+ verify(mockEventManager).execute(any());
+ verify(mockAsyncFile).enableReadEvents(true);
+
+ final byte[] capturedData = arrayCaptor.getValue();
+ assertEquals(2, posCaptor.getValue().intValue());
+ assertEquals(len1, lenCaptor.getValue().intValue());
+ assertEquals((byte) 1, capturedData[2]);
+
+ assertEquals(0, bufferedFile.getOutboundBufferSize());
+ assertEquals(0, inboundBuffer.size());
+ }
+
+ @Test
+ public void shutdownReading() throws Exception {
+ final int maxBufferedInboundPackets = 10;
+ final int maxBufferedOutboundPackets = 20;
+
+ final StreamingPacketFile file =
+ createFile(maxBufferedInboundPackets, maxBufferedOutboundPackets);
+ final BufferedFile bufferedFile = file.getUnderlyingFileForTest();
+
+ final byte[] data = new byte[100];
+ final ReadableDataAnswer dataAnswer = new ReadableDataAnswer(data);
+ when(mockAsyncFile.read(any(), anyInt(), anyInt())).thenAnswer(dataAnswer);
+
+ doAnswer(new Answer() {
+ @Override public Object answer(InvocationOnMock invocation) {
+ file.shutdownReading();
+ return Integer.valueOf(-1);
+ }}).when(mockFileListener).onPreambleData(any(), anyInt(), anyInt());
+
+ bufferedFile.onReadReady(mockAsyncFile);
+
+ verify(mockFileListener).onInboundBuffered(data.length, data.length);
+ verify(mockAsyncFile).enableReadEvents(false);
+
+ assertEquals(0, bufferedFile.getInboundBuffer().size());
+ }
+
+ private void checkAndResetMocks() {
+ verifyNoMoreInteractions(ignoreStubs(mockFileListener, mockAsyncFile, mockEventManager,
+ mockParcelFileDescriptor));
+ reset(mockFileListener, mockAsyncFile, mockEventManager);
+ }
+
+ private StreamingPacketFile createFile(
+ int maxBufferedInboundPackets, int maxBufferedOutboundPackets) throws Exception {
+ when(mockEventManager.registerFile(any(), any())).thenReturn(mockAsyncFile);
+ return new StreamingPacketFile(
+ mockEventManager,
+ FileHandle.fromFileDescriptor(mockParcelFileDescriptor),
+ mockFileListener,
+ MAX_PACKET_SIZE,
+ maxBufferedInboundPackets,
+ maxBufferedOutboundPackets);
+ }
+}
diff --git a/common/tests/unit/src/com/android/testutils/TestableNetworkCallbackTest.kt b/common/tests/unit/src/com/android/testutils/TestableNetworkCallbackTest.kt
index ec7cdbdf..4ed881af 100644
--- a/common/tests/unit/src/com/android/testutils/TestableNetworkCallbackTest.kt
+++ b/common/tests/unit/src/com/android/testutils/TestableNetworkCallbackTest.kt
@@ -366,18 +366,6 @@ class TestableNetworkCallbackTest {
}
@Test
- fun testPollOrThrow() {
- assertFails { mCallback.pollOrThrow(SHORT_TIMEOUT_MS) }
- TNCInterpreter.interpretTestSpec(initial = mCallback, lineShift = 1,
- threadTransform = { cb -> cb.createLinkedCopy() }, spec = """
- sleep; onAvailable(133) | pollOrThrow(2) = Available(133) time 1..4
- | pollOrThrow(1) fails
- onCapabilitiesChanged(108) | pollOrThrow(1) = CapabilitiesChanged(108) time 0..3
- onBlockedStatus(199) | pollOrThrow(1) = BlockedStatus(199) time 0..3
- """)
- }
-
- @Test
fun testEventuallyExpect() {
// TODO: Current test does not verify the inline one. Also verify the behavior after
// aligning two eventuallyExpect()
@@ -455,7 +443,6 @@ private val interpretTable = listOf<InterpretMatcher<TestableNetworkCallback>>(
}
},
Regex("""poll\((\d+)\)""") to { i, cb, t -> cb.poll(t.timeArg(1)) },
- Regex("""pollOrThrow\((\d+)\)""") to { i, cb, t -> cb.pollOrThrow(t.timeArg(1)) },
// Interpret "eventually(Available(xx), timeout)" as calling eventuallyExpect that expects
// CallbackEntry.AVAILABLE with netId of xx within timeout*INTERPRET_TIME_UNIT timeout, and
// likewise for all callback types.
diff --git a/common/testutils/Android.bp b/common/testutils/Android.bp
index bcf89b34..33821564 100644
--- a/common/testutils/Android.bp
+++ b/common/testutils/Android.bp
@@ -38,6 +38,7 @@ java_library {
"net-utils-device-common",
"net-utils-device-common-async",
"net-utils-device-common-netlink",
+ "net-utils-device-common-wear",
"modules-utils-build_system",
],
lint: { strict_updatability_linting: true },
diff --git a/common/testutils/devicetests/com/android/testutils/TestableNetworkCallback.kt b/common/testutils/devicetests/com/android/testutils/TestableNetworkCallback.kt
index e7d86e00..0e73112d 100644
--- a/common/testutils/devicetests/com/android/testutils/TestableNetworkCallback.kt
+++ b/common/testutils/devicetests/com/android/testutils/TestableNetworkCallback.kt
@@ -223,18 +223,6 @@ open class TestableNetworkCallback private constructor(
fun poll(timeoutMs: Long = defaultTimeoutMs, predicate: (CallbackEntry) -> Boolean = { true }) =
history.poll(timeoutMs, predicate)
- /**
- * Get the next callback or throw if timeout.
- *
- * With no argument, this method waits out the default timeout. To wait forever, pass
- * Long.MAX_VALUE.
- */
- @JvmOverloads
- fun pollOrThrow(
- timeoutMs: Long = defaultTimeoutMs,
- errorMsg: String = "Did not receive callback after $timeoutMs"
- ): CallbackEntry = poll(timeoutMs) ?: fail(errorMsg)
-
/*****
* expect family of methods.
* These methods fetch the next callback and assert it matches the conditions : type,
@@ -350,15 +338,16 @@ open class TestableNetworkCallback private constructor(
timeoutMs: Long = defaultTimeoutMs,
errorMsg: String? = null,
test: (T) -> Boolean = { true }
- ) = pollOrThrow(timeoutMs, "Did not receive ${T::class.simpleName} after ${timeoutMs}ms").also {
- if (it !is T) fail("Expected callback ${T::class.simpleName}, got $it")
- if (ANY_NETWORK !== network && it.network != network) {
- fail("Expected network $network for callback : $it")
- }
- if (!test(it)) {
- fail("${errorMsg ?: "Callback doesn't match predicate"} : $it")
- }
- } as T
+ ) = (poll(timeoutMs) ?: fail("Did not receive ${T::class.simpleName} after ${timeoutMs}ms"))
+ .also {
+ if (it !is T) fail("Expected callback ${T::class.simpleName}, got $it")
+ if (ANY_NETWORK !== network && it.network != network) {
+ fail("Expected network $network for callback : $it")
+ }
+ if (!test(it)) {
+ fail("${errorMsg ?: "Callback doesn't match predicate"} : $it")
+ }
+ } as T
inline fun <reified T : CallbackEntry> expect(
network: HasNetwork,
@@ -367,6 +356,12 @@ open class TestableNetworkCallback private constructor(
test: (T) -> Boolean = { true }
) = expect(network.network, timeoutMs, errorMsg, test)
+ /*****
+ * assertNoCallback family of methods.
+ * These methods make sure that no callback that matches the predicate was received.
+ * If no predicate is given, they make sure that no callback at all was received.
+ * These methods run the waiter func given in the constructor if any.
+ */
@JvmOverloads
fun assertNoCallback(
timeoutMs: Long = defaultNoCallbackTimeoutMs,
@@ -379,9 +374,12 @@ open class TestableNetworkCallback private constructor(
fun assertNoCallback(valid: (CallbackEntry) -> Boolean) =
assertNoCallback(defaultNoCallbackTimeoutMs, valid)
- // Expects a callback of the specified type matching the predicate within the timeout.
- // Any callback that doesn't match the predicate will be skipped. Fails only if
- // no matching callback is received within the timeout.
+ /*****
+ * eventuallyExpect family of methods.
+ * These methods make sure a callback that matches the type/predicate is received eventually.
+ * Any callback of the wrong type, or doesn't match the optional predicate, is ignored.
+ * They fail if no callback matching the predicate is received within the timeout.
+ */
inline fun <reified T : CallbackEntry> eventuallyExpect(
timeoutMs: Long = defaultTimeoutMs,
from: Int = mark,
@@ -408,13 +406,6 @@ open class TestableNetworkCallback private constructor(
assertNotNull(it, "Callback ${type.java} not received within ${timeoutMs}ms")
} as T
- // TODO (b/157405399) remove this method when there are no longer any uses of it.
- inline fun <reified T : CallbackEntry> eventuallyExpectOrNull(
- timeoutMs: Long = defaultTimeoutMs,
- from: Int = mark,
- crossinline predicate: (T) -> Boolean = { true }
- ) = history.poll(timeoutMs, from) { it is T && predicate(it) } as T?
-
// Expects onAvailable and the callbacks that follow it. These are:
// - onSuspended, iff the network was suspended when the callbacks fire.
// - onCapabilitiesChanged.
diff --git a/common/testutils/devicetests/com/android/testutils/async/FakeOsAccess.java b/common/testutils/devicetests/com/android/testutils/async/FakeOsAccess.java
index 1b8e26b3..48b57d70 100644
--- a/common/testutils/devicetests/com/android/testutils/async/FakeOsAccess.java
+++ b/common/testutils/devicetests/com/android/testutils/async/FakeOsAccess.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.testutils;
+package com.android.testutils.async;
import android.os.ParcelFileDescriptor;
import android.system.StructPollfd;
diff --git a/common/testutils/devicetests/com/android/testutils/async/RateLimiter.java b/common/testutils/devicetests/com/android/testutils/async/RateLimiter.java
index 137873db..d5cca0a4 100644
--- a/common/testutils/devicetests/com/android/testutils/async/RateLimiter.java
+++ b/common/testutils/devicetests/com/android/testutils/async/RateLimiter.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.testutils;
+package com.android.testutils.async;
import com.android.net.module.util.async.OsAccess;
diff --git a/common/testutils/devicetests/com/android/testutils/async/ReadableDataAnswer.java b/common/testutils/devicetests/com/android/testutils/async/ReadableDataAnswer.java
new file mode 100644
index 00000000..4bf55270
--- /dev/null
+++ b/common/testutils/devicetests/com/android/testutils/async/ReadableDataAnswer.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2023 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.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.testutils.async;
+
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
+import java.util.ArrayList;
+
+public class ReadableDataAnswer implements Answer {
+ private final ArrayList<byte[]> mBuffers = new ArrayList<>();
+ private int mBufferPos;
+
+ public ReadableDataAnswer(byte[] ... buffers) {
+ for (byte[] buffer : buffers) {
+ addBuffer(buffer);
+ }
+ }
+
+ public void addBuffer(byte[] buffer) {
+ if (buffer.length != 0) {
+ mBuffers.add(buffer);
+ }
+ }
+
+ public int getRemainingSize() {
+ int totalSize = 0;
+ for (byte[] buffer : mBuffers) {
+ totalSize += buffer.length;
+ }
+ return totalSize - mBufferPos;
+ }
+
+ private void cleanupBuffers() {
+ if (!mBuffers.isEmpty() && mBufferPos == mBuffers.get(0).length) {
+ mBuffers.remove(0);
+ mBufferPos = 0;
+ }
+ }
+
+ @Override
+ public Object answer(InvocationOnMock invocation) throws Throwable {
+ cleanupBuffers();
+
+ if (mBuffers.isEmpty()) {
+ return Integer.valueOf(0);
+ }
+
+ byte[] src = mBuffers.get(0);
+
+ byte[] dst = invocation.<byte[]>getArgument(0);
+ int dstPos = invocation.<Integer>getArgument(1);
+ int dstLen = invocation.<Integer>getArgument(2);
+
+ int copyLen = Math.min(dstLen, src.length - mBufferPos);
+ System.arraycopy(src, mBufferPos, dst, dstPos, copyLen);
+ mBufferPos += copyLen;
+
+ cleanupBuffers();
+ return Integer.valueOf(copyLen);
+ }
+}