Merge "Move TestableNetworkAgent to Common Util Location"
diff --git a/staticlibs/device/com/android/net/module/util/netlink/NetlinkConstants.java b/staticlibs/device/com/android/net/module/util/netlink/NetlinkConstants.java
index 83a82b7..c44a5b4 100644
--- a/staticlibs/device/com/android/net/module/util/netlink/NetlinkConstants.java
+++ b/staticlibs/device/com/android/net/module/util/netlink/NetlinkConstants.java
@@ -151,6 +151,7 @@
public static final int RTMGRP_ND_USEROPT = 1 << (RTNLGRP_ND_USEROPT - 1);
// Device flags.
+ public static final int IFF_UP = 1 << 0;
public static final int IFF_LOWER_UP = 1 << 16;
// Known values for struct rtmsg rtm_protocol.
diff --git a/staticlibs/device/com/android/net/module/util/netlink/RtNetlinkAddressMessage.java b/staticlibs/device/com/android/net/module/util/netlink/RtNetlinkAddressMessage.java
index a518c76..f7b0d02 100644
--- a/staticlibs/device/com/android/net/module/util/netlink/RtNetlinkAddressMessage.java
+++ b/staticlibs/device/com/android/net/module/util/netlink/RtNetlinkAddressMessage.java
@@ -114,9 +114,10 @@
// and will overwrite the flags set above.
byteBuffer.position(baseOffset);
nlAttr = StructNlAttr.findNextAttrOfType(IFA_FLAGS, byteBuffer);
- if (nlAttr != null) {
- addrMsg.mFlags = nlAttr.getValueAsInt(0 /* default value */);
- }
+ if (nlAttr == null) return null;
+ final Integer value = nlAttr.getValueAsInteger();
+ if (value == null) return null;
+ addrMsg.mFlags = value;
return addrMsg;
}
diff --git a/staticlibs/device/com/android/net/module/util/netlink/StructNlAttr.java b/staticlibs/device/com/android/net/module/util/netlink/StructNlAttr.java
index 485e67c..a9b6495 100644
--- a/staticlibs/device/com/android/net/module/util/netlink/StructNlAttr.java
+++ b/staticlibs/device/com/android/net/module/util/netlink/StructNlAttr.java
@@ -287,14 +287,22 @@
}
/**
- * Get attribute value as Integer.
+ * Get attribute value as Integer, or null if malformed (e.g., length is not 4 bytes).
*/
- public int getValueAsInt(int defaultValue) {
+ public Integer getValueAsInteger() {
final ByteBuffer byteBuffer = getValueAsByteBuffer();
if (byteBuffer == null || byteBuffer.remaining() != Integer.BYTES) {
- return defaultValue;
+ return null;
}
- return getValueAsByteBuffer().getInt();
+ return byteBuffer.getInt();
+ }
+
+ /**
+ * Get attribute value as Int, default value if malformed.
+ */
+ public int getValueAsInt(int defaultValue) {
+ final Integer value = getValueAsInteger();
+ return (value != null) ? value : defaultValue;
}
/**
@@ -341,6 +349,7 @@
public String getValueAsString() {
if (nla_value == null) return null;
// Check the attribute value length after removing string termination flag '\0'.
+ // This assumes that all netlink strings are null-terminated.
if (nla_value.length < (nla_len - NLA_HEADERLEN - 1)) return null;
try {
diff --git a/staticlibs/device/com/android/net/module/util/structs/Ipv4Header.java b/staticlibs/device/com/android/net/module/util/structs/Ipv4Header.java
new file mode 100644
index 0000000..5249454
--- /dev/null
+++ b/staticlibs/device/com/android/net/module/util/structs/Ipv4Header.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2021 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 androidx.annotation.VisibleForTesting;
+
+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.net.Inet4Address;
+
+/**
+ * L3 IPv4 header as per https://tools.ietf.org/html/rfc791.
+ * This class doesn't contain options field.
+ *
+ * 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
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * |Version| IHL |Type of Service| Total Length |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | Identification |Flags| Fragment Offset |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | Time to Live | Protocol | Header Checksum |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | Source Address |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | Destination Address |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ */
+public class Ipv4Header extends Struct {
+ // IP Version=IPv4, IHL is always 5(*4bytes) because options are not supported.
+ @VisibleForTesting
+ public static final byte IPHDR_VERSION_IHL = 0x45;
+
+ @Field(order = 0, type = Type.S8)
+ // version (4 bits), IHL (4 bits)
+ public final byte vi;
+ @Field(order = 1, type = Type.S8)
+ public final byte tos;
+ @Field(order = 2, type = Type.U16)
+ public final int totalLength;
+ @Field(order = 3, type = Type.S16)
+ public final short id;
+ @Field(order = 4, type = Type.S16)
+ // flags (3 bits), fragment offset (13 bits)
+ public final short flagsAndFragmentOffset;
+ @Field(order = 5, type = Type.U8)
+ public final short ttl;
+ @Field(order = 6, type = Type.S8)
+ public final byte protocol;
+ @Field(order = 7, type = Type.S16)
+ public final short checksum;
+ @Field(order = 8, type = Type.Ipv4Address)
+ public final Inet4Address srcIp;
+ @Field(order = 9, type = Type.Ipv4Address)
+ public final Inet4Address dstIp;
+
+ public Ipv4Header(final byte tos, final int totalLength, final short id,
+ final short flagsAndFragmentOffset, final short ttl, final byte protocol,
+ final short checksum, final Inet4Address srcIp, final Inet4Address dstIp) {
+ this(IPHDR_VERSION_IHL, tos, totalLength, id, flagsAndFragmentOffset, ttl,
+ protocol, checksum, srcIp, dstIp);
+ }
+
+ private Ipv4Header(final byte vi, final byte tos, final int totalLength, final short id,
+ final short flagsAndFragmentOffset, final short ttl, final byte protocol,
+ final short checksum, final Inet4Address srcIp, final Inet4Address dstIp) {
+ this.vi = vi;
+ this.tos = tos;
+ this.totalLength = totalLength;
+ this.id = id;
+ this.flagsAndFragmentOffset = flagsAndFragmentOffset;
+ this.ttl = ttl;
+ this.protocol = protocol;
+ this.checksum = checksum;
+ this.srcIp = srcIp;
+ this.dstIp = dstIp;
+ }
+}
diff --git a/staticlibs/device/com/android/net/module/util/structs/TcpHeader.java b/staticlibs/device/com/android/net/module/util/structs/TcpHeader.java
new file mode 100644
index 0000000..0c97401
--- /dev/null
+++ b/staticlibs/device/com/android/net/module/util/structs/TcpHeader.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2021 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 com.android.net.module.util.Struct;
+import com.android.net.module.util.Struct.Field;
+import com.android.net.module.util.Struct.Type;
+
+/**
+ * L4 TCP header as per https://tools.ietf.org/html/rfc793.
+ * This class does not contain option and data fields.
+ *
+ * 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
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | Source Port | Destination Port |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | Sequence Number |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | Acknowledgment Number |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | Data | |U|A|P|R|S|F| |
+ * | Offset| Reserved |R|C|S|S|Y|I| Window |
+ * | | |G|K|H|T|N|N| |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | Checksum | Urgent Pointer |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | Options | Padding |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | data |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ */
+public class TcpHeader extends Struct {
+ @Field(order = 0, type = Type.U16)
+ public final int srcPort;
+ @Field(order = 1, type = Type.U16)
+ public final int dstPort;
+ @Field(order = 2, type = Type.U32)
+ public final long seq;
+ @Field(order = 3, type = Type.U32)
+ public final long ack;
+ @Field(order = 4, type = Type.S16)
+ // data Offset (4 bits), reserved (6 bits), control bits (6 bits)
+ // TODO: update with bitfields once class Struct supports it
+ public final short dataOffsetAndControlBits;
+ @Field(order = 5, type = Type.U16)
+ public final int window;
+ @Field(order = 6, type = Type.S16)
+ public final short checksum;
+ @Field(order = 7, type = Type.U16)
+ public final int urgentPointer;
+
+ public TcpHeader(final int srcPort, final int dstPort, final long seq, final long ack,
+ final short dataOffsetAndControlBits, final int window, final short checksum,
+ final int urgentPointer) {
+ this.srcPort = srcPort;
+ this.dstPort = dstPort;
+ this.seq = seq;
+ this.ack = ack;
+ this.dataOffsetAndControlBits = dataOffsetAndControlBits;
+ this.window = window;
+ this.checksum = checksum;
+ this.urgentPointer = urgentPointer;
+ }
+}
diff --git a/staticlibs/device/com/android/net/module/util/structs/UdpHeader.java b/staticlibs/device/com/android/net/module/util/structs/UdpHeader.java
new file mode 100644
index 0000000..8b0316b
--- /dev/null
+++ b/staticlibs/device/com/android/net/module/util/structs/UdpHeader.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2021 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 com.android.net.module.util.Struct;
+import com.android.net.module.util.Struct.Field;
+import com.android.net.module.util.Struct.Type;
+
+/**
+ * L4 UDP header as per https://tools.ietf.org/html/rfc768.
+ *
+ * 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
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | Source Port | Destination Port |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | Length | Checksum |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | data octets ...
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ ...
+ */
+public class UdpHeader extends Struct {
+ @Field(order = 0, type = Type.U16)
+ public final int srcPort;
+ @Field(order = 1, type = Type.U16)
+ public final int dstPort;
+ @Field(order = 2, type = Type.U16)
+ public final int length;
+ @Field(order = 3, type = Type.S16)
+ public final short checksum;
+
+ public UdpHeader(final int srcPort, final int dstPort, final int length,
+ final short checksum) {
+ this.srcPort = srcPort;
+ this.dstPort = dstPort;
+ this.length = length;
+ this.checksum = checksum;
+ }
+}
diff --git a/staticlibs/native/bpf_map_utils/Android.bp b/staticlibs/native/bpf_map_utils/Android.bp
new file mode 100644
index 0000000..e14c259
--- /dev/null
+++ b/staticlibs/native/bpf_map_utils/Android.bp
@@ -0,0 +1,79 @@
+// Copyright (C) 2021 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 {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+cc_library_headers {
+ name: "bpf_map_utils",
+ vendor_available: true,
+ host_supported: true,
+ native_bridge_supported: true,
+ export_include_dirs: ["include"],
+ cflags: [
+ "-Wall",
+ "-Werror",
+ ],
+ min_sdk_version: "30",
+ apex_available: [
+ "//apex_available:platform",
+ "com.android.tethering",
+ "com.android.art.debug",
+ ],
+ visibility: [
+ "//bootable/libbootloader/vts",
+ "//frameworks/base/services/core/jni",
+ "//frameworks/native/libs/cputimeinstate",
+ "//frameworks/native/services/gpuservice",
+ "//frameworks/native/services/gpuservice/gpumem",
+ "//frameworks/native/services/gpuservice/tests/unittests",
+ "//frameworks/native/services/gpuservice/tracing",
+ "//packages/modules/Connectivity/netd",
+ "//packages/modules/Connectivity/tests/unit/jni",
+ "//packages/modules/DnsResolver/tests",
+ "//system/bpf/bpfloader",
+ "//system/bpf/libbpf_android",
+ "//system/memory/libmeminfo",
+ "//system/netd/libnetdbpf",
+ "//system/netd/server",
+ "//system/netd/tests",
+ "//system/netd/tests/benchmarks",
+ "//test/vts-testcase/kernel/api/bpf_native_test",
+ ],
+}
+
+
+cc_test {
+ // TODO: Rename to bpf_map_test and modify .gcls as well.
+ name: "libbpf_android_test",
+ srcs: [
+ "BpfMapTest.cpp",
+ ],
+ defaults: ["bpf_defaults"],
+ cflags: [
+ "-Wall",
+ "-Werror",
+ "-Wno-error=unused-variable",
+ ],
+ static_libs: ["libgmock"],
+ shared_libs: [
+ "libbpf_android",
+ "libbase",
+ "liblog",
+ "libutils",
+ ],
+ require_root: true,
+ test_suites: ["general-tests"],
+}
diff --git a/staticlibs/native/bpf_map_utils/BpfMapTest.cpp b/staticlibs/native/bpf_map_utils/BpfMapTest.cpp
new file mode 100644
index 0000000..d0737b0
--- /dev/null
+++ b/staticlibs/native/bpf_map_utils/BpfMapTest.cpp
@@ -0,0 +1,244 @@
+/*
+ * Copyright (C) 2018 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.
+ */
+
+#include <fstream>
+#include <iostream>
+#include <string>
+#include <vector>
+
+#include <fcntl.h>
+#include <inttypes.h>
+#include <linux/inet_diag.h>
+#include <linux/sock_diag.h>
+#include <net/if.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <gtest/gtest.h>
+
+#include <android-base/stringprintf.h>
+#include <android-base/strings.h>
+
+#include "bpf/BpfMap.h"
+#include "bpf/BpfUtils.h"
+
+using ::testing::Test;
+
+namespace android {
+namespace bpf {
+
+using base::Result;
+using base::unique_fd;
+
+constexpr uint32_t TEST_MAP_SIZE = 10;
+constexpr uint32_t TEST_KEY1 = 1;
+constexpr uint32_t TEST_VALUE1 = 10;
+constexpr const char PINNED_MAP_PATH[] = "/sys/fs/bpf/testMap";
+
+class BpfMapTest : public testing::Test {
+ protected:
+ BpfMapTest() {}
+
+ void SetUp() {
+ EXPECT_EQ(0, setrlimitForTest());
+ if (!access(PINNED_MAP_PATH, R_OK)) {
+ EXPECT_EQ(0, remove(PINNED_MAP_PATH));
+ }
+ }
+
+ void TearDown() {
+ if (!access(PINNED_MAP_PATH, R_OK)) {
+ EXPECT_EQ(0, remove(PINNED_MAP_PATH));
+ }
+ }
+
+ void checkMapInvalid(BpfMap<uint32_t, uint32_t>& map) {
+ EXPECT_FALSE(map.isValid());
+ EXPECT_EQ(-1, map.getMap().get());
+ }
+
+ void checkMapValid(BpfMap<uint32_t, uint32_t>& map) {
+ EXPECT_LE(0, map.getMap().get());
+ EXPECT_TRUE(map.isValid());
+ }
+
+ void writeToMapAndCheck(BpfMap<uint32_t, uint32_t>& map, uint32_t key, uint32_t value) {
+ ASSERT_RESULT_OK(map.writeValue(key, value, BPF_ANY));
+ uint32_t value_read;
+ ASSERT_EQ(0, findMapEntry(map.getMap(), &key, &value_read));
+ checkValueAndStatus(value, value_read);
+ }
+
+ void checkValueAndStatus(uint32_t refValue, Result<uint32_t> value) {
+ ASSERT_RESULT_OK(value);
+ ASSERT_EQ(refValue, value.value());
+ }
+
+ void populateMap(uint32_t total, BpfMap<uint32_t, uint32_t>& map) {
+ for (uint32_t key = 0; key < total; key++) {
+ uint32_t value = key * 10;
+ EXPECT_RESULT_OK(map.writeValue(key, value, BPF_ANY));
+ }
+ }
+
+ void expectMapEmpty(BpfMap<uint32_t, uint32_t>& map) {
+ Result<bool> isEmpty = map.isEmpty();
+ ASSERT_RESULT_OK(isEmpty);
+ ASSERT_TRUE(isEmpty.value());
+ }
+};
+
+TEST_F(BpfMapTest, constructor) {
+ BpfMap<uint32_t, uint32_t> testMap1;
+ checkMapInvalid(testMap1);
+
+ BpfMap<uint32_t, uint32_t> testMap2(BPF_MAP_TYPE_HASH, TEST_MAP_SIZE, BPF_F_NO_PREALLOC);
+ checkMapValid(testMap2);
+}
+
+TEST_F(BpfMapTest, basicHelpers) {
+ BpfMap<uint32_t, uint32_t> testMap(BPF_MAP_TYPE_HASH, TEST_MAP_SIZE, BPF_F_NO_PREALLOC);
+ uint32_t key = TEST_KEY1;
+ uint32_t value_write = TEST_VALUE1;
+ writeToMapAndCheck(testMap, key, value_write);
+ Result<uint32_t> value_read = testMap.readValue(key);
+ checkValueAndStatus(value_write, value_read);
+ Result<uint32_t> key_read = testMap.getFirstKey();
+ checkValueAndStatus(key, key_read);
+ ASSERT_RESULT_OK(testMap.deleteValue(key));
+ ASSERT_GT(0, findMapEntry(testMap.getMap(), &key, &value_read));
+ ASSERT_EQ(ENOENT, errno);
+}
+
+TEST_F(BpfMapTest, reset) {
+ BpfMap<uint32_t, uint32_t> testMap(BPF_MAP_TYPE_HASH, TEST_MAP_SIZE, BPF_F_NO_PREALLOC);
+ uint32_t key = TEST_KEY1;
+ uint32_t value_write = TEST_VALUE1;
+ writeToMapAndCheck(testMap, key, value_write);
+
+ testMap.reset(-1);
+ checkMapInvalid(testMap);
+ ASSERT_GT(0, findMapEntry(testMap.getMap(), &key, &value_write));
+ ASSERT_EQ(EBADF, errno);
+}
+
+TEST_F(BpfMapTest, moveConstructor) {
+ BpfMap<uint32_t, uint32_t> testMap1(BPF_MAP_TYPE_HASH, TEST_MAP_SIZE, BPF_F_NO_PREALLOC);
+ BpfMap<uint32_t, uint32_t> testMap2;
+ testMap2 = std::move(testMap1);
+ uint32_t key = TEST_KEY1;
+ checkMapInvalid(testMap1);
+ uint32_t value = TEST_VALUE1;
+ writeToMapAndCheck(testMap2, key, value);
+}
+
+TEST_F(BpfMapTest, SetUpMap) {
+ EXPECT_NE(0, access(PINNED_MAP_PATH, R_OK));
+ BpfMap<uint32_t, uint32_t> testMap1(BPF_MAP_TYPE_HASH, TEST_MAP_SIZE, BPF_F_NO_PREALLOC);
+ ASSERT_EQ(0, bpfFdPin(testMap1.getMap(), PINNED_MAP_PATH));
+ EXPECT_EQ(0, access(PINNED_MAP_PATH, R_OK));
+ checkMapValid(testMap1);
+ BpfMap<uint32_t, uint32_t> testMap2;
+ EXPECT_RESULT_OK(testMap2.init(PINNED_MAP_PATH));
+ checkMapValid(testMap2);
+ uint32_t key = TEST_KEY1;
+ uint32_t value = TEST_VALUE1;
+ writeToMapAndCheck(testMap1, key, value);
+ Result<uint32_t> value_read = testMap2.readValue(key);
+ checkValueAndStatus(value, value_read);
+}
+
+TEST_F(BpfMapTest, iterate) {
+ BpfMap<uint32_t, uint32_t> testMap(BPF_MAP_TYPE_HASH, TEST_MAP_SIZE, BPF_F_NO_PREALLOC);
+ populateMap(TEST_MAP_SIZE, testMap);
+ int totalCount = 0;
+ int totalSum = 0;
+ const auto iterateWithDeletion = [&totalCount, &totalSum](const uint32_t& key,
+ BpfMap<uint32_t, uint32_t>& map) {
+ EXPECT_GE((uint32_t)TEST_MAP_SIZE, key);
+ totalCount++;
+ totalSum += key;
+ return map.deleteValue(key);
+ };
+ EXPECT_RESULT_OK(testMap.iterate(iterateWithDeletion));
+ EXPECT_EQ((int)TEST_MAP_SIZE, totalCount);
+ EXPECT_EQ(((1 + TEST_MAP_SIZE - 1) * (TEST_MAP_SIZE - 1)) / 2, (uint32_t)totalSum);
+ expectMapEmpty(testMap);
+}
+
+TEST_F(BpfMapTest, iterateWithValue) {
+ BpfMap<uint32_t, uint32_t> testMap(BPF_MAP_TYPE_HASH, TEST_MAP_SIZE, BPF_F_NO_PREALLOC);
+ populateMap(TEST_MAP_SIZE, testMap);
+ int totalCount = 0;
+ int totalSum = 0;
+ const auto iterateWithDeletion = [&totalCount, &totalSum](const uint32_t& key,
+ const uint32_t& value,
+ BpfMap<uint32_t, uint32_t>& map) {
+ EXPECT_GE((uint32_t)TEST_MAP_SIZE, key);
+ EXPECT_EQ(value, key * 10);
+ totalCount++;
+ totalSum += value;
+ return map.deleteValue(key);
+ };
+ EXPECT_RESULT_OK(testMap.iterateWithValue(iterateWithDeletion));
+ EXPECT_EQ((int)TEST_MAP_SIZE, totalCount);
+ EXPECT_EQ(((1 + TEST_MAP_SIZE - 1) * (TEST_MAP_SIZE - 1)) * 5, (uint32_t)totalSum);
+ expectMapEmpty(testMap);
+}
+
+TEST_F(BpfMapTest, mapIsEmpty) {
+ BpfMap<uint32_t, uint32_t> testMap(BPF_MAP_TYPE_HASH, TEST_MAP_SIZE, BPF_F_NO_PREALLOC);
+ expectMapEmpty(testMap);
+ uint32_t key = TEST_KEY1;
+ uint32_t value_write = TEST_VALUE1;
+ writeToMapAndCheck(testMap, key, value_write);
+ Result<bool> isEmpty = testMap.isEmpty();
+ ASSERT_RESULT_OK(isEmpty);
+ ASSERT_FALSE(isEmpty.value());
+ ASSERT_RESULT_OK(testMap.deleteValue(key));
+ ASSERT_GT(0, findMapEntry(testMap.getMap(), &key, &value_write));
+ ASSERT_EQ(ENOENT, errno);
+ expectMapEmpty(testMap);
+ int entriesSeen = 0;
+ EXPECT_RESULT_OK(testMap.iterate(
+ [&entriesSeen](const unsigned int&,
+ const BpfMap<unsigned int, unsigned int>&) -> Result<void> {
+ entriesSeen++;
+ return {};
+ }));
+ EXPECT_EQ(0, entriesSeen);
+ EXPECT_RESULT_OK(testMap.iterateWithValue(
+ [&entriesSeen](const unsigned int&, const unsigned int&,
+ const BpfMap<unsigned int, unsigned int>&) -> Result<void> {
+ entriesSeen++;
+ return {};
+ }));
+ EXPECT_EQ(0, entriesSeen);
+}
+
+TEST_F(BpfMapTest, mapClear) {
+ BpfMap<uint32_t, uint32_t> testMap(BPF_MAP_TYPE_HASH, TEST_MAP_SIZE, BPF_F_NO_PREALLOC);
+ populateMap(TEST_MAP_SIZE, testMap);
+ Result<bool> isEmpty = testMap.isEmpty();
+ ASSERT_RESULT_OK(isEmpty);
+ ASSERT_FALSE(*isEmpty);
+ ASSERT_RESULT_OK(testMap.clear());
+ expectMapEmpty(testMap);
+}
+
+} // namespace bpf
+} // namespace android
diff --git a/staticlibs/native/bpf_map_utils/TEST_MAPPING b/staticlibs/native/bpf_map_utils/TEST_MAPPING
new file mode 100644
index 0000000..9ec8a40
--- /dev/null
+++ b/staticlibs/native/bpf_map_utils/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+ "presubmit": [
+ {
+ "name": "libbpf_android_test"
+ }
+ ]
+}
diff --git a/staticlibs/native/bpf_map_utils/include/bpf/BpfMap.h b/staticlibs/native/bpf_map_utils/include/bpf/BpfMap.h
new file mode 100644
index 0000000..bdffc0f
--- /dev/null
+++ b/staticlibs/native/bpf_map_utils/include/bpf/BpfMap.h
@@ -0,0 +1,260 @@
+/*
+ * Copyright (C) 2018 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 <linux/bpf.h>
+
+#include <android-base/result.h>
+#include <android-base/stringprintf.h>
+#include <android-base/unique_fd.h>
+#include <utils/Log.h>
+#include "bpf/BpfUtils.h"
+
+namespace android {
+namespace bpf {
+
+// This is a class wrapper for eBPF maps. The eBPF map is a special in-kernel
+// data structure that stores data in <Key, Value> pairs. It can be read/write
+// from userspace by passing syscalls with the map file descriptor. This class
+// is used to generalize the procedure of interacting with eBPF maps and hide
+// the implementation detail from other process. Besides the basic syscalls
+// wrapper, it also provides some useful helper functions as well as an iterator
+// nested class to iterate the map more easily.
+//
+// NOTE: A kernel eBPF map may be accessed by both kernel and userspace
+// processes at the same time. Or if the map is pinned as a virtual file, it can
+// be obtained by multiple eBPF map class object and accessed concurrently.
+// Though the map class object and the underlying kernel map are thread safe, it
+// is not safe to iterate over a map while another thread or process is deleting
+// from it. In this case the iteration can return duplicate entries.
+template <class Key, class Value>
+class BpfMap {
+ public:
+ BpfMap<Key, Value>() {};
+
+ protected:
+ // flag must be within BPF_OBJ_FLAG_MASK, ie. 0, BPF_F_RDONLY, BPF_F_WRONLY
+ BpfMap<Key, Value>(const char* pathname, uint32_t flags) {
+ int map_fd = mapRetrieve(pathname, flags);
+ if (map_fd >= 0) mMapFd.reset(map_fd);
+ }
+
+ public:
+ explicit BpfMap<Key, Value>(const char* pathname) : BpfMap<Key, Value>(pathname, 0) {}
+
+ BpfMap<Key, Value>(bpf_map_type map_type, uint32_t max_entries, uint32_t map_flags = 0) {
+ int map_fd = createMap(map_type, sizeof(Key), sizeof(Value), max_entries, map_flags);
+ if (map_fd >= 0) mMapFd.reset(map_fd);
+ }
+
+ base::Result<Key> getFirstKey() const {
+ Key firstKey;
+ if (getFirstMapKey(mMapFd, &firstKey)) {
+ return ErrnoErrorf("Get firstKey map {} failed", mMapFd.get());
+ }
+ return firstKey;
+ }
+
+ base::Result<Key> getNextKey(const Key& key) const {
+ Key nextKey;
+ if (getNextMapKey(mMapFd, &key, &nextKey)) {
+ return ErrnoErrorf("Get next key of map {} failed", mMapFd.get());
+ }
+ return nextKey;
+ }
+
+ base::Result<void> writeValue(const Key& key, const Value& value, uint64_t flags) {
+ if (writeToMapEntry(mMapFd, &key, &value, flags)) {
+ return ErrnoErrorf("Write to map {} failed", mMapFd.get());
+ }
+ return {};
+ }
+
+ base::Result<Value> readValue(const Key key) const {
+ Value value;
+ if (findMapEntry(mMapFd, &key, &value)) {
+ return ErrnoErrorf("Read value of map {} failed", mMapFd.get());
+ }
+ return value;
+ }
+
+ base::Result<void> deleteValue(const Key& key) {
+ if (deleteMapEntry(mMapFd, &key)) {
+ return ErrnoErrorf("Delete entry from map {} failed", mMapFd.get());
+ }
+ return {};
+ }
+
+ // Function that tries to get map from a pinned path.
+ base::Result<void> init(const char* path);
+
+ // Iterate through the map and handle each key retrieved based on the filter
+ // without modification of map content.
+ base::Result<void> iterate(
+ const std::function<base::Result<void>(const Key& key, const BpfMap<Key, Value>& map)>&
+ filter) const;
+
+ // Iterate through the map and get each <key, value> pair, handle each <key,
+ // value> pair based on the filter without modification of map content.
+ base::Result<void> iterateWithValue(
+ const std::function<base::Result<void>(const Key& key, const Value& value,
+ const BpfMap<Key, Value>& map)>& filter) const;
+
+ // Iterate through the map and handle each key retrieved based on the filter
+ base::Result<void> iterate(
+ const std::function<base::Result<void>(const Key& key, BpfMap<Key, Value>& map)>&
+ filter);
+
+ // Iterate through the map and get each <key, value> pair, handle each <key,
+ // value> pair based on the filter.
+ base::Result<void> iterateWithValue(
+ const std::function<base::Result<void>(const Key& key, const Value& value,
+ BpfMap<Key, Value>& map)>& filter);
+
+ const base::unique_fd& getMap() const { return mMapFd; };
+
+ // Copy assignment operator
+ BpfMap<Key, Value>& operator=(const BpfMap<Key, Value>& other) {
+ if (this != &other) mMapFd.reset(fcntl(other.mMapFd.get(), F_DUPFD_CLOEXEC, 0));
+ return *this;
+ }
+
+ // Move assignment operator
+ BpfMap<Key, Value>& operator=(BpfMap<Key, Value>&& other) noexcept {
+ mMapFd = std::move(other.mMapFd);
+ other.reset(-1);
+ return *this;
+ }
+
+ void reset(base::unique_fd fd) = delete;
+
+ void reset(int fd) { mMapFd.reset(fd); }
+
+ bool isValid() const { return mMapFd != -1; }
+
+ base::Result<void> clear() {
+ while (true) {
+ auto key = getFirstKey();
+ if (!key.ok()) {
+ if (key.error().code() == ENOENT) return {}; // empty: success
+ return key.error(); // Anything else is an error
+ }
+ auto res = deleteValue(key.value());
+ if (!res.ok()) {
+ // Someone else could have deleted the key, so ignore ENOENT
+ if (res.error().code() == ENOENT) continue;
+ ALOGE("Failed to delete data %s", strerror(res.error().code()));
+ return res.error();
+ }
+ }
+ }
+
+ base::Result<bool> isEmpty() const {
+ auto key = getFirstKey();
+ if (!key.ok()) {
+ // Return error code ENOENT means the map is empty
+ if (key.error().code() == ENOENT) return true;
+ return key.error();
+ }
+ return false;
+ }
+
+ private:
+ base::unique_fd mMapFd;
+};
+
+template <class Key, class Value>
+base::Result<void> BpfMap<Key, Value>::init(const char* path) {
+ mMapFd = base::unique_fd(mapRetrieveRW(path));
+ if (mMapFd == -1) {
+ return ErrnoErrorf("Pinned map not accessible or does not exist: ({})", path);
+ }
+ return {};
+}
+
+template <class Key, class Value>
+base::Result<void> BpfMap<Key, Value>::iterate(
+ const std::function<base::Result<void>(const Key& key, const BpfMap<Key, Value>& map)>&
+ filter) const {
+ base::Result<Key> curKey = getFirstKey();
+ while (curKey.ok()) {
+ const base::Result<Key>& nextKey = getNextKey(curKey.value());
+ base::Result<void> status = filter(curKey.value(), *this);
+ if (!status.ok()) return status;
+ curKey = nextKey;
+ }
+ if (curKey.error().code() == ENOENT) return {};
+ return curKey.error();
+}
+
+template <class Key, class Value>
+base::Result<void> BpfMap<Key, Value>::iterateWithValue(
+ const std::function<base::Result<void>(const Key& key, const Value& value,
+ const BpfMap<Key, Value>& map)>& filter) const {
+ base::Result<Key> curKey = getFirstKey();
+ while (curKey.ok()) {
+ const base::Result<Key>& nextKey = getNextKey(curKey.value());
+ base::Result<Value> curValue = readValue(curKey.value());
+ if (!curValue.ok()) return curValue.error();
+ base::Result<void> status = filter(curKey.value(), curValue.value(), *this);
+ if (!status.ok()) return status;
+ curKey = nextKey;
+ }
+ if (curKey.error().code() == ENOENT) return {};
+ return curKey.error();
+}
+
+template <class Key, class Value>
+base::Result<void> BpfMap<Key, Value>::iterate(
+ const std::function<base::Result<void>(const Key& key, BpfMap<Key, Value>& map)>& filter) {
+ base::Result<Key> curKey = getFirstKey();
+ while (curKey.ok()) {
+ const base::Result<Key>& nextKey = getNextKey(curKey.value());
+ base::Result<void> status = filter(curKey.value(), *this);
+ if (!status.ok()) return status;
+ curKey = nextKey;
+ }
+ if (curKey.error().code() == ENOENT) return {};
+ return curKey.error();
+}
+
+template <class Key, class Value>
+base::Result<void> BpfMap<Key, Value>::iterateWithValue(
+ const std::function<base::Result<void>(const Key& key, const Value& value,
+ BpfMap<Key, Value>& map)>& filter) {
+ base::Result<Key> curKey = getFirstKey();
+ while (curKey.ok()) {
+ const base::Result<Key>& nextKey = getNextKey(curKey.value());
+ base::Result<Value> curValue = readValue(curKey.value());
+ if (!curValue.ok()) return curValue.error();
+ base::Result<void> status = filter(curKey.value(), curValue.value(), *this);
+ if (!status.ok()) return status;
+ curKey = nextKey;
+ }
+ if (curKey.error().code() == ENOENT) return {};
+ return curKey.error();
+}
+
+template <class Key, class Value>
+class BpfMapRO : public BpfMap<Key, Value> {
+ public:
+ explicit BpfMapRO<Key, Value>(const char* pathname)
+ : BpfMap<Key, Value>(pathname, BPF_F_RDONLY) {}
+};
+
+} // namespace bpf
+} // namespace android
diff --git a/staticlibs/native/bpf_map_utils/include/bpf/BpfUtils.h b/staticlibs/native/bpf_map_utils/include/bpf/BpfUtils.h
new file mode 100644
index 0000000..265d4b6
--- /dev/null
+++ b/staticlibs/native/bpf_map_utils/include/bpf/BpfUtils.h
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2017 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 <linux/if_ether.h>
+#include <linux/pfkeyv2.h>
+#include <net/if.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/resource.h>
+#include <sys/socket.h>
+#include <sys/utsname.h>
+
+#include <string>
+
+#include <android-base/unique_fd.h>
+#include <log/log.h>
+
+#include "BpfSyscallWrappers.h"
+
+// The buffer size for the buffer that records program loading logs, needs to be large enough for
+// the largest kernel program.
+
+namespace android {
+namespace bpf {
+
+constexpr const int OVERFLOW_COUNTERSET = 2;
+
+constexpr const uint64_t NONEXISTENT_COOKIE = 0;
+
+static inline uint64_t getSocketCookie(int sockFd) {
+ uint64_t sock_cookie;
+ socklen_t cookie_len = sizeof(sock_cookie);
+ int res = getsockopt(sockFd, SOL_SOCKET, SO_COOKIE, &sock_cookie, &cookie_len);
+ if (res < 0) {
+ res = -errno;
+ ALOGE("Failed to get socket cookie: %s\n", strerror(errno));
+ errno = -res;
+ // 0 is an invalid cookie. See sock_gen_cookie.
+ return NONEXISTENT_COOKIE;
+ }
+ return sock_cookie;
+}
+
+static inline int synchronizeKernelRCU() {
+ // This is a temporary hack for network stats map swap on devices running
+ // 4.9 kernels. The kernel code of socket release on pf_key socket will
+ // explicitly call synchronize_rcu() which is exactly what we need.
+ int pfSocket = socket(AF_KEY, SOCK_RAW | SOCK_CLOEXEC, PF_KEY_V2);
+
+ if (pfSocket < 0) {
+ int ret = -errno;
+ ALOGE("create PF_KEY socket failed: %s", strerror(errno));
+ return ret;
+ }
+
+ // When closing socket, synchronize_rcu() gets called in sock_release().
+ if (close(pfSocket)) {
+ int ret = -errno;
+ ALOGE("failed to close the PF_KEY socket: %s", strerror(errno));
+ return ret;
+ }
+ return 0;
+}
+
+static inline int setrlimitForTest() {
+ // Set the memory rlimit for the test process if the default MEMLOCK rlimit is not enough.
+ struct rlimit limit = {
+ .rlim_cur = 1073741824, // 1 GiB
+ .rlim_max = 1073741824, // 1 GiB
+ };
+ int res = setrlimit(RLIMIT_MEMLOCK, &limit);
+ if (res) {
+ ALOGE("Failed to set the default MEMLOCK rlimit: %s", strerror(errno));
+ }
+ return res;
+}
+
+#define KVER(a, b, c) (((a) << 24) + ((b) << 16) + (c))
+
+static inline unsigned kernelVersion() {
+ struct utsname buf;
+ int ret = uname(&buf);
+ if (ret) return 0;
+
+ unsigned kver_major;
+ unsigned kver_minor;
+ unsigned kver_sub;
+ char unused;
+ ret = sscanf(buf.release, "%u.%u.%u%c", &kver_major, &kver_minor, &kver_sub, &unused);
+ // Check the device kernel version
+ if (ret < 3) return 0;
+
+ return KVER(kver_major, kver_minor, kver_sub);
+}
+
+static inline bool isAtLeastKernelVersion(unsigned major, unsigned minor, unsigned sub) {
+ return kernelVersion() >= KVER(major, minor, sub);
+}
+
+#define SKIP_IF_BPF_SUPPORTED \
+ do { \
+ if (android::bpf::isAtLeastKernelVersion(4, 9, 0)) { \
+ GTEST_LOG_(INFO) << "This test is skipped since bpf is supported\n"; \
+ return; \
+ } \
+ } while (0)
+
+#define SKIP_IF_BPF_NOT_SUPPORTED \
+ do { \
+ if (!android::bpf::isAtLeastKernelVersion(4, 9, 0)) { \
+ GTEST_LOG_(INFO) << "This test is skipped since bpf is not supported\n"; \
+ return; \
+ } \
+ } while (0)
+
+#define SKIP_IF_EXTENDED_BPF_NOT_SUPPORTED \
+ do { \
+ if (!android::bpf::isAtLeastKernelVersion(4, 14, 0)) { \
+ GTEST_LOG_(INFO) << "This test is skipped since extended bpf feature" \
+ << "not supported\n"; \
+ return; \
+ } \
+ } while (0)
+
+#define SKIP_IF_XDP_NOT_SUPPORTED \
+ do { \
+ if (!android::bpf::isAtLeastKernelVersion(5, 9, 0)) { \
+ GTEST_LOG_(INFO) << "This test is skipped since xdp" \
+ << "not supported\n"; \
+ return; \
+ } \
+ } while (0)
+
+} // namespace bpf
+} // namespace android
diff --git a/staticlibs/native/bpf_map_utils/include/bpf/WaitForProgsLoaded.h b/staticlibs/native/bpf_map_utils/include/bpf/WaitForProgsLoaded.h
new file mode 100644
index 0000000..bc4168e
--- /dev/null
+++ b/staticlibs/native/bpf_map_utils/include/bpf/WaitForProgsLoaded.h
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ * Android BPF library - public API
+ *
+ * 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 <log/log.h>
+
+#include <android-base/properties.h>
+
+namespace android {
+namespace bpf {
+
+// Wait for bpfloader to load BPF programs.
+static inline void waitForProgsLoaded() {
+ // infinite loop until success with 5/10/20/40/60/60/60... delay
+ for (int delay = 5;; delay *= 2) {
+ if (delay > 60) delay = 60;
+ if (android::base::WaitForProperty("bpf.progs_loaded", "1", std::chrono::seconds(delay)))
+ return;
+ ALOGW("Waited %ds for bpf.progs_loaded, still waiting...", delay);
+ }
+}
+
+} // namespace bpf
+} // namespace android
diff --git a/staticlibs/native/bpf_syscall_wrappers/Android.bp b/staticlibs/native/bpf_syscall_wrappers/Android.bp
index 1416b6b..037e10d 100644
--- a/staticlibs/native/bpf_syscall_wrappers/Android.bp
+++ b/staticlibs/native/bpf_syscall_wrappers/Android.bp
@@ -30,12 +30,15 @@
min_sdk_version: "30",
apex_available: [
"//apex_available:platform",
+ "com.android.mediaprovider",
"com.android.tethering",
],
visibility: [
"//frameworks/libs/net/common/native/bpfmapjni",
+ "//packages/modules/Connectivity/netd",
"//packages/modules/Connectivity/service",
"//packages/modules/Connectivity/Tethering",
+ "//packages/providers/MediaProvider/jni",
"//system/bpf/libbpf_android",
"//system/memory/lmkd",
],
diff --git a/staticlibs/native/ip_checksum/Android.bp b/staticlibs/native/ip_checksum/Android.bp
new file mode 100644
index 0000000..d7e195d
--- /dev/null
+++ b/staticlibs/native/ip_checksum/Android.bp
@@ -0,0 +1,40 @@
+// Copyright (C) 2021 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 {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+cc_library_static {
+ name: "libip_checksum",
+
+ srcs: [
+ "checksum.c",
+ ],
+
+ cflags: [
+ "-Wall",
+ "-Werror",
+ ],
+
+ export_include_dirs: ["."],
+
+ // Needed because libnetutils depends on libip_checksum, and libnetutils has
+ // vendor_available = true. Making this library vendor_available does not create any maintenance
+ // burden or version skew issues because this library is only static, not dynamic, and thus is
+ // not installed on the device.
+ //
+ // TODO: delete libnetutils from the VNDK in T, and remove this.
+ vendor_available: true,
+}
diff --git a/staticlibs/native/ip_checksum/checksum.c b/staticlibs/native/ip_checksum/checksum.c
new file mode 100644
index 0000000..04217a7
--- /dev/null
+++ b/staticlibs/native/ip_checksum/checksum.c
@@ -0,0 +1,145 @@
+/*
+ * Copyright 2011 Daniel Drown
+ *
+ * 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.
+ *
+ * checksum.c - ipv4/ipv6 checksum calculation
+ */
+#include <netinet/icmp6.h>
+#include <netinet/in.h>
+#include <netinet/ip.h>
+#include <netinet/ip6.h>
+#include <netinet/ip_icmp.h>
+#include <netinet/tcp.h>
+#include <netinet/udp.h>
+
+#include "checksum.h"
+
+/* function: ip_checksum_add
+ * adds data to a checksum. only known to work on little-endian hosts
+ * current - the current checksum (or 0 to start a new checksum)
+ * data - the data to add to the checksum
+ * len - length of data
+ */
+uint32_t ip_checksum_add(uint32_t current, const void* data, int len) {
+ uint32_t checksum = current;
+ int left = len;
+ const uint16_t* data_16 = data;
+
+ while (left > 1) {
+ checksum += *data_16;
+ data_16++;
+ left -= 2;
+ }
+ if (left) {
+ checksum += *(uint8_t*)data_16;
+ }
+
+ return checksum;
+}
+
+/* function: ip_checksum_fold
+ * folds a 32-bit partial checksum into 16 bits
+ * temp_sum - sum from ip_checksum_add
+ * returns: the folded checksum in network byte order
+ */
+uint16_t ip_checksum_fold(uint32_t temp_sum) {
+ while (temp_sum > 0xffff) {
+ temp_sum = (temp_sum >> 16) + (temp_sum & 0xFFFF);
+ }
+ return temp_sum;
+}
+
+/* function: ip_checksum_finish
+ * folds and closes the checksum
+ * temp_sum - sum from ip_checksum_add
+ * returns: a header checksum value in network byte order
+ */
+uint16_t ip_checksum_finish(uint32_t temp_sum) {
+ return ~ip_checksum_fold(temp_sum);
+}
+
+/* function: ip_checksum
+ * combined ip_checksum_add and ip_checksum_finish
+ * data - data to checksum
+ * len - length of data
+ */
+uint16_t ip_checksum(const void* data, int len) {
+ // TODO: consider starting from 0xffff so the checksum of a buffer entirely consisting of zeros
+ // is correctly calculated as 0.
+ uint32_t temp_sum;
+
+ temp_sum = ip_checksum_add(0, data, len);
+ return ip_checksum_finish(temp_sum);
+}
+
+/* function: ipv6_pseudo_header_checksum
+ * calculate the pseudo header checksum for use in tcp/udp/icmp headers
+ * ip6 - the ipv6 header
+ * len - the transport length (transport header + payload)
+ * protocol - the transport layer protocol, can be different from ip6->ip6_nxt for fragments
+ */
+uint32_t ipv6_pseudo_header_checksum(const struct ip6_hdr* ip6, uint32_t len, uint8_t protocol) {
+ uint32_t checksum_len = htonl(len);
+ uint32_t checksum_next = htonl(protocol);
+
+ uint32_t current = 0;
+
+ current = ip_checksum_add(current, &(ip6->ip6_src), sizeof(struct in6_addr));
+ current = ip_checksum_add(current, &(ip6->ip6_dst), sizeof(struct in6_addr));
+ current = ip_checksum_add(current, &checksum_len, sizeof(checksum_len));
+ current = ip_checksum_add(current, &checksum_next, sizeof(checksum_next));
+
+ return current;
+}
+
+/* function: ipv4_pseudo_header_checksum
+ * calculate the pseudo header checksum for use in tcp/udp headers
+ * ip - the ipv4 header
+ * len - the transport length (transport header + payload)
+ */
+uint32_t ipv4_pseudo_header_checksum(const struct iphdr* ip, uint16_t len) {
+ uint16_t temp_protocol, temp_length;
+
+ temp_protocol = htons(ip->protocol);
+ temp_length = htons(len);
+
+ uint32_t current = 0;
+
+ current = ip_checksum_add(current, &(ip->saddr), sizeof(uint32_t));
+ current = ip_checksum_add(current, &(ip->daddr), sizeof(uint32_t));
+ current = ip_checksum_add(current, &temp_protocol, sizeof(uint16_t));
+ current = ip_checksum_add(current, &temp_length, sizeof(uint16_t));
+
+ return current;
+}
+
+/* function: ip_checksum_adjust
+ * calculates a new checksum given a previous checksum and the old and new pseudo-header checksums
+ * checksum - the header checksum in the original packet in network byte order
+ * old_hdr_sum - the pseudo-header checksum of the original packet
+ * new_hdr_sum - the pseudo-header checksum of the translated packet
+ * returns: the new header checksum in network byte order
+ */
+uint16_t ip_checksum_adjust(uint16_t checksum, uint32_t old_hdr_sum, uint32_t new_hdr_sum) {
+ // Algorithm suggested in RFC 1624.
+ // http://tools.ietf.org/html/rfc1624#section-3
+ checksum = ~checksum;
+ uint16_t folded_sum = ip_checksum_fold(checksum + new_hdr_sum);
+ uint16_t folded_old = ip_checksum_fold(old_hdr_sum);
+ if (folded_sum > folded_old) {
+ return ~(folded_sum - folded_old);
+ } else {
+ return ~(folded_sum - folded_old - 1); // end-around borrow
+ }
+}
diff --git a/staticlibs/native/ip_checksum/checksum.h b/staticlibs/native/ip_checksum/checksum.h
new file mode 100644
index 0000000..868217c
--- /dev/null
+++ b/staticlibs/native/ip_checksum/checksum.h
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2011 Daniel Drown
+ *
+ * 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.
+ *
+ * checksum.h - checksum functions
+ */
+#ifndef __CHECKSUM_H__
+#define __CHECKSUM_H__
+
+#include <netinet/ip.h>
+#include <netinet/ip6.h>
+#include <stdint.h>
+
+uint32_t ip_checksum_add(uint32_t current, const void* data, int len);
+uint16_t ip_checksum_finish(uint32_t temp_sum);
+uint16_t ip_checksum(const void* data, int len);
+
+uint32_t ipv6_pseudo_header_checksum(const struct ip6_hdr* ip6, uint32_t len, uint8_t protocol);
+uint32_t ipv4_pseudo_header_checksum(const struct iphdr* ip, uint16_t len);
+
+uint16_t ip_checksum_adjust(uint16_t checksum, uint32_t old_hdr_sum, uint32_t new_hdr_sum);
+
+#endif /* __CHECKSUM_H__ */
diff --git a/staticlibs/netd/libnetdutils/Android.bp b/staticlibs/netd/libnetdutils/Android.bp
index 3bb6270..732e37d 100644
--- a/staticlibs/netd/libnetdutils/Android.bp
+++ b/staticlibs/netd/libnetdutils/Android.bp
@@ -18,6 +18,7 @@
"Syscalls.cpp",
"UniqueFd.cpp",
"UniqueFile.cpp",
+ "Utils.cpp",
],
defaults: ["netd_defaults"],
cflags: ["-Wall", "-Werror"],
@@ -36,6 +37,7 @@
apex_available: [
"//apex_available:platform",
"com.android.resolv",
+ "com.android.tethering",
],
min_sdk_version: "29",
}
diff --git a/staticlibs/netd/libnetdutils/Utils.cpp b/staticlibs/netd/libnetdutils/Utils.cpp
new file mode 100644
index 0000000..16ec882
--- /dev/null
+++ b/staticlibs/netd/libnetdutils/Utils.cpp
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+
+#include <map>
+
+#include <net/if.h>
+
+#include "dirent.h"
+#include "netdutils/Status.h"
+#include "netdutils/Utils.h"
+
+namespace android {
+namespace netdutils {
+
+StatusOr<std::vector<std::string>> getIfaceNames() {
+ std::vector<std::string> ifaceNames;
+ DIR* d;
+ struct dirent* de;
+
+ if (!(d = opendir("/sys/class/net"))) {
+ return statusFromErrno(errno, "Cannot open iface directory");
+ }
+ while ((de = readdir(d))) {
+ if ((de->d_type != DT_DIR) && (de->d_type != DT_LNK)) continue;
+ if (de->d_name[0] == '.') continue;
+ ifaceNames.push_back(std::string(de->d_name));
+ }
+ closedir(d);
+ return ifaceNames;
+}
+
+StatusOr<std::map<std::string, uint32_t>> getIfaceList() {
+ std::map<std::string, uint32_t> ifacePairs;
+
+ ASSIGN_OR_RETURN(auto ifaceNames, getIfaceNames());
+
+ for (const auto& name : ifaceNames) {
+ uint32_t ifaceIndex = if_nametoindex(name.c_str());
+ if (ifaceIndex) {
+ ifacePairs.insert(std::pair<std::string, uint32_t>(name, ifaceIndex));
+ }
+ }
+ return ifacePairs;
+}
+
+} // namespace netdutils
+} // namespace android
diff --git a/staticlibs/netd/libnetdutils/include/netdutils/Utils.h b/staticlibs/netd/libnetdutils/include/netdutils/Utils.h
new file mode 100644
index 0000000..83c583b
--- /dev/null
+++ b/staticlibs/netd/libnetdutils/include/netdutils/Utils.h
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+
+#ifndef NETUTILS_UTILS_H
+#define NETUTILS_UTILS_H
+
+#include "netdutils/StatusOr.h"
+
+namespace android {
+namespace netdutils {
+
+StatusOr<std::vector<std::string>> getIfaceNames();
+
+StatusOr<std::map<std::string, uint32_t>> getIfaceList();
+
+} // namespace netdutils
+} // namespace android
+
+#endif /* NETUTILS_UTILS_H */
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/CleanupTest.kt b/staticlibs/tests/unit/src/com/android/net/module/util/CleanupTest.kt
index 0067931..649b30e 100644
--- a/staticlibs/tests/unit/src/com/android/net/module/util/CleanupTest.kt
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/CleanupTest.kt
@@ -32,6 +32,7 @@
class CleanupTest {
class TestException1 : Exception()
class TestException2 : Exception()
+ class TestException3 : Exception()
@Test
fun testNotThrow() {
@@ -172,4 +173,32 @@
assertTrue(x == 4)
assertTrue(thrown.suppressedExceptions.isEmpty())
}
+
+ @Test
+ fun testMultipleCleanups() {
+ var x = 1
+ val thrown = assertFailsWith<TestException1> {
+ tryTest {
+ x = 2
+ throw TestException1()
+ } cleanupStep {
+ assertTrue(x == 2)
+ x = 3
+ throw TestException2()
+ x = 4
+ } cleanupStep {
+ assertTrue(x == 3)
+ x = 5
+ throw TestException3()
+ x = 6
+ } cleanup {
+ assertTrue(x == 5)
+ x = 7
+ }
+ }
+ assertEquals(2, thrown.suppressedExceptions.size)
+ assertTrue(thrown.suppressedExceptions[0] is TestException2)
+ assertTrue(thrown.suppressedExceptions[1] is TestException3)
+ assert(x == 7)
+ }
}
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/CleanupTestJava.java b/staticlibs/tests/unit/src/com/android/net/module/util/CleanupTestJava.java
index 83abfa1..8a13397 100644
--- a/staticlibs/tests/unit/src/com/android/net/module/util/CleanupTestJava.java
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/CleanupTestJava.java
@@ -20,6 +20,7 @@
import static com.android.testutils.MiscAsserts.assertThrows;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
import android.util.Log;
@@ -31,6 +32,7 @@
private static final String TAG = CleanupTestJava.class.getSimpleName();
private static final class TestException1 extends Exception {}
private static final class TestException2 extends Exception {}
+ private static final class TestException3 extends Exception {}
@Test
public void testNotThrow() {
@@ -93,4 +95,27 @@
);
assertEquals(3, x.get());
}
+
+ @Test
+ public void testMultipleCleanups() {
+ final AtomicInteger x = new AtomicInteger(1);
+ final TestException1 exception = assertThrows(TestException1.class, () ->
+ testAndCleanup(() -> {
+ x.compareAndSet(1, 2);
+ throw new TestException1();
+ }, () -> {
+ x.compareAndSet(2, 3);
+ throw new TestException2();
+ }, () -> {
+ x.compareAndSet(3, 4);
+ throw new TestException3();
+ }, () -> {
+ x.compareAndSet(4, 5);
+ })
+ );
+ assertEquals(2, exception.getSuppressed().length);
+ assertTrue(exception.getSuppressed()[0] instanceof TestException2);
+ assertTrue(exception.getSuppressed()[1] instanceof TestException3);
+ assertEquals(5, x.get());
+ }
}
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/netlink/RtNetlinkLinkMessageTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/netlink/RtNetlinkLinkMessageTest.java
index 5d446b8..9db63db 100644
--- a/staticlibs/tests/unit/src/com/android/net/module/util/netlink/RtNetlinkLinkMessageTest.java
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/netlink/RtNetlinkLinkMessageTest.java
@@ -47,7 +47,7 @@
private static final String RTM_NEWLINK_HEX =
"64000000100000000000000000000000" // struct nlmsghr
+ "000001001E0000000210000000000000" // struct ifinfo
- + "0A000300776C616E30000000" // IFLA_IFNAME
+ + "0A000300776C616E30000000" // IFLA_IFNAME(wlan0)
+ "08000D00B80B0000" // IFLA_PROTINFO
+ "0500100002000000" // IFLA_OPERSTATE
+ "0500110001000000" // IFLA_LINKMODE
@@ -88,12 +88,47 @@
assertTrue(linkMsg.getInterfaceName().equals("wlan0"));
}
+ /**
+ * Example:
+ * # adb shell ip tunnel add トン0 mode sit local any remote 8.8.8.8
+ * # adb shell ip link show | grep トン
+ * 33: トン0@NONE: <POINTOPOINT,NOARP> mtu 1480 qdisc noop state DOWN mode DEFAULT group
+ * default qlen 1000
+ *
+ * IFLA_IFNAME attribute: \x0c\x00\x03\x00\xe3\x83\x88\xe3\x83\xb3\x30\x00
+ * length: 0x000c
+ * type: 0x0003
+ * value: \xe3\x83\x88\xe3\x83\xb3\x30\x00
+ * ト (\xe3\x83\x88)
+ * ン (\xe3\x83\xb3)
+ * 0 (\x30)
+ * null terminated (\x00)
+ */
+ private static final String RTM_NEWLINK_UTF8_HEX =
+ "34000000100000000000000000000000" // struct nlmsghr
+ + "000001001E0000000210000000000000" // struct ifinfo
+ + "08000400DC050000" // IFLA_MTU
+ + "0A00010092C3E3C9374E0000" // IFLA_ADDRESS
+ + "0C000300E38388E383B33000"; // IFLA_IFNAME(トン0)
+
+ @Test
+ public void testParseRtmNewLink_utf8Ifname() {
+ final ByteBuffer byteBuffer = toByteBuffer(RTM_NEWLINK_UTF8_HEX);
+ byteBuffer.order(ByteOrder.LITTLE_ENDIAN); // For testing.
+ final NetlinkMessage msg = NetlinkMessage.parse(byteBuffer, NETLINK_ROUTE);
+ assertNotNull(msg);
+ assertTrue(msg instanceof RtNetlinkLinkMessage);
+ final RtNetlinkLinkMessage linkMsg = (RtNetlinkLinkMessage) msg;
+
+ assertTrue(linkMsg.getInterfaceName().equals("トン0"));
+ }
+
private static final String RTM_NEWLINK_PACK_HEX =
"34000000100000000000000000000000" // struct nlmsghr
+ "000001001E0000000210000000000000" // struct ifinfo
+ "08000400DC050000" // IFLA_MTU
+ "0A00010092C3E3C9374E0000" // IFLA_ADDRESS
- + "0A000300776C616E30000000"; // IFLA_IFNAME
+ + "0A000300776C616E30000000"; // IFLA_IFNAME(wlan0)
@Test
public void testPackRtmNewLink() {
@@ -117,7 +152,7 @@
+ "0500100002000000" // IFLA_OPERSTATE
+ "0800010092C3E3C9" // IFLA_ADDRESS(truncated)
+ "0500110001000000" // IFLA_LINKMODE
- + "0A000300776C616E30000000" // IFLA_IFNAME
+ + "0A000300776C616E30000000" // IFLA_IFNAME(wlan0)
+ "08000400DC050000"; // IFLA_MTU
@Test
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/netlink/StructNlAttrTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/netlink/StructNlAttrTest.java
index 72e179b..af3fac2 100644
--- a/staticlibs/tests/unit/src/com/android/net/module/util/netlink/StructNlAttrTest.java
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/netlink/StructNlAttrTest.java
@@ -16,6 +16,7 @@
package com.android.net.module.util.netlink;
+import static com.android.net.module.util.netlink.RtNetlinkAddressMessage.IFA_FLAGS;
import static com.android.net.module.util.netlink.RtNetlinkLinkMessage.IFLA_ADDRESS;
import static com.android.net.module.util.netlink.RtNetlinkLinkMessage.IFLA_IFNAME;
@@ -35,6 +36,7 @@
public class StructNlAttrTest {
private static final MacAddress TEST_MAC_ADDRESS = MacAddress.fromString("00:11:22:33:44:55");
private static final String TEST_INTERFACE_NAME = "wlan0";
+ private static final int TEST_ADDR_FLAGS = 0x80;
@Test
public void testGetValueAsMacAddress() {
@@ -65,4 +67,29 @@
final String str2 = attr2.getValueAsString();
assertEquals(str2, TEST_INTERFACE_NAME);
}
+
+ @Test
+ public void testGetValueAsIntger() {
+ final StructNlAttr attr1 = new StructNlAttr(IFA_FLAGS, TEST_ADDR_FLAGS);
+ final Integer integer1 = attr1.getValueAsInteger();
+ final int int1 = attr1.getValueAsInt(0x08 /* default value */);
+ assertEquals(integer1, new Integer(TEST_ADDR_FLAGS));
+ assertEquals(int1, TEST_ADDR_FLAGS);
+
+ // Malformed attribute.
+ final byte[] malformed_int = new byte[] { (byte) 0x0, (byte) 0x0, (byte) 0x80, };
+ final StructNlAttr attr2 = new StructNlAttr(IFA_FLAGS, malformed_int);
+ final Integer integer2 = attr2.getValueAsInteger();
+ final int int2 = attr2.getValueAsInt(0x08 /* default value */);
+ assertNull(integer2);
+ assertEquals(int2, 0x08 /* default value */);
+
+ // Null attribute value.
+ final byte[] null_int = null;
+ final StructNlAttr attr3 = new StructNlAttr(IFA_FLAGS, null_int);
+ final Integer integer3 = attr3.getValueAsInteger();
+ final int int3 = attr3.getValueAsInt(0x08 /* default value */);
+ assertNull(integer3);
+ assertEquals(int3, 0x08 /* default value */);
+ }
}
diff --git a/staticlibs/testutils/devicetests/com/android/testutils/TestableNetworkStatsProvider.kt b/staticlibs/testutils/devicetests/com/android/testutils/TestableNetworkStatsProvider.kt
index be5c9b2..4a7b351 100644
--- a/staticlibs/testutils/devicetests/com/android/testutils/TestableNetworkStatsProvider.kt
+++ b/staticlibs/testutils/devicetests/com/android/testutils/TestableNetworkStatsProvider.kt
@@ -17,6 +17,7 @@
package com.android.testutils
import android.net.netstats.provider.NetworkStatsProvider
+import android.util.Log
import com.android.net.module.util.ArrayTrackRecord
import kotlin.test.assertEquals
import kotlin.test.assertTrue
@@ -43,23 +44,28 @@
data class OnSetAlert(val quotaBytes: Long) : CallbackType()
}
+ private val TAG = this::class.simpleName
val history = ArrayTrackRecord<CallbackType>().newReadHead()
// See ReadHead#mark
val mark get() = history.mark
override fun onRequestStatsUpdate(token: Int) {
+ Log.d(TAG, "onRequestStatsUpdate $token")
history.add(CallbackType.OnRequestStatsUpdate(token))
}
override fun onSetWarningAndLimit(iface: String, warningBytes: Long, limitBytes: Long) {
+ Log.d(TAG, "onSetWarningAndLimit $iface $warningBytes $limitBytes")
history.add(CallbackType.OnSetWarningAndLimit(iface, warningBytes, limitBytes))
}
override fun onSetLimit(iface: String, quotaBytes: Long) {
+ Log.d(TAG, "onSetLimit $iface $quotaBytes")
history.add(CallbackType.OnSetLimit(iface, quotaBytes))
}
override fun onSetAlert(quotaBytes: Long) {
+ Log.d(TAG, "onSetAlert $quotaBytes")
history.add(CallbackType.OnSetAlert(quotaBytes))
}
diff --git a/staticlibs/testutils/hostdevice/com/android/testutils/Cleanup.kt b/staticlibs/testutils/hostdevice/com/android/testutils/Cleanup.kt
index 1b67f68..45783d8 100644
--- a/staticlibs/testutils/hostdevice/com/android/testutils/Cleanup.kt
+++ b/staticlibs/testutils/hostdevice/com/android/testutils/Cleanup.kt
@@ -22,14 +22,6 @@
import com.android.testutils.ExceptionUtils.ThrowingSupplier
import javax.annotation.CheckReturnValue
-@CheckReturnValue
-fun <T> tryTest(block: () -> T) = TryExpr(
- try {
- Result.success(block())
- } catch (e: Throwable) {
- Result.failure(e)
- })
-
/**
* Utility to do cleanup in tests without replacing exceptions with those from a finally block.
*
@@ -54,11 +46,15 @@
* to the standard try{}finally{}, if both throws, the construct throws the exception that happened
* in tryTest{} rather than the one that happened in cleanup{}.
*
- * Kotlin usage is as try{}finally{} :
+ * Kotlin usage is as try{}finally{}, but with multiple finally{} blocks :
* tryTest {
* testing code
+ * } cleanupStep {
+ * cleanup code 1
+ * } cleanupStep {
+ * cleanup code 2
* } cleanup {
- * cleanup code
+ * cleanup code 3
* }
* Catch blocks can be added with the following syntax :
* tryTest {
@@ -67,14 +63,24 @@
* do something to it
* }
*
- * Java doesn't allow this kind of syntax, so instead a function taking 2 lambdas is provided.
+ * Java doesn't allow this kind of syntax, so instead a function taking lambdas is provided.
* testAndCleanup(() -> {
* testing code
* }, () -> {
- * cleanup code
+ * cleanup code 1
+ * }, () -> {
+ * cleanup code 2
* });
*/
+@CheckReturnValue
+fun <T> tryTest(block: () -> T) = TryExpr(
+ try {
+ Result.success(block())
+ } catch (e: Throwable) {
+ Result.failure(e)
+ })
+
// Some downstream branches have an older kotlin that doesn't know about value classes.
// TODO : Change this to "value class" when aosp no longer merges into such branches.
@Suppress("INLINE_CLASS_DEPRECATED")
@@ -89,30 +95,31 @@
})
}
- inline infix fun cleanup(block: () -> Unit): T {
+ @CheckReturnValue
+ inline infix fun cleanupStep(block: () -> Unit): TryExpr<T> {
try {
block()
} catch (e: Throwable) {
val originalException = result.exceptionOrNull()
- if (null == originalException) {
- throw e
+ return TryExpr(if (null == originalException) {
+ Result.failure(e)
} else {
originalException.addSuppressed(e)
- throw originalException
- }
+ Result.failure(originalException)
+ })
}
- return result.getOrThrow()
+ return this
}
+
+ inline infix fun cleanup(block: () -> Unit): T = cleanupStep(block).result.getOrThrow()
}
// Java support
-fun <T> testAndCleanup(tryBlock: ThrowingSupplier<T>, cleanupBlock: ThrowingRunnable): T {
- return tryTest {
- tryBlock.get()
- } cleanup {
- cleanupBlock.run()
- }
+fun <T> testAndCleanup(tryBlock: ThrowingSupplier<T>, vararg cleanupBlock: ThrowingRunnable): T {
+ return cleanupBlock.fold(tryTest { tryBlock.get() }) { previousExpr, nextCleanup ->
+ previousExpr.cleanupStep { nextCleanup.run() }
+ }.cleanup {}
}
-fun testAndCleanup(tryBlock: ThrowingRunnable, cleanupBlock: ThrowingRunnable) {
- return testAndCleanup(ThrowingSupplier { tryBlock.run() }, cleanupBlock)
+fun testAndCleanup(tryBlock: ThrowingRunnable, vararg cleanupBlock: ThrowingRunnable) {
+ return testAndCleanup(ThrowingSupplier { tryBlock.run() }, *cleanupBlock)
}