Merge "Revert "Return default value when getModuleVersion throwed""
diff --git a/staticlibs/Android.bp b/staticlibs/Android.bp
index d8a397d..1fe6c2c 100644
--- a/staticlibs/Android.bp
+++ b/staticlibs/Android.bp
@@ -304,6 +304,30 @@
lint: { strict_updatability_linting: true },
}
+java_library {
+ name: "net-utils-device-common-async",
+ srcs: [
+ "device/com/android/net/module/util/async/*.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: [
+ ],
+ apex_available: [
+ "com.android.tethering",
+ "//apex_available:platform",
+ ],
+ lint: { strict_updatability_linting: true },
+}
+
// 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/staticlibs/device/com/android/net/module/util/async/Assertions.java b/staticlibs/device/com/android/net/module/util/async/Assertions.java
new file mode 100644
index 0000000..ce701d0
--- /dev/null
+++ b/staticlibs/device/com/android/net/module/util/async/Assertions.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2022 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 android.os.Build;
+
+/**
+ * Implements basic assert functions for runtime error-checking.
+ *
+ * @hide
+ */
+public final class Assertions {
+ public static final boolean IS_USER_BUILD = "user".equals(Build.TYPE);
+
+ public static void throwsIfOutOfBounds(int totalLength, int pos, int len) {
+ if (!IS_USER_BUILD && ((totalLength | pos | len) < 0 || pos > totalLength - len)) {
+ throw new ArrayIndexOutOfBoundsException(
+ "length=" + totalLength + "; regionStart=" + pos + "; regionLength=" + len);
+ }
+ }
+
+ public static void throwsIfOutOfBounds(byte[] buffer, int pos, int len) {
+ throwsIfOutOfBounds(buffer != null ? buffer.length : 0, pos, len);
+ }
+
+ private Assertions() {}
+}
diff --git a/staticlibs/device/com/android/net/module/util/async/AsyncFile.java b/staticlibs/device/com/android/net/module/util/async/AsyncFile.java
new file mode 100644
index 0000000..2a3231b
--- /dev/null
+++ b/staticlibs/device/com/android/net/module/util/async/AsyncFile.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2022 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;
+
+/**
+ * Represents an EventManager-managed file with Async IO semantics.
+ *
+ * Implements level-based Asyn IO semantics. This means that:
+ * - onReadReady() callback would keep happening as long as there's any remaining
+ * data to read, or the user calls enableReadEvents(false)
+ * - onWriteReady() callback would keep happening as long as there's remaining space
+ * to write to, or the user calls enableWriteEvents(false)
+ *
+ * All operations except close() must be called on the EventManager thread.
+ *
+ * @hide
+ */
+public interface AsyncFile {
+ /**
+ * Receives notifications when file readability or writeability changes.
+ * @hide
+ */
+ public interface Listener {
+ /** Invoked after the underlying file has been closed. */
+ void onClosed(AsyncFile file);
+
+ /** Invoked while the file has readable data and read notifications are enabled. */
+ void onReadReady(AsyncFile file);
+
+ /** Invoked while the file has writeable space and write notifications are enabled. */
+ void onWriteReady(AsyncFile file);
+ }
+
+ /** Requests this file to be closed. */
+ void close();
+
+ /** Enables or disables onReadReady() events. */
+ void enableReadEvents(boolean enable);
+
+ /** Enables or disables onWriteReady() events. */
+ void enableWriteEvents(boolean enable);
+
+ /** Returns true if the input stream has reached its end, or has been closed. */
+ boolean reachedEndOfFile();
+
+ /**
+ * Reads available data from the given non-blocking file descriptor.
+ *
+ * Returns zero if there's no data to read at this moment.
+ * Returns -1 if the file has reached its end or the input stream has been closed.
+ * Otherwise returns the number of bytes read.
+ */
+ int read(byte[] buffer, int pos, int len) throws IOException;
+
+ /**
+ * Writes data into the given non-blocking file descriptor.
+ *
+ * Returns zero if there's no buffer space to write to at this moment.
+ * Otherwise returns the number of bytes written.
+ */
+ int write(byte[] buffer, int pos, int len) throws IOException;
+}
diff --git a/staticlibs/device/com/android/net/module/util/async/CircularByteBuffer.java b/staticlibs/device/com/android/net/module/util/async/CircularByteBuffer.java
new file mode 100644
index 0000000..92daa08
--- /dev/null
+++ b/staticlibs/device/com/android/net/module/util/async/CircularByteBuffer.java
@@ -0,0 +1,210 @@
+/*
+ * Copyright (C) 2022 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.util.Arrays;
+
+/**
+ * Implements a circular read-write byte buffer.
+ *
+ * @hide
+ */
+public final class CircularByteBuffer implements ReadableByteBuffer {
+ private final byte[] mBuffer;
+ private final int mCapacity;
+ private int mReadPos;
+ private int mWritePos;
+ private int mSize;
+
+ public CircularByteBuffer(int capacity) {
+ mCapacity = capacity;
+ mBuffer = new byte[mCapacity];
+ }
+
+ @Override
+ public void clear() {
+ mReadPos = 0;
+ mWritePos = 0;
+ mSize = 0;
+ Arrays.fill(mBuffer, (byte) 0);
+ }
+
+ @Override
+ public int capacity() {
+ return mCapacity;
+ }
+
+ @Override
+ public int size() {
+ return mSize;
+ }
+
+ /** Returns the amount of remaining writeable space in this buffer. */
+ public int freeSize() {
+ return mCapacity - mSize;
+ }
+
+ @Override
+ public byte peek(int offset) {
+ if (offset < 0 || offset >= size()) {
+ throw new IllegalArgumentException("Invalid offset=" + offset + ", size=" + size());
+ }
+
+ return mBuffer[(mReadPos + offset) % mCapacity];
+ }
+
+ @Override
+ public void readBytes(byte[] dst, int dstPos, int dstLen) {
+ if (dst != null) {
+ Assertions.throwsIfOutOfBounds(dst, dstPos, dstLen);
+ }
+ if (dstLen > size()) {
+ throw new IllegalArgumentException("Invalid len=" + dstLen + ", size=" + size());
+ }
+
+ while (dstLen > 0) {
+ final int copyLen = getCopyLen(mReadPos, mWritePos, dstLen);
+ if (dst != null) {
+ System.arraycopy(mBuffer, mReadPos, dst, dstPos, copyLen);
+ }
+ dstPos += copyLen;
+ dstLen -= copyLen;
+ mSize -= copyLen;
+ mReadPos = (mReadPos + copyLen) % mCapacity;
+ }
+
+ if (mSize == 0) {
+ // Reset to the beginning for better contiguous access.
+ mReadPos = 0;
+ mWritePos = 0;
+ }
+ }
+
+ @Override
+ public void peekBytes(int offset, byte[] dst, int dstPos, int dstLen) {
+ Assertions.throwsIfOutOfBounds(dst, dstPos, dstLen);
+ if (offset + dstLen > size()) {
+ throw new IllegalArgumentException("Invalid len=" + dstLen
+ + ", offset=" + offset + ", size=" + size());
+ }
+
+ int tmpReadPos = (mReadPos + offset) % mCapacity;
+ while (dstLen > 0) {
+ final int copyLen = getCopyLen(tmpReadPos, mWritePos, dstLen);
+ System.arraycopy(mBuffer, tmpReadPos, dst, dstPos, copyLen);
+ dstPos += copyLen;
+ dstLen -= copyLen;
+ tmpReadPos = (tmpReadPos + copyLen) % mCapacity;
+ }
+ }
+
+ @Override
+ public int getDirectReadSize() {
+ if (size() == 0) {
+ return 0;
+ }
+ return (mReadPos < mWritePos ? (mWritePos - mReadPos) : (mCapacity - mReadPos));
+ }
+
+ @Override
+ public int getDirectReadPos() {
+ return mReadPos;
+ }
+
+ @Override
+ public byte[] getDirectReadBuffer() {
+ return mBuffer;
+ }
+
+ @Override
+ public void accountForDirectRead(int len) {
+ if (len < 0 || len > size()) {
+ throw new IllegalArgumentException("Invalid len=" + len + ", size=" + size());
+ }
+
+ mSize -= len;
+ mReadPos = (mReadPos + len) % mCapacity;
+ }
+
+ /** Copies given data to the end of the buffer. */
+ public void writeBytes(byte[] buffer, int pos, int len) {
+ Assertions.throwsIfOutOfBounds(buffer, pos, len);
+ if (len > freeSize()) {
+ throw new IllegalArgumentException("Invalid len=" + len + ", size=" + freeSize());
+ }
+
+ while (len > 0) {
+ final int copyLen = getCopyLen(mWritePos, mReadPos,len);
+ System.arraycopy(buffer, pos, mBuffer, mWritePos, copyLen);
+ pos += copyLen;
+ len -= copyLen;
+ mSize += copyLen;
+ mWritePos = (mWritePos + copyLen) % mCapacity;
+ }
+ }
+
+ private int getCopyLen(int startPos, int endPos, int len) {
+ if (startPos < endPos) {
+ return Math.min(len, endPos - startPos);
+ } else {
+ return Math.min(len, mCapacity - startPos);
+ }
+ }
+
+ /** Returns the amount of contiguous writeable space. */
+ public int getDirectWriteSize() {
+ if (freeSize() == 0) {
+ return 0; // Return zero in case buffer is full.
+ }
+ return (mWritePos < mReadPos ? (mReadPos - mWritePos) : (mCapacity - mWritePos));
+ }
+
+ /** Returns the position of contiguous writeable space. */
+ public int getDirectWritePos() {
+ return mWritePos;
+ }
+
+ /** Returns the buffer reference for direct write operation. */
+ public byte[] getDirectWriteBuffer() {
+ return mBuffer;
+ }
+
+ /** Must be called after performing a direct write using getDirectWriteBuffer(). */
+ public void accountForDirectWrite(int len) {
+ if (len < 0 || len > freeSize()) {
+ throw new IllegalArgumentException("Invalid len=" + len + ", size=" + freeSize());
+ }
+
+ mSize += len;
+ mWritePos = (mWritePos + len) % mCapacity;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("CircularByteBuffer{c=");
+ sb.append(mCapacity);
+ sb.append(",s=");
+ sb.append(mSize);
+ sb.append(",r=");
+ sb.append(mReadPos);
+ sb.append(",w=");
+ sb.append(mWritePos);
+ sb.append('}');
+ return sb.toString();
+ }
+}
diff --git a/staticlibs/device/com/android/net/module/util/async/EventManager.java b/staticlibs/device/com/android/net/module/util/async/EventManager.java
new file mode 100644
index 0000000..4ed4a70
--- /dev/null
+++ b/staticlibs/device/com/android/net/module/util/async/EventManager.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2022 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.Executor;
+
+/**
+ * Manages Async IO files and scheduled alarms, and executes all related callbacks
+ * in its own thread.
+ *
+ * All callbacks of AsyncFile, Alarm and EventManager will execute on EventManager's thread.
+ *
+ * Methods of this interface can be called from any thread.
+ *
+ * @hide
+ */
+public interface EventManager extends Executor {
+ /**
+ * Represents a scheduled alarm, allowing caller to attempt to cancel that alarm
+ * before it executes.
+ *
+ * @hide
+ */
+ public interface Alarm {
+ /** @hide */
+ public interface Listener {
+ void onAlarm(Alarm alarm, long elapsedTimeMs);
+ void onAlarmCancelled(Alarm alarm);
+ }
+
+ /**
+ * Attempts to cancel this alarm. Note that this request is inherently
+ * racy if executed close to the alarm's expiration time.
+ */
+ void cancel();
+ }
+
+ /**
+ * Requests EventManager to manage the given file.
+ *
+ * The file descriptors are not cloned, and EventManager takes ownership of all files passed.
+ *
+ * No event callbacks are enabled by this method.
+ */
+ AsyncFile registerFile(FileHandle fileHandle, AsyncFile.Listener listener) throws IOException;
+
+ /**
+ * Schedules Alarm with the given timeout.
+ *
+ * Timeout of zero can be used for immediate execution.
+ */
+ Alarm scheduleAlarm(long timeout, Alarm.Listener callback);
+
+ /** Schedules Runnable for immediate execution. */
+ @Override
+ void execute(Runnable callback);
+
+ /** Throws a runtime exception if the caller is not executing on this EventManager's thread. */
+ void assertInThread();
+}
diff --git a/staticlibs/device/com/android/net/module/util/async/FileHandle.java b/staticlibs/device/com/android/net/module/util/async/FileHandle.java
new file mode 100644
index 0000000..9f7942d
--- /dev/null
+++ b/staticlibs/device/com/android/net/module/util/async/FileHandle.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2022 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 android.os.ParcelFileDescriptor;
+
+import java.io.Closeable;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+/**
+ * Represents an file descriptor or another way to access a file.
+ *
+ * @hide
+ */
+public final class FileHandle {
+ private final ParcelFileDescriptor mFd;
+ private final Closeable mCloseable;
+ private final InputStream mInputStream;
+ private final OutputStream mOutputStream;
+
+ public static FileHandle fromFileDescriptor(ParcelFileDescriptor fd) {
+ if (fd == null) {
+ throw new NullPointerException();
+ }
+ return new FileHandle(fd, null, null, null);
+ }
+
+ public static FileHandle fromBlockingStream(
+ Closeable closeable, InputStream is, OutputStream os) {
+ if (closeable == null || is == null || os == null) {
+ throw new NullPointerException();
+ }
+ return new FileHandle(null, closeable, is, os);
+ }
+
+ private FileHandle(ParcelFileDescriptor fd, Closeable closeable,
+ InputStream is, OutputStream os) {
+ mFd = fd;
+ mCloseable = closeable;
+ mInputStream = is;
+ mOutputStream = os;
+ }
+
+ ParcelFileDescriptor getFileDescriptor() {
+ return mFd;
+ }
+
+ Closeable getCloseable() {
+ return mCloseable;
+ }
+
+ InputStream getInputStream() {
+ return mInputStream;
+ }
+
+ OutputStream getOutputStream() {
+ return mOutputStream;
+ }
+}
diff --git a/staticlibs/device/com/android/net/module/util/async/OsAccess.java b/staticlibs/device/com/android/net/module/util/async/OsAccess.java
new file mode 100644
index 0000000..df0ded2
--- /dev/null
+++ b/staticlibs/device/com/android/net/module/util/async/OsAccess.java
@@ -0,0 +1,66 @@
+/*
+ * 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 android.os.ParcelFileDescriptor;
+import android.system.StructPollfd;
+
+import java.io.FileDescriptor;
+import java.io.IOException;
+
+/**
+ * Provides access to all relevant OS functions..
+ *
+ * @hide
+ */
+public abstract class OsAccess {
+ /** Closes the given file, suppressing IO exceptions. */
+ public abstract void close(ParcelFileDescriptor fd);
+
+ /** Returns file name for debugging purposes. */
+ public abstract String getFileDebugName(ParcelFileDescriptor fd);
+
+ /** Returns inner FileDescriptor instance. */
+ public abstract FileDescriptor getInnerFileDescriptor(ParcelFileDescriptor fd);
+
+ /**
+ * Reads available data from the given non-blocking file descriptor.
+ *
+ * Returns zero if there's no data to read at this moment.
+ * Returns -1 if the file has reached its end or the input stream has been closed.
+ * Otherwise returns the number of bytes read.
+ */
+ public abstract int read(FileDescriptor fd, byte[] buffer, int pos, int len)
+ throws IOException;
+
+ /**
+ * Writes data into the given non-blocking file descriptor.
+ *
+ * Returns zero if there's no buffer space to write to at this moment.
+ * Otherwise returns the number of bytes written.
+ */
+ public abstract int write(FileDescriptor fd, byte[] buffer, int pos, int len)
+ throws IOException;
+
+ public abstract long monotonicTimeMillis();
+ public abstract void setNonBlocking(FileDescriptor fd) throws IOException;
+ public abstract ParcelFileDescriptor[] pipe() throws IOException;
+
+ public abstract int poll(StructPollfd[] fds, int timeoutMs) throws IOException;
+ public abstract short getPollInMask();
+ public abstract short getPollOutMask();
+}
diff --git a/staticlibs/device/com/android/net/module/util/async/ReadableByteBuffer.java b/staticlibs/device/com/android/net/module/util/async/ReadableByteBuffer.java
new file mode 100644
index 0000000..7f82404
--- /dev/null
+++ b/staticlibs/device/com/android/net/module/util/async/ReadableByteBuffer.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2022 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;
+
+/**
+ * Allows reading from a buffer of bytes. The data can be read and thus removed,
+ * or peeked at without disturbing the buffer state.
+ *
+ * @hide
+ */
+public interface ReadableByteBuffer {
+ /** Returns the size of the buffered data. */
+ int size();
+
+ /**
+ * Returns the maximum amount of the buffered data.
+ *
+ * The caller may use this method in combination with peekBytes()
+ * to estimate when the buffer needs to be emptied using readData().
+ */
+ int capacity();
+
+ /** Clears all buffered data. */
+ void clear();
+
+ /** Returns a single byte at the given offset. */
+ byte peek(int offset);
+
+ /** Copies an array of bytes from the given offset to "dst". */
+ void peekBytes(int offset, byte[] dst, int dstPos, int dstLen);
+
+ /** Reads and removes an array of bytes from the head of the buffer. */
+ void readBytes(byte[] dst, int dstPos, int dstLen);
+
+ /** Returns the amount of contiguous readable data. */
+ int getDirectReadSize();
+
+ /** Returns the position of contiguous readable data. */
+ int getDirectReadPos();
+
+ /** Returns the buffer reference for direct read operation. */
+ byte[] getDirectReadBuffer();
+
+ /** Must be called after performing a direct read using getDirectReadBuffer(). */
+ void accountForDirectRead(int len);
+}
diff --git a/staticlibs/device/com/android/net/module/util/structs/IaPdOption.java b/staticlibs/device/com/android/net/module/util/structs/IaPdOption.java
new file mode 100644
index 0000000..5a09c40
--- /dev/null
+++ b/staticlibs/device/com/android/net/module/util/structs/IaPdOption.java
@@ -0,0 +1,81 @@
+/*
+ * 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.structs;
+
+import static com.android.net.module.util.NetworkStackConstants.DHCP6_OPTION_IA_PD;
+
+import com.android.net.module.util.Struct;
+import com.android.net.module.util.Struct.Field;
+import com.android.net.module.util.Struct.Type;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+/**
+ * DHCPv6 IA_PD option.
+ * https://tools.ietf.org/html/rfc8415. This does not contain any option.
+ *
+ * 0 1 2 3
+ * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | OPTION_IA_PD | option-len |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | IAID (4 octets) |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | T1 |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | T2 |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * . .
+ * . IA_PD-options .
+ * . .
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ *
+ */
+public class IaPdOption extends Struct {
+ public static final int LENGTH = 12; // option length excluding IA_PD options
+
+ @Field(order = 0, type = Type.S16)
+ public short code;
+ @Field(order = 1, type = Type.S16)
+ public short length;
+ @Field(order = 2, type = Type.U32)
+ public long id;
+ @Field(order = 3, type = Type.U32)
+ public long t1;
+ @Field(order = 4, type = Type.U32)
+ public long t2;
+
+ IaPdOption(final short code, final short length, final long id, final long t1,
+ final long t2) {
+ this.code = code;
+ this.length = length;
+ this.id = id;
+ this.t1 = t1;
+ this.t2 = t2;
+ }
+
+ /**
+ * Build an IA_PD option from the required specific parameters.
+ */
+ public static ByteBuffer build(final short length, final long id, final long t1,
+ final long t2) {
+ final IaPdOption option = new IaPdOption((short) DHCP6_OPTION_IA_PD,
+ length /* 12 + IA_PD options length */, id, t1, t2);
+ return ByteBuffer.wrap(option.writeToBytes(ByteOrder.BIG_ENDIAN));
+ }
+}
diff --git a/staticlibs/device/com/android/net/module/util/structs/IaPrefixOption.java b/staticlibs/device/com/android/net/module/util/structs/IaPrefixOption.java
new file mode 100644
index 0000000..1ac21ff
--- /dev/null
+++ b/staticlibs/device/com/android/net/module/util/structs/IaPrefixOption.java
@@ -0,0 +1,89 @@
+/*
+ * 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.structs;
+
+import static com.android.net.module.util.NetworkStackConstants.DHCP6_OPTION_IAPREFIX;
+
+import com.android.net.module.util.Struct;
+import com.android.net.module.util.Struct.Field;
+import com.android.net.module.util.Struct.Type;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+/**
+ * DHCPv6 IA Prefix Option.
+ * https://tools.ietf.org/html/rfc8415. This does not contain any option.
+ *
+ * 0 1 2 3
+ * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | OPTION_IAPREFIX | option-len |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | preferred-lifetime |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | valid-lifetime |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | prefix-length | |
+ * +-+-+-+-+-+-+-+-+ IPv6-prefix |
+ * | (16 octets) |
+ * | |
+ * | |
+ * | |
+ * | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | | .
+ * +-+-+-+-+-+-+-+-+ .
+ * . IAprefix-options .
+ * . .
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ */
+public class IaPrefixOption extends Struct {
+ public static final int LENGTH = 25; // option length excluding IAprefix-options
+
+ @Field(order = 0, type = Type.S16)
+ public short code;
+ @Field(order = 1, type = Type.S16)
+ public short length;
+ @Field(order = 2, type = Type.U32)
+ public long preferred;
+ @Field(order = 3, type = Type.U32)
+ public long valid;
+ @Field(order = 4, type = Type.U8)
+ public short prefixLen;
+ @Field(order = 5, type = Type.ByteArray, arraysize = 16)
+ public byte[] prefix;
+
+ IaPrefixOption(final short code, final short length, final long preferred,
+ final long valid, final short prefixLen, final byte[] prefix) {
+ this.code = code;
+ this.length = length;
+ this.preferred = preferred;
+ this.valid = valid;
+ this.prefixLen = prefixLen;
+ this.prefix = prefix.clone();
+ }
+
+ /**
+ * Build an IA_PD prefix option with given specific parameters.
+ */
+ public static ByteBuffer build(final short length, final long preferred, final long valid,
+ final short prefixLen, final byte[] prefix) {
+ final IaPrefixOption option = new IaPrefixOption((byte) DHCP6_OPTION_IAPREFIX,
+ length /* 25 + IAPrefix options length */, preferred, valid, prefixLen, prefix);
+ return ByteBuffer.wrap(option.writeToBytes(ByteOrder.BIG_ENDIAN));
+ }
+}
diff --git a/staticlibs/framework/com/android/net/module/util/BitUtils.java b/staticlibs/framework/com/android/net/module/util/BitUtils.java
index 2b32e86..3062d8c 100644
--- a/staticlibs/framework/com/android/net/module/util/BitUtils.java
+++ b/staticlibs/framework/com/android/net/module/util/BitUtils.java
@@ -17,6 +17,7 @@
package com.android.net.module.util;
import android.annotation.NonNull;
+import android.annotation.Nullable;
/**
* @hide
@@ -107,4 +108,33 @@
++bitPos;
}
}
+
+ /**
+ * Returns a short but human-readable string of updates between an old and a new bit fields.
+ *
+ * @param oldVal the old bit field to diff from
+ * @param newVal the new bit field to diff to
+ * @return a string fit for logging differences, or null if no differences.
+ * this method cannot return the empty string.
+ */
+ @Nullable
+ public static String describeDifferences(final long oldVal, final long newVal,
+ @NonNull final NameOf nameFetcher) {
+ final long changed = oldVal ^ newVal;
+ if (0 == changed) return null;
+ // If the control reaches here, there are changes (additions, removals, or both) so
+ // the code below is guaranteed to add something to the string and can't return "".
+ final long removed = oldVal & changed;
+ final long added = newVal & changed;
+ final StringBuilder sb = new StringBuilder();
+ if (0 != removed) {
+ sb.append("-");
+ appendStringRepresentationOfBitMaskToStringBuilder(sb, removed, nameFetcher, "-");
+ }
+ if (0 != added) {
+ sb.append("+");
+ appendStringRepresentationOfBitMaskToStringBuilder(sb, added, nameFetcher, "+");
+ }
+ return sb.toString();
+ }
}
diff --git a/staticlibs/framework/com/android/net/module/util/ConnectivitySettingsUtils.java b/staticlibs/framework/com/android/net/module/util/ConnectivitySettingsUtils.java
index b7eb70b..f4856b3 100644
--- a/staticlibs/framework/com/android/net/module/util/ConnectivitySettingsUtils.java
+++ b/staticlibs/framework/com/android/net/module/util/ConnectivitySettingsUtils.java
@@ -60,6 +60,10 @@
}
private static int getPrivateDnsModeAsInt(String mode) {
+ // If both PRIVATE_DNS_MODE and PRIVATE_DNS_DEFAULT_MODE are not set, choose
+ // PRIVATE_DNS_MODE_OPPORTUNISTIC as default mode.
+ if (TextUtils.isEmpty(mode))
+ return PRIVATE_DNS_MODE_OPPORTUNISTIC;
switch (mode) {
case "off":
return PRIVATE_DNS_MODE_OFF;
@@ -68,7 +72,10 @@
case "opportunistic":
return PRIVATE_DNS_MODE_OPPORTUNISTIC;
default:
- throw new IllegalArgumentException("Invalid private dns mode: " + mode);
+ // b/260211513: adb shell settings put global private_dns_mode foo
+ // can result in arbitrary strings - treat any unknown value as empty string.
+ // throw new IllegalArgumentException("Invalid private dns mode: " + mode);
+ return PRIVATE_DNS_MODE_OPPORTUNISTIC;
}
}
@@ -82,9 +89,6 @@
final ContentResolver cr = context.getContentResolver();
String mode = Settings.Global.getString(cr, PRIVATE_DNS_MODE);
if (TextUtils.isEmpty(mode)) mode = Settings.Global.getString(cr, PRIVATE_DNS_DEFAULT_MODE);
- // If both PRIVATE_DNS_MODE and PRIVATE_DNS_DEFAULT_MODE are not set, choose
- // PRIVATE_DNS_MODE_OPPORTUNISTIC as default mode.
- if (TextUtils.isEmpty(mode)) return PRIVATE_DNS_MODE_OPPORTUNISTIC;
return getPrivateDnsModeAsInt(mode);
}
diff --git a/staticlibs/framework/com/android/net/module/util/NetworkStackConstants.java b/staticlibs/framework/com/android/net/module/util/NetworkStackConstants.java
index 7b3943c..1d88d6e 100644
--- a/staticlibs/framework/com/android/net/module/util/NetworkStackConstants.java
+++ b/staticlibs/framework/com/android/net/module/util/NetworkStackConstants.java
@@ -214,11 +214,14 @@
*
* See also:
* - https://datatracker.ietf.org/doc/html/rfc8415
+ * - https://www.iana.org/assignments/dhcpv6-parameters/dhcpv6-parameters.xhtml
*/
public static final int DHCP6_CLIENT_PORT = 546;
public static final int DHCP6_SERVER_PORT = 547;
public static final Inet6Address ALL_DHCP_RELAY_AGENTS_AND_SERVERS =
(Inet6Address) InetAddresses.parseNumericAddress("ff02::1:2");
+ public static final int DHCP6_OPTION_IA_PD = 25;
+ public static final int DHCP6_OPTION_IAPREFIX = 26;
/**
* IEEE802.11 standard constants.
diff --git a/staticlibs/native/bpf_headers/BpfRingbufTest.cpp b/staticlibs/native/bpf_headers/BpfRingbufTest.cpp
index 4a45a93..d23afae 100644
--- a/staticlibs/native/bpf_headers/BpfRingbufTest.cpp
+++ b/staticlibs/native/bpf_headers/BpfRingbufTest.cpp
@@ -23,14 +23,20 @@
#include <unistd.h>
#include "BpfSyscallWrappers.h"
+#include "bpf/BpfRingbuf.h"
#include "bpf/BpfUtils.h"
+#define TEST_RINGBUF_MAGIC_NUM 12345
+
namespace android {
namespace bpf {
using ::android::base::testing::HasError;
using ::android::base::testing::HasValue;
-using ::android::base::testing::WithMessage;
+using ::android::base::testing::WithCode;
+using ::testing::AllOf;
+using ::testing::Gt;
using ::testing::HasSubstr;
+using ::testing::Lt;
class BpfRingbufTest : public ::testing::Test {
protected:
@@ -40,8 +46,11 @@
void SetUp() {
if (!android::bpf::isAtLeastKernelVersion(5, 8, 0)) {
- GTEST_SKIP() << "BPF ring buffers not supported";
- return;
+ GTEST_SKIP() << "BPF ring buffers not supported below 5.8";
+ }
+
+ if (sizeof(unsigned long) != 8) {
+ GTEST_SKIP() << "BPF ring buffers not supported on 32 bit arch";
}
errno = 0;
@@ -51,12 +60,82 @@
<< mProgPath << " was either not found or inaccessible.";
}
+ void RunProgram() {
+ char fake_skb[128] = {};
+ EXPECT_EQ(runProgram(mProgram, fake_skb, sizeof(fake_skb)), 0);
+ }
+
+ void RunTestN(int n) {
+ int run_count = 0;
+ uint64_t output = 0;
+ auto callback = [&](const uint64_t& value) {
+ output = value;
+ run_count++;
+ };
+
+ auto result = BpfRingbuf<uint64_t>::Create(mRingbufPath.c_str());
+ ASSERT_RESULT_OK(result);
+
+ for (int i = 0; i < n; i++) {
+ RunProgram();
+ }
+
+ EXPECT_THAT(result.value()->ConsumeAll(callback), HasValue(n));
+ EXPECT_EQ(output, TEST_RINGBUF_MAGIC_NUM);
+ EXPECT_EQ(run_count, n);
+ }
+
std::string mProgPath;
std::string mRingbufPath;
android::base::unique_fd mProgram;
};
-TEST_F(BpfRingbufTest, CheckSetUp) {}
+TEST_F(BpfRingbufTest, ConsumeSingle) { RunTestN(1); }
+TEST_F(BpfRingbufTest, ConsumeMultiple) { RunTestN(3); }
+
+TEST_F(BpfRingbufTest, FillAndWrap) {
+ int run_count = 0;
+ auto callback = [&](const uint64_t&) { run_count++; };
+
+ auto result = BpfRingbuf<uint64_t>::Create(mRingbufPath.c_str());
+ ASSERT_RESULT_OK(result);
+
+ // 4kb buffer with 16 byte payloads (8 byte data, 8 byte header) should fill
+ // after 255 iterations. Exceed that so that some events are dropped.
+ constexpr int iterations = 300;
+ for (int i = 0; i < iterations; i++) {
+ RunProgram();
+ }
+
+ // Some events were dropped, but consume all that succeeded.
+ EXPECT_THAT(result.value()->ConsumeAll(callback),
+ HasValue(AllOf(Gt(250), Lt(260))));
+ EXPECT_THAT(run_count, AllOf(Gt(250), Lt(260)));
+
+ // After consuming everything, we should be able to use the ring buffer again.
+ run_count = 0;
+ RunProgram();
+ EXPECT_THAT(result.value()->ConsumeAll(callback), HasValue(1));
+ EXPECT_EQ(run_count, 1);
+}
+
+TEST_F(BpfRingbufTest, WrongTypeSize) {
+ // The program under test writes 8-byte uint64_t values so a ringbuffer for
+ // 1-byte uint8_t values will fail to read from it. Note that the map_def does
+ // not specify the value size, so we fail on read, not creation.
+ auto result = BpfRingbuf<uint8_t>::Create(mRingbufPath.c_str());
+ ASSERT_RESULT_OK(result);
+
+ RunProgram();
+
+ EXPECT_THAT(result.value()->ConsumeAll([](const uint8_t&) {}),
+ HasError(WithCode(EMSGSIZE)));
+}
+
+TEST_F(BpfRingbufTest, InvalidPath) {
+ EXPECT_THAT(BpfRingbuf<int>::Create("/sys/fs/bpf/bad_path"),
+ HasError(WithCode(ENOENT)));
+}
} // namespace bpf
} // namespace android
diff --git a/staticlibs/native/bpf_headers/include/bpf/BpfRingbuf.h b/staticlibs/native/bpf_headers/include/bpf/BpfRingbuf.h
new file mode 100644
index 0000000..cac1e43
--- /dev/null
+++ b/staticlibs/native/bpf_headers/include/bpf/BpfRingbuf.h
@@ -0,0 +1,261 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+#pragma once
+
+#include <android-base/result.h>
+#include <android-base/unique_fd.h>
+#include <linux/bpf.h>
+#include <sys/mman.h>
+#include <utils/Log.h>
+
+#include "bpf/BpfUtils.h"
+
+namespace android {
+namespace bpf {
+
+// BpfRingbufBase contains the non-templated functionality of BPF ring buffers.
+class BpfRingbufBase {
+ public:
+ ~BpfRingbufBase() {
+ if (mConsumerPos) munmap(mConsumerPos, mConsumerSize);
+ if (mProducerPos) munmap(mProducerPos, mProducerSize);
+ mConsumerPos = nullptr;
+ mProducerPos = nullptr;
+ }
+
+ protected:
+ // Non-initializing constructor, used by Create.
+ BpfRingbufBase(size_t value_size) : mValueSize(value_size) {}
+
+ // Full construction that aborts on error (use Create/Init to handle errors).
+ BpfRingbufBase(const char* path, size_t value_size) : mValueSize(value_size) {
+ if (auto status = Init(path); !status.ok()) {
+ ALOGE("BpfRingbuf init failed: %s", status.error().message().c_str());
+ abort();
+ }
+ }
+
+ // Delete copy constructor (class owns raw pointers).
+ BpfRingbufBase(const BpfRingbufBase&) = delete;
+
+ // Initialize the base ringbuffer components. Must be called exactly once.
+ base::Result<void> Init(const char* path);
+
+ // Consumes all messages from the ring buffer, passing them to the callback.
+ base::Result<int> ConsumeAll(
+ const std::function<void(const void*)>& callback);
+
+ // Replicates c-style void* "byte-wise" pointer addition.
+ template <typename Ptr>
+ static Ptr pointerAddBytes(void* base, ssize_t offset_bytes) {
+ return reinterpret_cast<Ptr>(reinterpret_cast<char*>(base) + offset_bytes);
+ }
+
+ // Rounds len by clearing bitmask, adding header, and aligning to 8 bytes.
+ static uint32_t roundLength(uint32_t len) {
+ len &= ~(BPF_RINGBUF_BUSY_BIT | BPF_RINGBUF_DISCARD_BIT);
+ len += BPF_RINGBUF_HDR_SZ;
+ return (len + 7) & ~7;
+ }
+
+ const size_t mValueSize;
+
+ size_t mConsumerSize;
+ size_t mProducerSize;
+ unsigned long mPosMask;
+ android::base::unique_fd mRingFd;
+
+ void* mDataPos = nullptr;
+ unsigned long* mConsumerPos = nullptr;
+ unsigned long* mProducerPos = nullptr;
+};
+
+// This is a class wrapper for eBPF ring buffers. An eBPF ring buffer is a
+// special type of eBPF map used for sending messages from eBPF to userspace.
+// The implementation relies on fast shared memory and atomics for the producer
+// and consumer management. Ring buffers are a faster alternative to eBPF perf
+// buffers.
+//
+// This class is thread compatible, but not thread safe.
+//
+// Note: A kernel eBPF ring buffer may be accessed by both kernel and userspace
+// processes at the same time. However, the userspace consumers of a given ring
+// buffer all share a single read pointer. There is no guarantee which readers
+// will read which messages.
+template <typename Value>
+class BpfRingbuf : public BpfRingbufBase {
+ public:
+ using MessageCallback = std::function<void(const Value&)>;
+
+ // Creates a ringbuffer wrapper from a pinned path. This initialization will
+ // abort on error. To handle errors, initialize with Create instead.
+ BpfRingbuf(const char* path) : BpfRingbufBase(path, sizeof(Value)) {}
+
+ // Creates a ringbuffer wrapper from a pinned path. There are no guarantees
+ // that the ringbuf outputs messaged of type `Value`, only that they are the
+ // same size. Size is only checked in ConsumeAll.
+ static base::Result<std::unique_ptr<BpfRingbuf<Value>>> Create(
+ const char* path);
+
+ // Consumes all messages from the ring buffer, passing them to the callback.
+ // Returns the number of messages consumed or a non-ok result on error. If the
+ // ring buffer has no pending messages an OK result with count 0 is returned.
+ base::Result<int> ConsumeAll(const MessageCallback& callback);
+
+ private:
+ // Empty ctor for use by Create.
+ BpfRingbuf() : BpfRingbufBase(sizeof(Value)) {}
+};
+
+#define ACCESS_ONCE(x) (*(volatile typeof(x)*)&(x))
+
+#if defined(__i386__) || defined(__x86_64__)
+#define smp_sync() asm volatile("" ::: "memory")
+#elif defined(__aarch64__)
+#define smp_sync() asm volatile("dmb ish" ::: "memory")
+#else
+#define smp_sync() __sync_synchronize()
+#endif
+
+#define smp_store_release(p, v) \
+ do { \
+ smp_sync(); \
+ ACCESS_ONCE(*(p)) = (v); \
+ } while (0)
+
+#define smp_load_acquire(p) \
+ ({ \
+ auto ___p = ACCESS_ONCE(*(p)); \
+ smp_sync(); \
+ ___p; \
+ })
+
+inline base::Result<void> BpfRingbufBase::Init(const char* path) {
+ if (sizeof(unsigned long) != 8) {
+ return android::base::Error()
+ << "BpfRingbuf does not support 32 bit architectures";
+ }
+ mRingFd.reset(mapRetrieveRW(path));
+ if (!mRingFd.ok()) {
+ return android::base::ErrnoError()
+ << "failed to retrieve ringbuffer at " << path;
+ }
+
+ int map_type = android::bpf::bpfGetFdMapType(mRingFd);
+ if (map_type != BPF_MAP_TYPE_RINGBUF) {
+ errno = EINVAL;
+ return android::base::ErrnoError()
+ << "bpf map has wrong type: want BPF_MAP_TYPE_RINGBUF ("
+ << BPF_MAP_TYPE_RINGBUF << ") got " << map_type;
+ }
+
+ int max_entries = android::bpf::bpfGetFdMaxEntries(mRingFd);
+ if (max_entries < 0) {
+ return android::base::ErrnoError()
+ << "failed to read max_entries from ringbuf";
+ }
+ if (max_entries == 0) {
+ errno = EINVAL;
+ return android::base::ErrnoError() << "max_entries must be non-zero";
+ }
+
+ mPosMask = max_entries - 1;
+ mConsumerSize = getpagesize();
+ mProducerSize = getpagesize() + 2 * max_entries;
+
+ {
+ void* ptr = mmap(NULL, mConsumerSize, PROT_READ | PROT_WRITE, MAP_SHARED,
+ mRingFd, 0);
+ if (ptr == MAP_FAILED) {
+ return android::base::ErrnoError()
+ << "failed to mmap ringbuf consumer pages";
+ }
+ mConsumerPos = reinterpret_cast<unsigned long*>(ptr);
+ }
+
+ {
+ void* ptr = mmap(NULL, mProducerSize, PROT_READ, MAP_SHARED, mRingFd,
+ mConsumerSize);
+ if (ptr == MAP_FAILED) {
+ return android::base::ErrnoError()
+ << "failed to mmap ringbuf producer page";
+ }
+ mProducerPos = reinterpret_cast<unsigned long*>(ptr);
+ }
+
+ mDataPos = pointerAddBytes<void*>(mProducerPos, getpagesize());
+ return {};
+}
+
+inline base::Result<int> BpfRingbufBase::ConsumeAll(
+ const std::function<void(const void*)>& callback) {
+ int64_t count = 0;
+ unsigned long cons_pos = smp_load_acquire(mConsumerPos);
+ unsigned long prod_pos = smp_load_acquire(mProducerPos);
+ while (cons_pos < prod_pos) {
+ // Find the start of the entry for this read (wrapping is done here).
+ void* start_ptr = pointerAddBytes<void*>(mDataPos, cons_pos & mPosMask);
+
+ // The entry has an 8 byte header containing the sample length.
+ uint32_t length = smp_load_acquire(reinterpret_cast<uint32_t*>(start_ptr));
+
+ // If the sample isn't committed, we're caught up with the producer.
+ if (length & BPF_RINGBUF_BUSY_BIT) return count;
+
+ cons_pos += roundLength(length);
+
+ if ((length & BPF_RINGBUF_DISCARD_BIT) == 0) {
+ if (length != mValueSize) {
+ smp_store_release(mConsumerPos, cons_pos);
+ errno = EMSGSIZE;
+ return android::base::ErrnoError()
+ << "BPF ring buffer message has unexpected size (want "
+ << mValueSize << " bytes, got " << length << " bytes)";
+ }
+ callback(pointerAddBytes<const void*>(start_ptr, BPF_RINGBUF_HDR_SZ));
+ count++;
+ }
+
+ smp_store_release(mConsumerPos, cons_pos);
+ }
+
+ return count;
+}
+
+template <typename Value>
+inline base::Result<std::unique_ptr<BpfRingbuf<Value>>>
+BpfRingbuf<Value>::Create(const char* path) {
+ auto rb = std::unique_ptr<BpfRingbuf>(new BpfRingbuf);
+ if (auto status = rb->Init(path); !status.ok()) return status.error();
+ return rb;
+}
+
+template <typename Value>
+inline base::Result<int> BpfRingbuf<Value>::ConsumeAll(
+ const MessageCallback& callback) {
+ return BpfRingbufBase::ConsumeAll([&](const void* value) {
+ callback(*reinterpret_cast<const Value*>(value));
+ });
+}
+
+#undef ACCESS_ONCE
+#undef smp_sync
+#undef smp_store_release
+#undef smp_load_acquire
+
+} // namespace bpf
+} // namespace android
diff --git a/staticlibs/native/bpf_headers/include/bpf/KernelVersion.h b/staticlibs/native/bpf_headers/include/bpf/KernelVersion.h
index e0e53a9..29a36e6 100644
--- a/staticlibs/native/bpf_headers/include/bpf/KernelVersion.h
+++ b/staticlibs/native/bpf_headers/include/bpf/KernelVersion.h
@@ -16,8 +16,7 @@
#pragma once
-#include <stdlib.h>
-#include <string.h>
+#include <stdio.h>
#include <sys/utsname.h>
namespace android {
diff --git a/staticlibs/native/bpf_headers/include/bpf/bpf_helpers.h b/staticlibs/native/bpf_headers/include/bpf/bpf_helpers.h
index 4d48720..36865f3 100644
--- a/staticlibs/native/bpf_headers/include/bpf/bpf_helpers.h
+++ b/staticlibs/native/bpf_headers/include/bpf/bpf_helpers.h
@@ -40,6 +40,9 @@
// BpfLoader v0.25+ support obj@ver.o files
#define BPFLOADER_OBJ_AT_VER_VERSION 25u
+// Bpfloader v0.33+ supports {map,prog}.ignore_on_{eng,user,userdebug}
+#define BPFLOADER_IGNORED_ON_VERSION 33u
+
/* For mainline module use, you can #define BPFLOADER_{MIN/MAX}_VER
* before #include "bpf_helpers.h" to change which bpfloaders will
* process the resulting .o file.
@@ -155,26 +158,37 @@
__attribute__ ((section(".maps." #name), used)) \
____btf_map_##name = { }
+#define BPF_ASSERT_LOADER_VERSION(min_loader, ignore_eng, ignore_user, ignore_userdebug) \
+ _Static_assert( \
+ (min_loader) >= BPFLOADER_IGNORED_ON_VERSION || \
+ !((ignore_eng) || (ignore_user) || (ignore_userdebug)), \
+ "bpfloader min version must be >= 0.33 in order to use ignored_on");
+
#define DEFINE_BPF_MAP_BASE(the_map, TYPE, keysize, valuesize, num_entries, \
usr, grp, md, selinux, pindir, share, minkver, \
- maxkver) \
- const struct bpf_map_def SECTION("maps") the_map = { \
- .type = BPF_MAP_TYPE_##TYPE, \
- .key_size = (keysize), \
- .value_size = (valuesize), \
- .max_entries = (num_entries), \
- .map_flags = 0, \
- .uid = (usr), \
- .gid = (grp), \
- .mode = (md), \
- .bpfloader_min_ver = DEFAULT_BPFLOADER_MIN_VER, \
- .bpfloader_max_ver = DEFAULT_BPFLOADER_MAX_VER, \
- .min_kver = (minkver), \
- .max_kver = (maxkver), \
- .selinux_context = (selinux), \
- .pin_subdir = (pindir), \
- .shared = (share), \
- };
+ maxkver, minloader, maxloader, ignore_eng, \
+ ignore_user, ignore_userdebug) \
+ const struct bpf_map_def SECTION("maps") the_map = { \
+ .type = BPF_MAP_TYPE_##TYPE, \
+ .key_size = (keysize), \
+ .value_size = (valuesize), \
+ .max_entries = (num_entries), \
+ .map_flags = 0, \
+ .uid = (usr), \
+ .gid = (grp), \
+ .mode = (md), \
+ .bpfloader_min_ver = (minloader), \
+ .bpfloader_max_ver = (maxloader), \
+ .min_kver = (minkver), \
+ .max_kver = (maxkver), \
+ .selinux_context = (selinux), \
+ .pin_subdir = (pindir), \
+ .shared = (share), \
+ .ignore_on_eng = (ignore_eng), \
+ .ignore_on_user = (ignore_user), \
+ .ignore_on_userdebug = (ignore_userdebug), \
+ }; \
+ BPF_ASSERT_LOADER_VERSION(minloader, ignore_eng, ignore_user, ignore_userdebug);
// Type safe macro to declare a ring buffer and related output functions.
// Compatibility:
@@ -182,22 +196,25 @@
// accessing the ring buffer should set a program level min_kver >= 5.8.
// * The definition below sets a map min_kver of 5.8 which requires targeting
// a BPFLOADER_MIN_VER >= BPFLOADER_S_VERSION.
-#define DEFINE_BPF_RINGBUF_EXT(the_map, ValueType, size_bytes, usr, grp, md, \
- selinux, pindir, share) \
- DEFINE_BPF_MAP_BASE(the_map, RINGBUF, 0, 0, size_bytes, usr, grp, md, \
- selinux, pindir, share, KVER(5, 8, 0), KVER_INF); \
- static inline __always_inline __unused int bpf_##the_map##_output( \
- const ValueType* v) { \
- return bpf_ringbuf_output_unsafe(&the_map, v, sizeof(*v), 0); \
- } \
- static inline __always_inline __unused \
- ValueType* bpf_##the_map##_reserve() { \
- return bpf_ringbuf_reserve_unsafe(&the_map, sizeof(ValueType), 0); \
- } \
- static inline __always_inline __unused void bpf_##the_map##_submit( \
- const ValueType* v) { \
- bpf_ringbuf_submit_unsafe(v, 0); \
- }
+#define DEFINE_BPF_RINGBUF_EXT(the_map, ValueType, size_bytes, usr, grp, md, \
+ selinux, pindir, share, min_loader, max_loader, \
+ ignore_eng, ignore_user, ignore_userdebug) \
+ DEFINE_BPF_MAP_BASE(the_map, RINGBUF, 0, 0, size_bytes, usr, grp, md, \
+ selinux, pindir, share, KVER(5, 8, 0), KVER_INF, \
+ min_loader, max_loader, ignore_eng, ignore_user, \
+ ignore_userdebug); \
+ static inline __always_inline __unused int bpf_##the_map##_output( \
+ const ValueType* v) { \
+ return bpf_ringbuf_output_unsafe(&the_map, v, sizeof(*v), 0); \
+ } \
+ static inline __always_inline __unused \
+ ValueType* bpf_##the_map##_reserve() { \
+ return bpf_ringbuf_reserve_unsafe(&the_map, sizeof(ValueType), 0); \
+ } \
+ static inline __always_inline __unused void bpf_##the_map##_submit( \
+ const ValueType* v) { \
+ bpf_ringbuf_submit_unsafe(v, 0); \
+ }
/* There exist buggy kernels with pre-T OS, that due to
* kernel patch "[ALPS05162612] bpf: fix ubsan error"
@@ -218,10 +235,12 @@
/* type safe macro to declare a map and related accessor functions */
#define DEFINE_BPF_MAP_EXT(the_map, TYPE, KeyType, ValueType, num_entries, usr, grp, md, \
- selinux, pindir, share) \
+ selinux, pindir, share, min_loader, max_loader, ignore_eng, \
+ ignore_user, ignore_userdebug) \
DEFINE_BPF_MAP_BASE(the_map, TYPE, sizeof(KeyType), sizeof(ValueType), \
num_entries, usr, grp, md, selinux, pindir, share, \
- KVER_NONE, KVER_INF); \
+ KVER_NONE, KVER_INF, min_loader, max_loader, \
+ ignore_eng, ignore_user, ignore_userdebug); \
BPF_MAP_ASSERT_OK(BPF_MAP_TYPE_##TYPE, (num_entries), (md)); \
BPF_ANNOTATE_KV_PAIR(the_map, KeyType, ValueType); \
\
@@ -253,9 +272,11 @@
#error "Bpf Map UID must be left at default of AID_ROOT for BpfLoader prior to v0.28"
#endif
-#define DEFINE_BPF_MAP_UGM(the_map, TYPE, KeyType, ValueType, num_entries, usr, grp, md) \
- DEFINE_BPF_MAP_EXT(the_map, TYPE, KeyType, ValueType, num_entries, usr, grp, md, \
- DEFAULT_BPF_MAP_SELINUX_CONTEXT, DEFAULT_BPF_MAP_PIN_SUBDIR, false)
+#define DEFINE_BPF_MAP_UGM(the_map, TYPE, KeyType, ValueType, num_entries, usr, grp, md) \
+ DEFINE_BPF_MAP_EXT(the_map, TYPE, KeyType, ValueType, num_entries, usr, grp, md, \
+ DEFAULT_BPF_MAP_SELINUX_CONTEXT, DEFAULT_BPF_MAP_PIN_SUBDIR, false, \
+ BPFLOADER_MIN_VER, BPFLOADER_MAX_VER, /*ignore_on_eng*/false, \
+ /*ignore_on_user*/false, /*ignore_on_userdebug*/false)
#define DEFINE_BPF_MAP(the_map, TYPE, KeyType, ValueType, num_entries) \
DEFINE_BPF_MAP_UGM(the_map, TYPE, KeyType, ValueType, num_entries, \
@@ -277,6 +298,11 @@
DEFINE_BPF_MAP_UGM(the_map, TYPE, KeyType, ValueType, num_entries, \
DEFAULT_BPF_MAP_UID, gid, 0660)
+// LLVM eBPF builtins: they directly generate BPF_LD_ABS/BPF_LD_IND (skb may be ignored?)
+unsigned long long load_byte(void* skb, unsigned long long off) asm("llvm.bpf.load.byte");
+unsigned long long load_half(void* skb, unsigned long long off) asm("llvm.bpf.load.half");
+unsigned long long load_word(void* skb, unsigned long long off) asm("llvm.bpf.load.word");
+
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 unsigned long long (*bpf_ktime_get_ns)(void) = (void*) BPF_FUNC_ktime_get_ns;
@@ -288,20 +314,24 @@
static long (*bpf_get_stackid)(void* ctx, void* map, uint64_t flags) = (void*) BPF_FUNC_get_stackid;
static long (*bpf_get_current_comm)(void* buf, uint32_t buf_size) = (void*) BPF_FUNC_get_current_comm;
-#define DEFINE_BPF_PROG_EXT(SECTION_NAME, prog_uid, prog_gid, the_prog, min_kv, max_kv, opt, \
- selinux, pindir) \
- const struct bpf_prog_def SECTION("progs") the_prog##_def = { \
- .uid = (prog_uid), \
- .gid = (prog_gid), \
- .min_kver = (min_kv), \
- .max_kver = (max_kv), \
- .optional = (opt), \
- .bpfloader_min_ver = DEFAULT_BPFLOADER_MIN_VER, \
- .bpfloader_max_ver = DEFAULT_BPFLOADER_MAX_VER, \
- .selinux_context = selinux, \
- .pin_subdir = pindir, \
- }; \
- SECTION(SECTION_NAME) \
+#define DEFINE_BPF_PROG_EXT(SECTION_NAME, prog_uid, prog_gid, the_prog, min_kv, max_kv, \
+ min_loader, max_loader, opt, selinux, pindir, ignore_eng, \
+ ignore_user, ignore_userdebug) \
+ const struct bpf_prog_def SECTION("progs") the_prog##_def = { \
+ .uid = (prog_uid), \
+ .gid = (prog_gid), \
+ .min_kver = (min_kv), \
+ .max_kver = (max_kv), \
+ .optional = (opt), \
+ .bpfloader_min_ver = (min_loader), \
+ .bpfloader_max_ver = (max_loader), \
+ .selinux_context = (selinux), \
+ .pin_subdir = (pindir), \
+ .ignore_on_eng = (ignore_eng), \
+ .ignore_on_user = (ignore_user), \
+ .ignore_on_userdebug = (ignore_userdebug), \
+ }; \
+ SECTION(SECTION_NAME) \
int the_prog
#ifndef DEFAULT_BPF_PROG_SELINUX_CONTEXT
@@ -313,9 +343,11 @@
#endif
#define DEFINE_BPF_PROG_KVER_RANGE_OPT(SECTION_NAME, prog_uid, prog_gid, the_prog, min_kv, max_kv, \
- opt) \
- DEFINE_BPF_PROG_EXT(SECTION_NAME, prog_uid, prog_gid, the_prog, min_kv, max_kv, opt, \
- DEFAULT_BPF_PROG_SELINUX_CONTEXT, DEFAULT_BPF_PROG_PIN_SUBDIR)
+ opt) \
+ DEFINE_BPF_PROG_EXT(SECTION_NAME, prog_uid, prog_gid, the_prog, min_kv, max_kv, \
+ BPFLOADER_MIN_VER, BPFLOADER_MAX_VER, opt, \
+ DEFAULT_BPF_PROG_SELINUX_CONTEXT, DEFAULT_BPF_PROG_PIN_SUBDIR, \
+ false, false, false)
// Programs (here used in the sense of functions/sections) marked optional are allowed to fail
// to load (for example due to missing kernel patches).
diff --git a/staticlibs/native/bpf_headers/include/bpf/bpf_map_def.h b/staticlibs/native/bpf_headers/include/bpf/bpf_map_def.h
index f17cf3a..d286eba 100644
--- a/staticlibs/native/bpf_headers/include/bpf/bpf_map_def.h
+++ b/staticlibs/native/bpf_headers/include/bpf/bpf_map_def.h
@@ -204,8 +204,8 @@
bool optional; // program section (ie. function) may fail to load, continue onto next func.
- // The following 3 ignore_on_* fields were added in version 0.32 (U). These are ignored in
- // older bpfloader versions, and zero in programs compiled before 0.32.
+ // The following 3 ignore_on_* fields were added in version 0.33 (U). These are ignored in
+ // older bpfloader versions, and zero in programs compiled before 0.33.
bool ignore_on_eng:1;
bool ignore_on_user:1;
bool ignore_on_userdebug:1;
diff --git a/staticlibs/native/bpf_syscall_wrappers/include/BpfSyscallWrappers.h b/staticlibs/native/bpf_syscall_wrappers/include/BpfSyscallWrappers.h
index f7d6a38..8502961 100644
--- a/staticlibs/native/bpf_syscall_wrappers/include/BpfSyscallWrappers.h
+++ b/staticlibs/native/bpf_syscall_wrappers/include/BpfSyscallWrappers.h
@@ -150,6 +150,18 @@
});
}
+// Available in 4.12 and later kernels.
+inline int runProgram(const BPF_FD_TYPE prog_fd, const void* data,
+ const uint32_t data_size) {
+ return bpf(BPF_PROG_RUN, {
+ .test = {
+ .prog_fd = BPF_FD_TO_U32(prog_fd),
+ .data_in = ptr_to_u64(data),
+ .data_size_in = data_size,
+ },
+ });
+}
+
// BPF_OBJ_GET_INFO_BY_FD requires 4.14+ kernel
//
// Note: some fields are only defined in newer kernels (ie. the map_info struct grows
diff --git a/staticlibs/native/tcutils/Android.bp b/staticlibs/native/tcutils/Android.bp
index 88a2683..84a32ed 100644
--- a/staticlibs/native/tcutils/Android.bp
+++ b/staticlibs/native/tcutils/Android.bp
@@ -20,7 +20,7 @@
name: "libtcutils",
srcs: ["tcutils.cpp"],
export_include_dirs: ["include"],
- header_libs: ["bpf_syscall_wrappers"],
+ header_libs: ["bpf_headers"],
shared_libs: [
"liblog",
],
@@ -53,7 +53,7 @@
"-Werror",
"-Wno-error=unused-variable",
],
- header_libs: ["bpf_syscall_wrappers"],
+ header_libs: ["bpf_headers"],
static_libs: [
"libgmock",
"libtcutils",
diff --git a/staticlibs/native/tcutils/kernelversion.h b/staticlibs/native/tcutils/kernelversion.h
deleted file mode 100644
index 9aab31d..0000000
--- a/staticlibs/native/tcutils/kernelversion.h
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * Copyright (C) 2022 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.
- */
-
-// -----------------------------------------------------------------------------
-// TODO - This should be replaced with BpfUtils in bpf_headers.
-// Currently, bpf_headers contains a bunch requirements it doesn't actually provide, such as a
-// non-ndk liblog version, and some version of libbase. libtcutils does not have access to either of
-// these, so I think this will have to wait until we figure out a way around this.
-//
-// In the mean time copying verbatim from:
-// frameworks/libs/net/common/native/bpf_headers
-
-#pragma once
-
-#include <stdio.h>
-#include <sys/utsname.h>
-
-#define KVER(a, b, c) (((a) << 24) + ((b) << 16) + (c))
-
-namespace android {
-
-static inline unsigned uncachedKernelVersion() {
- struct utsname buf;
- if (uname(&buf)) return 0;
-
- unsigned kver_major = 0;
- unsigned kver_minor = 0;
- unsigned kver_sub = 0;
- (void)sscanf(buf.release, "%u.%u.%u", &kver_major, &kver_minor, &kver_sub);
- return KVER(kver_major, kver_minor, kver_sub);
-}
-
-static unsigned kernelVersion() {
- static unsigned kver = uncachedKernelVersion();
- return kver;
-}
-
-static inline bool isAtLeastKernelVersion(unsigned major, unsigned minor,
- unsigned sub) {
- return kernelVersion() >= KVER(major, minor, sub);
-}
-
-} // namespace android
diff --git a/staticlibs/native/tcutils/tcutils.cpp b/staticlibs/native/tcutils/tcutils.cpp
index 4101885..37a7ec8 100644
--- a/staticlibs/native/tcutils/tcutils.cpp
+++ b/staticlibs/native/tcutils/tcutils.cpp
@@ -19,7 +19,7 @@
#include "tcutils/tcutils.h"
#include "logging.h"
-#include "kernelversion.h"
+#include "bpf/KernelVersion.h"
#include "scopeguard.h"
#include <arpa/inet.h>
@@ -504,7 +504,7 @@
// shipped with Android S, so (for safety) let's limit ourselves to
// >5.10, ie. 5.11+ as a guarantee we're on Android T+ and thus no
// longer need this non-upstream compatibility logic
- static bool is_pre_5_11_kernel = !isAtLeastKernelVersion(5, 11, 0);
+ static bool is_pre_5_11_kernel = !bpf::isAtLeastKernelVersion(5, 11, 0);
if (is_pre_5_11_kernel)
return false;
}
diff --git a/staticlibs/native/tcutils/tests/tcutils_test.cpp b/staticlibs/native/tcutils/tests/tcutils_test.cpp
index 3a89696..53835d7 100644
--- a/staticlibs/native/tcutils/tests/tcutils_test.cpp
+++ b/staticlibs/native/tcutils/tests/tcutils_test.cpp
@@ -18,7 +18,7 @@
#include <gtest/gtest.h>
-#include "kernelversion.h"
+#include "bpf/KernelVersion.h"
#include <tcutils/tcutils.h>
#include <BpfSyscallWrappers.h>
@@ -82,7 +82,7 @@
// TODO: this should likely be in the tethering module, where using netd.h would be ok
static constexpr char bpfProgPath[] =
"/sys/fs/bpf/tethering/prog_offload_schedcls_tether_downstream6_ether";
- const int errNOENT = isAtLeastKernelVersion(4, 19, 0) ? ENOENT : EINVAL;
+ const int errNOENT = bpf::isAtLeastKernelVersion(4, 19, 0) ? ENOENT : EINVAL;
// static test values
static constexpr bool ingress = true;
@@ -118,7 +118,7 @@
ASSERT_LE(3, fd);
close(fd);
- const int errNOENT = isAtLeastKernelVersion(4, 19, 0) ? ENOENT : EINVAL;
+ const int errNOENT = bpf::isAtLeastKernelVersion(4, 19, 0) ? ENOENT : EINVAL;
// static test values
static constexpr unsigned rateInBytesPerSec =
diff --git a/staticlibs/tests/unit/Android.bp b/staticlibs/tests/unit/Android.bp
index 21e8c64..6e223bd 100644
--- a/staticlibs/tests/unit/Android.bp
+++ b/staticlibs/tests/unit/Android.bp
@@ -12,14 +12,15 @@
min_sdk_version: "29",
defaults: ["framework-connectivity-test-defaults"],
static_libs: [
- "net-utils-framework-common",
- "net-utils-device-common-ip",
"androidx.test.rules",
"mockito-target-extended-minus-junit4",
- "net-utils-device-common",
- "net-utils-device-common-bpf",
- "net-tests-utils",
"netd-client",
+ "net-tests-utils",
+ "net-utils-framework-common",
+ "net-utils-device-common",
+ "net-utils-device-common-async",
+ "net-utils-device-common-bpf",
+ "net-utils-device-common-ip",
],
libs: [
"android.test.runner",
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/BitUtilsTests.kt b/staticlibs/tests/unit/src/com/android/net/module/util/BitUtilsTests.kt
index 0236716..49940ea 100644
--- a/staticlibs/tests/unit/src/com/android/net/module/util/BitUtilsTests.kt
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/BitUtilsTests.kt
@@ -17,11 +17,13 @@
package com.android.net.module.util
import com.android.net.module.util.BitUtils.appendStringRepresentationOfBitMaskToStringBuilder
+import com.android.net.module.util.BitUtils.describeDifferences
import com.android.net.module.util.BitUtils.packBits
import com.android.net.module.util.BitUtils.unpackBits
-import org.junit.Test
import kotlin.test.assertEquals
+import kotlin.test.assertNull
import kotlin.test.assertTrue
+import org.junit.Test
class BitUtilsTests {
@Test
@@ -58,4 +60,23 @@
assertEquals(expected, it.toString())
}
}
+
+ @Test
+ fun testDescribeDifferences() {
+ fun describe(a: Long, b: Long) = describeDifferences(a, b, Integer::toString)
+ assertNull(describe(0, 0))
+ assertNull(describe(5, 5))
+ assertNull(describe(Long.MAX_VALUE, Long.MAX_VALUE))
+
+ assertEquals("+0", describe(0, 1))
+ assertEquals("-0", describe(1, 0))
+
+ assertEquals("+0+2", describe(0, 5))
+ assertEquals("+2", describe(1, 5))
+ assertEquals("-0+2", describe(1, 4))
+
+ fun makeField(vararg i: Int) = i.sumOf { 1L shl it }
+ assertEquals("-0-4-6-9+1+3+11", describe(makeField(0, 4, 6, 9), makeField(1, 3, 11)))
+ assertEquals("-1-5-9+6+8", describe(makeField(0, 1, 3, 4, 5, 9), makeField(0, 3, 4, 6, 8)))
+ }
}
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/async/CircularByteBufferTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/async/CircularByteBufferTest.java
new file mode 100644
index 0000000..01abee2
--- /dev/null
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/async/CircularByteBufferTest.java
@@ -0,0 +1,267 @@
+/*
+ * Copyright (C) 2022 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.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.fail;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class CircularByteBufferTest {
+ @Test
+ public void writeBytes() {
+ final int capacity = 23;
+ CircularByteBuffer buffer = new CircularByteBuffer(capacity);
+ assertEquals(0, buffer.size());
+ assertEquals(0, buffer.getDirectReadSize());
+ assertEquals(capacity, buffer.freeSize());
+ assertEquals(capacity, buffer.getDirectWriteSize());
+
+ final byte[] writeBuffer = new byte[15];
+ buffer.writeBytes(writeBuffer, 0, writeBuffer.length);
+
+ assertEquals(writeBuffer.length, buffer.size());
+ assertEquals(writeBuffer.length, buffer.getDirectReadSize());
+ assertEquals(capacity - writeBuffer.length, buffer.freeSize());
+ assertEquals(capacity - writeBuffer.length, buffer.getDirectWriteSize());
+
+ buffer.clear();
+ assertEquals(0, buffer.size());
+ assertEquals(0, buffer.getDirectReadSize());
+ assertEquals(capacity, buffer.freeSize());
+ assertEquals(capacity, buffer.getDirectWriteSize());
+ }
+
+ @Test
+ public void writeBytes_withRollover() {
+ doTestReadWriteWithRollover(new BufferAccessor(false, false));
+ }
+
+ @Test
+ public void writeBytes_withFullBuffer() {
+ doTestReadWriteWithFullBuffer(new BufferAccessor(false, false));
+ }
+
+ @Test
+ public void directWriteBytes_withRollover() {
+ doTestReadWriteWithRollover(new BufferAccessor(true, true));
+ }
+
+ @Test
+ public void directWriteBytes_withFullBuffer() {
+ doTestReadWriteWithFullBuffer(new BufferAccessor(true, true));
+ }
+
+ private void doTestReadWriteWithFullBuffer(BufferAccessor accessor) {
+ CircularByteBuffer buffer = doTestReadWrite(accessor, 20, 5, 4);
+
+ assertEquals(0, buffer.size());
+ assertEquals(20, buffer.freeSize());
+ }
+
+ private void doTestReadWriteWithRollover(BufferAccessor accessor) {
+ // All buffer sizes are prime numbers to ensure that some read or write
+ // operations will roll over the end of the internal buffer.
+ CircularByteBuffer buffer = doTestReadWrite(accessor, 31, 13, 7);
+
+ assertNotEquals(0, buffer.size());
+ }
+
+ private CircularByteBuffer doTestReadWrite(BufferAccessor accessor,
+ final int capacity, final int writeLen, final int readLen) {
+ CircularByteBuffer buffer = new CircularByteBuffer(capacity);
+
+ final byte[] writeBuffer = new byte[writeLen + 2];
+ final byte[] peekBuffer = new byte[readLen + 2];
+ final byte[] readBuffer = new byte[readLen + 2];
+
+ final int numIterations = 1011;
+ final int maxRemaining = readLen - 1;
+
+ int currentWriteSymbol = 0;
+ int expectedReadSymbol = 0;
+ int expectedSize = 0;
+ int totalWritten = 0;
+ int totalRead = 0;
+
+ for (int i = 0; i < numIterations; i++) {
+ // Fill in with write buffers as much as possible.
+ while (buffer.freeSize() >= writeLen) {
+ currentWriteSymbol = fillTestBytes(writeBuffer, 1, writeLen, currentWriteSymbol);
+ accessor.writeBytes(buffer, writeBuffer, 1, writeLen);
+
+ expectedSize += writeLen;
+ totalWritten += writeLen;
+ assertEquals(expectedSize, buffer.size());
+ assertEquals(capacity - expectedSize, buffer.freeSize());
+ }
+
+ // Keep reading into read buffers while there's still data.
+ while (buffer.size() >= readLen) {
+ peekBuffer[1] = 0;
+ peekBuffer[2] = 0;
+ buffer.peekBytes(2, peekBuffer, 3, readLen - 2);
+ assertEquals(0, peekBuffer[1]);
+ assertEquals(0, peekBuffer[2]);
+
+ peekBuffer[2] = buffer.peek(1);
+
+ accessor.readBytes(buffer, readBuffer, 1, readLen);
+ peekBuffer[1] = readBuffer[1];
+
+ expectedReadSymbol = checkTestBytes(
+ readBuffer, 1, readLen, expectedReadSymbol, totalRead);
+
+ assertArrayEquals(peekBuffer, readBuffer);
+
+ expectedSize -= readLen;
+ totalRead += readLen;
+ assertEquals(expectedSize, buffer.size());
+ assertEquals(capacity - expectedSize, buffer.freeSize());
+ }
+
+ if (buffer.size() > maxRemaining) {
+ fail("Too much data remaining: " + buffer.size());
+ }
+ }
+
+ final int maxWritten = capacity * numIterations;
+ final int minWritten = maxWritten / 2;
+ if (totalWritten < minWritten || totalWritten > maxWritten
+ || (totalWritten - totalRead) > maxRemaining) {
+ fail("Unexpected counts: read=" + totalRead + ", written=" + totalWritten
+ + ", minWritten=" + minWritten + ", maxWritten=" + maxWritten);
+ }
+
+ return buffer;
+ }
+
+ @Test
+ public void readBytes_overflow() {
+ CircularByteBuffer buffer = new CircularByteBuffer(23);
+
+ final byte[] dataBuffer = new byte[15];
+ buffer.writeBytes(dataBuffer, 0, dataBuffer.length - 2);
+
+ try {
+ buffer.readBytes(dataBuffer, 0, dataBuffer.length);
+ assertTrue(false);
+ } catch (IllegalArgumentException e) {
+ // expected
+ }
+
+ assertEquals(13, buffer.size());
+ assertEquals(10, buffer.freeSize());
+ }
+
+ @Test
+ public void writeBytes_overflow() {
+ CircularByteBuffer buffer = new CircularByteBuffer(23);
+
+ final byte[] dataBuffer = new byte[15];
+ buffer.writeBytes(dataBuffer, 0, dataBuffer.length);
+
+ try {
+ buffer.writeBytes(dataBuffer, 0, dataBuffer.length);
+ assertTrue(false);
+ } catch (IllegalArgumentException e) {
+ // expected
+ }
+
+ assertEquals(15, buffer.size());
+ assertEquals(8, buffer.freeSize());
+ }
+
+ private static int fillTestBytes(byte[] buffer, int pos, int len, int startValue) {
+ for (int i = 0; i < len; i++) {
+ buffer[pos + i] = (byte) (startValue & 0xFF);
+ startValue = (startValue + 1) % 256;
+ }
+ return startValue;
+ }
+
+ private static int checkTestBytes(
+ byte[] buffer, int pos, int len, int startValue, int totalRead) {
+ for (int i = 0; i < len; i++) {
+ byte expectedValue = (byte) (startValue & 0xFF);
+ if (expectedValue != buffer[pos + i]) {
+ fail("Unexpected byte=" + (((int) buffer[pos + i]) & 0xFF)
+ + ", expected=" + (((int) expectedValue) & 0xFF)
+ + ", pos=" + (totalRead + i));
+ }
+ startValue = (startValue + 1) % 256;
+ }
+ return startValue;
+ }
+
+ private static final class BufferAccessor {
+ private final boolean mDirectRead;
+ private final boolean mDirectWrite;
+
+ BufferAccessor(boolean directRead, boolean directWrite) {
+ mDirectRead = directRead;
+ mDirectWrite = directWrite;
+ }
+
+ void writeBytes(CircularByteBuffer buffer, byte[] src, int pos, int len) {
+ if (mDirectWrite) {
+ while (len > 0) {
+ if (buffer.getDirectWriteSize() == 0) {
+ fail("Direct write size is zero: free=" + buffer.freeSize()
+ + ", size=" + buffer.size());
+ }
+ int copyLen = Math.min(len, buffer.getDirectWriteSize());
+ System.arraycopy(src, pos, buffer.getDirectWriteBuffer(),
+ buffer.getDirectWritePos(), copyLen);
+ buffer.accountForDirectWrite(copyLen);
+ len -= copyLen;
+ pos += copyLen;
+ }
+ } else {
+ buffer.writeBytes(src, pos, len);
+ }
+ }
+
+ void readBytes(CircularByteBuffer buffer, byte[] dst, int pos, int len) {
+ if (mDirectRead) {
+ while (len > 0) {
+ if (buffer.getDirectReadSize() == 0) {
+ fail("Direct read size is zero: free=" + buffer.freeSize()
+ + ", size=" + buffer.size());
+ }
+ int copyLen = Math.min(len, buffer.getDirectReadSize());
+ System.arraycopy(
+ buffer.getDirectReadBuffer(), buffer.getDirectReadPos(), dst, pos, copyLen);
+ buffer.accountForDirectRead(copyLen);
+ len -= copyLen;
+ pos += copyLen;
+ }
+ } else {
+ buffer.readBytes(dst, pos, len);
+ }
+ }
+ }
+}
diff --git a/staticlibs/tests/unit/src/com/android/testutils/HandlerUtilsTest.kt b/staticlibs/tests/unit/src/com/android/testutils/HandlerUtilsTest.kt
new file mode 100644
index 0000000..30e0daf
--- /dev/null
+++ b/staticlibs/tests/unit/src/com/android/testutils/HandlerUtilsTest.kt
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * 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
+
+import android.os.Handler
+import android.os.HandlerThread
+import kotlin.test.assertEquals
+import kotlin.test.assertFailsWith
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+private const val ATTEMPTS = 50 // Causes testWaitForIdle to take about 150ms on aosp_crosshatch-eng
+private const val TIMEOUT_MS = 200
+
+@RunWith(JUnit4::class)
+class HandlerUtilsTest {
+ @Test
+ fun testWaitForIdle() {
+ val handlerThread = HandlerThread("testHandler").apply { start() }
+
+ // Tests that waitForIdle can be called many times without ill impact if the service is
+ // already idle.
+ repeat(ATTEMPTS) {
+ handlerThread.waitForIdle(TIMEOUT_MS)
+ }
+
+ // Tests that calling waitForIdle waits for messages to be processed. Use both an
+ // inline runnable that's instantiated at each loop run and a runnable that's instantiated
+ // once for all.
+ val tempRunnable = object : Runnable {
+ // Use StringBuilder preferentially to StringBuffer because StringBuilder is NOT
+ // thread-safe. It's part of the point that both runnables run on the same thread
+ // so if anything is wrong in that space it's better to opportunistically use a class
+ // where things might go wrong, even if there is no guarantee of failure.
+ var memory = StringBuilder()
+ override fun run() {
+ memory.append("b")
+ }
+ }
+ repeat(ATTEMPTS) { i ->
+ handlerThread.threadHandler.post { tempRunnable.memory.append("a"); }
+ handlerThread.threadHandler.post(tempRunnable)
+ handlerThread.waitForIdle(TIMEOUT_MS)
+ assertEquals(tempRunnable.memory.toString(), "ab".repeat(i + 1))
+ }
+ }
+
+ // Statistical test : even if visibleOnHandlerThread doesn't work this is likely to succeed,
+ // but it will be at least flaky.
+ @Test
+ fun testVisibleOnHandlerThread() {
+ val handlerThread = HandlerThread("testHandler").apply { start() }
+ val handler = Handler(handlerThread.looper)
+
+ repeat(ATTEMPTS) { attempt ->
+ var x = -10
+ visibleOnHandlerThread(handler) { x = attempt }
+ assertEquals(attempt, x)
+ handler.post { assertEquals(attempt, x) }
+ }
+
+ assertFailsWith<IllegalArgumentException> {
+ visibleOnHandlerThread(handler) { throw IllegalArgumentException() }
+ }
+ }
+}
diff --git a/staticlibs/tests/unit/src/com/android/testutils/TestableNetworkCallbackTest.kt b/staticlibs/tests/unit/src/com/android/testutils/TestableNetworkCallbackTest.kt
index f8f2da0..ec7cdbd 100644
--- a/staticlibs/tests/unit/src/com/android/testutils/TestableNetworkCallbackTest.kt
+++ b/staticlibs/tests/unit/src/com/android/testutils/TestableNetworkCallbackTest.kt
@@ -22,29 +22,30 @@
import android.net.Network
import android.net.NetworkCapabilities
import com.android.testutils.RecorderCallback.CallbackEntry
+import com.android.testutils.RecorderCallback.CallbackEntry.Available
+import com.android.testutils.RecorderCallback.CallbackEntry.BlockedStatus
+import com.android.testutils.RecorderCallback.CallbackEntry.CapabilitiesChanged
import com.android.testutils.RecorderCallback.CallbackEntry.Companion.AVAILABLE
import com.android.testutils.RecorderCallback.CallbackEntry.Companion.BLOCKED_STATUS
import com.android.testutils.RecorderCallback.CallbackEntry.Companion.LINK_PROPERTIES_CHANGED
import com.android.testutils.RecorderCallback.CallbackEntry.Companion.LOSING
-import com.android.testutils.RecorderCallback.CallbackEntry.Companion.NETWORK_CAPS_UPDATED
import com.android.testutils.RecorderCallback.CallbackEntry.Companion.LOST
+import com.android.testutils.RecorderCallback.CallbackEntry.Companion.NETWORK_CAPS_UPDATED
import com.android.testutils.RecorderCallback.CallbackEntry.Companion.RESUMED
import com.android.testutils.RecorderCallback.CallbackEntry.Companion.SUSPENDED
import com.android.testutils.RecorderCallback.CallbackEntry.Companion.UNAVAILABLE
-import com.android.testutils.RecorderCallback.CallbackEntry.Available
-import com.android.testutils.RecorderCallback.CallbackEntry.BlockedStatus
-import com.android.testutils.RecorderCallback.CallbackEntry.CapabilitiesChanged
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-import org.junit.Assume.assumeTrue
+import com.android.testutils.RecorderCallback.CallbackEntry.LinkPropertiesChanged
import kotlin.reflect.KClass
import kotlin.test.assertEquals
import kotlin.test.assertFails
import kotlin.test.assertNull
import kotlin.test.assertTrue
import kotlin.test.fail
+import org.junit.Assume.assumeTrue
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
const val SHORT_TIMEOUT_MS = 20L
const val DEFAULT_LINGER_DELAY_MS = 30000
@@ -121,20 +122,16 @@
mCallback.assertNoCallback(SHORT_TIMEOUT_MS)
mCallback.onAvailable(Network(100))
assertFails { mCallback.assertNoCallback(SHORT_TIMEOUT_MS) }
- }
-
- @Test
- fun testAssertNoCallbackThat() {
val net = Network(101)
- mCallback.assertNoCallbackThat { it is Available }
+ mCallback.assertNoCallback { it is Available }
mCallback.onAvailable(net)
// Expect no blocked status change. Receive other callback does not fail the test.
- mCallback.assertNoCallbackThat { it is BlockedStatus }
+ mCallback.assertNoCallback { it is BlockedStatus }
mCallback.onBlockedStatusChanged(net, true)
- assertFails { mCallback.assertNoCallbackThat { it is BlockedStatus } }
+ assertFails { mCallback.assertNoCallback { it is BlockedStatus } }
mCallback.onBlockedStatusChanged(net, false)
mCallback.onCapabilitiesChanged(net, NetworkCapabilities())
- assertFails { mCallback.assertNoCallbackThat { it is CapabilitiesChanged } }
+ assertFails { mCallback.assertNoCallback { it is CapabilitiesChanged } }
}
@Test
@@ -144,20 +141,28 @@
val meteredNc = NetworkCapabilities()
val unmeteredNc = NetworkCapabilities().addCapability(NOT_METERED)
// Check that expecting caps (with or without) fails when no callback has been received.
- assertFails { mCallback.expectCapabilitiesWith(NOT_METERED, matcher, SHORT_TIMEOUT_MS) }
- assertFails { mCallback.expectCapabilitiesWithout(NOT_METERED, matcher, SHORT_TIMEOUT_MS) }
+ assertFails {
+ mCallback.expectCaps(matcher, SHORT_TIMEOUT_MS) { it.hasCapability(NOT_METERED) }
+ }
+ assertFails {
+ mCallback.expectCaps(matcher, SHORT_TIMEOUT_MS) { !it.hasCapability(NOT_METERED) }
+ }
// Add NOT_METERED and check that With succeeds and Without fails.
mCallback.onCapabilitiesChanged(net, unmeteredNc)
- mCallback.expectCapabilitiesWith(NOT_METERED, matcher)
+ mCallback.expectCaps(matcher) { it.hasCapability(NOT_METERED) }
mCallback.onCapabilitiesChanged(net, unmeteredNc)
- assertFails { mCallback.expectCapabilitiesWithout(NOT_METERED, matcher, SHORT_TIMEOUT_MS) }
+ assertFails {
+ mCallback.expectCaps(matcher, SHORT_TIMEOUT_MS) { !it.hasCapability(NOT_METERED) }
+ }
// Don't add NOT_METERED and check that With fails and Without succeeds.
mCallback.onCapabilitiesChanged(net, meteredNc)
- assertFails { mCallback.expectCapabilitiesWith(NOT_METERED, matcher, SHORT_TIMEOUT_MS) }
+ assertFails {
+ mCallback.expectCaps(matcher, SHORT_TIMEOUT_MS) { it.hasCapability(NOT_METERED) }
+ }
mCallback.onCapabilitiesChanged(net, meteredNc)
- mCallback.expectCapabilitiesWithout(NOT_METERED, matcher)
+ mCallback.expectCaps(matcher) { !it.hasCapability(NOT_METERED) }
}
@Test
@@ -183,37 +188,35 @@
}
@Test
- fun testCapabilitiesThat() {
+ fun testExpectCaps() {
val net = Network(101)
val netCaps = NetworkCapabilities().addCapability(NOT_METERED).addTransportType(WIFI)
// Check that expecting capabilitiesThat anything fails when no callback has been received.
- assertFails { mCallback.expectCapabilitiesThat(net, SHORT_TIMEOUT_MS) { true } }
+ assertFails { mCallback.expectCaps(net, SHORT_TIMEOUT_MS) { true } }
// Basic test for true and false
mCallback.onCapabilitiesChanged(net, netCaps)
- mCallback.expectCapabilitiesThat(net) { true }
+ mCallback.expectCaps(net) { true }
mCallback.onCapabilitiesChanged(net, netCaps)
- assertFails { mCallback.expectCapabilitiesThat(net, SHORT_TIMEOUT_MS) { false } }
+ assertFails { mCallback.expectCaps(net, SHORT_TIMEOUT_MS) { false } }
// Try a positive and a negative case
mCallback.onCapabilitiesChanged(net, netCaps)
- mCallback.expectCapabilitiesThat(net) { caps ->
- caps.hasCapability(NOT_METERED) &&
- caps.hasTransport(WIFI) &&
- !caps.hasTransport(CELLULAR)
+ mCallback.expectCaps(net) {
+ it.hasCapability(NOT_METERED) && it.hasTransport(WIFI) && !it.hasTransport(CELLULAR)
}
mCallback.onCapabilitiesChanged(net, netCaps)
- assertFails { mCallback.expectCapabilitiesThat(net, SHORT_TIMEOUT_MS) { caps ->
- caps.hasTransport(CELLULAR)
- } }
+ assertFails { mCallback.expectCaps(net, SHORT_TIMEOUT_MS) { it.hasTransport(CELLULAR) } }
// Try a matching callback on the wrong network
mCallback.onCapabilitiesChanged(net, netCaps)
- assertFails { mCallback.expectCapabilitiesThat(Network(100), SHORT_TIMEOUT_MS) { true } }
+ assertFails {
+ mCallback.expectCaps(Network(100), SHORT_TIMEOUT_MS) { true }
+ }
}
@Test
- fun testLinkPropertiesThat() {
+ fun testLinkPropertiesCallbacks() {
val net = Network(112)
val linkAddress = LinkAddress("fe80::ace:d00d/64")
val mtu = 1984
@@ -224,30 +227,30 @@
}
// Check that expecting linkPropsThat anything fails when no callback has been received.
- assertFails { mCallback.expectLinkPropertiesThat(net, SHORT_TIMEOUT_MS) { true } }
+ assertFails { mCallback.expect<LinkPropertiesChanged>(net, SHORT_TIMEOUT_MS) { true } }
// Basic test for true and false
mCallback.onLinkPropertiesChanged(net, linkProps)
- mCallback.expectLinkPropertiesThat(net) { true }
+ mCallback.expect<LinkPropertiesChanged>(net) { true }
mCallback.onLinkPropertiesChanged(net, linkProps)
- assertFails { mCallback.expectLinkPropertiesThat(net, SHORT_TIMEOUT_MS) { false } }
+ assertFails { mCallback.expect<LinkPropertiesChanged>(net, SHORT_TIMEOUT_MS) { false } }
// Try a positive and negative case
mCallback.onLinkPropertiesChanged(net, linkProps)
- mCallback.expectLinkPropertiesThat(net) { lp ->
- lp.interfaceName == TEST_INTERFACE_NAME &&
- lp.linkAddresses.contains(linkAddress) &&
- lp.mtu == mtu
+ mCallback.expect<LinkPropertiesChanged>(net) {
+ it.lp.interfaceName == TEST_INTERFACE_NAME &&
+ it.lp.linkAddresses.contains(linkAddress) &&
+ it.lp.mtu == mtu
}
mCallback.onLinkPropertiesChanged(net, linkProps)
- assertFails { mCallback.expectLinkPropertiesThat(net, SHORT_TIMEOUT_MS) { lp ->
- lp.interfaceName != TEST_INTERFACE_NAME
+ assertFails { mCallback.expect<LinkPropertiesChanged>(net, SHORT_TIMEOUT_MS) {
+ it.lp.interfaceName != TEST_INTERFACE_NAME
} }
// Try a matching callback on the wrong network
mCallback.onLinkPropertiesChanged(net, linkProps)
- assertFails { mCallback.expectLinkPropertiesThat(Network(114), SHORT_TIMEOUT_MS) { lp ->
- lp.interfaceName == TEST_INTERFACE_NAME
+ assertFails { mCallback.expect<LinkPropertiesChanged>(Network(114), SHORT_TIMEOUT_MS) {
+ it.lp.interfaceName == TEST_INTERFACE_NAME
} }
}
diff --git a/staticlibs/testutils/Android.bp b/staticlibs/testutils/Android.bp
index c9b3d07..bcf89b3 100644
--- a/staticlibs/testutils/Android.bp
+++ b/staticlibs/testutils/Android.bp
@@ -36,6 +36,7 @@
"libnanohttpd",
"net-tests-utils-host-device-common",
"net-utils-device-common",
+ "net-utils-device-common-async",
"net-utils-device-common-netlink",
"modules-utils-build_system",
],
diff --git a/staticlibs/testutils/devicetests/com/android/testutils/HandlerUtils.kt b/staticlibs/testutils/devicetests/com/android/testutils/HandlerUtils.kt
index 861f45e..aa252a5 100644
--- a/staticlibs/testutils/devicetests/com/android/testutils/HandlerUtils.kt
+++ b/staticlibs/testutils/devicetests/com/android/testutils/HandlerUtils.kt
@@ -21,9 +21,14 @@
import android.os.ConditionVariable
import android.os.Handler
import android.os.HandlerThread
+import android.util.Log
+import com.android.testutils.FunctionalUtils.ThrowingRunnable
+import java.lang.Exception
import java.util.concurrent.Executor
import kotlin.test.fail
+private const val TAG = "HandlerUtils"
+
/**
* Block until the specified Handler or HandlerThread becomes idle, or until timeoutMs has passed.
*/
@@ -48,3 +53,31 @@
fail("Executor did not become idle after ${timeoutMs}ms")
}
}
+
+/**
+ * Executes a block of code, making its side effects visible on the caller and the handler thread
+ *
+ * After this function returns, the side effects of the passed block of code are guaranteed to be
+ * observed both on the thread running the handler and on the thread running this method.
+ * To achieve this, this method runs the passed block on the handler and blocks this thread
+ * until it's executed, so keep in mind this method will block, (including, if the handler isn't
+ * running, blocking forever).
+ */
+fun visibleOnHandlerThread(handler: Handler, r: ThrowingRunnable) {
+ val cv = ConditionVariable()
+ var e: Exception? = null
+ handler.post {
+ try {
+ r.run()
+ } catch (exception: Exception) {
+ Log.e(TAG, "visibleOnHandlerThread caught exception", exception)
+ e = exception
+ }
+ cv.open()
+ }
+ // After block() returns, the handler thread has seen the change (since it ran it)
+ // and this thread also has seen the change (since cv.open() happens-before cv.block()
+ // returns).
+ cv.block()
+ e?.let { throw it }
+}
diff --git a/staticlibs/testutils/devicetests/com/android/testutils/TestableNetworkCallback.kt b/staticlibs/testutils/devicetests/com/android/testutils/TestableNetworkCallback.kt
index 124d134..485799c 100644
--- a/staticlibs/testutils/devicetests/com/android/testutils/TestableNetworkCallback.kt
+++ b/staticlibs/testutils/devicetests/com/android/testutils/TestableNetworkCallback.kt
@@ -83,7 +83,7 @@
) : CallbackEntry()
data class BlockedStatusInt(
override val network: Network,
- val blocked: Int
+ val reason: Int
) : CallbackEntry()
// Convenience constants for expecting a type
companion object {
@@ -167,20 +167,43 @@
private const val DEFAULT_TIMEOUT = 30_000L // ms
private const val DEFAULT_NO_CALLBACK_TIMEOUT = 200L // ms
+private val NOOP = Runnable {}
+/**
+ * See comments on the public constructor below for a description of the arguments.
+ */
open class TestableNetworkCallback private constructor(
src: TestableNetworkCallback?,
val defaultTimeoutMs: Long = DEFAULT_TIMEOUT,
- val defaultNoCallbackTimeoutMs: Long = DEFAULT_NO_CALLBACK_TIMEOUT
+ val defaultNoCallbackTimeoutMs: Long = DEFAULT_NO_CALLBACK_TIMEOUT,
+ val waiterFunc: Runnable = NOOP // "() -> Unit" would forbid calling with a void func from Java
) : RecorderCallback(src) {
+ /**
+ * Construct a testable network callback.
+ * @param timeoutMs the default timeout for expecting a callback. Default 30 seconds. This
+ * should be long in most cases, because the success case doesn't incur
+ * the wait.
+ * @param noCallbackTimeoutMs the timeout for expecting that no callback is received. Default
+ * 200ms. Because the success case does incur the timeout, this
+ * should be short in most cases, but not so short as to frequently
+ * time out before an incorrect callback is received.
+ * @param waiterFunc a function to use before asserting no callback. For some specific tests,
+ * it is useful to run test-specific code before asserting no callback to
+ * increase the likelihood that a spurious callback is correctly detected.
+ * As an example, a unit test using mock loopers may want to use this to
+ * make sure the loopers are drained before asserting no callback, since
+ * one of them may cause a callback to be called. @see ConnectivityServiceTest
+ * for such an example.
+ */
@JvmOverloads
constructor(
timeoutMs: Long = DEFAULT_TIMEOUT,
- noCallbackTimeoutMs: Long = DEFAULT_NO_CALLBACK_TIMEOUT
- ) : this(null, timeoutMs, noCallbackTimeoutMs)
+ noCallbackTimeoutMs: Long = DEFAULT_NO_CALLBACK_TIMEOUT,
+ waiterFunc: Runnable = NOOP
+ ) : this(null, timeoutMs, noCallbackTimeoutMs, waiterFunc)
fun createLinkedCopy() = TestableNetworkCallback(
- this, defaultTimeoutMs, defaultNoCallbackTimeoutMs)
+ this, defaultTimeoutMs, defaultNoCallbackTimeoutMs, waiterFunc)
// The last available network, or null if any network was lost since the last call to
// onAvailable. TODO : fix this by fixing the tests that rely on this behavior
@@ -343,25 +366,18 @@
test: (T) -> Boolean = { true }
) = expect(network.network, timeoutMs, errorMsg, test)
- // Make open for use in ConnectivityServiceTest which is the only one knowing its handlers.
- // TODO : remove the necessity to overload this, remove the open qualifier, and give a
- // default argument to assertNoCallback instead, possibly with @JvmOverloads if necessary.
- open fun assertNoCallback() = assertNoCallback(defaultNoCallbackTimeoutMs)
-
- fun assertNoCallback(timeoutMs: Long) {
- val cb = history.poll(timeoutMs)
- if (null != cb) fail("Expected no callback but got $cb")
- }
-
- fun assertNoCallbackThat(
+ @JvmOverloads
+ fun assertNoCallback(
timeoutMs: Long = defaultNoCallbackTimeoutMs,
- valid: (CallbackEntry) -> Boolean
+ valid: (CallbackEntry) -> Boolean = { true }
) {
- val cb = history.poll(timeoutMs) { valid(it) }.let {
- if (null != it) fail("Expected no callback but got $it")
- }
+ waiterFunc.run()
+ history.poll(timeoutMs) { valid(it) }?.let { fail("Expected no callback but got $it") }
}
+ 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.
@@ -398,20 +414,6 @@
crossinline predicate: (T) -> Boolean = { true }
) = history.poll(timeoutMs, from) { it is T && predicate(it) } as T?
- inline fun expectCapabilitiesThat(
- net: Network,
- tmt: Long = defaultTimeoutMs,
- valid: (NetworkCapabilities) -> Boolean
- ): CapabilitiesChanged =
- expect(net, tmt, "Capabilities don't match expectations") { valid(it.caps) }
-
- inline fun expectLinkPropertiesThat(
- net: Network,
- tmt: Long = defaultTimeoutMs,
- valid: (LinkProperties) -> Boolean
- ): LinkPropertiesChanged =
- expect(net, tmt, "LinkProperties don't match expectations") { valid(it.lp) }
-
// Expects onAvailable and the callbacks that follow it. These are:
// - onSuspended, iff the network was suspended when the callbacks fire.
// - onCapabilitiesChanged.
@@ -432,18 +434,18 @@
tmt: Long = defaultTimeoutMs
) {
expectAvailableCallbacksCommon(net, suspended, validated, tmt)
- expectBlockedStatusCallback(blocked, net, tmt)
+ expect<BlockedStatus>(net, tmt) { it.blocked == blocked }
}
fun expectAvailableCallbacks(
net: Network,
suspended: Boolean,
validated: Boolean,
- blockedStatus: Int,
+ blockedReason: Int,
tmt: Long
) {
expectAvailableCallbacksCommon(net, suspended, validated, tmt)
- expectBlockedStatusCallback(blockedStatus, net)
+ expect<BlockedStatusInt>(net) { it.reason == blockedReason }
}
private fun expectAvailableCallbacksCommon(
@@ -456,10 +458,8 @@
if (suspended) {
expect<Suspended>(net, tmt)
}
- expectCapabilitiesThat(net, tmt) {
- validated == null || validated == it.hasCapability(
- NET_CAPABILITY_VALIDATED
- )
+ expect<CapabilitiesChanged>(net, tmt) {
+ validated == null || validated == it.caps.hasCapability(NET_CAPABILITY_VALIDATED)
}
expect<LinkPropertiesChanged>(net, tmt)
}
@@ -472,16 +472,6 @@
tmt: Long = defaultTimeoutMs
) = expectAvailableCallbacks(net, suspended = true, validated = validated, tmt = tmt)
- fun expectBlockedStatusCallback(blocked: Boolean, net: Network, tmt: Long = defaultTimeoutMs) =
- expect<BlockedStatus>(net, tmt, "Unexpected blocked status") {
- it.blocked == blocked
- }
-
- fun expectBlockedStatusCallback(blocked: Int, net: Network, tmt: Long = defaultTimeoutMs) =
- expect<BlockedStatusInt>(net, tmt, "Unexpected blocked status") {
- it.blocked == blocked
- }
-
// Expects the available callbacks (where the onCapabilitiesChanged must contain the
// VALIDATED capability), plus another onCapabilitiesChanged which is identical to the
// one we just sent.
@@ -498,17 +488,17 @@
// when a network connects and satisfies a callback, and then immediately validates.
fun expectAvailableThenValidatedCallbacks(net: Network, tmt: Long = defaultTimeoutMs) {
expectAvailableCallbacks(net, validated = false, tmt = tmt)
- expectCapabilitiesThat(net, tmt) { it.hasCapability(NET_CAPABILITY_VALIDATED) }
+ expectCaps(net, tmt) { it.hasCapability(NET_CAPABILITY_VALIDATED) }
}
fun expectAvailableThenValidatedCallbacks(
net: Network,
- blockedStatus: Int,
+ blockedReason: Int,
tmt: Long = defaultTimeoutMs
) {
expectAvailableCallbacks(net, validated = false, suspended = false,
- blockedStatus = blockedStatus, tmt = tmt)
- expectCapabilitiesThat(net, tmt) { it.hasCapability(NET_CAPABILITY_VALIDATED) }
+ blockedReason = blockedReason, tmt = tmt)
+ expectCaps(net, tmt) { it.hasCapability(NET_CAPABILITY_VALIDATED) }
}
// Temporary Java compat measure : have MockNetworkAgent implement this so that all existing
@@ -555,38 +545,26 @@
}
@JvmOverloads
- fun expectLinkPropertiesThat(
+ fun expectCaps(
n: HasNetwork,
tmt: Long = defaultTimeoutMs,
- valid: (LinkProperties) -> Boolean
- ) = expectLinkPropertiesThat(n.network, tmt, valid)
+ valid: (NetworkCapabilities) -> Boolean = { true }
+ ) = expect<CapabilitiesChanged>(n.network, tmt) { valid(it.caps) }.caps
@JvmOverloads
- fun expectCapabilitiesThat(
- n: HasNetwork,
+ fun expectCaps(
+ n: Network,
tmt: Long = defaultTimeoutMs,
valid: (NetworkCapabilities) -> Boolean
- ) = expectCapabilitiesThat(n.network, tmt, valid)
+ ) = expect<CapabilitiesChanged>(n, tmt) { valid(it.caps) }.caps
- @JvmOverloads
- fun expectCapabilitiesWith(
- capability: Int,
+ fun expectCaps(
n: HasNetwork,
- timeoutMs: Long = defaultTimeoutMs
- ): NetworkCapabilities {
- return expectCapabilitiesThat(n.network, timeoutMs) { it.hasCapability(capability) }.caps
- }
+ valid: (NetworkCapabilities) -> Boolean
+ ) = expect<CapabilitiesChanged>(n.network) { valid(it.caps) }.caps
- @JvmOverloads
- fun expectCapabilitiesWithout(
- capability: Int,
- n: HasNetwork,
- timeoutMs: Long = defaultTimeoutMs
- ): NetworkCapabilities {
- return expectCapabilitiesThat(n.network, timeoutMs) { !it.hasCapability(capability) }.caps
- }
-
- fun expectBlockedStatusCallback(expectBlocked: Boolean, n: HasNetwork) {
- expectBlockedStatusCallback(expectBlocked, n.network, defaultTimeoutMs)
- }
+ fun expectCaps(
+ tmt: Long,
+ valid: (NetworkCapabilities) -> Boolean
+ ) = expect<CapabilitiesChanged>(ANY_NETWORK, tmt) { valid(it.caps) }.caps
}
diff --git a/staticlibs/testutils/devicetests/com/android/testutils/async/FakeOsAccess.java b/staticlibs/testutils/devicetests/com/android/testutils/async/FakeOsAccess.java
new file mode 100644
index 0000000..1b8e26b
--- /dev/null
+++ b/staticlibs/testutils/devicetests/com/android/testutils/async/FakeOsAccess.java
@@ -0,0 +1,568 @@
+/*
+ * 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;
+
+import android.os.ParcelFileDescriptor;
+import android.system.StructPollfd;
+import android.util.Log;
+
+import com.android.net.module.util.async.CircularByteBuffer;
+import com.android.net.module.util.async.OsAccess;
+
+import java.io.FileDescriptor;
+import java.io.InterruptedIOException;
+import java.io.IOException;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
+import java.lang.reflect.Field;
+import java.util.HashMap;
+import java.util.concurrent.TimeUnit;
+
+public class FakeOsAccess extends OsAccess {
+ public static final boolean ENABLE_FINE_DEBUG = true;
+
+ public static final int DEFAULT_FILE_DATA_QUEUE_SIZE = 8 * 1024;
+
+ private enum FileType { PAIR, PIPE }
+
+ // Common poll() constants:
+ private static final short POLLIN = 0x0001;
+ private static final short POLLOUT = 0x0004;
+ private static final short POLLERR = 0x0008;
+ private static final short POLLHUP = 0x0010;
+
+ private static final Constructor<FileDescriptor> FD_CONSTRUCTOR;
+ private static final Field FD_FIELD_DESCRIPTOR;
+ private static final Field PFD_FIELD_DESCRIPTOR;
+ private static final Field PFD_FIELD_GUARD;
+ private static final Method CLOSE_GUARD_METHOD_CLOSE;
+
+ private final int mReadQueueSize = DEFAULT_FILE_DATA_QUEUE_SIZE;
+ private final int mWriteQueueSize = DEFAULT_FILE_DATA_QUEUE_SIZE;
+ private final HashMap<Integer, File> mFiles = new HashMap<>();
+ private final byte[] mTmpBuffer = new byte[1024];
+ private final long mStartTime;
+ private final String mLogTag;
+ private int mFileNumberGen = 3;
+ private boolean mHasRateLimitedData;
+
+ public FakeOsAccess(String logTag) {
+ mLogTag = logTag;
+ mStartTime = monotonicTimeMillis();
+ }
+
+ @Override
+ public long monotonicTimeMillis() {
+ return System.nanoTime() / 1000000;
+ }
+
+ @Override
+ public FileDescriptor getInnerFileDescriptor(ParcelFileDescriptor fd) {
+ try {
+ return (FileDescriptor) PFD_FIELD_DESCRIPTOR.get(fd);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public void close(ParcelFileDescriptor fd) {
+ if (fd != null) {
+ close(getInnerFileDescriptor(fd));
+
+ try {
+ // Reduce CloseGuard warnings.
+ Object guard = PFD_FIELD_GUARD.get(fd);
+ CLOSE_GUARD_METHOD_CLOSE.invoke(guard);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+
+ public synchronized void close(FileDescriptor fd) {
+ if (fd != null) {
+ File file = getFileOrNull(fd);
+ if (file != null) {
+ file.decreaseRefCount();
+ mFiles.remove(getFileDescriptorNumber(fd));
+ setFileDescriptorNumber(fd, -1);
+ notifyAll();
+ }
+ }
+ }
+
+ private File getFile(String func, FileDescriptor fd) throws IOException {
+ File file = getFileOrNull(fd);
+ if (file == null) {
+ throw newIOException(func, "Unknown file descriptor: " + getFileDebugName(fd));
+ }
+ return file;
+ }
+
+ private File getFileOrNull(FileDescriptor fd) {
+ return mFiles.get(getFileDescriptorNumber(fd));
+ }
+
+ @Override
+ public String getFileDebugName(ParcelFileDescriptor fd) {
+ return (fd != null ? getFileDebugName(getInnerFileDescriptor(fd)) : "null");
+ }
+
+ public String getFileDebugName(FileDescriptor fd) {
+ if (fd == null) {
+ return "null";
+ }
+
+ final int fdNumber = getFileDescriptorNumber(fd);
+ File file = mFiles.get(fdNumber);
+
+ StringBuilder sb = new StringBuilder();
+ if (file != null) {
+ if (file.name != null) {
+ sb.append(file.name);
+ sb.append("/");
+ }
+ sb.append(file.type);
+ sb.append("/");
+ } else {
+ sb.append("BADFD/");
+ }
+ sb.append(fdNumber);
+ return sb.toString();
+ }
+
+ public synchronized void setFileName(FileDescriptor fd, String name) {
+ File file = getFileOrNull(fd);
+ if (file != null) {
+ file.name = name;
+ }
+ }
+
+ @Override
+ public synchronized void setNonBlocking(FileDescriptor fd) throws IOException {
+ File file = getFile("fcntl", fd);
+ file.isBlocking = false;
+ }
+
+ @Override
+ public synchronized int read(FileDescriptor fd, byte[] buffer, int pos, int len)
+ throws IOException {
+ checkBoundaries("read", buffer, pos, len);
+
+ File file = getFile("read", fd);
+ if (file.readQueue == null) {
+ throw newIOException("read", "File not readable");
+ }
+ file.checkNonBlocking("read");
+
+ if (len == 0) {
+ return 0;
+ }
+
+ final int availSize = file.readQueue.size();
+ if (availSize == 0) {
+ if (file.isEndOfStream) {
+ // Java convention uses -1 to indicate end of stream.
+ return -1;
+ }
+ return 0; // EAGAIN
+ }
+
+ final int readCount = Math.min(len, availSize);
+ file.readQueue.readBytes(buffer, pos, readCount);
+ maybeTransferData(file);
+ return readCount;
+ }
+
+ @Override
+ public synchronized int write(FileDescriptor fd, byte[] buffer, int pos, int len)
+ throws IOException {
+ checkBoundaries("write", buffer, pos, len);
+
+ File file = getFile("write", fd);
+ if (file.writeQueue == null) {
+ throw newIOException("read", "File not writable");
+ }
+ if (file.type == FileType.PIPE && file.sink.openCount == 0) {
+ throw newIOException("write", "The other end of pipe is closed");
+ }
+ file.checkNonBlocking("write");
+
+ if (len == 0) {
+ return 0;
+ }
+
+ final int originalFreeSize = file.writeQueue.freeSize();
+ if (originalFreeSize == 0) {
+ return 0; // EAGAIN
+ }
+
+ final int writeCount = Math.min(len, originalFreeSize);
+ file.writeQueue.writeBytes(buffer, pos, writeCount);
+ maybeTransferData(file);
+
+ if (file.writeQueue.freeSize() < originalFreeSize) {
+ final int additionalQueuedCount = originalFreeSize - file.writeQueue.freeSize();
+ Log.i(mLogTag, logStr("Delaying transfer of " + additionalQueuedCount
+ + " bytes, queued=" + file.writeQueue.size() + ", type=" + file.type
+ + ", src_red=" + file.outboundLimiter + ", dst_red=" + file.sink.inboundLimiter));
+ }
+
+ return writeCount;
+ }
+
+ private void maybeTransferData(File file) {
+ boolean hasChanges = copyFileBuffers(file, file.sink);
+ hasChanges = copyFileBuffers(file.source, file) || hasChanges;
+
+ if (hasChanges) {
+ // TODO(b/245971639): Avoid notifying if no-one is polling.
+ notifyAll();
+ }
+ }
+
+ private boolean copyFileBuffers(File src, File dst) {
+ if (src.writeQueue == null || dst.readQueue == null) {
+ return false;
+ }
+
+ final int originalCopyCount = Math.min(mTmpBuffer.length,
+ Math.min(src.writeQueue.size(), dst.readQueue.freeSize()));
+
+ final int allowedCopyCount = RateLimiter.limit(
+ src.outboundLimiter, dst.inboundLimiter, originalCopyCount);
+
+ if (allowedCopyCount < originalCopyCount) {
+ if (ENABLE_FINE_DEBUG) {
+ Log.i(mLogTag, logStr("Delaying transfer of "
+ + (originalCopyCount - allowedCopyCount) + " bytes, original="
+ + originalCopyCount + ", allowed=" + allowedCopyCount
+ + ", type=" + src.type));
+ }
+ if (originalCopyCount > 0) {
+ mHasRateLimitedData = true;
+ }
+ if (allowedCopyCount == 0) {
+ return false;
+ }
+ }
+
+ boolean hasChanges = false;
+ if (allowedCopyCount > 0) {
+ if (dst.readQueue.size() == 0 || src.writeQueue.freeSize() == 0) {
+ hasChanges = true; // Read queue had no data, or write queue was full.
+ }
+ src.writeQueue.readBytes(mTmpBuffer, 0, allowedCopyCount);
+ dst.readQueue.writeBytes(mTmpBuffer, 0, allowedCopyCount);
+ }
+
+ if (!dst.isEndOfStream && src.openCount == 0
+ && src.writeQueue.size() == 0 && dst.readQueue.size() == 0) {
+ dst.isEndOfStream = true;
+ hasChanges = true;
+ }
+
+ return hasChanges;
+ }
+
+ public void clearInboundRateLimit(FileDescriptor fd) {
+ setInboundRateLimit(fd, Integer.MAX_VALUE);
+ }
+
+ public void clearOutboundRateLimit(FileDescriptor fd) {
+ setOutboundRateLimit(fd, Integer.MAX_VALUE);
+ }
+
+ public synchronized void setInboundRateLimit(FileDescriptor fd, int bytesPerSecond) {
+ File file = getFileOrNull(fd);
+ if (file != null) {
+ file.inboundLimiter.setBytesPerSecond(bytesPerSecond);
+ maybeTransferData(file);
+ }
+ }
+
+ public synchronized void setOutboundRateLimit(FileDescriptor fd, int bytesPerSecond) {
+ File file = getFileOrNull(fd);
+ if (file != null) {
+ file.outboundLimiter.setBytesPerSecond(bytesPerSecond);
+ maybeTransferData(file);
+ }
+ }
+
+ public synchronized ParcelFileDescriptor[] socketpair() throws IOException {
+ int fdNumber1 = getNextFd("socketpair");
+ int fdNumber2 = getNextFd("socketpair");
+
+ File file1 = new File(FileType.PAIR, mReadQueueSize, mWriteQueueSize);
+ File file2 = new File(FileType.PAIR, mReadQueueSize, mWriteQueueSize);
+
+ return registerFilePair(fdNumber1, file1, fdNumber2, file2);
+ }
+
+ @Override
+ public synchronized ParcelFileDescriptor[] pipe() throws IOException {
+ int fdNumber1 = getNextFd("pipe");
+ int fdNumber2 = getNextFd("pipe");
+
+ File file1 = new File(FileType.PIPE, mReadQueueSize, 0);
+ File file2 = new File(FileType.PIPE, 0, mWriteQueueSize);
+
+ return registerFilePair(fdNumber1, file1, fdNumber2, file2);
+ }
+
+ private ParcelFileDescriptor[] registerFilePair(
+ int fdNumber1, File file1, int fdNumber2, File file2) {
+ file1.sink = file2;
+ file1.source = file2;
+ file2.sink = file1;
+ file2.source = file1;
+
+ mFiles.put(fdNumber1, file1);
+ mFiles.put(fdNumber2, file2);
+ return new ParcelFileDescriptor[] {
+ newParcelFileDescriptor(fdNumber1), newParcelFileDescriptor(fdNumber2)};
+ }
+
+ @Override
+ public short getPollInMask() {
+ return POLLIN;
+ }
+
+ @Override
+ public short getPollOutMask() {
+ return POLLOUT;
+ }
+
+ @Override
+ public synchronized int poll(StructPollfd[] fds, int timeoutMs) throws IOException {
+ if (timeoutMs < 0) {
+ timeoutMs = (int) TimeUnit.HOURS.toMillis(1); // Make "infinite" equal to 1 hour.
+ }
+
+ if (fds == null || fds.length > 1000) {
+ throw newIOException("poll", "Invalid fds param");
+ }
+ for (StructPollfd pollFd : fds) {
+ getFile("poll", pollFd.fd);
+ }
+
+ int waitCallCount = 0;
+ final long deadline = monotonicTimeMillis() + timeoutMs;
+ while (true) {
+ if (mHasRateLimitedData) {
+ mHasRateLimitedData = false;
+ for (File file : mFiles.values()) {
+ if (file.inboundLimiter.getLastRequestReduction() != 0) {
+ copyFileBuffers(file.source, file);
+ }
+ if (file.outboundLimiter.getLastRequestReduction() != 0) {
+ copyFileBuffers(file, file.sink);
+ }
+ }
+ }
+
+ final int readyCount = calculateReadyCount(fds);
+ if (readyCount > 0) {
+ if (ENABLE_FINE_DEBUG) {
+ Log.v(mLogTag, logStr("Poll returns " + readyCount
+ + " after " + waitCallCount + " wait calls"));
+ }
+ return readyCount;
+ }
+
+ long remainingTimeoutMs = deadline - monotonicTimeMillis();
+ if (remainingTimeoutMs <= 0) {
+ if (ENABLE_FINE_DEBUG) {
+ Log.v(mLogTag, logStr("Poll timeout " + timeoutMs
+ + "ms after " + waitCallCount + " wait calls"));
+ }
+ return 0;
+ }
+
+ if (mHasRateLimitedData) {
+ remainingTimeoutMs = Math.min(RateLimiter.BUCKET_DURATION_MS, remainingTimeoutMs);
+ }
+
+ try {
+ wait(remainingTimeoutMs);
+ } catch (InterruptedException e) {
+ // Ignore and retry
+ }
+ waitCallCount++;
+ }
+ }
+
+ private int calculateReadyCount(StructPollfd[] fds) {
+ int fdCount = 0;
+ for (StructPollfd pollFd : fds) {
+ pollFd.revents = 0;
+
+ File file = getFileOrNull(pollFd.fd);
+ if (file == null) {
+ Log.w(mLogTag, logStr("Ignoring FD concurrently closed by a buggy app: "
+ + getFileDebugName(pollFd.fd)));
+ continue;
+ }
+
+ if (ENABLE_FINE_DEBUG) {
+ Log.v(mLogTag, logStr("calculateReadyCount fd=" + getFileDebugName(pollFd.fd)
+ + ", events=" + pollFd.events + ", eof=" + file.isEndOfStream
+ + ", r=" + (file.readQueue != null ? file.readQueue.size() : -1)
+ + ", w=" + (file.writeQueue != null ? file.writeQueue.freeSize() : -1)));
+ }
+
+ if ((pollFd.events & POLLIN) != 0) {
+ if (file.readQueue != null && file.readQueue.size() != 0) {
+ pollFd.revents |= POLLIN;
+ }
+ if (file.isEndOfStream) {
+ pollFd.revents |= POLLHUP;
+ }
+ }
+
+ if ((pollFd.events & POLLOUT) != 0) {
+ if (file.type == FileType.PIPE && file.sink.openCount == 0) {
+ pollFd.revents |= POLLERR;
+ }
+ if (file.writeQueue != null && file.writeQueue.freeSize() != 0) {
+ pollFd.revents |= POLLOUT;
+ }
+ }
+
+ if (pollFd.revents != 0) {
+ fdCount++;
+ }
+ }
+ return fdCount;
+ }
+
+ private int getNextFd(String func) throws IOException {
+ if (mFileNumberGen > 100000) {
+ throw newIOException(func, "Too many files open");
+ }
+
+ return mFileNumberGen++;
+ }
+
+ private static IOException newIOException(String func, String message) {
+ return new IOException(message + ", func=" + func);
+ }
+
+ public static void checkBoundaries(String func, byte[] buffer, int pos, int len)
+ throws IOException {
+ if (((buffer.length | pos | len) < 0 || pos > buffer.length - len)) {
+ throw newIOException(func, "Invalid array bounds");
+ }
+ }
+
+ private ParcelFileDescriptor newParcelFileDescriptor(int fdNumber) {
+ try {
+ return new ParcelFileDescriptor(newFileDescriptor(fdNumber));
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private FileDescriptor newFileDescriptor(int fdNumber) {
+ try {
+ return FD_CONSTRUCTOR.newInstance(Integer.valueOf(fdNumber));
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public int getFileDescriptorNumber(FileDescriptor fd) {
+ try {
+ return (Integer) FD_FIELD_DESCRIPTOR.get(fd);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private void setFileDescriptorNumber(FileDescriptor fd, int fdNumber) {
+ try {
+ FD_FIELD_DESCRIPTOR.set(fd, Integer.valueOf(fdNumber));
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private String logStr(String message) {
+ return "[FakeOs " + (monotonicTimeMillis() - mStartTime) + "] " + message;
+ }
+
+ private class File {
+ final FileType type;
+ final CircularByteBuffer readQueue;
+ final CircularByteBuffer writeQueue;
+ final RateLimiter inboundLimiter = new RateLimiter(FakeOsAccess.this, Integer.MAX_VALUE);
+ final RateLimiter outboundLimiter = new RateLimiter(FakeOsAccess.this, Integer.MAX_VALUE);
+ String name;
+ int openCount = 1;
+ boolean isBlocking = true;
+ File sink;
+ File source;
+ boolean isEndOfStream;
+
+ File(FileType type, int readQueueSize, int writeQueueSize) {
+ this.type = type;
+ readQueue = (readQueueSize > 0 ? new CircularByteBuffer(readQueueSize) : null);
+ writeQueue = (writeQueueSize > 0 ? new CircularByteBuffer(writeQueueSize) : null);
+ }
+
+ void decreaseRefCount() {
+ if (openCount <= 0) {
+ throw new IllegalStateException();
+ }
+ openCount--;
+ }
+
+ void checkNonBlocking(String func) throws IOException {
+ if (isBlocking) {
+ throw newIOException(func, "File in blocking mode");
+ }
+ }
+ }
+
+ static {
+ try {
+ FD_CONSTRUCTOR = FileDescriptor.class.getDeclaredConstructor(int.class);
+ FD_CONSTRUCTOR.setAccessible(true);
+
+ Field descriptorIntField;
+ try {
+ descriptorIntField = FileDescriptor.class.getDeclaredField("descriptor");
+ } catch (NoSuchFieldException e) {
+ descriptorIntField = FileDescriptor.class.getDeclaredField("fd");
+ }
+ FD_FIELD_DESCRIPTOR = descriptorIntField;
+ FD_FIELD_DESCRIPTOR.setAccessible(true);
+
+ PFD_FIELD_DESCRIPTOR = ParcelFileDescriptor.class.getDeclaredField("mFd");
+ PFD_FIELD_DESCRIPTOR.setAccessible(true);
+
+ PFD_FIELD_GUARD = ParcelFileDescriptor.class.getDeclaredField("mGuard");
+ PFD_FIELD_GUARD.setAccessible(true);
+
+ CLOSE_GUARD_METHOD_CLOSE = Class.forName("dalvik.system.CloseGuard")
+ .getDeclaredMethod("close");
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+}
diff --git a/staticlibs/testutils/devicetests/com/android/testutils/async/RateLimiter.java b/staticlibs/testutils/devicetests/com/android/testutils/async/RateLimiter.java
new file mode 100644
index 0000000..137873d
--- /dev/null
+++ b/staticlibs/testutils/devicetests/com/android/testutils/async/RateLimiter.java
@@ -0,0 +1,131 @@
+/*
+ * 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;
+
+import com.android.net.module.util.async.OsAccess;
+
+import java.util.Arrays;
+
+/**
+ * Limits the number of bytes processed to the given maximum of bytes per second.
+ *
+ * The limiter tracks the total for the past second, along with sums for each 10ms
+ * in the past second, allowing the total to be adjusted as the time passes.
+ */
+public final class RateLimiter {
+ private static final int PERIOD_DURATION_MS = 1000;
+ private static final int BUCKET_COUNT = 100;
+
+ public static final int BUCKET_DURATION_MS = PERIOD_DURATION_MS / BUCKET_COUNT;
+
+ private final OsAccess mOsAccess;
+ private final int[] mStatBuckets = new int[BUCKET_COUNT];
+ private int mMaxPerPeriodBytes;
+ private int mMaxPerBucketBytes;
+ private int mRecordedPeriodBytes;
+ private long mLastLimitTimestamp;
+ private int mLastRequestReduction;
+
+ public RateLimiter(OsAccess osAccess, int bytesPerSecond) {
+ mOsAccess = osAccess;
+ setBytesPerSecond(bytesPerSecond);
+ clear();
+ }
+
+ public int getBytesPerSecond() {
+ return mMaxPerPeriodBytes;
+ }
+
+ public void setBytesPerSecond(int bytesPerSecond) {
+ mMaxPerPeriodBytes = bytesPerSecond;
+ mMaxPerBucketBytes = Math.max(1, (mMaxPerPeriodBytes / BUCKET_COUNT) * 2);
+ }
+
+ public void clear() {
+ mLastLimitTimestamp = mOsAccess.monotonicTimeMillis();
+ mRecordedPeriodBytes = 0;
+ Arrays.fill(mStatBuckets, 0);
+ }
+
+ public static int limit(RateLimiter limiter1, RateLimiter limiter2, int requestedBytes) {
+ final long now = limiter1.mOsAccess.monotonicTimeMillis();
+ final int allowedCount = Math.min(limiter1.calculateLimit(now, requestedBytes),
+ limiter2.calculateLimit(now, requestedBytes));
+ limiter1.recordBytes(now, requestedBytes, allowedCount);
+ limiter2.recordBytes(now, requestedBytes, allowedCount);
+ return allowedCount;
+ }
+
+ public int limit(int requestedBytes) {
+ final long now = mOsAccess.monotonicTimeMillis();
+ final int allowedCount = calculateLimit(now, requestedBytes);
+ recordBytes(now, requestedBytes, allowedCount);
+ return allowedCount;
+ }
+
+ public int getLastRequestReduction() {
+ return mLastRequestReduction;
+ }
+
+ public boolean acceptAllOrNone(int requestedBytes) {
+ final long now = mOsAccess.monotonicTimeMillis();
+ final int allowedCount = calculateLimit(now, requestedBytes);
+ if (allowedCount < requestedBytes) {
+ return false;
+ }
+ recordBytes(now, requestedBytes, allowedCount);
+ return true;
+ }
+
+ private int calculateLimit(long now, int requestedBytes) {
+ // First remove all stale bucket data and adjust the total.
+ final long currentBucketAbsIdx = now / BUCKET_DURATION_MS;
+ final long staleCutoffIdx = currentBucketAbsIdx - BUCKET_COUNT;
+ for (long i = mLastLimitTimestamp / BUCKET_DURATION_MS; i < staleCutoffIdx; i++) {
+ final int idx = (int) (i % BUCKET_COUNT);
+ mRecordedPeriodBytes -= mStatBuckets[idx];
+ mStatBuckets[idx] = 0;
+ }
+
+ final int bucketIdx = (int) (currentBucketAbsIdx % BUCKET_COUNT);
+ final int maxAllowed = Math.min(mMaxPerPeriodBytes - mRecordedPeriodBytes,
+ Math.min(mMaxPerBucketBytes - mStatBuckets[bucketIdx], requestedBytes));
+ return Math.max(0, maxAllowed);
+ }
+
+ private void recordBytes(long now, int requestedBytes, int actualBytes) {
+ mStatBuckets[(int) ((now / BUCKET_DURATION_MS) % BUCKET_COUNT)] += actualBytes;
+ mRecordedPeriodBytes += actualBytes;
+ mLastRequestReduction = requestedBytes - actualBytes;
+ mLastLimitTimestamp = now;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("{max=");
+ sb.append(mMaxPerPeriodBytes);
+ sb.append(",max_bucket=");
+ sb.append(mMaxPerBucketBytes);
+ sb.append(",total=");
+ sb.append(mRecordedPeriodBytes);
+ sb.append(",last_red=");
+ sb.append(mLastRequestReduction);
+ sb.append('}');
+ return sb.toString();
+ }
+}
diff --git a/staticlibs/testutils/devicetests/com/android/testutils/filters/CtsNetTestCasesMaxTargetSdk33.kt b/staticlibs/testutils/devicetests/com/android/testutils/filters/CtsNetTestCasesMaxTargetSdk33.kt
new file mode 100644
index 0000000..5af890f
--- /dev/null
+++ b/staticlibs/testutils/devicetests/com/android/testutils/filters/CtsNetTestCasesMaxTargetSdk33.kt
@@ -0,0 +1,22 @@
+/*
+ * 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.filters
+
+/**
+ * Only run this test in the CtsNetTestCasesMaxTargetSdk33 suite.
+ */
+annotation class CtsNetTestCasesMaxTargetSdk33(val reason: String)