Merge "PrivateAddressCoordinator: get rid of UpstreamNetworkState for updateUpstreamPrefix()" into main
diff --git a/bpf/loader/netbpfload.rc b/bpf/loader/netbpfload.rc
index e1af47f..10bfbb2 100644
--- a/bpf/loader/netbpfload.rc
+++ b/bpf/loader/netbpfload.rc
@@ -1,22 +1,3 @@
-# zygote-start is what officially starts netd (see //system/core/rootdir/init.rc)
-# However, on some hardware it's started from post-fs-data as well, which is just
-# a tad earlier.  There's no benefit to that though, since on 4.9+ P+ devices netd
-# will just block until bpfloader finishes and sets the bpf.progs_loaded property.
-#
-# It is important that we start bpfloader after:
-#   - /sys/fs/bpf is already mounted,
-#   - apex (incl. rollback) is initialized (so that in the future we can load bpf
-#     programs shipped as part of apex mainline modules)
-#   - logd is ready for us to log stuff
-#
-# At the same time we want to be as early as possible to reduce races and thus
-# failures (before memory is fragmented, and cpu is busy running tons of other
-# stuff) and we absolutely want to be before netd and the system boot slot is
-# considered to have booted successfully.
-#
-on load_bpf_programs
-    exec_start bpfloader
-
 # Note: This will actually execute /apex/com.android.tethering/bin/netbpfload
 # by virtue of 'service bpfloader' being overridden by the apex shipped .rc
 # Warning: most of the below settings are irrelevant unless the apex is missing.
diff --git a/nearby/framework/java/android/nearby/NearbyManager.java b/nearby/framework/java/android/nearby/NearbyManager.java
index 39adee3..150394b 100644
--- a/nearby/framework/java/android/nearby/NearbyManager.java
+++ b/nearby/framework/java/android/nearby/NearbyManager.java
@@ -123,9 +123,12 @@
 
     private static final int POWERED_OFF_FINDING_EID_LENGTH = 20;
 
-    private static final String POWER_OFF_FINDING_SUPPORTED_PROPERTY =
+    private static final String POWER_OFF_FINDING_SUPPORTED_PROPERTY_RO =
             "ro.bluetooth.finder.supported";
 
+    private static final String POWER_OFF_FINDING_SUPPORTED_PROPERTY_PERSIST =
+            "persist.bluetooth.finder.supported";
+
     /**
      * TODO(b/286137024): Remove this when CTS R5 is rolled out.
      * Whether allows Fast Pair to scan.
@@ -618,7 +621,9 @@
     }
 
     private boolean isPoweredOffFindingSupported() {
-        return Boolean.parseBoolean(SystemProperties.get(POWER_OFF_FINDING_SUPPORTED_PROPERTY));
+        return Boolean.parseBoolean(SystemProperties.get(POWER_OFF_FINDING_SUPPORTED_PROPERTY_RO))
+                || Boolean.parseBoolean(SystemProperties.get(
+                        POWER_OFF_FINDING_SUPPORTED_PROPERTY_PERSIST));
     }
 
     private boolean areLocationAndBluetoothEnabled() {
diff --git a/staticlibs/device/com/android/net/module/util/SyncStateMachine.java b/staticlibs/device/com/android/net/module/util/SyncStateMachine.java
index da184d3..fc0161b 100644
--- a/staticlibs/device/com/android/net/module/util/SyncStateMachine.java
+++ b/staticlibs/device/com/android/net/module/util/SyncStateMachine.java
@@ -225,7 +225,8 @@
             consideredState = mStateInfo.get(consideredState.parent);
         }
         if (null == consideredState) {
-            Log.wtf(mName, "Message " + msg.what + " was not handled");
+            final String state = mCurrentState == null ? "null" : mCurrentState.getName();
+            Log.wtf(mName, "Message " + msg.what + " was not handled. Current state: " + state);
         }
 
         performTransitions();
diff --git a/staticlibs/tests/unit/host/python/apf_utils_test.py b/staticlibs/tests/unit/host/python/apf_utils_test.py
index b5a941b..96b967b 100644
--- a/staticlibs/tests/unit/host/python/apf_utils_test.py
+++ b/staticlibs/tests/unit/host/python/apf_utils_test.py
@@ -25,6 +25,8 @@
     get_apf_capabilities,
     get_apf_counter,
     get_apf_counters_from_dumpsys,
+    get_ipv4_address,
+    get_ipv6_address,
     get_hardware_address,
     is_send_raw_packet_downstream_supported,
     send_raw_packet_downstream,
@@ -112,6 +114,46 @@
       get_hardware_address(self.mock_ad, "wlan0")
 
   @patch("net_tests_utils.host.python.adb_utils.adb_shell")
+  def test_get_ipv4_address_success(
+      self, mock_adb_shell: MagicMock
+  ) -> None:
+    mock_adb_shell.return_value = """
+54: wlan0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 state UP qlen 1000
+inet 192.168.195.162/24 brd 192.168.195.255 scope global wlan0
+valid_lft forever preferred_lft forever
+"""
+    ip_address = get_ipv4_address(self.mock_ad, "wlan0")
+    asserts.assert_equal(ip_address, "192.168.195.162")
+
+  @patch("net_tests_utils.host.python.adb_utils.adb_shell")
+  def test_get_ipv4_address_not_found(
+      self, mock_adb_shell: MagicMock
+  ) -> None:
+     mock_adb_shell.return_value = ""
+     with asserts.assert_raises(PatternNotFoundException):
+       get_ipv4_address(self.mock_ad, "wlan0")
+
+  @patch("net_tests_utils.host.python.adb_utils.adb_shell")
+  def test_get_ipv6_address_success(
+      self, mock_adb_shell: MagicMock
+  ) -> None:
+    mock_adb_shell.return_value = """
+54: wlan0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 state UP qlen 1000
+inet6 fe80::10a3:5dff:fe52:de32/64 scope link
+valid_lft forever preferred_lft forever
+"""
+    ip_address = get_ipv6_address(self.mock_ad, "wlan0")
+    asserts.assert_equal(ip_address, "fe80::10a3:5dff:fe52:de32")
+
+  @patch("net_tests_utils.host.python.adb_utils.adb_shell")
+  def test_get_ipv6_address_not_found(
+          self, mock_adb_shell: MagicMock
+  ) -> None:
+      mock_adb_shell.return_value = ""
+      with asserts.assert_raises(PatternNotFoundException):
+          get_ipv6_address(self.mock_ad, "wlan0")
+
+  @patch("net_tests_utils.host.python.adb_utils.adb_shell")
   def test_send_raw_packet_downstream_success(
       self, mock_adb_shell: MagicMock
   ) -> None:
diff --git a/staticlibs/tests/unit/host/python/packet_utils_test.py b/staticlibs/tests/unit/host/python/packet_utils_test.py
new file mode 100644
index 0000000..8ad9576
--- /dev/null
+++ b/staticlibs/tests/unit/host/python/packet_utils_test.py
@@ -0,0 +1,72 @@
+#  Copyright (C) 2024 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.
+
+from mobly import asserts
+from mobly import base_test
+from net_tests_utils.host.python import packet_utils
+
+class TestPacketUtils(base_test.BaseTestClass):
+    def test_unicast_arp_request(self):
+        # Using scapy to generate unicast arp request packet:
+        #   eth = Ether(src="00:01:02:03:04:05", dst="01:02:03:04:05:06")
+        #   arp = ARP(op=1, pdst="192.168.1.1", hwsrc="00:01:02:03:04:05", psrc="192.168.1.2")
+        #   pkt = eth/arp
+        expect_arp_request = """
+            01020304050600010203040508060001080006040001000102030405c0a80102000000000000c0a80101
+        """.upper().replace(" ", "").replace("\n", "")
+        arp_request = packet_utils.construct_arp_packet(
+            src_mac="00:01:02:03:04:05",
+            dst_mac="01:02:03:04:05:06",
+            src_ip="192.168.1.2",
+            dst_ip="192.168.1.1",
+            op=packet_utils.ARP_REQUEST_OP
+        )
+        asserts.assert_equal(expect_arp_request, arp_request)
+
+    def test_broadcast_arp_request(self):
+        # Using scapy to generate unicast arp request packet:
+        #   eth = Ether(src="00:01:02:03:04:05", dst="FF:FF:FF:FF:FF:FF")
+        #   arp = ARP(op=1, pdst="192.168.1.1", hwsrc="00:01:02:03:04:05", psrc="192.168.1.2")
+        #   pkt = eth/arp
+        expect_arp_request = """
+            ffffffffffff00010203040508060001080006040001000102030405c0a80102000000000000c0a80101
+        """.upper().replace(" ", "").replace("\n", "")
+        arp_request = packet_utils.construct_arp_packet(
+            src_mac="00:01:02:03:04:05",
+            dst_mac=packet_utils.ETHER_BROADCAST_MAC_ADDRESS,
+            src_ip="192.168.1.2",
+            dst_ip="192.168.1.1",
+            op=packet_utils.ARP_REQUEST_OP
+        )
+        asserts.assert_equal(expect_arp_request, arp_request)
+
+    def test_arp_reply(self):
+        # Using scapy to generate unicast arp request packet:
+        #   eth = Ether(src="01:02:03:04:05:06", dst="00:01:02:03:04:05")
+        #   arp = ARP(op=2, pdst="192.168.1.2", \
+        #             hwsrc="01:02:03:04:05:06", \
+        #             psrc="192.168.1.1", \
+        #             hwdst="00:01:02:03:04:05")
+        #   pkt = eth/arp
+        expect_arp_reply = """
+            00010203040501020304050608060001080006040002010203040506c0a80101000102030405c0a80102
+        """.upper().replace(" ", "").replace("\n", "")
+        arp_reply = packet_utils.construct_arp_packet(
+            src_mac="01:02:03:04:05:06",
+            dst_mac="00:01:02:03:04:05",
+            src_ip="192.168.1.1",
+            dst_ip="192.168.1.2",
+            op=packet_utils.ARP_REPLY_OP
+        )
+        asserts.assert_equal(expect_arp_reply, arp_reply)
diff --git a/staticlibs/tests/unit/host/python/run_tests.py b/staticlibs/tests/unit/host/python/run_tests.py
index fa6a310..498dbaf 100644
--- a/staticlibs/tests/unit/host/python/run_tests.py
+++ b/staticlibs/tests/unit/host/python/run_tests.py
@@ -18,6 +18,7 @@
 from host.python.adb_utils_test import TestAdbUtils
 from host.python.apf_utils_test import TestApfUtils
 from host.python.assert_utils_test import TestAssertUtils
+from host.python.packet_utils_test import TestPacketUtils
 from mobly import suite_runner
 
 
@@ -31,5 +32,5 @@
   sys.argv.pop(1)
   # TODO: make the tests can be executed without manually list classes.
   suite_runner.run_suite(
-      [TestAssertUtils, TestAdbUtils, TestApfUtils], sys.argv
+      [TestAssertUtils, TestAdbUtils, TestApfUtils, TestPacketUtils], sys.argv
   )
diff --git a/staticlibs/testutils/host/python/apf_utils.py b/staticlibs/testutils/host/python/apf_utils.py
index c3330d2..b312bcf 100644
--- a/staticlibs/testutils/host/python/apf_utils.py
+++ b/staticlibs/testutils/host/python/apf_utils.py
@@ -83,6 +83,80 @@
   ad.log.debug("Getting apf counters: " + str(result))
   return result
 
+def get_ipv4_address(
+    ad: android_device.AndroidDevice, iface_name: str
+) -> str:
+  """Retrieves the IPv4 address of a given interface on an Android device.
+
+  This function executes an ADB shell command (`ip -4 address show`) to get the
+  network interface information and extracts the IPv4 address from the output.
+  If devices has multiple IPv4 addresses, return the first one.
+  If devices have no IPv4 address, raise PatternNotFoundException.
+
+  Args:
+      ad: The Android device object.
+      iface_name: The name of the network interface (e.g., "wlan0").
+
+  Returns:
+      The IPv4 address of the interface as a string.
+
+  Raises:
+      PatternNotFoundException: If the IPv4 address is not found in the command
+      output.
+  """
+  # output format:
+  # 54: wlan2: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
+  # inet 192.168.195.162/24 brd 192.168.195.255 scope global wlan2
+  # valid_lft forever preferred_lft forever
+  output = adb_utils.adb_shell(ad, f"ip -4 address show {iface_name}")
+  pattern = r"inet\s+(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\/\d+"
+  match = re.search(pattern, output)
+
+  if match:
+    return match.group(1)  # Extract the IPv4 address string.
+  else:
+    raise PatternNotFoundException(
+      "Cannot get hardware address for " + iface_name
+    )
+
+def get_ipv6_address(
+    ad: android_device.AndroidDevice, iface_name: str
+) -> str:
+  """Retrieves the IPv6 address of a given interface on an Android device.
+
+  This function executes an ADB shell command (`ip -6 address show`) to get the
+  network interface information and extracts the IPv6 address from the output.
+  If devices has multiple IPv6 addresses, return the first one.
+  If devices have no IPv6 address, raise PatternNotFoundException.
+
+  Args:
+      ad: The Android device object.
+      iface_name: The name of the network interface (e.g., "wlan0").
+
+  Returns:
+      The IPv6 address of the interface as a string.
+
+  Raises:
+      PatternNotFoundException: If the IPv6 address is not found in the command
+      output.
+  """
+  # output format
+  # 54: wlan2: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 state UP qlen 1000
+  # inet6 fe80::10a3:5dff:fe52:de32/64 scope link
+  # valid_lft forever preferred_lft forever
+  output = adb_utils.adb_shell(ad, f"ip -6 address show {iface_name}")
+  if output is "":
+    raise PatternNotFoundException(
+      "Cannot get ipv6 address for " + iface_name
+    )
+  pattern = r"inet6\s+([0-9a-fA-F:]+)\/\d+"
+  match = re.search(pattern, output)
+  if match:
+    return match.group(1)  # Extract the IPv6 address string.
+  else:
+    raise PatternNotFoundException(
+      "Cannot get IPv6 address for " + iface_name
+    )
 
 def get_hardware_address(
     ad: android_device.AndroidDevice, iface_name: str
diff --git a/staticlibs/testutils/host/python/packet_utils.py b/staticlibs/testutils/host/python/packet_utils.py
new file mode 100644
index 0000000..b613f03
--- /dev/null
+++ b/staticlibs/testutils/host/python/packet_utils.py
@@ -0,0 +1,70 @@
+#  Copyright (C) 2024 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.
+from ipaddress import IPv4Address
+from socket import inet_aton
+
+ETHER_BROADCAST_MAC_ADDRESS = "FF:FF:FF:FF:FF:FF"
+ARP_REQUEST_OP = 1
+ARP_REPLY_OP = 2
+
+"""
+This variable defines a template for constructing ARP packets in hexadecimal format.
+It's used to provide the common fields for ARP packet, and replaced needed fields when constructing
+"""
+ARP_TEMPLATE = (
+    # Ether Header (14 bytes)
+    "{dst_mac}" + # DA
+    "{src_mac}" + # SA
+    "0806" + # ARP
+    # ARP Header (28 bytes)
+    "0001" + # Hardware type (Ethernet)
+    "0800" + # Protocol type (IPv4)
+    "06" + # hardware address length
+    "04" + # protocol address length
+    "{opcode}" + # opcode
+    "{sender_mac}" + # sender MAC
+    "{sender_ip}" + # sender IP
+    "{target_mac}" + # target MAC
+    "{target_ip}" # target IP
+)
+
+def construct_arp_packet(src_mac, dst_mac, src_ip, dst_ip, op) -> str:
+    """Constructs an ARP packet as a hexadecimal string.
+
+    This function creates an ARP packet by filling in the required fields
+    in a predefined ARP packet template.
+
+    Args:
+    src_mac: The MAC address of the sender. (e.g. "11:22:33:44:55:66")
+    dst_mac: The MAC address of the recipient. (e.g. "aa:bb:cc:dd:ee:ff")
+    src_ip: The IP address of the sender. (e.g. "1.1.1.1")
+    dst_ip: The IP address of the target machine. (e.g. "2.2.2.2")
+    op: The op code of the ARP packet, refer to ARP_*_OP
+
+    Returns:
+    A string representing the ARP packet in hexadecimal format.
+    """
+    # Replace the needed fields from packet template
+    arp_pkt = ARP_TEMPLATE.format(
+            dst_mac=dst_mac.replace(":",""),
+            src_mac=src_mac.replace(":",""),
+            opcode=str(op).rjust(4, "0"),
+            sender_mac=src_mac.replace(":",""),
+            sender_ip=inet_aton(src_ip).hex(),
+            target_mac=("000000000000" if op == ARP_REQUEST_OP else dst_mac.replace(":", "")),
+            target_ip=inet_aton(dst_ip).hex()
+    )
+
+    # always convert to upper case hex string
+    return arp_pkt.upper()
\ No newline at end of file