Snap for 11274957 from 4443636fd203e989cd475ddd658632689dad935f to mainline-scheduling-release

Change-Id: I2e6d1a2d8ec19141f31320bab21fb8038b705b55
diff --git a/Android.bp b/Android.bp
index 4461c26..a7f57ae 100644
--- a/Android.bp
+++ b/Android.bp
@@ -58,6 +58,7 @@
     name: "NetworkStackNextEnableDefaults",
     enabled: true,
 }
+
 // This is a placeholder comment to avoid merge conflicts
 // as the above target may have different "enabled" values
 // depending on the branch
@@ -72,7 +73,7 @@
         "framework-connectivity-t",
         "framework-statsd",
         "framework-wifi",
-    ]
+    ],
 }
 
 // Common defaults for NetworkStack integration tests, root tests and coverage tests
@@ -85,7 +86,7 @@
 
 java_defaults {
     name: "NetworkStackReleaseApiLevel",
-    defaults:["NetworkStackReleaseTargetSdk"],
+    defaults: ["NetworkStackReleaseTargetSdk"],
     sdk_version: module_34_version,
     libs: [
         "framework-configinfrastructure",
@@ -93,7 +94,7 @@
         "framework-connectivity-t",
         "framework-statsd",
         "framework-wifi",
-    ]
+    ],
 }
 
 // Libraries for the API shims
@@ -103,12 +104,12 @@
         "androidx.annotation_annotation",
         "networkstack-aidl-latest",
     ],
-    static_libs : [
-        "modules-utils-build_system"
+    static_libs: [
+        "modules-utils-build_system",
     ],
     apex_available: [
         "com.android.tethering",
-        "//apex_available:platform",  // For InProcessNetworkStack
+        "//apex_available:platform", // For InProcessNetworkStack
     ],
     min_sdk_version: "30",
 }
@@ -124,6 +125,9 @@
     srcs: ["apishim/common/**/*.java"],
     sdk_version: "system_current",
     visibility: ["//visibility:private"],
+    lint: {
+        baseline_filename: "lint-baseline.xml",
+    },
 }
 
 // Each level of the shims (29, 30, ...) is its own java_library compiled against the corresponding
@@ -137,6 +141,9 @@
     ],
     sdk_version: "system_29",
     visibility: ["//visibility:private"],
+    lint: {
+        baseline_filename: "lint-baseline.xml",
+    },
 }
 
 java_library {
@@ -153,6 +160,7 @@
     visibility: ["//visibility:private"],
     lint: {
         strict_updatability_linting: true,
+        baseline_filename: "lint-baseline.xml",
     },
 }
 
@@ -176,6 +184,9 @@
     ],
     sdk_version: "module_31",
     visibility: ["//visibility:private"],
+    lint: {
+        baseline_filename: "lint-baseline.xml",
+    },
 }
 
 java_library {
@@ -196,6 +207,9 @@
     ],
     sdk_version: "module_33",
     visibility: ["//visibility:private"],
+    lint: {
+        baseline_filename: "lint-baseline.xml",
+    },
 }
 
 java_library {
@@ -217,6 +231,9 @@
     ],
     sdk_version: module_34_version,
     visibility: ["//visibility:private"],
+    lint: {
+        baseline_filename: "lint-baseline.xml",
+    },
 }
 
 // Shims for APIs being added to the current development version of Android. These APIs are not
@@ -228,7 +245,10 @@
 // are part of the stable shims and scanned when generating jarjar rules.
 java_library {
     name: "NetworkStackApi35Shims",
-    defaults: ["NetworkStackShimsDefaults", "ConnectivityNextEnableDefaults"],
+    defaults: [
+        "NetworkStackShimsDefaults",
+        "ConnectivityNextEnableDefaults",
+    ],
     srcs: [
         "apishim/35/**/*.java",
     ],
@@ -243,10 +263,13 @@
         "framework-connectivity",
         "framework-connectivity-t.stubs.module_lib",
         "framework-tethering",
-        "android.net.ipsec.ike.stubs.module_lib"
+        "android.net.ipsec.ike.stubs.module_lib",
     ],
     sdk_version: "module_current",
     visibility: ["//visibility:private"],
+    lint: {
+        baseline_filename: "lint-baseline.xml",
+    },
 }
 
 // API current uses the API current shims directly.
@@ -274,6 +297,9 @@
         "//packages/modules/Connectivity/service-t",
         "//packages/modules/Connectivity/tests:__subpackages__",
     ],
+    lint: {
+        baseline_filename: "lint-baseline.xml",
+    },
 }
 
 // API stable uses jarjar to rename the latest stable apishim package from
@@ -281,7 +307,10 @@
 // the networkstack code.
 java_library {
     name: "NetworkStackApiStableShims",
-    defaults: ["NetworkStackShimsDefaults", "NetworkStackReleaseApiLevel"],
+    defaults: [
+        "NetworkStackShimsDefaults",
+        "NetworkStackReleaseApiLevel",
+    ],
     static_libs: [
         "NetworkStackShimsCommon",
         "NetworkStackApi29Shims",
@@ -297,6 +326,9 @@
         "//packages/modules/Connectivity/service-t",
         "//packages/modules/Connectivity/tests:__subpackages__",
     ],
+    lint: {
+        baseline_filename: "lint-baseline.xml",
+    },
 }
 
 // Common defaults for android libraries containing network stack code, used to compile variants of
@@ -336,7 +368,7 @@
     ],
     srcs: [
         "src/**/*.java",
-        ":statslog-networkstack-java-gen-current"
+        ":statslog-networkstack-java-gen-current",
     ],
     static_libs: [
         "NetworkStackApiCurrentShims",
@@ -348,12 +380,18 @@
         "//packages/modules/NetworkStack/tests/unit",
         "//packages/modules/NetworkStack/tests/integration",
     ],
-    lint: { strict_updatability_linting: true },
+    lint: {
+        strict_updatability_linting: true,
+        baseline_filename: "lint-baseline.xml",
+    },
 }
 
 android_library {
     name: "NetworkStackApiStableLib",
-    defaults: ["NetworkStackReleaseApiLevel", "NetworkStackAndroidLibraryDefaults"],
+    defaults: [
+        "NetworkStackReleaseApiLevel",
+        "NetworkStackAndroidLibraryDefaults",
+    ],
     srcs: [
         "src/**/*.java",
         ":statslog-networkstack-java-gen-stable",
@@ -370,7 +408,10 @@
         "//packages/modules/NetworkStack/tests/unit",
         "//packages/modules/NetworkStack/tests/integration",
     ],
-    lint: { strict_updatability_linting: true },
+    lint: {
+        strict_updatability_linting: true,
+        baseline_filename: "lint-baseline.xml",
+    },
 }
 
 java_library {
@@ -396,6 +437,9 @@
         "//packages/modules/Connectivity/Tethering/tests/integration",
         "//packages/modules/Connectivity/tests/cts/net",
     ],
+    lint: {
+        baseline_filename: "lint-baseline.xml",
+    },
 }
 
 java_genrule {
@@ -455,12 +499,18 @@
     certificate: "platform",
     manifest: "AndroidManifest_InProcess.xml",
     // InProcessNetworkStack is a replacement for NetworkStack
-    overrides: ["NetworkStack", "NetworkStackNext"],
+    overrides: [
+        "NetworkStack",
+        "NetworkStackNext",
+    ],
     // The InProcessNetworkStack goes together with the PlatformCaptivePortalLogin, which replaces
     // the default CaptivePortalLogin.
     required: [
         "PlatformCaptivePortalLogin",
     ],
+    lint: {
+        baseline_filename: "lint-baseline.xml",
+    },
 }
 
 // Pre-merge the AndroidManifest for NetworkStackNext, so that its manifest can be merged on top
@@ -472,7 +522,10 @@
         "ConnectivityNextEnableDefaults",
     ],
     static_libs: ["NetworkStackApiCurrentLib"],
-    manifest: "AndroidManifest.xml"
+    manifest: "AndroidManifest.xml",
+    lint: {
+        baseline_filename: "lint-baseline.xml",
+    },
 }
 
 // NetworkStack build targeting the current API release, for testing on in-development SDK
@@ -490,13 +543,19 @@
         "privapp_whitelist_com.android.networkstack",
     ],
     updatable: true,
-    lint: { strict_updatability_linting: true },
+    lint: {
+        strict_updatability_linting: true,
+        baseline_filename: "lint-baseline.xml",
+    },
 }
 
 // Updatable network stack for finalized API
 android_app {
     name: "NetworkStack",
-    defaults: ["NetworkStackAppDefaults", "NetworkStackReleaseApiLevel"],
+    defaults: [
+        "NetworkStackAppDefaults",
+        "NetworkStackReleaseApiLevel",
+    ],
     static_libs: ["NetworkStackApiStableLib"],
     certificate: "networkstack",
     manifest: "AndroidManifest.xml",
@@ -504,13 +563,16 @@
         "privapp_whitelist_com.android.networkstack",
     ],
     updatable: true,
-    lint: { strict_updatability_linting: true },
+    lint: {
+        strict_updatability_linting: true,
+        baseline_filename: "lint-baseline.xml",
+    },
 }
 
 cc_library_shared {
     name: "libnetworkstackutilsjni",
     srcs: [
-        "jni/network_stack_utils_jni.cpp"
+        "jni/network_stack_utils_jni.cpp",
     ],
     header_libs: [
         "bpf_headers",
@@ -548,8 +610,8 @@
     name: "statslog-networkstack-java-gen-current",
     tools: ["stats-log-api-gen"],
     cmd: "$(location stats-log-api-gen) --java $(out) --module network_stack" +
-         " --javaPackage com.android.networkstack.metrics --javaClass NetworkStackStatsLog" +
-         " --minApiLevel 30",
+        " --javaPackage com.android.networkstack.metrics --javaClass NetworkStackStatsLog" +
+        " --minApiLevel 30",
     out: ["com/android/networkstack/metrics/NetworkStackStatsLog.java"],
 }
 
@@ -557,12 +619,11 @@
     name: "statslog-networkstack-java-gen-stable",
     tools: ["stats-log-api-gen"],
     cmd: "$(location stats-log-api-gen) --java $(out) --module network_stack" +
-         " --javaPackage com.android.networkstack.metrics --javaClass NetworkStackStatsLog" +
-         " --minApiLevel 30 --compileApiLevel 30",
+        " --javaPackage com.android.networkstack.metrics --javaClass NetworkStackStatsLog" +
+        " --minApiLevel 30 --compileApiLevel 30",
     out: ["com/android/networkstack/metrics/NetworkStackStatsLog.java"],
 }
 
-
 version_code_networkstack_next = "300000000"
 version_code_networkstack_test = "999999999"
 
@@ -570,21 +631,27 @@
     name: "NetworkStackTestAndroidManifest",
     srcs: ["AndroidManifest.xml"],
     out: ["TestAndroidManifest.xml"],
-    cmd: "sed -E 's/versionCode=\"[0-9]+\"/versionCode=\""
-        + version_code_networkstack_test
-        + "\"/' $(in) > $(out)",
+    cmd: "sed -E 's/versionCode=\"[0-9]+\"/versionCode=\"" +
+        version_code_networkstack_test +
+        "\"/' $(in) > $(out)",
     visibility: ["//visibility:private"],
 }
 
 android_app {
     name: "TestNetworkStack",
-    defaults: ["NetworkStackAppDefaults", "NetworkStackReleaseApiLevel"],
+    defaults: [
+        "NetworkStackAppDefaults",
+        "NetworkStackReleaseApiLevel",
+    ],
     static_libs: ["NetworkStackApiStableLib"],
     certificate: "networkstack",
     manifest: ":NetworkStackTestAndroidManifest",
     required: [
         "privapp_whitelist_com.android.networkstack",
     ],
+    lint: {
+        baseline_filename: "lint-baseline.xml",
+    },
 }
 
 // When adding or modifying protos, the jarjar rules and possibly proguard rules need
@@ -601,4 +668,7 @@
         "networkstackprotos",
     ],
     defaults: ["NetworkStackReleaseApiLevel"],
+    lint: {
+        baseline_filename: "lint-baseline.xml",
+    },
 }
diff --git a/common/networkstackclient/Android.bp b/common/networkstackclient/Android.bp
index 07fd52e..060f0da 100644
--- a/common/networkstackclient/Android.bp
+++ b/common/networkstackclient/Android.bp
@@ -221,6 +221,9 @@
         "com.android.tethering",
         "com.android.wifi",
     ],
+    lint: {
+        baseline_filename: "lint-baseline.xml",
+    },
 }
 
 java_library {
@@ -265,4 +268,7 @@
         "com.android.tethering",
         "com.android.wifi",
     ],
+    lint: {
+        baseline_filename: "lint-baseline.xml",
+    },
 }
diff --git a/src/android/net/apf/ApfFilter.java b/src/android/net/apf/ApfFilter.java
index 2ef9c08..ee2990b 100644
--- a/src/android/net/apf/ApfFilter.java
+++ b/src/android/net/apf/ApfFilter.java
@@ -1148,7 +1148,7 @@
                 // Generate code to match the packet bytes.
                 if (section.type == PacketSection.Type.MATCH) {
                     gen.addLoadImmediate(Register.R0, section.start);
-                    gen.addJumpIfBytesNotEqual(Register.R0,
+                    gen.addJumpIfBytesAtR0NotEqual(
                             Arrays.copyOfRange(mPacket.array(), section.start,
                                     section.start + section.length),
                             nextFilterLabel);
@@ -1283,7 +1283,7 @@
             final String nextFilterLabel = "natt_keepalive_filter" + getUniqueNumberLocked();
 
             gen.addLoadImmediate(Register.R0, ETH_HEADER_LEN + IPV4_SRC_ADDR_OFFSET);
-            gen.addJumpIfBytesNotEqual(Register.R0, mSrcDstAddr, nextFilterLabel);
+            gen.addJumpIfBytesAtR0NotEqual(mSrcDstAddr, nextFilterLabel);
 
             // A NAT-T keepalive packet contains 1 byte payload with the value 0xff
             // Check payload length is 1
@@ -1298,11 +1298,11 @@
             // Check that the ports match
             gen.addLoadFromMemory(Register.R0, gen.IPV4_HEADER_SIZE_MEMORY_SLOT);
             gen.addAdd(ETH_HEADER_LEN);
-            gen.addJumpIfBytesNotEqual(Register.R0, mPortFingerprint, nextFilterLabel);
+            gen.addJumpIfBytesAtR0NotEqual(mPortFingerprint, nextFilterLabel);
 
             // Payload offset = R0 + UDP header length
             gen.addAdd(UDP_HEADER_LEN);
-            gen.addJumpIfBytesNotEqual(Register.R0, mPayload, nextFilterLabel);
+            gen.addJumpIfBytesAtR0NotEqual(mPayload, nextFilterLabel);
 
             maybeSetupCounter(gen, Counter.DROPPED_IPV4_NATT_KEEPALIVE);
             gen.addJump(mCountAndDropLabel);
@@ -1398,7 +1398,7 @@
             final String nextFilterLabel = "keepalive_ack" + getUniqueNumberLocked();
 
             gen.addLoadImmediate(Register.R0, ETH_HEADER_LEN + IPV4_SRC_ADDR_OFFSET);
-            gen.addJumpIfBytesNotEqual(Register.R0, mSrcDstAddr, nextFilterLabel);
+            gen.addJumpIfBytesAtR0NotEqual(mSrcDstAddr, nextFilterLabel);
 
             // Skip to the next filter if it's not zero-sized :
             // TCP_HEADER_SIZE + IPV4_HEADER_SIZE - ipv4_total_length == 0
@@ -1420,7 +1420,7 @@
             gen.addLoadFromMemory(Register.R1, gen.IPV4_HEADER_SIZE_MEMORY_SLOT);
             gen.addLoadImmediate(Register.R0, ETH_HEADER_LEN);
             gen.addAddR1();
-            gen.addJumpIfBytesNotEqual(Register.R0, mPortSeqAckFingerprint, nextFilterLabel);
+            gen.addJumpIfBytesAtR0NotEqual(mPortSeqAckFingerprint, nextFilterLabel);
 
             maybeSetupCounter(gen, Counter.DROPPED_IPV4_KEEPALIVE_ACK);
             gen.addJump(mCountAndDropLabel);
@@ -1522,7 +1522,7 @@
         // Drop if not ARP IPv4.
         gen.addLoadImmediate(Register.R0, ARP_HEADER_OFFSET);
         maybeSetupCounter(gen, Counter.DROPPED_ARP_NON_IPV4);
-        gen.addJumpIfBytesNotEqual(Register.R0, ARP_IPV4_HEADER, mCountAndDropLabel);
+        gen.addJumpIfBytesAtR0NotEqual(ARP_IPV4_HEADER, mCountAndDropLabel);
 
         // Drop if unknown ARP opcode.
         gen.addLoad16(Register.R0, ARP_OPCODE_OFFSET);
@@ -1538,7 +1538,7 @@
         // Pass if non-broadcast reply.
         gen.addLoadImmediate(Register.R0, ETH_DEST_ADDR_OFFSET);
         maybeSetupCounter(gen, Counter.PASSED_ARP_UNICAST_REPLY);
-        gen.addJumpIfBytesNotEqual(Register.R0, ETHER_BROADCAST, mCountAndPassLabel);
+        gen.addJumpIfBytesAtR0NotEqual(ETHER_BROADCAST, mCountAndPassLabel);
 
         // Either a request, or a broadcast reply.
         gen.defineLabel(checkTargetIPv4);
@@ -1552,7 +1552,7 @@
             // and broadcast replies with a different target IPv4 address.
             gen.addLoadImmediate(Register.R0, ARP_TARGET_IP_ADDRESS_OFFSET);
             maybeSetupCounter(gen, Counter.DROPPED_ARP_OTHER_HOST);
-            gen.addJumpIfBytesNotEqual(Register.R0, mIPv4Address, mCountAndDropLabel);
+            gen.addJumpIfBytesAtR0NotEqual(mIPv4Address, mCountAndDropLabel);
         }
 
         maybeSetupCounter(gen, Counter.PASSED_ARP);
@@ -1600,7 +1600,7 @@
             gen.addLoadImmediate(Register.R0, DHCP_CLIENT_MAC_OFFSET);
             // NOTE: Relies on R1 containing IPv4 header offset.
             gen.addAddR1();
-            gen.addJumpIfBytesNotEqual(Register.R0, mHardwareAddress, skipDhcpv4Filter);
+            gen.addJumpIfBytesAtR0NotEqual(mHardwareAddress, skipDhcpv4Filter);
             maybeSetupCounter(gen, Counter.PASSED_DHCP);
             gen.addJump(mCountAndPassLabel);
 
@@ -1639,7 +1639,7 @@
             // TODO: can we invert this condition to fall through to the common pass case below?
             maybeSetupCounter(gen, Counter.PASSED_IPV4_UNICAST);
             gen.addLoadImmediate(Register.R0, ETH_DEST_ADDR_OFFSET);
-            gen.addJumpIfBytesNotEqual(Register.R0, ETHER_BROADCAST, mCountAndPassLabel);
+            gen.addJumpIfBytesAtR0NotEqual(ETHER_BROADCAST, mCountAndPassLabel);
             maybeSetupCounter(gen, Counter.DROPPED_IPV4_L2_BROADCAST);
             gen.addJump(mCountAndDropLabel);
         }
@@ -1763,8 +1763,7 @@
         // TODO: Drop only if they don't contain the address of on-link neighbours.
         final byte[] unsolicitedNaDropPrefix = Arrays.copyOf(IPV6_ALL_NODES_ADDRESS, 15);
         gen.addLoadImmediate(Register.R0, IPV6_DEST_ADDR_OFFSET);
-        gen.addJumpIfBytesNotEqual(Register.R0, unsolicitedNaDropPrefix,
-                skipUnsolicitedMulticastNALabel);
+        gen.addJumpIfBytesAtR0NotEqual(unsolicitedNaDropPrefix, skipUnsolicitedMulticastNALabel);
 
         maybeSetupCounter(gen, Counter.DROPPED_IPV6_MULTICAST_NA);
         gen.addJump(mCountAndDropLabel);
@@ -1821,8 +1820,7 @@
 
         // Check it's L2 mDNS multicast address.
         gen.addLoadImmediate(Register.R0, ETH_DEST_ADDR_OFFSET);
-        gen.addJumpIfBytesNotEqual(Register.R0, ETH_MULTICAST_MDNS_V4_MAC_ADDRESS,
-                skipMdnsv4Filter);
+        gen.addJumpIfBytesAtR0NotEqual(ETH_MULTICAST_MDNS_V4_MAC_ADDRESS, skipMdnsv4Filter);
 
         // Checks it's IPv4.
         gen.addLoad16(Register.R0, ETH_ETHERTYPE_OFFSET);
@@ -1845,8 +1843,7 @@
 
         // Checks it's L2 mDNS multicast address.
         // Relies on R0 containing the ethernet destination mac address offset.
-        gen.addJumpIfBytesNotEqual(Register.R0, ETH_MULTICAST_MDNS_V6_MAC_ADDRESS,
-                skipMdnsFilter);
+        gen.addJumpIfBytesAtR0NotEqual(ETH_MULTICAST_MDNS_V6_MAC_ADDRESS, skipMdnsFilter);
 
         // Checks it's IPv6.
         gen.addLoad16(Register.R0, ETH_ETHERTYPE_OFFSET);
@@ -1877,7 +1874,7 @@
         for (int i = 0; i < mMdnsAllowList.size(); ++i) {
             final String mDnsNextAllowedQnameCheck = "mdns_next_allowed_qname_check" + i;
             final byte[] encodedQname = encodeQname(mMdnsAllowList.get(i));
-            gen.addJumpIfBytesNotEqual(Register.R0, encodedQname, mDnsNextAllowedQnameCheck);
+            gen.addJumpIfBytesAtR0NotEqual(encodedQname, mDnsNextAllowedQnameCheck);
             // QNAME matched
             gen.addJump(mDnsAcceptPacket);
             // QNAME not matched
@@ -2023,7 +2020,7 @@
         // Drop non-IP non-ARP broadcasts, pass the rest
         gen.addLoadImmediate(Register.R0, ETH_DEST_ADDR_OFFSET);
         maybeSetupCounter(gen, Counter.PASSED_NON_IP_UNICAST);
-        gen.addJumpIfBytesNotEqual(Register.R0, ETHER_BROADCAST, mCountAndPassLabel);
+        gen.addJumpIfBytesAtR0NotEqual(ETHER_BROADCAST, mCountAndPassLabel);
         maybeSetupCounter(gen, Counter.DROPPED_ETH_BROADCAST);
         gen.addJump(mCountAndDropLabel);
 
diff --git a/src/android/net/apf/ApfGenerator.java b/src/android/net/apf/ApfGenerator.java
index d038e96..1c08228 100644
--- a/src/android/net/apf/ApfGenerator.java
+++ b/src/android/net/apf/ApfGenerator.java
@@ -16,6 +16,9 @@
 
 package android.net.apf;
 
+import static android.net.apf.ApfGenerator.Register.R0;
+import static android.net.apf.ApfGenerator.Register.R1;
+
 import androidx.annotation.NonNull;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -65,7 +68,12 @@
         OR(11),    // Or, e.g. "or R0,5"
         SH(12),    // Left shift, e.g, "sh R0, 5" or "sh R0, -5" (shifts right)
         LI(13),    // Load immediate, e.g. "li R0,5" (immediate encoded as signed value)
-        JMP(14),   // Jump, e.g. "jmp label"
+        // Jump, e.g. "jmp label"
+        // In APFv6, we use JMP(R=1) to encode the DATA instruction. DATA is executed as a jump.
+        // It tells how many bytes of the program regions are used to store the data and followed
+        // by the actual data bytes.
+        // "e.g. data 5, abcde"
+        JMP(14),
         JEQ(15),   // Compare equal and branch, e.g. "jeq R0,5,label"
         JNE(16),   // Compare not equal and branch, e.g. "jne R0,5,label"
         JGT(17),   // Compare greater than and branch, e.g. "jgt R0,5,label"
@@ -75,12 +83,17 @@
         EXT(21),   // Followed by immediate indicating ExtendedOpcodes.
         LDDW(22),  // Load 4 bytes from data memory address (register + immediate): "lddw R0, [5]R1"
         STDW(23),  // Store 4 bytes to data memory address (register + immediate): "stdw R0, [5]R1"
-        WRITE(24),  // Write 1, 2 or 4 bytes imm to the output buffer, e.g. "WRITE 5"
-        // Copy the data from input packet or APF data region to output buffer. Register bit is
-        // used to specify the source of data copy: R=0 means copy from packet, R=1 means copy
-        // from APF data region. The source offset is encoded in the first imm and the copy length
-        // is encoded in the second imm. "e.g. MEMCOPY(R=0), 5, 5"
-        MEMCOPY(25);
+        // Write 1, 2 or 4 bytes immediate to the output buffer and auto-increment the pointer to
+        // write. e.g. "write 5"
+        WRITE(24),
+        // Copy bytes from input packet/APF program/data region to output buffer and
+        // auto-increment the output buffer pointer.
+        // Register bit is used to specify the source of data copy.
+        // R=0 means copy from packet.
+        // R=1 means copy from APF program/data region.
+        // The copy length is stored in (u8)imm2.
+        // e.g. "pktcopy 5, 5" "datacopy 5, 5"
+        PKTDATACOPY(25);
 
         final int value;
 
@@ -97,21 +110,30 @@
         NEG(33),  // Negate, e.g. "neg R0"
         SWAP(34), // Swap, e.g. "swap R0,R1"
         MOVE(35),  // Move, e.g. "move R0,R1"
-        ALLOC(36), // Allocate buffer, "e.g. ALLOC R0"
+        // Allocate writable output buffer.
+        // R=0, use register R0 to store the length. R=1, encode the length in the u16 int imm2.
+        // "e.g. allocate R0"
+        // "e.g. allocate 123"
+        ALLOCATE(36),
         //  Transmit and deallocate the buffer (transmission can be delayed until the program
         //  terminates). R=0 means discard the buffer, R=1 means transmit the buffer.
         // "e.g. trans"
         // "e.g. discard"
         TRANSMIT(37),
         DISCARD(37),
-        EWRITE1(38), // Write 1 byte from register to the output buffer, e.g. "EWRITE1 R0"
-        EWRITE2(39), // Write 2 bytes from register to the output buffer, e.g. "EWRITE2 R0"
-        EWRITE4(40), // Write 4 bytes from register to the output buffer, e.g. "EWRITE4 R0"
-        // Copy the data from input packet to output buffer. The source offset is encoded as [Rx
-        // + second imm]. The copy length is encoded in the third imm. "e.g. EPKTCOPY [R0 + 5], 5"
+        // Write 1, 2 or 4 byte value from register to the output buffer and auto-increment the
+        // output buffer pointer.
+        // e.g. "ewrite1 r0"
+        EWRITE1(38),
+        EWRITE2(39),
+        EWRITE4(40),
+        // Copy bytes from input packet/APF program/data region to output buffer and
+        // auto-increment the output buffer pointer.
+        // The copy src offset is stored in R0.
+        // when R=0, the copy length is stored in (u8)imm2.
+        // when R=1, the copy length is stored in R1.
+        // e.g. "pktcopy r0, 5", "pktcopy r0, r1", "datacopy r0, 5", "datacopy r0, r1"
         EPKTCOPY(41),
-        // Copy the data from APF data region to output buffer. The source offset is encoded as [Rx
-        // + second imm]. The copy length is encoded in the third imm. "e.g. EDATACOPY [R0 + 5], 5"
         EDATACOPY(42);
 
         final int value;
@@ -144,43 +166,11 @@
 
     private static class IntImmediate {
         public final IntImmediateType mImmediateType;
-        // TODO: remove mSigned and mImmSize fields in follow up CL
-        public final boolean mSigned;
-        public final int mImmSize;
         public final int mValue;
 
-        IntImmediate(int value, boolean signed) {
-            this(value, signed, calculateImmSize(value, signed));
-        }
-
-        // TODO: Remove this constructor in follow up CLs.
-        IntImmediate(int value, boolean signed, int size) {
-            mValue = value;
-            mSigned = signed;
-            mImmSize = size;
-            if (signed) {
-                mImmediateType = IntImmediateType.INDETERMINATE_SIZE_SIGNED;
-            } else {
-                mImmediateType = IntImmediateType.INDETERMINATE_SIZE_UNSIGNED;
-            }
-        }
-
         IntImmediate(int value, IntImmediateType type) {
             mImmediateType = type;
             mValue = value;
-            // The following logic for initialize mSigned and mImmSize could be removed after we
-            // remove those two variable
-            switch (type) {
-                case INDETERMINATE_SIZE_SIGNED:
-                case SIGNED_8:
-                case SIGNED_BE16:
-                case SIGNED_BE32:
-                    mSigned = true;
-                    break;
-                default:
-                    mSigned = false;
-            }
-            mImmSize = calculateImmSize(value, mSigned);
         }
 
         private int calculateIndeterminateSize() {
@@ -226,51 +216,63 @@
                     getEncodingSize(immFieldSize));
         }
 
-        public static IntImmediate newSignedIndeterminate(int imm) {
+        public static IntImmediate newSigned(int imm) {
             return new IntImmediate(imm, IntImmediateType.INDETERMINATE_SIZE_SIGNED);
         }
 
-        public static IntImmediate newUnsignedIndeterminate(long imm) {
+        public static IntImmediate newUnsigned(long imm) {
             // upperBound is 2^32 - 1
-            checkRange("Unsigned Indeterminate IMM", imm, 0 /* lowerBound */,
+            checkRange("Unsigned IMM", imm, 0 /* lowerBound */,
                     4294967295L /* upperBound */);
             return new IntImmediate((int) imm, IntImmediateType.INDETERMINATE_SIZE_UNSIGNED);
         }
 
-        public static IntImmediate newSigned8(byte imm) {
-            checkRange("Signed8 IMM", imm, Byte.MIN_VALUE, Byte.MAX_VALUE);
+        public static IntImmediate newTwosComplementUnsigned(long imm) {
+            checkRange("Unsigned TwosComplement IMM", imm, Integer.MIN_VALUE,
+                    4294967295L /* upperBound */);
+            return new IntImmediate((int) imm, IntImmediateType.INDETERMINATE_SIZE_UNSIGNED);
+        }
+
+        public static IntImmediate newTwosComplementSigned(long imm) {
+            checkRange("Signed TwosComplement IMM", imm, Integer.MIN_VALUE,
+                    4294967295L /* upperBound */);
+            return new IntImmediate((int) imm, IntImmediateType.INDETERMINATE_SIZE_SIGNED);
+        }
+
+        public static IntImmediate newS8(byte imm) {
+            checkRange("S8 IMM", imm, Byte.MIN_VALUE, Byte.MAX_VALUE);
             return new IntImmediate(imm, IntImmediateType.SIGNED_8);
         }
 
-        public static IntImmediate newUnsigned8(int imm) {
-            checkRange("Unsigned8 IMM", imm, 0, 255);
+        public static IntImmediate newU8(int imm) {
+            checkRange("U8 IMM", imm, 0, 255);
             return new IntImmediate(imm, IntImmediateType.UNSIGNED_8);
         }
 
-        public static IntImmediate newSignedBe16(short imm) {
+        public static IntImmediate newS16(short imm) {
             return new IntImmediate(imm, IntImmediateType.SIGNED_BE16);
         }
 
-        public static IntImmediate newUnsignedBe16(int imm) {
-            checkRange("UnsignedBe16 IMM", imm, 0, 65535);
+        public static IntImmediate newU16(int imm) {
+            checkRange("U16 IMM", imm, 0, 65535);
             return new IntImmediate(imm, IntImmediateType.UNSIGNED_BE16);
         }
 
-        public static IntImmediate newSignedBe32(int imm) {
+        public static IntImmediate newS32(int imm) {
             return new IntImmediate(imm, IntImmediateType.SIGNED_BE32);
         }
 
-        public static IntImmediate newUnsignedBe32(long imm) {
+        public static IntImmediate newU32(long imm) {
             // upperBound is 2^32 - 1
-            checkRange("UnsignedBe32 IMM", imm, 0 /* lowerBound */,
+            checkRange("U32 IMM", imm, 0 /* lowerBound */,
                     4294967295L /* upperBound */);
             return new IntImmediate((int) imm, IntImmediateType.UNSIGNED_BE32);
         }
 
         @Override
         public String toString() {
-            return "IntImmediate{" + "mImmediateType=" + mImmediateType + ", mSigned=" + mSigned
-                    + ", mImmSize=" + mImmSize + ", mValue=" + mValue + '}';
+            return "IntImmediate{" + "mImmediateType=" + mImmediateType + ", mValue=" + mValue
+                    + '}';
         }
     }
 
@@ -280,6 +282,7 @@
         public final List<IntImmediate> mIntImms = new ArrayList<>();
         // When mOpcode is a jump:
         private int mTargetLabelSize;
+        private int mLenFieldOverride = -1;
         private String mTargetLabel;
         // When mOpcode == Opcodes.LABEL:
         private String mLabel;
@@ -292,27 +295,81 @@
             mRegister = (byte) register.value;
         }
 
+        Instruction(ExtendedOpcodes extendedOpcodes, Register register) {
+            this(Opcodes.EXT, register);
+            addUnsigned(extendedOpcodes.value);
+        }
+
+        Instruction(ExtendedOpcodes extendedOpcodes, int slot, Register register)
+                throws IllegalInstructionException {
+            this(Opcodes.EXT, register);
+            if (slot < 0 || slot >= MEMORY_SLOTS) {
+                throw new IllegalInstructionException("illegal memory slot number: " + slot);
+            }
+            addUnsigned(extendedOpcodes.value + slot);
+        }
+
         Instruction(Opcodes opcode) {
-            this(opcode, Register.R0);
+            this(opcode, R0);
         }
 
-        void addUnsignedImm(int imm) {
-            addImm(new IntImmediate(imm, false));
+        Instruction(ExtendedOpcodes extendedOpcodes) {
+            this(extendedOpcodes, R0);
         }
 
-        void addUnsignedImm(int imm, byte size) {
-            addImm(new IntImmediate(imm, false, size));
+        Instruction addSigned(int imm) {
+            mIntImms.add(IntImmediate.newSigned(imm));
+            return this;
         }
 
-        void addSignedImm(int imm) {
-            addImm(new IntImmediate(imm, true));
+        Instruction addUnsigned(int imm) {
+            mIntImms.add(IntImmediate.newUnsigned(imm));
+            return this;
         }
 
-        void addImm(IntImmediate imm) {
-            mIntImms.add(imm);
+
+        Instruction addTwosCompSigned(int imm) {
+            mIntImms.add(IntImmediate.newTwosComplementSigned(imm));
+            return this;
         }
 
-        void setLabel(String label) throws IllegalInstructionException {
+
+        Instruction addTwosCompUnsigned(int imm) {
+            mIntImms.add(IntImmediate.newTwosComplementUnsigned(imm));
+            return this;
+        }
+
+        Instruction addS8(byte imm) {
+            mIntImms.add(IntImmediate.newS8(imm));
+            return this;
+        }
+
+        Instruction addU8(int imm) {
+            mIntImms.add(IntImmediate.newU8(imm));
+            return this;
+        }
+
+        Instruction addS16(short imm) {
+            mIntImms.add(IntImmediate.newS16(imm));
+            return this;
+        }
+
+        Instruction addU16(int imm) {
+            mIntImms.add(IntImmediate.newU16(imm));
+            return this;
+        }
+
+        Instruction addS32(int imm) {
+            mIntImms.add(IntImmediate.newS32(imm));
+            return this;
+        }
+
+        Instruction addU32(long imm) {
+            mIntImms.add(IntImmediate.newU32(imm));
+            return this;
+        }
+
+        Instruction setLabel(String label) throws IllegalInstructionException {
             if (mLabels.containsKey(label)) {
                 throw new IllegalInstructionException("duplicate label " + label);
             }
@@ -321,18 +378,23 @@
             }
             mLabel = label;
             mLabels.put(label, this);
+            return this;
         }
 
-        void setTargetLabel(String label) {
+        Instruction setTargetLabel(String label) {
             mTargetLabel = label;
             mTargetLabelSize = 4; // May shrink later on in generate().
+            return this;
         }
 
-        void setBytesImm(byte[] bytes) {
-            if (mOpcode != Opcodes.JNEBS.value) {
-                throw new IllegalStateException("adding compare bytes to non-JNEBS instruction");
-            }
+        Instruction overrideLenField(int size) {
+            mLenFieldOverride = size;
+            return this;
+        }
+
+        Instruction setBytesImm(byte[] bytes) {
             mBytesImm = bytes;
+            return this;
         }
 
         /**
@@ -377,6 +439,21 @@
          * Assemble value for instruction size field.
          */
         private int generateImmSizeField() {
+            // If we already know the size the length field, just use it
+            switch (mLenFieldOverride) {
+                case -1:
+                    break;
+                case 1:
+                    return 1;
+                case 2:
+                    return 2;
+                case 4:
+                    return 3;
+                default:
+                    throw new IllegalStateException(
+                            "mLenFieldOverride has invalid value: " + mLenFieldOverride);
+            }
+            // Otherwise, calculate
             int immSize = calculateRequiredIndeterminateSize();
             // Encode size field to fit in 2 bits: 0->0, 1->1, 2->2, 3->4.
             return immSize == 4 ? 3 : immSize;
@@ -522,6 +599,7 @@
     // This version number syncs up with APF_VERSION in hardware/google/apf/apf_interpreter.h
     public static final int MIN_APF_VERSION = 2;
     public static final int MIN_APF_VERSION_IN_DEV = 5;
+    public static final int APF_VERSION_4 = 4;
 
 
     private final ArrayList<Instruction> mInstructions = new ArrayList<Instruction>();
@@ -555,11 +633,12 @@
         }
     }
 
-    private void addInstruction(Instruction instruction) {
+    private ApfGenerator append(Instruction instruction) {
         if (mGenerated) {
             throw new IllegalStateException("Program already generated");
         }
         mInstructions.add(instruction);
+        return this;
     }
 
     /**
@@ -578,53 +657,38 @@
      * In this case "next_filter" may not have any generated code associated with it.
      */
     public ApfGenerator defineLabel(String name) throws IllegalInstructionException {
-        Instruction instruction = new Instruction(Opcodes.LABEL);
-        instruction.setLabel(name);
-        addInstruction(instruction);
-        return this;
+        return append(new Instruction(Opcodes.LABEL).setLabel(name));
     }
 
     /**
      * Add an unconditional jump instruction to the end of the program.
      */
     public ApfGenerator addJump(String target) {
-        Instruction instruction = new Instruction(Opcodes.JMP);
-        instruction.setTargetLabel(target);
-        addInstruction(instruction);
-        return this;
+        return append(new Instruction(Opcodes.JMP).setTargetLabel(target));
     }
 
     /**
      * Add an instruction to the end of the program to load the byte at offset {@code offset}
      * bytes from the beginning of the packet into {@code register}.
      */
-    public ApfGenerator addLoad8(Register register, int offset) {
-        Instruction instruction = new Instruction(Opcodes.LDB, register);
-        instruction.addUnsignedImm(offset);
-        addInstruction(instruction);
-        return this;
+    public ApfGenerator addLoad8(Register r, int ofs) {
+        return append(new Instruction(Opcodes.LDB, r).addUnsigned(ofs));
     }
 
     /**
      * Add an instruction to the end of the program to load 16-bits at offset {@code offset}
      * bytes from the beginning of the packet into {@code register}.
      */
-    public ApfGenerator addLoad16(Register register, int offset) {
-        Instruction instruction = new Instruction(Opcodes.LDH, register);
-        instruction.addUnsignedImm(offset);
-        addInstruction(instruction);
-        return this;
+    public ApfGenerator addLoad16(Register r, int ofs) {
+        return append(new Instruction(Opcodes.LDH, r).addUnsigned(ofs));
     }
 
     /**
      * Add an instruction to the end of the program to load 32-bits at offset {@code offset}
      * bytes from the beginning of the packet into {@code register}.
      */
-    public ApfGenerator addLoad32(Register register, int offset) {
-        Instruction instruction = new Instruction(Opcodes.LDW, register);
-        instruction.addUnsignedImm(offset);
-        addInstruction(instruction);
-        return this;
+    public ApfGenerator addLoad32(Register r, int ofs) {
+        return append(new Instruction(Opcodes.LDW, r).addUnsigned(ofs));
     }
 
     /**
@@ -632,11 +696,8 @@
      * {@code register}. The offset of the loaded byte from the beginning of the packet is
      * the sum of {@code offset} and the value in register R1.
      */
-    public ApfGenerator addLoad8Indexed(Register register, int offset) {
-        Instruction instruction = new Instruction(Opcodes.LDBX, register);
-        instruction.addUnsignedImm(offset);
-        addInstruction(instruction);
-        return this;
+    public ApfGenerator addLoad8Indexed(Register r, int ofs) {
+        return append(new Instruction(Opcodes.LDBX, r).addUnsigned(ofs));
     }
 
     /**
@@ -644,11 +705,8 @@
      * {@code register}. The offset of the loaded 16-bits from the beginning of the packet is
      * the sum of {@code offset} and the value in register R1.
      */
-    public ApfGenerator addLoad16Indexed(Register register, int offset) {
-        Instruction instruction = new Instruction(Opcodes.LDHX, register);
-        instruction.addUnsignedImm(offset);
-        addInstruction(instruction);
-        return this;
+    public ApfGenerator addLoad16Indexed(Register r, int ofs) {
+        return append(new Instruction(Opcodes.LDHX, r).addUnsigned(ofs));
     }
 
     /**
@@ -656,109 +714,81 @@
      * {@code register}. The offset of the loaded 32-bits from the beginning of the packet is
      * the sum of {@code offset} and the value in register R1.
      */
-    public ApfGenerator addLoad32Indexed(Register register, int offset) {
-        Instruction instruction = new Instruction(Opcodes.LDWX, register);
-        instruction.addUnsignedImm(offset);
-        addInstruction(instruction);
-        return this;
+    public ApfGenerator addLoad32Indexed(Register r, int ofs) {
+        return append(new Instruction(Opcodes.LDWX, r).addUnsigned(ofs));
     }
 
     /**
      * Add an instruction to the end of the program to add {@code value} to register R0.
      */
-    public ApfGenerator addAdd(int value) {
-        Instruction instruction = new Instruction(Opcodes.ADD);
-        instruction.addUnsignedImm(value);
-        addInstruction(instruction);
-        return this;
+    public ApfGenerator addAdd(int val) {
+        return append(new Instruction(Opcodes.ADD).addTwosCompUnsigned(val));
     }
 
     /**
      * Add an instruction to the end of the program to multiply register R0 by {@code value}.
      */
-    public ApfGenerator addMul(int value) {
-        Instruction instruction = new Instruction(Opcodes.MUL);
-        instruction.addUnsignedImm(value);
-        addInstruction(instruction);
-        return this;
+    public ApfGenerator addMul(int val) {
+        return append(new Instruction(Opcodes.MUL).addUnsigned(val));
     }
 
     /**
      * Add an instruction to the end of the program to divide register R0 by {@code value}.
      */
-    public ApfGenerator addDiv(int value) {
-        Instruction instruction = new Instruction(Opcodes.DIV);
-        instruction.addUnsignedImm(value);
-        addInstruction(instruction);
-        return this;
+    public ApfGenerator addDiv(int val) {
+        return append(new Instruction(Opcodes.DIV).addUnsigned(val));
     }
 
     /**
      * Add an instruction to the end of the program to logically and register R0 with {@code value}.
      */
-    public ApfGenerator addAnd(int value) {
-        Instruction instruction = new Instruction(Opcodes.AND);
-        instruction.addUnsignedImm(value);
-        addInstruction(instruction);
-        return this;
+    public ApfGenerator addAnd(int val) {
+        return append(new Instruction(Opcodes.AND).addTwosCompUnsigned(val));
     }
 
     /**
      * Add an instruction to the end of the program to logically or register R0 with {@code value}.
      */
-    public ApfGenerator addOr(int value) {
-        Instruction instruction = new Instruction(Opcodes.OR);
-        instruction.addUnsignedImm(value);
-        addInstruction(instruction);
-        return this;
+    public ApfGenerator addOr(int val) {
+        return append(new Instruction(Opcodes.OR).addTwosCompUnsigned(val));
     }
 
     /**
      * Add an instruction to the end of the program to shift left register R0 by {@code value} bits.
      */
-    public ApfGenerator addLeftShift(int value) {
-        Instruction instruction = new Instruction(Opcodes.SH);
-        instruction.addSignedImm(value);
-        addInstruction(instruction);
-        return this;
+    // TODO: consider whether should change the argument type to byte
+    public ApfGenerator addLeftShift(int val) {
+        return append(new Instruction(Opcodes.SH).addSigned(val));
     }
 
     /**
      * Add an instruction to the end of the program to shift right register R0 by {@code value}
      * bits.
      */
-    public ApfGenerator addRightShift(int value) {
-        Instruction instruction = new Instruction(Opcodes.SH);
-        instruction.addSignedImm(-value);
-        addInstruction(instruction);
-        return this;
+    // TODO: consider whether should change the argument type to byte
+    public ApfGenerator addRightShift(int val) {
+        return append(new Instruction(Opcodes.SH).addSigned(-val));
     }
 
     /**
      * Add an instruction to the end of the program to add register R1 to register R0.
      */
     public ApfGenerator addAddR1() {
-        Instruction instruction = new Instruction(Opcodes.ADD, Register.R1);
-        addInstruction(instruction);
-        return this;
+        return append(new Instruction(Opcodes.ADD, R1));
     }
 
     /**
      * Add an instruction to the end of the program to multiply register R0 by register R1.
      */
     public ApfGenerator addMulR1() {
-        Instruction instruction = new Instruction(Opcodes.MUL, Register.R1);
-        addInstruction(instruction);
-        return this;
+        return append(new Instruction(Opcodes.MUL, R1));
     }
 
     /**
      * Add an instruction to the end of the program to divide register R0 by register R1.
      */
     public ApfGenerator addDivR1() {
-        Instruction instruction = new Instruction(Opcodes.DIV, Register.R1);
-        addInstruction(instruction);
-        return this;
+        return append(new Instruction(Opcodes.DIV, R1));
     }
 
     /**
@@ -766,9 +796,7 @@
      * and store the result back into register R0.
      */
     public ApfGenerator addAndR1() {
-        Instruction instruction = new Instruction(Opcodes.AND, Register.R1);
-        addInstruction(instruction);
-        return this;
+        return append(new Instruction(Opcodes.AND, R1));
     }
 
     /**
@@ -776,9 +804,7 @@
      * and store the result back into register R0.
      */
     public ApfGenerator addOrR1() {
-        Instruction instruction = new Instruction(Opcodes.OR, Register.R1);
-        addInstruction(instruction);
-        return this;
+        return append(new Instruction(Opcodes.OR, R1));
     }
 
     /**
@@ -786,111 +812,77 @@
      * register R1.
      */
     public ApfGenerator addLeftShiftR1() {
-        Instruction instruction = new Instruction(Opcodes.SH, Register.R1);
-        addInstruction(instruction);
-        return this;
+        return append(new Instruction(Opcodes.SH, R1));
     }
 
     /**
      * Add an instruction to the end of the program to move {@code value} into {@code register}.
      */
     public ApfGenerator addLoadImmediate(Register register, int value) {
-        Instruction instruction = new Instruction(Opcodes.LI, register);
-        instruction.addSignedImm(value);
-        addInstruction(instruction);
-        return this;
+        return append(new Instruction(Opcodes.LI, register).addSigned(value));
     }
 
     /**
      * Add an instruction to the end of the program to jump to {@code target} if register R0's
      * value equals {@code value}.
      */
-    public ApfGenerator addJumpIfR0Equals(int value, String target) {
-        Instruction instruction = new Instruction(Opcodes.JEQ);
-        instruction.addUnsignedImm(value);
-        instruction.setTargetLabel(target);
-        addInstruction(instruction);
-        return this;
+    public ApfGenerator addJumpIfR0Equals(int val, String tgt) {
+        return append(new Instruction(Opcodes.JEQ).addTwosCompUnsigned(val).setTargetLabel(tgt));
     }
 
     /**
      * Add an instruction to the end of the program to jump to {@code target} if register R0's
      * value does not equal {@code value}.
      */
-    public ApfGenerator addJumpIfR0NotEquals(int value, String target) {
-        Instruction instruction = new Instruction(Opcodes.JNE);
-        instruction.addUnsignedImm(value);
-        instruction.setTargetLabel(target);
-        addInstruction(instruction);
-        return this;
+    public ApfGenerator addJumpIfR0NotEquals(int val, String tgt) {
+        return append(new Instruction(Opcodes.JNE).addTwosCompUnsigned(val).setTargetLabel(tgt));
     }
 
     /**
      * Add an instruction to the end of the program to jump to {@code target} if register R0's
      * value is greater than {@code value}.
      */
-    public ApfGenerator addJumpIfR0GreaterThan(int value, String target) {
-        Instruction instruction = new Instruction(Opcodes.JGT);
-        instruction.addUnsignedImm(value);
-        instruction.setTargetLabel(target);
-        addInstruction(instruction);
-        return this;
+    public ApfGenerator addJumpIfR0GreaterThan(int val, String tgt) {
+        return append(new Instruction(Opcodes.JGT).addUnsigned(val).setTargetLabel(tgt));
     }
 
     /**
      * Add an instruction to the end of the program to jump to {@code target} if register R0's
      * value is less than {@code value}.
      */
-    public ApfGenerator addJumpIfR0LessThan(int value, String target) {
-        Instruction instruction = new Instruction(Opcodes.JLT);
-        instruction.addUnsignedImm(value);
-        instruction.setTargetLabel(target);
-        addInstruction(instruction);
-        return this;
+    public ApfGenerator addJumpIfR0LessThan(int val, String tgt) {
+        return append(new Instruction(Opcodes.JLT).addUnsigned(val).setTargetLabel(tgt));
     }
 
     /**
      * Add an instruction to the end of the program to jump to {@code target} if register R0's
      * value has any bits set that are also set in {@code value}.
      */
-    public ApfGenerator addJumpIfR0AnyBitsSet(int value, String target) {
-        Instruction instruction = new Instruction(Opcodes.JSET);
-        instruction.addUnsignedImm(value);
-        instruction.setTargetLabel(target);
-        addInstruction(instruction);
-        return this;
+    public ApfGenerator addJumpIfR0AnyBitsSet(int val, String tgt) {
+        return append(new Instruction(Opcodes.JSET).addTwosCompUnsigned(val).setTargetLabel(tgt));
     }
     /**
      * Add an instruction to the end of the program to jump to {@code target} if register R0's
      * value equals register R1's value.
      */
-    public ApfGenerator addJumpIfR0EqualsR1(String target) {
-        Instruction instruction = new Instruction(Opcodes.JEQ, Register.R1);
-        instruction.setTargetLabel(target);
-        addInstruction(instruction);
-        return this;
+    public ApfGenerator addJumpIfR0EqualsR1(String tgt) {
+        return append(new Instruction(Opcodes.JEQ, R1).setTargetLabel(tgt));
     }
 
     /**
      * Add an instruction to the end of the program to jump to {@code target} if register R0's
      * value does not equal register R1's value.
      */
-    public ApfGenerator addJumpIfR0NotEqualsR1(String target) {
-        Instruction instruction = new Instruction(Opcodes.JNE, Register.R1);
-        instruction.setTargetLabel(target);
-        addInstruction(instruction);
-        return this;
+    public ApfGenerator addJumpIfR0NotEqualsR1(String tgt) {
+        return append(new Instruction(Opcodes.JNE, R1).setTargetLabel(tgt));
     }
 
     /**
      * Add an instruction to the end of the program to jump to {@code target} if register R0's
      * value is greater than register R1's value.
      */
-    public ApfGenerator addJumpIfR0GreaterThanR1(String target) {
-        Instruction instruction = new Instruction(Opcodes.JGT, Register.R1);
-        instruction.setTargetLabel(target);
-        addInstruction(instruction);
-        return this;
+    public ApfGenerator addJumpIfR0GreaterThanR1(String tgt) {
+        return append(new Instruction(Opcodes.JGT, R1).setTargetLabel(tgt));
     }
 
     /**
@@ -898,133 +890,104 @@
      * value is less than register R1's value.
      */
     public ApfGenerator addJumpIfR0LessThanR1(String target) {
-        Instruction instruction = new Instruction(Opcodes.JLT, Register.R1);
-        instruction.setTargetLabel(target);
-        addInstruction(instruction);
-        return this;
+        return append(new Instruction(Opcodes.JLT, R1).setTargetLabel(target));
     }
 
     /**
      * Add an instruction to the end of the program to jump to {@code target} if register R0's
      * value has any bits set that are also set in R1's value.
      */
-    public ApfGenerator addJumpIfR0AnyBitsSetR1(String target) {
-        Instruction instruction = new Instruction(Opcodes.JSET, Register.R1);
-        instruction.setTargetLabel(target);
-        addInstruction(instruction);
-        return this;
+    public ApfGenerator addJumpIfR0AnyBitsSetR1(String tgt) {
+        return append(new Instruction(Opcodes.JSET, R1).setTargetLabel(tgt));
     }
 
     /**
-     * Add an instruction to the end of the program to jump to {@code target} if the bytes of the
-     * packet at an offset specified by {@code register} don't match {@code bytes}, {@code register}
-     * must be R0.
+     * Add an instruction to the end of the program to jump to {@code tgt} if the bytes of the
+     * packet at an offset specified by {@code register} don't match {@code bytes}
+     * R=0 means check for not equal
      */
-    public ApfGenerator addJumpIfBytesNotEqual(Register register, byte[] bytes, String target)
+    public ApfGenerator addJumpIfBytesAtR0NotEqual(byte[] bytes, String tgt) {
+        return append(new Instruction(Opcodes.JNEBS).addUnsigned(
+                bytes.length).setTargetLabel(tgt).setBytesImm(bytes));
+    }
+
+    /**
+     * Add an instruction to the end of the program to jump to {@code tgt} if the bytes of the
+     * packet at an offset specified by {@code register} match {@code bytes}
+     * R=1 means check for equal.
+     */
+    public ApfGenerator addJumpIfBytesAtR0Equal(byte[] bytes, String tgt)
             throws IllegalInstructionException {
-        if (register == Register.R1) {
-            throw new IllegalInstructionException("JNEBS fails with R1");
-        }
-        Instruction instruction = new Instruction(Opcodes.JNEBS, register);
-        instruction.addUnsignedImm(bytes.length);
-        instruction.setTargetLabel(target);
-        instruction.setBytesImm(bytes);
-        addInstruction(instruction);
-        return this;
+        requireApfVersion(MIN_APF_VERSION_IN_DEV);
+        return append(new Instruction(Opcodes.JNEBS, R1).addUnsigned(
+                bytes.length).setTargetLabel(tgt).setBytesImm(bytes));
     }
 
     /**
      * Add an instruction to the end of the program to load memory slot {@code slot} into
      * {@code register}.
      */
-    public ApfGenerator addLoadFromMemory(Register register, int slot)
+    public ApfGenerator addLoadFromMemory(Register r, int slot)
             throws IllegalInstructionException {
-        if (slot < 0 || slot > (MEMORY_SLOTS - 1)) {
-            throw new IllegalInstructionException("illegal memory slot number: " + slot);
-        }
-        Instruction instruction = new Instruction(Opcodes.EXT, register);
-        instruction.addUnsignedImm(ExtendedOpcodes.LDM.value + slot);
-        addInstruction(instruction);
-        return this;
+        return append(new Instruction(ExtendedOpcodes.LDM, slot, r));
     }
 
     /**
      * Add an instruction to the end of the program to store {@code register} into memory slot
      * {@code slot}.
      */
-    public ApfGenerator addStoreToMemory(Register register, int slot)
+    public ApfGenerator addStoreToMemory(Register r, int slot)
             throws IllegalInstructionException {
-        if (slot < 0 || slot > (MEMORY_SLOTS - 1)) {
-            throw new IllegalInstructionException("illegal memory slot number: " + slot);
-        }
-        Instruction instruction = new Instruction(Opcodes.EXT, register);
-        instruction.addUnsignedImm(ExtendedOpcodes.STM.value + slot);
-        addInstruction(instruction);
-        return this;
+        return append(new Instruction(ExtendedOpcodes.STM, slot, r));
     }
 
     /**
      * Add an instruction to the end of the program to logically not {@code register}.
      */
-    public ApfGenerator addNot(Register register) {
-        Instruction instruction = new Instruction(Opcodes.EXT, register);
-        instruction.addUnsignedImm(ExtendedOpcodes.NOT.value);
-        addInstruction(instruction);
-        return this;
+    public ApfGenerator addNot(Register r) {
+        return append(new Instruction(ExtendedOpcodes.NOT, r));
     }
 
     /**
      * Add an instruction to the end of the program to negate {@code register}.
      */
-    public ApfGenerator addNeg(Register register) {
-        Instruction instruction = new Instruction(Opcodes.EXT, register);
-        instruction.addUnsignedImm(ExtendedOpcodes.NEG.value);
-        addInstruction(instruction);
-        return this;
+    public ApfGenerator addNeg(Register r) {
+        return append(new Instruction(ExtendedOpcodes.NEG, r));
     }
 
     /**
      * Add an instruction to swap the values in register R0 and register R1.
      */
     public ApfGenerator addSwap() {
-        Instruction instruction = new Instruction(Opcodes.EXT);
-        instruction.addUnsignedImm(ExtendedOpcodes.SWAP.value);
-        addInstruction(instruction);
-        return this;
+        return append(new Instruction(ExtendedOpcodes.SWAP));
     }
 
     /**
      * Add an instruction to the end of the program to move the value into
      * {@code register} from the other register.
      */
-    public ApfGenerator addMove(Register register) {
-        Instruction instruction = new Instruction(Opcodes.EXT, register);
-        instruction.addUnsignedImm(ExtendedOpcodes.MOVE.value);
-        addInstruction(instruction);
-        return this;
+    public ApfGenerator addMove(Register r) {
+        return append(new Instruction(ExtendedOpcodes.MOVE, r));
     }
 
     /**
      * Add an instruction to the end of the program to let the program immediately return PASS.
      */
-    public ApfGenerator addPass() throws IllegalInstructionException {
-        Instruction instruction = new Instruction(Opcodes.PASS, Register.R0);
-        addInstruction(instruction);
-        return this;
+    public ApfGenerator addPass() {
+        // PASS requires using R0 because it shares opcode with DROP
+        return append(new Instruction(Opcodes.PASS));
     }
 
     /**
      * Add an instruction to the end of the program to increment the counter value and
      * immediately return PASS.
      */
-    public ApfGenerator addCountAndPass(int counterNumber) throws IllegalInstructionException {
+    public ApfGenerator addCountAndPass(int cnt) throws IllegalInstructionException {
         requireApfVersion(MIN_APF_VERSION_IN_DEV);
-        checkRange("CounterNumber", counterNumber /* value */, 1 /* lowerBound */,
+        checkRange("CounterNumber", cnt /* value */, 1 /* lowerBound */,
                 1000 /* upperBound */);
-        Instruction instruction = new Instruction(Opcodes.PASS, Register.R0);
-        instruction.addUnsignedImm(counterNumber);
-        addInstruction(instruction);
-        return this;
+        // PASS requires using R0 because it shares opcode with DROP
+        return append(new Instruction(Opcodes.PASS).addUnsigned(cnt));
     }
 
     /**
@@ -1032,36 +995,52 @@
      */
     public ApfGenerator addDrop() throws IllegalInstructionException {
         requireApfVersion(MIN_APF_VERSION_IN_DEV);
-        Instruction instruction = new Instruction(Opcodes.DROP, Register.R1);
-        addInstruction(instruction);
-        return this;
+        // DROP requires using R1 because it shares opcode with PASS
+        return append(new Instruction(Opcodes.DROP, R1));
     }
 
     /**
      * Add an instruction to the end of the program to increment the counter value and
      * immediately return DROP.
      */
-    public ApfGenerator addCountAndDrop(int counterNumber) throws IllegalInstructionException {
+    public ApfGenerator addCountAndDrop(int cnt) throws IllegalInstructionException {
         requireApfVersion(MIN_APF_VERSION_IN_DEV);
-        checkRange("CounterNumber", counterNumber /* value */, 1 /* lowerBound */,
+        checkRange("CounterNumber", cnt /* value */, 1 /* lowerBound */,
                 1000 /* upperBound */);
-        Instruction instruction = new Instruction(Opcodes.DROP, Register.R1);
-        instruction.addUnsignedImm(counterNumber);
-        addInstruction(instruction);
-        return this;
+        // DROP requires using R1 because it shares opcode with PASS
+        return append(new Instruction(Opcodes.DROP, R1).addUnsigned(cnt));
+    }
+
+    /**
+     * Add an instruction to the end of the program to call the apf_allocate_buffer() function.
+     * Buffer length to be allocated is stored in register 0.
+     */
+    public ApfGenerator addAllocateR0() throws IllegalInstructionException {
+        requireApfVersion(MIN_APF_VERSION_IN_DEV);
+        return append(new Instruction(ExtendedOpcodes.ALLOCATE));
     }
 
     /**
      * Add an instruction to the end of the program to call the apf_allocate_buffer() function.
      *
-     * @param register the register value contains the buffer size.
+     * @param size the buffer length to be allocated.
      */
-    public ApfGenerator addAlloc(Register register) throws IllegalInstructionException {
-        requireApfVersion(5);
-        Instruction instruction = new Instruction(Opcodes.EXT, register);
-        instruction.addUnsignedImm(ExtendedOpcodes.ALLOC.value);
-        addInstruction(instruction);
-        return this;
+    public ApfGenerator addAllocate(int size) throws IllegalInstructionException {
+        requireApfVersion(MIN_APF_VERSION_IN_DEV);
+        // R1 means the extra be16 immediate is present
+        return append(new Instruction(ExtendedOpcodes.ALLOCATE, R1).addU16(size));
+    }
+
+    /**
+     * Add an instruction to the beginning of the program to reserve the data region.
+     * @param data the actual data byte
+     */
+    public ApfGenerator addData(byte[] data) throws IllegalInstructionException {
+        requireApfVersion(MIN_APF_VERSION_IN_DEV);
+        if (!mInstructions.isEmpty()) {
+            throw new IllegalInstructionException("data instruction has to come first");
+        }
+        return append(new Instruction(Opcodes.JMP, R1).addUnsigned(data.length).setBytesImm(data));
     }
 
     /**
@@ -1069,10 +1048,8 @@
      */
     public ApfGenerator addTransmit() throws IllegalInstructionException {
         requireApfVersion(MIN_APF_VERSION_IN_DEV);
-        Instruction instruction = new Instruction(Opcodes.EXT, Register.R0);
-        instruction.addUnsignedImm(ExtendedOpcodes.TRANSMIT.value);
-        addInstruction(instruction);
-        return this;
+        // TRANSMIT requires using R0 because it shares opcode with DISCARD
+        return append(new Instruction(ExtendedOpcodes.TRANSMIT));
     }
 
     /**
@@ -1080,176 +1057,141 @@
      */
     public ApfGenerator addDiscard() throws IllegalInstructionException {
         requireApfVersion(MIN_APF_VERSION_IN_DEV);
-        Instruction instruction = new Instruction(Opcodes.EXT, Register.R1);
-        instruction.addUnsignedImm(ExtendedOpcodes.DISCARD.value);
-        addInstruction(instruction);
-        return this;
+        // DISCARD requires using R1 because it shares opcode with TRANSMIT
+        return append(new Instruction(ExtendedOpcodes.DISCARD, R1));
     }
 
-    // TODO: add back when support WRITE opcode
-//    /**
-//     * Add an instruction to the end of the program to write 1, 2 or 4 bytes value to output
-//     buffer.
-//     *
-//     * @param value the value to write
-//     * @param size the size of the value
-//     * @return the ApfGenerator object
-//     * @throws IllegalInstructionException throws when size is not 1, 2 or 4
-//     */
-//    public ApfGenerator addWrite(int value, byte size) throws IllegalInstructionException {
-//        requireApfVersion(5);
-//        if (!(size == 1 || size == 2 || size == 4)) {
-//            throw new IllegalInstructionException("length field must be 1, 2 or 4");
-//        }
-//        if (size < calculateImmSize(value, false)) {
-//            throw new IllegalInstructionException(
-//                    String.format("the value %d is unfit into size: %d", value, size));
-//        }
-//        Instruction instruction = new Instruction(Opcodes.WRITE);
-//        instruction.addUnsignedImm(value, size);
-//        addInstruction(instruction);
-//        return this;
-//    }
-
-    // TODO: add back when support EWRITE opcode
-//    /**
-//     * Add an instruction to the end of the program to write 1, 2 or 4 bytes value from register
-//     * to output buffer.
-//     *
-//     * @param register the register contains the value to be written
-//     * @param size the size of the value
-//     * @return the ApfGenerator object
-//     * @throws IllegalInstructionException throws when size is not 1, 2 or 4
-//     */
-//    public ApfGenerator addWrite(Register register, byte size)
-//            throws IllegalInstructionException {
-//        requireApfVersion(5);
-//        if (!(size == 1 || size == 2 || size == 4)) {
-//            throw new IllegalInstructionException(
-//                    "length field must be 1, 2 or 4");
-//        }
-//        Instruction instruction = new Instruction(Opcodes.EXT, register);
-//        if (size == 1) {
-//            instruction.addUnsignedImm(ExtendedOpcodes.EWRITE1.value);
-//        } else if (size == 2) {
-//            instruction.addUnsignedImm(ExtendedOpcodes.EWRITE2.value);
-//        } else {
-//            instruction.addUnsignedImm(ExtendedOpcodes.EWRITE4.value);
-//        }
-//        addInstruction(instruction);
-//        return this;
-//    }
-
-    // TODO: add back when support PKTCOPY/DATACOPY opcode
-//    /**
-//     * Add an instruction to the end of the program to copy data from APF data region to output
-//     * buffer.
-//     *
-//     * @param srcOffset the offset inside the APF data region for where to start copy
-//     * @param length the length of bytes needed to be copied, only <= 255 bytes can be copied at
-//     *               one time.
-//     * @return the ApfGenerator object
-//     * @throws IllegalInstructionException throws when imm size is incorrectly set.
-//     */
-//    public ApfGenerator addDataCopy(int srcOffset, int length)
-//            throws IllegalInstructionException {
-//        return addMemCopy(srcOffset, length, Register.R1);
-//    }
-//
-//    /**
-//     * Add an instruction to the end of the program to copy data from input packet to output
-//     buffer.
-//     *
-//     * @param srcOffset the offset inside the input packet for where to start copy
-//     * @param length the length of bytes needed to be copied, only <= 255 bytes can be copied at
-//     *               one time.
-//     * @return the ApfGenerator object
-//     * @throws IllegalInstructionException throws when imm size is incorrectly set.
-//     */
-//    public ApfGenerator addPacketCopy(int srcOffset, int length)
-//            throws IllegalInstructionException {
-//        return addMemCopy(srcOffset, length, Register.R0);
-//    }
-//
-//    private ApfGenerator addMemCopy(int srcOffset, int length, Register register)
-//            throws IllegalInstructionException {
-//        requireApfVersion(5);
-//        checkCopyLength(length);
-//        checkCopyOffset(srcOffset);
-//        Instruction instruction = new Instruction(Opcodes.MEMCOPY, register);
-//        // if the offset == 0, it should still be encoded with 1 byte size.
-//        if (srcOffset == 0) {
-//            instruction.addUnsignedImm(srcOffset, (byte) 1 /* size */);
-//        } else {
-//            instruction.addUnsignedImm(srcOffset);
-//        }
-//        instruction.addUnsignedImm(length, (byte) 1 /* size */);
-//        addInstruction(instruction);
-//        return this;
-//    }
-//    TODO: add back when support EPKTCOPY/EDATACOPY opcode
-//    /**
-//     * Add an instruction to the end of the program to copy data from APF data region to output
-//     * buffer.
-//     *
-//     * @param register the register that stored the base offset value.
-//     * @param relativeOffset the offset inside the APF data region for where to start copy
-//     * @param length the length of bytes needed to be copied, only <= 255 bytes can be copied at
-//     *               one time.
-//     * @return the ApfGenerator object
-//     * @throws IllegalInstructionException throws when imm size is incorrectly set.
-//     */
-//    public ApfGenerator addDataCopy(Register register, int relativeOffset, int length)
-//            throws IllegalInstructionException {
-//        return addMemcopy(register, relativeOffset, length, ExtendedOpcodes.EDATACOPY.value);
-//    }
-//
-//    /**
-//     * Add an instruction to the end of the program to copy data from input packet to output
-//     buffer.
-//     *
-//     * @param register the register that stored the base offset value.
-//     * @param relativeOffset the offset inside the input packet for where to start copy
-//     * @param length the length of bytes needed to be copied, only <= 255 bytes can be copied at
-//     *               one time.
-//     * @return the ApfGenerator object
-//     * @throws IllegalInstructionException throws when imm size is incorrectly set.
-//     */
-//    public ApfGenerator addPacketCopy(Register register, int relativeOffset, int length)
-//            throws IllegalInstructionException {
-//        return addMemcopy(register, relativeOffset, length, ExtendedOpcodes.EPKTCOPY.value);
-//    }
-//
-//    private ApfGenerator addMemcopy(Register register, int relativeOffset, int length, int opcode)
-//            throws IllegalInstructionException {
-//        requireApfVersion(5);
-//        checkCopyLength(length);
-//        checkCopyOffset(relativeOffset);
-//        Instruction instruction = new Instruction(Opcodes.EXT, register);
-//        instruction.addUnsignedImm(opcode);
-//        // if the offset == 0, it should still be encoded with 1 byte size.
-//        if (relativeOffset == 0) {
-//            instruction.addUnsignedImm(relativeOffset, (byte) 1 /* size */);
-//        } else {
-//            instruction.addUnsignedImm(relativeOffset);
-//        }
-//        instruction.addUnsignedImm(length, (byte) 1 /* size */);
-//        addInstruction(instruction);
-//        return this;
-//    }
-
-    private void checkCopyLength(int length) {
-        if (length < 0 || length > 255) {
-            throw new IllegalArgumentException(
-                    "copy length must between 0 to 255, length: " + length);
-        }
+    /**
+     * Add an instruction to the end of the program to write 1 byte value to output buffer.
+     */
+    public ApfGenerator addWriteU8(int val) throws IllegalInstructionException {
+        requireApfVersion(MIN_APF_VERSION_IN_DEV);
+        return append(new Instruction(Opcodes.WRITE).overrideLenField(1).addU8(val));
     }
 
-    private void checkCopyOffset(int offset) {
-        if (offset < 0) {
-            throw new IllegalArgumentException(
-                    "offset must be non less than zero, offset: " + offset);
-        }
+    /**
+     * Add an instruction to the end of the program to write 2 bytes value to output buffer.
+     */
+    public ApfGenerator addWriteU16(int val) throws IllegalInstructionException {
+        requireApfVersion(MIN_APF_VERSION_IN_DEV);
+        return append(new Instruction(Opcodes.WRITE).overrideLenField(2).addU16(val));
+    }
+
+    /**
+     * Add an instruction to the end of the program to write 4 bytes value to output buffer.
+     */
+    public ApfGenerator addWriteU32(long val) throws IllegalInstructionException {
+        requireApfVersion(MIN_APF_VERSION_IN_DEV);
+        return append(new Instruction(Opcodes.WRITE).overrideLenField(4).addU32(val));
+    }
+
+    /**
+     * Add an instruction to the end of the program to write 1 byte value from register to output
+     * buffer.
+     */
+    public ApfGenerator addWriteU8(Register reg) throws IllegalInstructionException {
+        requireApfVersion(MIN_APF_VERSION_IN_DEV);
+        return append(new Instruction(ExtendedOpcodes.EWRITE1, reg));
+    }
+
+    /**
+     * Add an instruction to the end of the program to write 2 byte value from register to output
+     * buffer.
+     */
+    public ApfGenerator addWriteU16(Register reg) throws IllegalInstructionException {
+        requireApfVersion(MIN_APF_VERSION_IN_DEV);
+        return append(new Instruction(ExtendedOpcodes.EWRITE2, reg));
+    }
+
+    /**
+     * Add an instruction to the end of the program to write 4 byte value from register to output
+     * buffer.
+     */
+    public ApfGenerator addWriteU32(Register reg) throws IllegalInstructionException {
+        requireApfVersion(MIN_APF_VERSION_IN_DEV);
+        return append(new Instruction(ExtendedOpcodes.EWRITE4, reg));
+    }
+
+    /**
+     * Add an instruction to the end of the program to copy data from APF program/data region to
+     * output buffer and auto-increment the output buffer pointer.
+     *
+     * @param src the offset inside the APF program/data region for where to start copy.
+     * @param len the length of bytes needed to be copied, only <= 255 bytes can be copied at
+     *               one time.
+     * @return the ApfGenerator object
+     */
+    public ApfGenerator addDataCopy(int src, int len)
+            throws IllegalInstructionException {
+        requireApfVersion(MIN_APF_VERSION_IN_DEV);
+        return append(new Instruction(Opcodes.PKTDATACOPY, R1).addUnsigned(src).addU8(len));
+    }
+
+    /**
+     * Add an instruction to the end of the program to copy data from input packet to output
+     * buffer and auto-increment the output buffer pointer.
+     *
+     * @param src the offset inside the input packet for where to start copy.
+     * @param len the length of bytes needed to be copied, only <= 255 bytes can be copied at
+     *               one time.
+     * @return the ApfGenerator object
+     */
+    public ApfGenerator addPacketCopy(int src, int len)
+            throws IllegalInstructionException {
+        requireApfVersion(MIN_APF_VERSION_IN_DEV);
+        return append(new Instruction(Opcodes.PKTDATACOPY, R0).addUnsigned(src).addU8(len));
+    }
+
+    /**
+     * Add an instruction to the end of the program to copy data from APF program/data region to
+     * output buffer and auto-increment the output buffer pointer.
+     * Source offset is stored in R0.
+     *
+     * @param len the number of bytes to be copied, only <= 255 bytes can be copied at once.
+     * @return the ApfGenerator object
+     */
+    public ApfGenerator addDataCopyFromR0(int len) throws IllegalInstructionException {
+        requireApfVersion(MIN_APF_VERSION_IN_DEV);
+        return append(new Instruction(ExtendedOpcodes.EDATACOPY).addU8(len));
+    }
+
+    /**
+     * Add an instruction to the end of the program to copy data from input packet to output
+     * buffer and auto-increment the output buffer pointer.
+     * Source offset is stored in R0.
+     *
+     * @param len the number of bytes to be copied, only <= 255 bytes can be copied at once.
+     * @return the ApfGenerator object
+     */
+    public ApfGenerator addPacketCopyFromR0(int len) throws IllegalInstructionException {
+        requireApfVersion(MIN_APF_VERSION_IN_DEV);
+        return append(new Instruction(ExtendedOpcodes.EPKTCOPY).addU8(len));
+    }
+
+    /**
+     * Add an instruction to the end of the program to copy data from APF program/data region to
+     * output buffer and auto-increment the output buffer pointer.
+     * Source offset is stored in R0.
+     * Copy length is stored in R1.
+     *
+     * @return the ApfGenerator object
+     */
+    public ApfGenerator addDataCopyFromR0LenR1() throws IllegalInstructionException {
+        requireApfVersion(MIN_APF_VERSION_IN_DEV);
+        return append(new Instruction(ExtendedOpcodes.EDATACOPY, R1));
+    }
+
+    /**
+     * Add an instruction to the end of the program to copy data from input packet to output
+     * buffer and auto-increment the output buffer pointer.
+     * Source offset is stored in R0.
+     * Copy length is stored in R1.
+     *
+     * @return the ApfGenerator object
+     */
+    public ApfGenerator addPacketCopyFromR0LenR1() throws IllegalInstructionException {
+        requireApfVersion(MIN_APF_VERSION_IN_DEV);
+        return append(new Instruction(ExtendedOpcodes.EPKTCOPY, R1));
     }
 
     private static void checkRange(@NonNull String variableName, long value, long lowerBound,
@@ -1266,30 +1208,24 @@
      * Add an instruction to the end of the program to load 32 bits from the data memory into
      * {@code register}. The source address is computed by adding the signed immediate
      * @{code offset} to the other register.
-     * Requires APF v3 or greater.
+     * Requires APF v4 or greater.
      */
-    public ApfGenerator addLoadData(Register destinationRegister, int offset)
+    public ApfGenerator addLoadData(Register dst, int ofs)
             throws IllegalInstructionException {
-        requireApfVersion(3);
-        Instruction instruction = new Instruction(Opcodes.LDDW, destinationRegister);
-        instruction.addSignedImm(offset);
-        addInstruction(instruction);
-        return this;
+        requireApfVersion(APF_VERSION_4);
+        return append(new Instruction(Opcodes.LDDW, dst).addSigned(ofs));
     }
 
     /**
      * Add an instruction to the end of the program to store 32 bits from {@code register} into the
      * data memory. The destination address is computed by adding the signed immediate
      * @{code offset} to the other register.
-     * Requires APF v3 or greater.
+     * Requires APF v4 or greater.
      */
-    public ApfGenerator addStoreData(Register sourceRegister, int offset)
+    public ApfGenerator addStoreData(Register src, int ofs)
             throws IllegalInstructionException {
-        requireApfVersion(3);
-        Instruction instruction = new Instruction(Opcodes.STDW, sourceRegister);
-        instruction.addSignedImm(offset);
-        addInstruction(instruction);
-        return this;
+        requireApfVersion(APF_VERSION_4);
+        return append(new Instruction(Opcodes.STDW, src).addSigned(ofs));
     }
 
     /**
diff --git a/src/android/net/apf/DnsUtils.java b/src/android/net/apf/DnsUtils.java
index 0300d34..5bd2515 100644
--- a/src/android/net/apf/DnsUtils.java
+++ b/src/android/net/apf/DnsUtils.java
@@ -301,7 +301,7 @@
         gen.addJumpIfR0NotEquals(label.length(), noMatchLabel);
         gen.addLoadFromMemory(R0, SLOT_CURRENT_PARSE_OFFSET);
         gen.addAdd(1);
-        gen.addJumpIfBytesNotEqual(R0, label.getBytes(), noMatchLabel);
+        gen.addJumpIfBytesAtR0NotEqual(label.getBytes(), noMatchLabel);
 
         // Prep offset of next label.
         gen.addAdd(label.length());
diff --git a/src/android/net/apf/LegacyApfFilter.java b/src/android/net/apf/LegacyApfFilter.java
index cc09856..6b93d89 100644
--- a/src/android/net/apf/LegacyApfFilter.java
+++ b/src/android/net/apf/LegacyApfFilter.java
@@ -982,7 +982,7 @@
                 // Generate code to match the packet bytes.
                 if (section.type == PacketSection.Type.MATCH) {
                     gen.addLoadImmediate(Register.R0, section.start);
-                    gen.addJumpIfBytesNotEqual(Register.R0,
+                    gen.addJumpIfBytesAtR0NotEqual(
                             Arrays.copyOfRange(mPacket.array(), section.start,
                                     section.start + section.length),
                             nextFilterLabel);
@@ -1065,7 +1065,7 @@
             final String nextFilterLabel = "natt_keepalive_filter" + getUniqueNumberLocked();
 
             gen.addLoadImmediate(Register.R0, ETH_HEADER_LEN + IPV4_SRC_ADDR_OFFSET);
-            gen.addJumpIfBytesNotEqual(Register.R0, mSrcDstAddr, nextFilterLabel);
+            gen.addJumpIfBytesAtR0NotEqual(mSrcDstAddr, nextFilterLabel);
 
             // A NAT-T keepalive packet contains 1 byte payload with the value 0xff
             // Check payload length is 1
@@ -1080,11 +1080,11 @@
             // Check that the ports match
             gen.addLoadFromMemory(Register.R0, gen.IPV4_HEADER_SIZE_MEMORY_SLOT);
             gen.addAdd(ETH_HEADER_LEN);
-            gen.addJumpIfBytesNotEqual(Register.R0, mPortFingerprint, nextFilterLabel);
+            gen.addJumpIfBytesAtR0NotEqual(mPortFingerprint, nextFilterLabel);
 
             // Payload offset = R0 + UDP header length
             gen.addAdd(UDP_HEADER_LEN);
-            gen.addJumpIfBytesNotEqual(Register.R0, mPayload, nextFilterLabel);
+            gen.addJumpIfBytesAtR0NotEqual(mPayload, nextFilterLabel);
 
             maybeSetupCounter(gen, Counter.DROPPED_IPV4_NATT_KEEPALIVE);
             gen.addJump(mCountAndDropLabel);
@@ -1180,7 +1180,7 @@
             final String nextFilterLabel = "keepalive_ack" + getUniqueNumberLocked();
 
             gen.addLoadImmediate(Register.R0, ETH_HEADER_LEN + IPV4_SRC_ADDR_OFFSET);
-            gen.addJumpIfBytesNotEqual(Register.R0, mSrcDstAddr, nextFilterLabel);
+            gen.addJumpIfBytesAtR0NotEqual(mSrcDstAddr, nextFilterLabel);
 
             // Skip to the next filter if it's not zero-sized :
             // TCP_HEADER_SIZE + IPV4_HEADER_SIZE - ipv4_total_length == 0
@@ -1202,7 +1202,7 @@
             gen.addLoadFromMemory(Register.R1, gen.IPV4_HEADER_SIZE_MEMORY_SLOT);
             gen.addLoadImmediate(Register.R0, ETH_HEADER_LEN);
             gen.addAddR1();
-            gen.addJumpIfBytesNotEqual(Register.R0, mPortSeqAckFingerprint, nextFilterLabel);
+            gen.addJumpIfBytesAtR0NotEqual(mPortSeqAckFingerprint, nextFilterLabel);
 
             maybeSetupCounter(gen, Counter.DROPPED_IPV4_KEEPALIVE_ACK);
             gen.addJump(mCountAndDropLabel);
@@ -1316,7 +1316,7 @@
         // Pass if not ARP IPv4.
         gen.addLoadImmediate(Register.R0, ARP_HEADER_OFFSET);
         maybeSetupCounter(gen, Counter.PASSED_ARP_NON_IPV4);
-        gen.addJumpIfBytesNotEqual(Register.R0, ARP_IPV4_HEADER, mCountAndPassLabel);
+        gen.addJumpIfBytesAtR0NotEqual(ARP_IPV4_HEADER, mCountAndPassLabel);
 
         // Pass if unknown ARP opcode.
         gen.addLoad16(Register.R0, ARP_OPCODE_OFFSET);
@@ -1332,7 +1332,7 @@
         // Pass if unicast reply.
         gen.addLoadImmediate(Register.R0, ETH_DEST_ADDR_OFFSET);
         maybeSetupCounter(gen, Counter.PASSED_ARP_UNICAST_REPLY);
-        gen.addJumpIfBytesNotEqual(Register.R0, ETHER_BROADCAST, mCountAndPassLabel);
+        gen.addJumpIfBytesAtR0NotEqual(ETHER_BROADCAST, mCountAndPassLabel);
 
         // Either a unicast request, a unicast reply, or a broadcast reply.
         gen.defineLabel(checkTargetIPv4);
@@ -1346,7 +1346,7 @@
             // and broadcast replies with a different target IPv4 address.
             gen.addLoadImmediate(Register.R0, ARP_TARGET_IP_ADDRESS_OFFSET);
             maybeSetupCounter(gen, Counter.DROPPED_ARP_OTHER_HOST);
-            gen.addJumpIfBytesNotEqual(Register.R0, mIPv4Address, mCountAndDropLabel);
+            gen.addJumpIfBytesAtR0NotEqual(mIPv4Address, mCountAndDropLabel);
         }
 
         maybeSetupCounter(gen, Counter.PASSED_ARP);
@@ -1394,7 +1394,7 @@
             gen.addLoadImmediate(Register.R0, DHCP_CLIENT_MAC_OFFSET);
             // NOTE: Relies on R1 containing IPv4 header offset.
             gen.addAddR1();
-            gen.addJumpIfBytesNotEqual(Register.R0, mHardwareAddress, skipDhcpv4Filter);
+            gen.addJumpIfBytesAtR0NotEqual(mHardwareAddress, skipDhcpv4Filter);
             maybeSetupCounter(gen, Counter.PASSED_DHCP);
             gen.addJump(mCountAndPassLabel);
 
@@ -1428,7 +1428,7 @@
             // TODO: can we invert this condition to fall through to the common pass case below?
             maybeSetupCounter(gen, Counter.PASSED_IPV4_UNICAST);
             gen.addLoadImmediate(Register.R0, ETH_DEST_ADDR_OFFSET);
-            gen.addJumpIfBytesNotEqual(Register.R0, ETHER_BROADCAST, mCountAndPassLabel);
+            gen.addJumpIfBytesAtR0NotEqual(ETHER_BROADCAST, mCountAndPassLabel);
             maybeSetupCounter(gen, Counter.DROPPED_IPV4_L2_BROADCAST);
             gen.addJump(mCountAndDropLabel);
         } else {
@@ -1555,8 +1555,7 @@
         // TODO: Drop only if they don't contain the address of on-link neighbours.
         final byte[] unsolicitedNaDropPrefix = Arrays.copyOf(IPV6_ALL_NODES_ADDRESS, 15);
         gen.addLoadImmediate(Register.R0, IPV6_DEST_ADDR_OFFSET);
-        gen.addJumpIfBytesNotEqual(Register.R0, unsolicitedNaDropPrefix,
-                skipUnsolicitedMulticastNALabel);
+        gen.addJumpIfBytesAtR0NotEqual(unsolicitedNaDropPrefix, skipUnsolicitedMulticastNALabel);
 
         maybeSetupCounter(gen, Counter.DROPPED_IPV6_MULTICAST_NA);
         gen.addJump(mCountAndDropLabel);
@@ -1613,7 +1612,7 @@
 
         // Check it's L2 mDNS multicast address.
         gen.addLoadImmediate(Register.R0, ETH_DEST_ADDR_OFFSET);
-        gen.addJumpIfBytesNotEqual(Register.R0, ETH_MULTICAST_MDNS_V4_MAC_ADDRESS,
+        gen.addJumpIfBytesAtR0NotEqual(ETH_MULTICAST_MDNS_V4_MAC_ADDRESS,
                 skipMdnsv4Filter);
 
         // Checks it's IPv4.
@@ -1631,8 +1630,7 @@
 
         // Checks it's L2 mDNS multicast address.
         // Relies on R0 containing the ethernet destination mac address offset.
-        gen.addJumpIfBytesNotEqual(Register.R0, ETH_MULTICAST_MDNS_V6_MAC_ADDRESS,
-                skipMdnsFilter);
+        gen.addJumpIfBytesAtR0NotEqual(ETH_MULTICAST_MDNS_V6_MAC_ADDRESS, skipMdnsFilter);
 
         // Checks it's IPv6.
         gen.addLoad16(Register.R0, ETH_ETHERTYPE_OFFSET);
@@ -1663,7 +1661,7 @@
         for (int i = 0; i < mMdnsAllowList.size(); ++i) {
             final String mDnsNextAllowedQnameCheck = "mdns_next_allowed_qname_check" + i;
             final byte[] encodedQname = encodeQname(mMdnsAllowList.get(i));
-            gen.addJumpIfBytesNotEqual(Register.R0, encodedQname, mDnsNextAllowedQnameCheck);
+            gen.addJumpIfBytesAtR0NotEqual(encodedQname, mDnsNextAllowedQnameCheck);
             // QNAME matched
             gen.addJump(mDnsAcceptPacket);
             // QNAME not matched
@@ -1777,7 +1775,7 @@
         // Drop non-IP non-ARP broadcasts, pass the rest
         gen.addLoadImmediate(Register.R0, ETH_DEST_ADDR_OFFSET);
         maybeSetupCounter(gen, Counter.PASSED_NON_IP_UNICAST);
-        gen.addJumpIfBytesNotEqual(Register.R0, ETHER_BROADCAST, mCountAndPassLabel);
+        gen.addJumpIfBytesAtR0NotEqual(ETHER_BROADCAST, mCountAndPassLabel);
         maybeSetupCounter(gen, Counter.DROPPED_ETH_BROADCAST);
         gen.addJump(mCountAndDropLabel);
 
diff --git a/src/android/net/dhcp6/Dhcp6Client.java b/src/android/net/dhcp6/Dhcp6Client.java
index 291a97a..8d53048 100644
--- a/src/android/net/dhcp6/Dhcp6Client.java
+++ b/src/android/net/dhcp6/Dhcp6Client.java
@@ -20,9 +20,7 @@
 import static android.net.dhcp6.Dhcp6Packet.PrefixDelegation;
 import static android.provider.DeviceConfig.NAMESPACE_CONNECTIVITY;
 import static android.system.OsConstants.AF_INET6;
-import static android.system.OsConstants.IFA_F_NODAD;
 import static android.system.OsConstants.IPPROTO_UDP;
-import static android.system.OsConstants.RT_SCOPE_UNIVERSE;
 import static android.system.OsConstants.SOCK_DGRAM;
 import static android.system.OsConstants.SOCK_NONBLOCK;
 
@@ -31,14 +29,8 @@
 import static com.android.net.module.util.NetworkStackConstants.DHCP6_SERVER_PORT;
 import static com.android.net.module.util.NetworkStackConstants.IPV6_ADDR_ANY;
 import static com.android.net.module.util.NetworkStackConstants.RFC7421_PREFIX_LENGTH;
-import static com.android.networkstack.apishim.ConstantsShim.IFA_F_MANAGETEMPADDR;
-import static com.android.networkstack.apishim.ConstantsShim.IFA_F_NOPREFIXROUTE;
-import static com.android.networkstack.util.NetworkStackUtils.createInet6AddressFromEui64;
-import static com.android.networkstack.util.NetworkStackUtils.macAddressToEui64;
 
 import android.content.Context;
-import android.net.IpPrefix;
-import android.net.LinkAddress;
 import android.net.ip.IpClient;
 import android.net.util.SocketUtils;
 import android.os.Handler;
@@ -58,12 +50,10 @@
 import com.android.net.module.util.DeviceConfigUtils;
 import com.android.net.module.util.InterfaceParams;
 import com.android.net.module.util.PacketReader;
-import com.android.net.module.util.netlink.NetlinkUtils;
 import com.android.net.module.util.structs.IaPrefixOption;
 
 import java.io.FileDescriptor;
 import java.io.IOException;
-import java.net.Inet6Address;
 import java.net.SocketException;
 import java.nio.ByteBuffer;
 import java.util.Collections;
@@ -95,8 +85,6 @@
     // Message.arg1 arguments to CMD_DHCP6_RESULT notification
     public static final int DHCP6_PD_SUCCESS = 1;
     public static final int DHCP6_PD_PREFIX_EXPIRED = 2;
-    public static final int DHCP6_PD_PREFIX_CHANGED = 3;
-    public static final int DHCP6_PD_PREFIX_MSG_EXCHANGE_TERMINATED = 4;
 
     // Notification from DHCPv6 state machine before quitting
     public static final int CMD_ON_QUIT = PUBLIC_BASE + 4;
@@ -436,8 +424,8 @@
         Log.d(TAG, "Scheduling IA_PD expiry in " + expirationTimeout + "s");
     }
 
-    private void notifyPrefixDelegation(int result, @Nullable final PrefixDelegation pd) {
-        mController.sendMessage(CMD_DHCP6_RESULT, result, 0, pd);
+    private void notifyPrefixDelegation(int result, @Nullable final List<IaPrefixOption> ipos) {
+        mController.sendMessage(CMD_DHCP6_RESULT, result, 0, ipos);
     }
 
     private void clearDhcp6State() {
@@ -557,10 +545,15 @@
             return sendSolicitPacket(transId, elapsedTimeMs, pd.build());
         }
 
-        // TODO: support multiple prefixes.
         @Override
         protected void receivePacket(Dhcp6Packet packet) {
             final PrefixDelegation pd = packet.mPrefixDelegation;
+            // Ignore any Advertise or Reply for Solicit(with Rapid Commit) with NoPrefixAvail
+            // status code, retransmit Solicit to see if any valid response from other Servers.
+            if (pd.statusCode == Dhcp6Packet.STATUS_NO_PREFIX_AVAIL) {
+                Log.w(TAG, "Server responded to Solicit without available prefix, ignoring");
+                return;
+            }
             if (packet instanceof Dhcp6AdvertisePacket) {
                 Log.d(TAG, "Get prefix delegation option from Advertise: " + pd);
                 mAdvertise = pd;
@@ -601,6 +594,11 @@
         protected void receivePacket(Dhcp6Packet packet) {
             if (!(packet instanceof Dhcp6ReplyPacket)) return;
             final PrefixDelegation pd = packet.mPrefixDelegation;
+            if (pd.statusCode == Dhcp6Packet.STATUS_NO_PREFIX_AVAIL) {
+                Log.w(TAG, "Server responded to Request without available prefix, restart Solicit");
+                transitionTo(mSolicitState);
+                return;
+            }
             Log.d(TAG, "Get prefix delegation option from Reply: " + pd);
             mReply = pd;
             mSolMaxRtMs = packet.getSolMaxRtMs().orElse(mSolMaxRtMs);
@@ -621,7 +619,7 @@
         public boolean processMessage(Message message) {
             switch (message.what) {
                 case CMD_DHCP6_PD_EXPIRE:
-                    notifyPrefixDelegation(DHCP6_PD_PREFIX_EXPIRED, null);
+                    notifyPrefixDelegation(DHCP6_PD_PREFIX_EXPIRED, mReply.getValidIaPrefixes());
                     transitionTo(mSolicitState);
                     return HANDLED;
                 default:
@@ -639,33 +637,6 @@
         }
     }
 
-    // Create an IPv6 address from the interface mac address with IFA_F_MANAGETEMPADDR
-    // flag, kernel will create another privacy IPv6 address on behalf of user space.
-    // We don't need to remember IPv6 addresses that need to extend the lifetime every
-    // time it enters BoundState.
-    private boolean addInterfaceAddress(@NonNull final Inet6Address address,
-            @NonNull final IaPrefixOption ipo) {
-        final int flags = IFA_F_NOPREFIXROUTE | IFA_F_MANAGETEMPADDR | IFA_F_NODAD;
-        final long now = SystemClock.elapsedRealtime();
-        final long deprecationTime = now + ipo.preferred;
-        final long expirationTime = now + ipo.valid;
-        final LinkAddress la = new LinkAddress(address, RFC7421_PREFIX_LENGTH, flags,
-                RT_SCOPE_UNIVERSE /* scope */, deprecationTime, expirationTime);
-        if (!la.isGlobalPreferred()) {
-            Log.e(TAG, la + " is not a global preferred IPv6 address");
-            return false;
-        }
-        if (!NetlinkUtils.sendRtmNewAddressRequest(mIface.index, address,
-                (short) RFC7421_PREFIX_LENGTH,
-                flags, (byte) RT_SCOPE_UNIVERSE /* scope */,
-                ipo.preferred, ipo.valid)) {
-            Log.e(TAG, "Failed to set IPv6 address " + address.getHostAddress()
-                    + "%" + mIface.index);
-            return false;
-        }
-        return true;
-    }
-
     /**
      * Client has already obtained the lease(e.g. IA_PD option) from server and stays in Bound
      * state until T1 expires, and then transition to Renew state to extend the lease duration.
@@ -675,26 +646,9 @@
         public void enter() {
             super.enter();
             scheduleLeaseTimers();
-
-            // TODO: roll back to SOLICIT state after a delay if something wrong happens
-            // instead of returning directly.
-            for (IaPrefixOption ipo : mReply.getValidIaPrefixes()) {
-                // TODO: The prefix with preferred/valid lifetime of 0 is valid, but client
-                // should stop using the prefix immediately. Actually kernel doesn't accept
-                // the address with valid lifetime of 0 and returns EINVAL when it sees that.
-                // We should send RTM_DELADDR netlink message to kernel to delete these addresses
-                // from the interface if any.
-                // Configure IPv6 addresses based on the delegated prefix(es) on the interface.
-                // We've checked that delegated prefix is valid upon receiving the response from
-                // DHCPv6 server, and the server may assign a prefix with length less than 64. So
-                // for SLAAC use case we always set the prefix length to 64 even if the delegated
-                // prefix length is less than 64.
-                final IpPrefix prefix = ipo.getIpPrefix();
-                final Inet6Address address = createInet6AddressFromEui64(prefix,
-                        macAddressToEui64(mIface.macAddr));
-                if (!addInterfaceAddress(address, ipo)) continue;
-            }
-            notifyPrefixDelegation(DHCP6_PD_SUCCESS, mReply);
+            // Pass valid delegated prefix(es) to IpClient for IPv6 address configuration and
+            // active prefix(es) maintenance.
+            notifyPrefixDelegation(DHCP6_PD_SUCCESS, mReply.getValidIaPrefixes());
         }
 
         @Override
@@ -723,8 +677,8 @@
      *   That forces previous delegated prefixes to expire in a natural way, and client should
      *   also stop trying to extend the lifetime for them. That being said, the global IPv6 address
      *   lifetime won't be updated in BoundState if corresponding prefix doesn't appear in Reply
-     *   message, resulting in these global IPv6 addresses eventually and IpClient obtains these
-     *   updates via netlink message and remove the delegated prefix(es) from LinkProperties.
+     *   message, resulting in these global IPv6 addresses expire eventually and IpClient obtains
+     *   these updates via netlink message and remove the delegated prefix(es) from LinkProperties.
      * - If some binding IA_PDs were absent in Reply message, client should still stay at RenewState
      *   or RebindState and retransmit Renew/Rebind messages to see if it can get all later. So far
      *   we only support one IA_PD option per interface, if the received Reply message doesn't take
@@ -744,9 +698,15 @@
         @Override
         protected void receivePacket(Dhcp6Packet packet) {
             if (!(packet instanceof Dhcp6ReplyPacket)) return;
+            final PrefixDelegation pd = packet.mPrefixDelegation;
+            // Stay at Renew/Rebind state if the Reply message takes NoPrefixAvail status code,
+            // retransmit Renew/Rebind message to server, to retry obtaining the prefixes.
+            if (pd.statusCode == Dhcp6Packet.STATUS_NO_PREFIX_AVAIL) {
+                Log.w(TAG, "Server responded to Renew/Rebind without available prefix, ignoring");
+                return;
+            }
             // TODO: send a Request message to the server that responded if any of the IA_PDs in
             // Reply message contain NoBinding status code.
-            final PrefixDelegation pd = packet.mPrefixDelegation;
             Log.d(TAG, "Get prefix delegation option from Reply as response to Renew/Rebind " + pd);
             if (pd.ipos.isEmpty()) return;
             mReply = pd;
diff --git a/src/android/net/dhcp6/Dhcp6Packet.java b/src/android/net/dhcp6/Dhcp6Packet.java
index 4ef3195..53dd274 100644
--- a/src/android/net/dhcp6/Dhcp6Packet.java
+++ b/src/android/net/dhcp6/Dhcp6Packet.java
@@ -32,7 +32,6 @@
 import java.nio.BufferUnderflowException;
 import java.nio.ByteBuffer;
 import java.nio.ByteOrder;
-import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
@@ -95,16 +94,16 @@
      * DHCPv6 Optional Type: Status Code.
      */
     public static final byte DHCP6_STATUS_CODE = 13;
+    private static final byte MIN_STATUS_CODE_OPT_LEN = 6;
     protected short mStatusCode;
-    protected String mStatusMsg;
 
     public static final short STATUS_SUCCESS           = 0;
     public static final short STATUS_UNSPEC_FAIL       = 1;
-    public static final short STATUS_NO_ADDR_AVAI      = 2;
+    public static final short STATUS_NO_ADDRS_AVAIL    = 2;
     public static final short STATUS_NO_BINDING        = 3;
-    public static final short STATUS_PREFIX_NOT_ONLINK = 4;
+    public static final short STATUS_NOT_ONLINK        = 4;
     public static final short STATUS_USE_MULTICAST     = 5;
-    public static final short STATUS_NO_PREFIX_AVAI    = 6;
+    public static final short STATUS_NO_PREFIX_AVAIL   = 6;
 
     /**
      * DHCPv6 zero-length Optional Type: Rapid Commit. Per RFC4039, both DHCPDISCOVER and DHCPACK
@@ -209,14 +208,22 @@
         public final int t2;
         @NonNull
         public final List<IaPrefixOption> ipos;
+        public final short statusCode;
 
+        @VisibleForTesting
         public PrefixDelegation(int iaid, int t1, int t2,
-                @NonNull final List<IaPrefixOption> ipos) {
+                @NonNull final List<IaPrefixOption> ipos, short statusCode) {
             Objects.requireNonNull(ipos);
             this.iaid = iaid;
             this.t1 = t1;
             this.t2 = t2;
             this.ipos = ipos;
+            this.statusCode = statusCode;
+        }
+
+        public PrefixDelegation(int iaid, int t1, int t2,
+                @NonNull final List<IaPrefixOption> ipos) {
+            this(iaid, t1, t2, ipos, STATUS_SUCCESS /* statusCode */);
         }
 
         /**
@@ -251,6 +258,7 @@
                 final int t1 = buffer.getInt();
                 final int t2 = buffer.getInt();
                 final List<IaPrefixOption> ipos = new ArrayList<IaPrefixOption>();
+                short statusCode = STATUS_SUCCESS;
                 while (buffer.remaining() > 0) {
                     final int original = buffer.position();
                     final short optionType = buffer.getShort();
@@ -262,12 +270,18 @@
                             Log.d(TAG, "IA Prefix Option: " + ipo);
                             ipos.add(ipo);
                             break;
-                        // TODO: support DHCP6_STATUS_CODE option
+                        case DHCP6_STATUS_CODE:
+                            statusCode = buffer.getShort();
+                            // Skip the status message if any.
+                            if (optionLen > 2) {
+                                skipOption(buffer, optionLen - 2);
+                            }
+                            break;
                         default:
                             skipOption(buffer, optionLen);
                     }
                 }
-                return new PrefixDelegation(iaid, t1, t2, ipos);
+                return new PrefixDelegation(iaid, t1, t2, ipos, statusCode);
             } catch (BufferUnderflowException e) {
                 throw new ParseException(e.getMessage());
             }
@@ -282,16 +296,26 @@
 
         /**
          * Build an IA_PD option from given specific parameters, including IA_PREFIX options.
+         *
+         * Per RFC8415 section 21.13 if the Status Code option does not appear in a message in
+         * which the option could appear, the status of the message is assumed to be Success. So
+         * only put the Status Code option in IA_PD when the status code is not Success.
          */
         public ByteBuffer build(@NonNull final List<IaPrefixOption> input) {
             final ByteBuffer iapd = ByteBuffer.allocate(IaPdOption.LENGTH
-                    + Struct.getSize(IaPrefixOption.class) * input.size());
+                    + Struct.getSize(IaPrefixOption.class) * input.size()
+                    + (statusCode != STATUS_SUCCESS ? MIN_STATUS_CODE_OPT_LEN : 0));
             iapd.putInt(iaid);
             iapd.putInt(t1);
             iapd.putInt(t2);
             for (IaPrefixOption ipo : input) {
                 ipo.writeToByteBuffer(iapd);
             }
+            if (statusCode != STATUS_SUCCESS) {
+                iapd.putShort(DHCP6_STATUS_CODE);
+                iapd.putShort((short) 2);
+                iapd.putShort(statusCode);
+            }
             iapd.flip();
             return iapd;
         }
@@ -314,8 +338,8 @@
 
         @Override
         public String toString() {
-            return "Prefix Delegation: iaid " + iaid + ", t1 " + t1 + ", t2 " + t2
-                    + ", IA prefix options: " + ipos;
+            return String.format("Prefix Delegation, iaid: %s, t1: %s, t2: %s, status code: %s,"
+                    + " IA prefix options: %s", iaid, t1, t2, statusCodeToString(statusCode), ipos);
         }
 
         /**
@@ -366,6 +390,27 @@
         }
     }
 
+    private static String statusCodeToString(short statusCode) {
+        switch (statusCode) {
+            case STATUS_SUCCESS:
+                return "Success";
+            case STATUS_UNSPEC_FAIL:
+                return "UnspecFail";
+            case STATUS_NO_ADDRS_AVAIL:
+                return "NoAddrsAvail";
+            case STATUS_NO_BINDING:
+                return "NoBinding";
+            case STATUS_NOT_ONLINK:
+                return "NotOnLink";
+            case STATUS_USE_MULTICAST:
+                return "UseMulticast";
+            case STATUS_NO_PREFIX_AVAIL:
+                return "NoPrefixAvail";
+            default:
+                return "Unknown";
+        }
+    }
+
     private static void skipOption(@NonNull final ByteBuffer packet, int optionLen)
             throws BufferUnderflowException {
         for (int i = 0; i < optionLen; i++) {
@@ -374,35 +419,6 @@
     }
 
     /**
-     * Reads a string of specified length from the buffer.
-     *
-     * TODO: move to a common place which can be shared with DhcpClient.
-     */
-    private static String readAsciiString(@NonNull final ByteBuffer buf, int byteCount,
-            boolean isNullOk) {
-        final byte[] bytes = new byte[byteCount];
-        buf.get(bytes);
-        return readAsciiString(bytes, isNullOk);
-    }
-
-    private static String readAsciiString(@NonNull final byte[] payload, boolean isNullOk) {
-        final byte[] bytes = payload;
-        int length = bytes.length;
-        if (!isNullOk) {
-            // Stop at the first null byte. This is because some DHCP options (e.g., the domain
-            // name) are passed to netd via FrameworkListener, which refuses arguments containing
-            // null bytes. We don't do this by default because vendorInfo is an opaque string which
-            // could in theory contain null bytes.
-            for (length = 0; length < bytes.length; length++) {
-                if (bytes[length] == 0) {
-                    break;
-                }
-            }
-        }
-        return new String(bytes, 0, length, StandardCharsets.US_ASCII);
-    }
-
-    /**
      * Creates a concrete Dhcp6Packet from the supplied ByteBuffer.
      *
      * The buffer only starts with a UDP encapsulation (i.e. DHCPv6 message). A subset of the
@@ -426,7 +442,6 @@
         byte[] serverDuid = null;
         byte[] clientDuid = null;
         short statusCode = STATUS_SUCCESS;
-        String statusMsg = null;
         boolean rapidCommit = false;
         int solMaxRt = 0;
         PrefixDelegation pd = null;
@@ -487,7 +502,12 @@
                     case DHCP6_STATUS_CODE:
                         expectedLen = optionLen;
                         statusCode = packet.getShort();
-                        statusMsg = readAsciiString(packet, expectedLen - 2, false /* isNullOk */);
+                        // Skip the status message (if any), which is a UTF-8 encoded text string
+                        // suitable for display to the end user, but is not useful for Dhcp6Client
+                        // to decide how to properly handle the status code.
+                        if (optionLen - 2 > 0) {
+                            skipOption(packet, optionLen - 2);
+                        }
                         break;
                     case DHCP6_SOL_MAX_RT:
                         expectedLen = 4;
@@ -545,7 +565,6 @@
             throw new ParseException("Missing IA_PD option");
         }
         newPacket.mStatusCode = statusCode;
-        newPacket.mStatusMsg = statusMsg;
         newPacket.mRapidCommit = rapidCommit;
         newPacket.mSolMaxRt =
                 (solMaxRt >= 60 && solMaxRt <= 86400)
diff --git a/src/android/net/ip/IpClient.java b/src/android/net/ip/IpClient.java
index 4f8eead..d7ef4df 100644
--- a/src/android/net/ip/IpClient.java
+++ b/src/android/net/ip/IpClient.java
@@ -19,7 +19,6 @@
 import static android.net.RouteInfo.RTN_UNICAST;
 import static android.net.RouteInfo.RTN_UNREACHABLE;
 import static android.net.dhcp.DhcpResultsParcelableUtil.toStableParcelable;
-import static android.net.dhcp6.Dhcp6Packet.PrefixDelegation;
 import static android.net.ip.IIpClient.PROV_IPV4_DISABLED;
 import static android.net.ip.IIpClient.PROV_IPV6_DISABLED;
 import static android.net.ip.IIpClient.PROV_IPV6_LINKLOCAL;
@@ -32,22 +31,28 @@
 import static android.system.OsConstants.AF_PACKET;
 import static android.system.OsConstants.ETH_P_ARP;
 import static android.system.OsConstants.ETH_P_IPV6;
+import static android.system.OsConstants.IFA_F_NODAD;
+import static android.system.OsConstants.RT_SCOPE_UNIVERSE;
 import static android.system.OsConstants.SOCK_NONBLOCK;
 import static android.system.OsConstants.SOCK_RAW;
 
+import static com.android.net.module.util.LinkPropertiesUtils.CompareResult;
 import static com.android.net.module.util.NetworkStackConstants.ARP_REPLY;
 import static com.android.net.module.util.NetworkStackConstants.ETHER_BROADCAST;
 import static com.android.net.module.util.NetworkStackConstants.IPV6_ADDR_ALL_ROUTERS_MULTICAST;
 import static com.android.net.module.util.NetworkStackConstants.RFC7421_PREFIX_LENGTH;
 import static com.android.net.module.util.NetworkStackConstants.VENDOR_SPECIFIC_IE_ID;
+import static com.android.networkstack.apishim.ConstantsShim.IFA_F_MANAGETEMPADDR;
+import static com.android.networkstack.apishim.ConstantsShim.IFA_F_NOPREFIXROUTE;
 import static com.android.networkstack.util.NetworkStackUtils.APF_HANDLE_LIGHT_DOZE_FORCE_DISABLE;
 import static com.android.networkstack.util.NetworkStackUtils.APF_NEW_RA_FILTER_VERSION;
-import static com.android.networkstack.util.NetworkStackUtils.APF_POLLING_COUNTERS_FORCE_DISABLE;
+import static com.android.networkstack.util.NetworkStackUtils.APF_POLLING_COUNTERS_VERSION;
 import static com.android.networkstack.util.NetworkStackUtils.IPCLIENT_DHCPV6_PREFIX_DELEGATION_VERSION;
 import static com.android.networkstack.util.NetworkStackUtils.IPCLIENT_GARP_NA_ROAMING_VERSION;
 import static com.android.networkstack.util.NetworkStackUtils.IPCLIENT_GRATUITOUS_NA_VERSION;
 import static com.android.networkstack.util.NetworkStackUtils.IPCLIENT_IGNORE_LOW_RA_LIFETIME_VERSION;
-import static com.android.networkstack.util.NetworkStackUtils.IPCLIENT_MULTICAST_NS_VERSION;
+import static com.android.networkstack.util.NetworkStackUtils.createInet6AddressFromEui64;
+import static com.android.networkstack.util.NetworkStackUtils.macAddressToEui64;
 import static com.android.server.util.PermissionUtil.enforceNetworkStackCallingPermission;
 
 import android.annotation.SuppressLint;
@@ -123,8 +128,10 @@
 import com.android.internal.util.WakeupMessage;
 import com.android.modules.utils.build.SdkLevel;
 import com.android.net.module.util.CollectionUtils;
+import com.android.net.module.util.ConnectivityUtils;
 import com.android.net.module.util.DeviceConfigUtils;
 import com.android.net.module.util.InterfaceParams;
+import com.android.net.module.util.LinkPropertiesUtils;
 import com.android.net.module.util.SharedLog;
 import com.android.net.module.util.SocketUtils;
 import com.android.net.module.util.arp.ArpPacket;
@@ -154,7 +161,6 @@
 import java.net.SocketAddress;
 import java.net.SocketException;
 import java.net.URL;
-import java.net.UnknownHostException;
 import java.nio.BufferUnderflowException;
 import java.nio.ByteBuffer;
 import java.util.ArrayList;
@@ -541,6 +547,8 @@
 
     // IpClient shares a handler with Dhcp6Client: commands must not overlap
     public static final int DHCP6CLIENT_CMD_BASE = 2000;
+    private static final int DHCPV6_PREFIX_DELEGATION_ADDRESS_FLAGS =
+            IFA_F_MANAGETEMPADDR | IFA_F_NOPREFIXROUTE | IFA_F_NODAD;
 
     // Settings and default values.
     private static final int MAX_LOG_RECORDS = 500;
@@ -677,6 +685,8 @@
     private final Set<Inet6Address> mGratuitousNaTargetAddresses = new HashSet<>();
     // Set of IPv6 addresses from which multicast NS packets have been sent.
     private final Set<Inet6Address> mMulticastNsSourceAddresses = new HashSet<>();
+    // Set of delegated prefixes.
+    private final Set<IpPrefix> mDelegatedPrefixes = new HashSet<>();
     @Nullable
     private final DevicePolicyManager mDevicePolicyManager;
 
@@ -694,7 +704,7 @@
     private final boolean mUseNewApfFilter;
     private final boolean mEnableIpClientIgnoreLowRaLifetime;
     private final boolean mApfShouldHandleLightDoze;
-    private final boolean mApfShouldPollingCounters;
+    private final boolean mEnableApfPollingCounters;
 
     private InterfaceParams mInterfaceParams;
 
@@ -721,7 +731,6 @@
     private Integer mDadTransmits = null;
     private int mMaxDtimMultiplier = DTIM_MULTIPLIER_RESET;
     private ApfCapabilities mCurrentApfCapabilities;
-    private PrefixDelegation mPrefixDelegation;
     private WakeupMessage mIpv6AutoconfTimeoutAlarm = null;
 
     /**
@@ -928,13 +937,13 @@
                 CONFIG_APF_COUNTER_POLLING_INTERVAL_SECS,
                 DEFAULT_APF_COUNTER_POLLING_INTERVAL_SECS) * DateUtils.SECOND_IN_MILLIS;
         mUseNewApfFilter = mDependencies.isFeatureEnabled(context, APF_NEW_RA_FILTER_VERSION);
+        mEnableApfPollingCounters = mDependencies.isFeatureEnabled(context,
+                APF_POLLING_COUNTERS_VERSION);
         mEnableIpClientIgnoreLowRaLifetime = mDependencies.isFeatureEnabled(context,
                 IPCLIENT_IGNORE_LOW_RA_LIFETIME_VERSION);
         // Light doze mode status checking API is only available at T or later releases.
         mApfShouldHandleLightDoze = SdkLevel.isAtLeastT() && mDependencies.isFeatureNotChickenedOut(
                 mContext, APF_HANDLE_LIGHT_DOZE_FORCE_DISABLE);
-        mApfShouldPollingCounters = mDependencies.isFeatureNotChickenedOut(
-                mContext, APF_POLLING_COUNTERS_FORCE_DISABLE);
 
         IpClientLinkObserver.Configuration config = new IpClientLinkObserver.Configuration(
                 mMinRdnssLifetimeSec);
@@ -1135,10 +1144,6 @@
         return mDependencies.isFeatureEnabled(mContext, IPCLIENT_GARP_NA_ROAMING_VERSION);
     }
 
-    private boolean isMulticastNsEnabled() {
-        return mDependencies.isFeatureNotChickenedOut(mContext, IPCLIENT_MULTICAST_NS_VERSION);
-    }
-
     @VisibleForTesting
     static MacAddress getInitialBssid(final Layer2Information layer2Info,
             final ScanResultInfo scanResultInfo, boolean isAtLeastS) {
@@ -1460,7 +1465,6 @@
         mDhcpResults = null;
         mTcpBufferSizes = "";
         mHttpProxy = null;
-        mPrefixDelegation = null;
 
         mLinkProperties = new LinkProperties();
         mLinkProperties.setInterfaceName(mInterfaceName);
@@ -1741,6 +1745,31 @@
         addAllReachableDnsServers(newLp, netlinkLinkProperties.getDnsServers());
         mShim.setNat64Prefix(newLp, mShim.getNat64Prefix(netlinkLinkProperties));
 
+        // Check if any link address update from netlink.
+        final CompareResult<LinkAddress> results =
+                LinkPropertiesUtils.compareAddresses(mLinkProperties, newLp);
+        // In the case that there are multiple netlink update events about a global IPv6 address
+        // derived from the delegated prefix, a flag-only change event(e.g. due to the duplicate
+        // address detection) will cause an identical IP address to be put into both Added and
+        // Removed list based on the CompareResult implementation. To prevent a prefix from being
+        // mistakenly removed from the delegate prefix list, it is better to always check the
+        // removed list before checking the added list(e.g. anyway we can add the removed prefix
+        // back again).
+        for (LinkAddress la : results.removed) {
+            if (mDhcp6PrefixDelegationEnabled && isIpv6StableDelegatedAddress(la)) {
+                final IpPrefix prefix = new IpPrefix(la.getAddress(), RFC7421_PREFIX_LENGTH);
+                mDelegatedPrefixes.remove(prefix);
+            }
+            // TODO: remove onIpv6AddressRemoved callback.
+        }
+
+        for (LinkAddress la : results.added) {
+            if (mDhcp6PrefixDelegationEnabled && isIpv6StableDelegatedAddress(la)) {
+                final IpPrefix prefix = new IpPrefix(la.getAddress(), RFC7421_PREFIX_LENGTH);
+                mDelegatedPrefixes.add(prefix);
+            }
+        }
+
         // [3] Add in data from DHCPv4, if available.
         //
         // mDhcpResults is never shared with any other owner so we don't have
@@ -1777,26 +1806,22 @@
             // TODO: also look at the IPv6 RA (netlink) for captive portal URL
         }
 
-        // [4] Add in data from DHCPv6 Prefix Delegation, if available.
-        if (mPrefixDelegation != null) {
-            for (IaPrefixOption ipo : mPrefixDelegation.ipos) {
-                try {
-                    final IpPrefix destination =
-                            new IpPrefix(Inet6Address.getByAddress(ipo.prefix), ipo.prefixLen);
-                    // Direct-connected route to delegated prefix. Add RTN_UNREACHABLE to this route
-                    // based on the delegated prefix. To prevent the traffic loop between host and
-                    // upstream delegated router. Because we specify the IFA_F_NOPREFIXROUTE when
-                    // adding the IPv6 address, the kernel does not create a delegated prefix route,
-                    // as a result, the user space won't receive any RTM_NEWROUTE message about the
-                    // delegated prefix, we still need to install an unreachable route for the
-                    // delegated prefix manually in LinkProperties to notify the caller this update.
-                    // TODO: support RTN_BLACKHOLE in netd and use that on newer Android versions.
-                    final RouteInfo route = new RouteInfo(destination, null /* gateway */,
-                            mInterfaceName, RTN_UNREACHABLE);
-                    newLp.addRoute(route);
-                } catch (UnknownHostException e) {
-                    Log.wtf(mTag, "Invalid delegated prefix " + HexDump.toHexString(ipo.prefix));
-                }
+        // [4] Add route with delegated prefix according to the global address update.
+        if (mDhcp6PrefixDelegationEnabled) {
+            for (IpPrefix destination : mDelegatedPrefixes) {
+                // Direct-connected route to delegated prefix. Add RTN_UNREACHABLE to
+                // this route based on the delegated prefix. To prevent the traffic loop
+                // between host and upstream delegated router. Because we specify the
+                // IFA_F_NOPREFIXROUTE when adding the IPv6 address, the kernel does not
+                // create a delegated prefix route, as a result, the user space won't
+                // receive any RTM_NEWROUTE message about the delegated prefix, we still
+                // need to install an unreachable route for the delegated prefix manually
+                // in LinkProperties to notify the caller this update.
+                // TODO: support RTN_BLACKHOLE in netd and use that on newer Android
+                // versions.
+                final RouteInfo route = new RouteInfo(destination,
+                        null /* gateway */, mInterfaceName, RTN_UNREACHABLE);
+                newLp.addRoute(route);
             }
         }
 
@@ -1994,6 +2019,24 @@
         }
     }
 
+    private static boolean hasFlag(@NonNull final LinkAddress la, final int flags) {
+        return (la.getFlags() & flags) == flags;
+
+    }
+
+    // Check whether a global IPv6 stable address is derived from DHCPv6 prefix delegation.
+    // Address derived from delegated prefix should be:
+    // - unicast global routable address
+    // - with prefix length of 64
+    // - has IFA_F_MANAGETEMPADDR, IFA_F_NOPREFIXROUTE and IFA_F_NODAD flags
+    private static boolean isIpv6StableDelegatedAddress(@NonNull final LinkAddress la) {
+        return la.isIpv6()
+                && !ConnectivityUtils.isIPv6ULA(la.getAddress())
+                && (la.getPrefixLength() == RFC7421_PREFIX_LENGTH)
+                && (la.getScope() == (byte) RT_SCOPE_UNIVERSE)
+                && hasFlag(la, DHCPV6_PREFIX_DELEGATION_ADDRESS_FLAGS);
+    }
+
     // Returns false if we have lost provisioning, true otherwise.
     private boolean handleLinkPropertiesUpdate(boolean sendCallbacks) {
         final LinkProperties newLp = assembleLinkProperties();
@@ -2035,9 +2078,7 @@
         //
         // TODO: stop sending this multicast NS after deployment of RFC9131 in the field, leverage
         // the gratuitous NA to update the first-hop router's neighbor cache entry.
-        if (isMulticastNsEnabled()) {
-            maybeSendMulticastNSes(newLp);
-        }
+        maybeSendMulticastNSes(newLp);
 
         // Either success IPv4 or IPv6 provisioning triggers new LinkProperties update,
         // wait for the provisioning completion and record the latency.
@@ -2441,6 +2482,7 @@
             mHasDisabledAcceptRaDefrtrOnProvLoss = false;
             mGratuitousNaTargetAddresses.clear();
             mMulticastNsSourceAddresses.clear();
+            mDelegatedPrefixes.clear();
 
             resetLinkProperties();
             if (mStartTimeMillis > 0) {
@@ -2903,7 +2945,7 @@
             if (mApfFilter == null) {
                 mCallback.setFallbackMulticastFilter(mMulticastFiltering);
             }
-            if (mApfShouldPollingCounters) {
+            if (mEnableApfPollingCounters) {
                 sendMessageDelayed(CMD_UPDATE_APF_DATA_SNAPSHOT, mApfCounterPollingIntervalMs);
             }
 
@@ -2998,30 +3040,71 @@
             }
         }
 
-        private void clearIpv6PrefixDelegationAddresses() {
-            if (mPrefixDelegation == null) {
-                Log.wtf(mTag, "PrefixDelegation shouldn't be null when DHCPv6 PD fails.");
-                return;
-            }
-            final IaPrefixOption ipo = mPrefixDelegation.ipos.get(0);
-            final IpPrefix prefix;
-            try {
-                prefix = new IpPrefix(Inet6Address.getByAddress(ipo.prefix), RFC7421_PREFIX_LENGTH);
-            } catch (UnknownHostException e) {
-                Log.wtf(TAG, "Invalid delegated prefix " + HexDump.toHexString(ipo.prefix));
-                return;
-            }
-
-            // Delete the global IPv6 address based on delegated prefix from interface.
+        private void deleteIpv6PrefixDelegationAddresses(final IpPrefix prefix) {
             for (LinkAddress la : mLinkProperties.getLinkAddresses()) {
                 final InetAddress address = la.getAddress();
                 if (prefix.contains(address)) {
-                    NetlinkUtils.sendRtmDelAddressRequest(mInterfaceParams.index,
-                            (Inet6Address) address, (short) la.getPrefixLength());
+                    if (!NetlinkUtils.sendRtmDelAddressRequest(mInterfaceParams.index,
+                            (Inet6Address) address, (short) la.getPrefixLength())) {
+                        Log.e(TAG, "Failed to delete IPv6 address " + address.getHostAddress());
+                    }
                 }
             }
         }
 
+        private void addInterfaceAddress(@NonNull final Inet6Address address,
+                @NonNull final IaPrefixOption ipo) {
+            final int flags = IFA_F_NOPREFIXROUTE | IFA_F_MANAGETEMPADDR | IFA_F_NODAD;
+            final long now = SystemClock.elapsedRealtime();
+            final long deprecationTime = now + ipo.preferred;
+            final long expirationTime = now + ipo.valid;
+            final LinkAddress la = new LinkAddress(address, RFC7421_PREFIX_LENGTH, flags,
+                    RT_SCOPE_UNIVERSE /* scope */, deprecationTime, expirationTime);
+            if (!la.isGlobalPreferred()) {
+                Log.w(TAG, la + " is not a global IPv6 address");
+                return;
+            }
+            if (!NetlinkUtils.sendRtmNewAddressRequest(mInterfaceParams.index, address,
+                    (short) RFC7421_PREFIX_LENGTH,
+                    flags, (byte) RT_SCOPE_UNIVERSE /* scope */,
+                    ipo.preferred, ipo.valid)) {
+                Log.e(TAG, "Failed to set IPv6 address on " + address.getHostAddress()
+                        + "%" + mInterfaceParams.index);
+            }
+        }
+
+        private void updateDelegatedAddresses(@NonNull final List<IaPrefixOption> valid) {
+            if (valid.isEmpty()) return;
+            for (IaPrefixOption ipo : valid) {
+                final IpPrefix prefix = ipo.getIpPrefix();
+                // The prefix with preferred/valid lifetime of 0 is considered as a valid prefix,
+                // it can be passed to IpClient from Dhcp6Client, however, client should stop using
+                // the global addresses derived from this prefix immediately.
+                if (ipo.withZeroLifetimes()) {
+                    Log.d(TAG, "Delete IPv6 address derived from prefix " + prefix
+                            + " with 0 preferred/valid lifetime");
+                    deleteIpv6PrefixDelegationAddresses(prefix);
+                }
+                // Otherwise, configure IPv6 addresses derived from the delegated prefix(es) on
+                // the interface. We've checked that delegated prefix is valid upon receiving the
+                // response from DHCPv6 server, and the server may assign a prefix with length less
+                // than 64. So for SLAAC use case we always set the prefix length to 64 even if the
+                // delegated prefix length is less than 64.
+                final Inet6Address address = createInet6AddressFromEui64(prefix,
+                        macAddressToEui64(mInterfaceParams.macAddr));
+                addInterfaceAddress(address, ipo);
+            }
+        }
+
+        private void removeExpiredDelegatedAddresses(@NonNull final List<IaPrefixOption> expired) {
+            if (expired.isEmpty()) return;
+            for (IaPrefixOption ipo : expired) {
+                final IpPrefix prefix = ipo.getIpPrefix();
+                Log.d(TAG, "Delete IPv6 address derived from expired prefix " + prefix);
+                deleteIpv6PrefixDelegationAddresses(prefix);
+            }
+        }
+
         @Override
         public boolean processMessage(Message msg) {
             switch (msg.what) {
@@ -3216,15 +3299,14 @@
                 case Dhcp6Client.CMD_DHCP6_RESULT:
                     switch(msg.arg1) {
                         case Dhcp6Client.DHCP6_PD_SUCCESS:
-                            mPrefixDelegation = (PrefixDelegation) msg.obj;
+                            final List<IaPrefixOption> toBeUpdated = (List<IaPrefixOption>) msg.obj;
+                            updateDelegatedAddresses(toBeUpdated);
                             handleLinkPropertiesUpdate(SEND_CALLBACKS);
                             break;
 
                         case Dhcp6Client.DHCP6_PD_PREFIX_EXPIRED:
-                        case Dhcp6Client.DHCP6_PD_PREFIX_CHANGED:
-                        case Dhcp6Client.DHCP6_PD_PREFIX_MSG_EXCHANGE_TERMINATED:
-                            clearIpv6PrefixDelegationAddresses();
-                            mPrefixDelegation = null;
+                            final List<IaPrefixOption> toBeRemoved = (List<IaPrefixOption>) msg.obj;
+                            removeExpiredDelegatedAddresses(toBeRemoved);
                             handleLinkPropertiesUpdate(SEND_CALLBACKS);
                             break;
 
diff --git a/src/android/net/ip/IpReachabilityMonitor.java b/src/android/net/ip/IpReachabilityMonitor.java
index 40a4bb6..e252a68 100644
--- a/src/android/net/ip/IpReachabilityMonitor.java
+++ b/src/android/net/ip/IpReachabilityMonitor.java
@@ -23,7 +23,6 @@
 
 import static com.android.networkstack.util.NetworkStackUtils.IP_REACHABILITY_IGNORE_INCOMPLETE_IPV6_DEFAULT_ROUTER_VERSION;
 import static com.android.networkstack.util.NetworkStackUtils.IP_REACHABILITY_IGNORE_INCOMPLETE_IPV6_DNS_SERVER_VERSION;
-import static com.android.networkstack.util.NetworkStackUtils.IP_REACHABILITY_MCAST_RESOLICIT_VERSION;
 
 import android.content.Context;
 import android.net.ConnectivityManager;
@@ -236,7 +235,6 @@
     private int mInterSolicitIntervalMs;
     @NonNull
     private final Callback mCallback;
-    private final boolean mMulticastResolicitEnabled;
     private final boolean mIgnoreIncompleteIpv6DnsServerEnabled;
     private final boolean mIgnoreIncompleteIpv6DefaultRouterEnabled;
 
@@ -260,8 +258,6 @@
         mUsingMultinetworkPolicyTracker = usingMultinetworkPolicyTracker;
         mCm = context.getSystemService(ConnectivityManager.class);
         mDependencies = dependencies;
-        mMulticastResolicitEnabled = dependencies.isFeatureNotChickenedOut(context,
-                IP_REACHABILITY_MCAST_RESOLICIT_VERSION);
         mIgnoreIncompleteIpv6DnsServerEnabled = dependencies.isFeatureNotChickenedOut(context,
                 IP_REACHABILITY_IGNORE_INCOMPLETE_IPV6_DNS_SERVER_VERSION);
         mIgnoreIncompleteIpv6DefaultRouterEnabled = dependencies.isFeatureEnabled(context,
@@ -274,10 +270,8 @@
         // In case the overylaid parameters specify an invalid configuration, set the parameters
         // to the hardcoded defaults first, then set them to the values used in the steady state.
         try {
-            int numResolicits = mMulticastResolicitEnabled
-                    ? NUD_MCAST_RESOLICIT_NUM
-                    : INVALID_NUD_MCAST_RESOLICIT_NUM;
-            setNeighborParameters(MIN_NUD_SOLICIT_NUM, MIN_NUD_SOLICIT_INTERVAL_MS, numResolicits);
+            setNeighborParameters(MIN_NUD_SOLICIT_NUM, MIN_NUD_SOLICIT_INTERVAL_MS,
+                    NUD_MCAST_RESOLICIT_NUM);
         } catch (Exception e) {
             Log.e(TAG, "Failed to adjust neighbor parameters with hardcoded defaults");
         }
@@ -414,8 +408,7 @@
 
     private void handleNeighborReachable(@Nullable final NeighborEvent prev,
             @NonNull final NeighborEvent event) {
-        if (mMulticastResolicitEnabled
-                && hasDefaultRouterNeighborMacAddressChanged(prev, event)) {
+        if (hasDefaultRouterNeighborMacAddressChanged(prev, event)) {
             // This implies device has confirmed the neighbor's reachability from
             // other states(e.g., NUD_PROBE or NUD_STALE), checking if the mac
             // address hasn't changed is required. If Mac address does change, then
@@ -581,8 +574,7 @@
 
     private long getProbeWakeLockDuration() {
         final long gracePeriodMs = 500;
-        final int numSolicits =
-                mNumSolicits + (mMulticastResolicitEnabled ? NUD_MCAST_RESOLICIT_NUM : 0);
+        final int numSolicits = mNumSolicits + NUD_MCAST_RESOLICIT_NUM;
         return (long) (numSolicits * mInterSolicitIntervalMs) + gracePeriodMs;
     }
 
diff --git a/src/com/android/networkstack/util/NetworkStackUtils.java b/src/com/android/networkstack/util/NetworkStackUtils.java
index 85da400..829d0c6 100755
--- a/src/com/android/networkstack/util/NetworkStackUtils.java
+++ b/src/com/android/networkstack/util/NetworkStackUtils.java
@@ -197,13 +197,6 @@
     public static final String IPCLIENT_GRATUITOUS_NA_VERSION = "ipclient_gratuitous_na_version";
 
     /**
-     * Experiment flag to send multicast NS from the global IPv6 GUA to the solicited-node
-     * multicast address based on the default router's IPv6 link-local address, which helps
-     * flush the first-hop routers' neighbor cache entry for the global IPv6 GUA.
-     */
-    public static final String IPCLIENT_MULTICAST_NS_VERSION = "ipclient_multicast_ns_version";
-
-    /**
      * Experiment flag to enable sending Gratuitous APR and Gratuitous Neighbor Advertisement for
      * all assigned IPv4 and IPv6 GUAs after completing L2 roaming.
      */
@@ -219,13 +212,6 @@
             "ipclient_accept_ipv6_link_local_dns_version";
 
     /**
-     * Experiment flag to enable "mcast_resolicit" neighbor parameter in IpReachabilityMonitor,
-     * set it to 3 by default.
-     */
-    public static final String IP_REACHABILITY_MCAST_RESOLICIT_VERSION =
-            "ip_reachability_mcast_resolicit_version";
-
-    /**
      * Experiment flag to attempt to ignore the on-link IPv6 DNS server which fails to respond to
      * address resolution.
      */
@@ -250,6 +236,10 @@
      */
     public static final String APF_NEW_RA_FILTER_VERSION = "apf_new_ra_filter_version";
     /**
+     * Experiment flag to enable the feature of polling counters in Apf.
+     */
+    public static final String APF_POLLING_COUNTERS_VERSION = "apf_polling_counters_version";
+    /**
      * Experiment flag to enable the feature of ignoring any individual RA section with lifetime
      * below accept_ra_min_lft sysctl.
      */
@@ -272,12 +262,6 @@
             "apf_handle_light_doze_force_disable";
 
     /**
-     * Kill switch flag to disable the feature of polling counters in Apf.
-     */
-    public static final String APF_POLLING_COUNTERS_FORCE_DISABLE =
-            "apf_polling_counters_force_disable";
-
-    /**
      * Kill switch flag to disable the feature of skipping Tcp socket info polling when light
      * doze mode is enabled.
      */
diff --git a/tests/integration/Android.bp b/tests/integration/Android.bp
index 8300c73..ec058d7 100644
--- a/tests/integration/Android.bp
+++ b/tests/integration/Android.bp
@@ -68,6 +68,9 @@
     static_libs: [
         "NetworkStackApiStableLib",
     ],
+    lint: {
+        baseline_filename: "lint-baseline.xml",
+    },
 }
 
 // Network stack integration tests.
@@ -84,6 +87,9 @@
     jarjar_rules: ":NetworkStackJarJarRules",
     host_required: ["net-tests-utils-host-common"],
     test_config_template: "AndroidTestTemplate_Integration.xml",
+    lint: {
+        baseline_filename: "lint-baseline.xml",
+    },
 }
 
 // Network stack next integration tests.
@@ -107,6 +113,9 @@
     jarjar_rules: ":NetworkStackJarJarRules",
     host_required: ["net-tests-utils-host-common"],
     test_config_template: "AndroidTestTemplate_Integration.xml",
+    lint: {
+        baseline_filename: "lint-baseline.xml",
+    },
 }
 
 // Network stack integration root tests.
@@ -124,12 +133,18 @@
         "NetworkStackApiStableLib",
     ],
     platform_apis: true,
-    test_suites: ["general-tests", "mts-networking"],
+    test_suites: [
+        "general-tests",
+        "mts-networking",
+    ],
     compile_multilib: "both",
     manifest: "AndroidManifest_root.xml",
     jarjar_rules: ":NetworkStackJarJarRules",
     host_required: ["net-tests-utils-host-common"],
     test_config_template: "AndroidTestTemplate_Integration.xml",
+    lint: {
+        baseline_filename: "lint-baseline.xml",
+    },
 }
 
 // Special version of the network stack tests that includes all tests necessary for code coverage
@@ -138,7 +153,10 @@
     name: "NetworkStackCoverageTests",
     certificate: "networkstack",
     platform_apis: true,
-    test_suites: ["device-tests", "mts-networking"],
+    test_suites: [
+        "device-tests",
+        "mts-networking",
+    ],
     test_config: "AndroidTest_Coverage.xml",
     defaults: [
         "NetworkStackReleaseTargetSdk",
@@ -154,4 +172,7 @@
     compile_multilib: "both",
     manifest: "AndroidManifest_coverage.xml",
     jarjar_rules: ":NetworkStackJarJarRules",
+    lint: {
+        baseline_filename: "lint-baseline.xml",
+    },
 }
diff --git a/tests/integration/common/android/net/ip/IpClientIntegrationTestCommon.java b/tests/integration/common/android/net/ip/IpClientIntegrationTestCommon.java
index c733f9c..2f1f7d1 100644
--- a/tests/integration/common/android/net/ip/IpClientIntegrationTestCommon.java
+++ b/tests/integration/common/android/net/ip/IpClientIntegrationTestCommon.java
@@ -312,6 +312,7 @@
 
     protected static final long TEST_TIMEOUT_MS = 2_000L;
     private static final long TEST_WAIT_ENOBUFS_TIMEOUT_MS = 30_000L;
+    private static final long TEST_WAIT_RENEW_REBIND_RETRANSMIT_MS = 15_000L;
 
     @Rule
     public final DevSdkIgnoreRule mIgnoreRule = new DevSdkIgnoreRule();
@@ -779,6 +780,8 @@
             enableRealAlarm("DhcpClient." + mIfaceName + ".KICK");
             // Enable alarm for IPv6 autoconf via SLAAC in IpClient.
             enableRealAlarm("IpClient." + mIfaceName + ".EVENT_IPV6_AUTOCONF_TIMEOUT");
+            // Enable packet retransmit alarm in Dhcp6Client.
+            enableRealAlarm("Dhcp6Client." + mIfaceName + ".KICK");
         }
 
         mIIpClient = makeIIpClient(mIfaceName, mCb);
@@ -4137,12 +4140,6 @@
         return ns;
     }
 
-    // Override this function with disabled experiment flag by default, in order not to
-    // affect those tests which are just related to basic IpReachabilityMonitor infra.
-    private void prepareIpReachabilityMonitorTest() throws Exception {
-        prepareIpReachabilityMonitorTest(false /* isMulticastResolicitEnabled */);
-    }
-
     private void assertNotifyNeighborLost(Inet6Address targetIp, NudEventType eventType)
             throws Exception {
         // For root test suite, rely on the IIpClient aidl interface version constant defined in
@@ -4175,8 +4172,7 @@
         verify(mCb, never()).onReachabilityLost(any());
     }
 
-    private void prepareIpReachabilityMonitorTest(boolean isMulticastResolicitEnabled)
-            throws Exception {
+    private void prepareIpReachabilityMonitorTest() throws Exception {
         final ScanResultInfo info = makeScanResultInfo(TEST_DEFAULT_SSID, TEST_DEFAULT_BSSID);
         ProvisioningConfiguration config = new ProvisioningConfiguration.Builder()
                 .withLayer2Information(new Layer2Information(TEST_L2KEY, TEST_CLUSTER,
@@ -4185,8 +4181,6 @@
                 .withDisplayName(TEST_DEFAULT_SSID)
                 .withoutIPv4()
                 .build();
-        setFeatureEnabled(NetworkStackUtils.IP_REACHABILITY_MCAST_RESOLICIT_VERSION,
-                isMulticastResolicitEnabled);
         startIpClientProvisioning(config);
         verify(mCb, timeout(TEST_TIMEOUT_MS)).setFallbackMulticastFilter(true);
         doIpv6OnlyProvisioning();
@@ -4200,11 +4194,15 @@
 
         final List<NeighborSolicitation> nsList = waitForMultipleNeighborSolicitations();
         final int expectedNudSolicitNum = readNudSolicitNumPostRoamingFromResource();
-        assertEquals(expectedNudSolicitNum, nsList.size());
-        for (NeighborSolicitation ns : nsList) {
+        int expectedSize = expectedNudSolicitNum + NUD_MCAST_RESOLICIT_NUM;
+        assertEquals(expectedSize, nsList.size());
+        for (NeighborSolicitation ns : nsList.subList(0, expectedNudSolicitNum)) {
             assertUnicastNeighborSolicitation(ns, ROUTER_MAC /* dstMac */,
                     ROUTER_LINK_LOCAL /* dstIp */, ROUTER_LINK_LOCAL /* targetIp */);
         }
+        for (NeighborSolicitation ns : nsList.subList(expectedNudSolicitNum, nsList.size())) {
+            assertMulticastNeighborSolicitation(ns, ROUTER_LINK_LOCAL /* targetIp */);
+        }
     }
 
     @Test
@@ -4239,43 +4237,10 @@
         assertNeverNotifyNeighborLost();
     }
 
-    private void runIpReachabilityMonitorMcastResolicitProbeFailedTest() throws Exception {
-        prepareIpReachabilityMonitorTest(true /* isMulticastResolicitEnabled */);
-
-        final List<NeighborSolicitation> nsList = waitForMultipleNeighborSolicitations();
-        final int expectedNudSolicitNum = readNudSolicitNumPostRoamingFromResource();
-        int expectedSize = expectedNudSolicitNum + NUD_MCAST_RESOLICIT_NUM;
-        assertEquals(expectedSize, nsList.size());
-        for (NeighborSolicitation ns : nsList.subList(0, expectedNudSolicitNum)) {
-            assertUnicastNeighborSolicitation(ns, ROUTER_MAC /* dstMac */,
-                    ROUTER_LINK_LOCAL /* dstIp */, ROUTER_LINK_LOCAL /* targetIp */);
-        }
-        for (NeighborSolicitation ns : nsList.subList(expectedNudSolicitNum, nsList.size())) {
-            assertMulticastNeighborSolicitation(ns, ROUTER_LINK_LOCAL /* targetIp */);
-        }
-    }
-
-    @Test
-    public void testIpReachabilityMonitor_mcastResolicitProbeFailed() throws Exception {
-        runIpReachabilityMonitorMcastResolicitProbeFailedTest();
-        assertNotifyNeighborLost(ROUTER_LINK_LOCAL /* targetIp */,
-                NudEventType.NUD_POST_ROAMING_FAILED_CRITICAL);
-    }
-
-    @Test @SignatureRequiredTest(reason = "requires mock callback object")
-    public void testIpReachabilityMonitor_mcastResolicitProbeFailed_legacyCallback()
-            throws Exception {
-        when(mCb.getInterfaceVersion()).thenReturn(12 /* assign an older interface aidl version */);
-
-        runIpReachabilityMonitorMcastResolicitProbeFailedTest();
-        verify(mCb, timeout(TEST_TIMEOUT_MS)).onReachabilityLost(any());
-        verify(mCb, never()).onReachabilityFailure(any());
-    }
-
     @Test
     public void testIpReachabilityMonitor_mcastResolicitProbeReachableWithSameLinkLayerAddress()
             throws Exception {
-        prepareIpReachabilityMonitorTest(true /* isMulticastResolicitEnabled */);
+        prepareIpReachabilityMonitorTest();
 
         final NeighborSolicitation ns = waitForUnicastNeighborSolicitation(ROUTER_MAC /* dstMac */,
                 ROUTER_LINK_LOCAL /* dstIp */, ROUTER_LINK_LOCAL /* targetIp */);
@@ -4292,7 +4257,7 @@
     @Test
     public void testIpReachabilityMonitor_mcastResolicitProbeReachableWithDiffLinkLayerAddress()
             throws Exception {
-        prepareIpReachabilityMonitorTest(true /* isMulticastResolicitEnabled */);
+        prepareIpReachabilityMonitorTest();
 
         final NeighborSolicitation ns = waitForUnicastNeighborSolicitation(ROUTER_MAC /* dstMac */,
                 ROUTER_LINK_LOCAL /* dstIp */, ROUTER_LINK_LOCAL /* targetIp */);
@@ -4664,9 +4629,6 @@
                 .withoutIPv4()
                 .build();
 
-        setFeatureEnabled(NetworkStackUtils.IPCLIENT_MULTICAST_NS_VERSION,
-                true /* isUnsolicitedNsEnabled */);
-        assertTrue(isFeatureEnabled(NetworkStackUtils.IPCLIENT_MULTICAST_NS_VERSION));
         startIpClientProvisioning(config);
 
         doIpv6OnlyProvisioning();
@@ -5216,9 +5178,7 @@
                 x -> x.isIpv6Provisioned()
                         && hasIpv6AddressPrefixedWith(x, prefix)
                         && hasIpv6AddressPrefixedWith(x, prefix1)
-                        // TODO: comment this line to make the test passed, remove the comment later
-                        // once IpClient supports multi-prefixes.
-                        // && hasRouteTo(x, "2001:db8:1::/64", RTN_UNREACHABLE)
+                        && hasRouteTo(x, "2001:db8:1::/64", RTN_UNREACHABLE)
                         && hasRouteTo(x, "2001:db8:2::/64", RTN_UNREACHABLE)
                         // IPv6 link-local, four global delegated IPv6 addresses
                         && x.getLinkAddresses().size() == 5
@@ -5280,14 +5240,15 @@
         mPacketReader.sendResponse(buildDhcp6Reply(packet, iapd.array(), mClientMac,
                 (Inet6Address) mClientIpAddress, false /* rapidCommit */));
         verify(mCb, never()).onProvisioningFailure(any());
+        // IPv6 addresses derived from prefix with 0 preferred/valid lifetime should be deleted.
         verify(mCb, timeout(TEST_TIMEOUT_MS)).onLinkPropertiesChange(argThat(
                 x -> x.isIpv6Provisioned()
-                        && hasIpv6AddressPrefixedWith(x, prefix)
+                        && !hasIpv6AddressPrefixedWith(x, prefix)
                         && hasIpv6AddressPrefixedWith(x, prefix1)
-                        && hasRouteTo(x, "2001:db8:1::/64", RTN_UNREACHABLE)
+                        && !hasRouteTo(x, "2001:db8:1::/64", RTN_UNREACHABLE)
                         && hasRouteTo(x, "2001:db8:2::/64", RTN_UNREACHABLE)
-                        // IPv6 link-local, four global delegated IPv6 addresses
-                        && x.getLinkAddresses().size() == 5
+                        // IPv6 link-local, two global delegated IPv6 addresses with prefix1
+                        && x.getLinkAddresses().size() == 3
         ));
 
         handler.post(() -> renewAlarm.onAlarm());
@@ -5296,7 +5257,8 @@
         packet = getNextDhcp6Packet();
         assertTrue(packet instanceof Dhcp6RenewPacket);
         final List<IaPrefixOption> renewIpos = packet.getPrefixDelegation().ipos;
-        assertEquals(1, renewIpos.size()); // don't renew prefix 2001:db8:1::/64
+        assertEquals(1, renewIpos.size()); // don't renew prefix 2001:db8:1::/64 with 0
+                                           // preferred/valid lifetime
         assertEquals(prefix1, renewIpos.get(0).getIpPrefix());
     }
 
@@ -5317,7 +5279,7 @@
 
         clearInvocations(mCb);
 
-        // Reply with the requested prefix with preferred/valid lifetime of 0.
+        // Reply with the requested prefix with the same t1/t2/lifetime.
         final IpPrefix prefix = new IpPrefix("2001:db8:1::/64");
         final IaPrefixOption ipo = buildIaPrefixOption(prefix, 3600 /* preferred */,
                 3600 /* valid */);
@@ -5339,6 +5301,220 @@
     }
 
     @Test
+    public void testDhcp6Pd_multipleIaPrefixOptions() throws Exception {
+        final InOrder inOrder = inOrder(mCb);
+        final IpPrefix prefix1 = new IpPrefix("2001:db8:1::/64");
+        final IpPrefix prefix2 = new IpPrefix("2400:db8:100::/64");
+        final IpPrefix prefix3 = new IpPrefix("fd7c:9df8:7f39:dc89::/64");
+        final IaPrefixOption ipo1 = buildIaPrefixOption(prefix1, 4500 /* preferred */,
+                7200 /* valid */);
+        final IaPrefixOption ipo2 = buildIaPrefixOption(prefix2, 5600 /* preferred */,
+                6000 /* valid */);
+        final IaPrefixOption ipo3 = buildIaPrefixOption(prefix3, 7200 /* preferred */,
+                14400 /* valid */);
+        prepareDhcp6PdTest();
+        handleDhcp6Packets(Arrays.asList(ipo1, ipo2, ipo3), 3600 /* t1 */, 4500 /* t2 */,
+                true /* shouldReplyRapidCommit */);
+
+        final ArgumentCaptor<LinkProperties> captor = ArgumentCaptor.forClass(LinkProperties.class);
+        verifyWithTimeout(inOrder, mCb).onProvisioningSuccess(captor.capture());
+        LinkProperties lp = captor.getValue();
+
+        // Sometimes privacy address or route may appear later along with onLinkPropertiesChange
+        // callback, in this case we wait a bit longer to see all of these properties appeared and
+        // then verify if they are what we are looking for.
+        if (lp.getLinkAddresses().size() < 5 || lp.getRoutes().size() < 4) {
+            final CompletableFuture<LinkProperties> lpFuture = new CompletableFuture<>();
+            verifyWithTimeout(inOrder, mCb).onLinkPropertiesChange(argThat(x -> {
+                if (!x.isIpv6Provisioned()) return false;
+                if (x.getLinkAddresses().size() != 5) return false;
+                if (x.getRoutes().size() != 4) return false;
+                lpFuture.complete(x);
+                return true;
+            }));
+            lp = lpFuture.get(TEST_TIMEOUT_MS, TimeUnit.MILLISECONDS);
+        }
+        assertNotNull(lp);
+        assertTrue(hasIpv6AddressPrefixedWith(lp, prefix1));
+        assertTrue(hasIpv6AddressPrefixedWith(lp, prefix2));
+        assertFalse(hasIpv6AddressPrefixedWith(lp, prefix3));
+        assertTrue(hasRouteTo(lp, prefix1.toString(), RTN_UNREACHABLE));
+        assertTrue(hasRouteTo(lp, prefix2.toString(), RTN_UNREACHABLE));
+        assertFalse(hasRouteTo(lp, prefix3.toString(), RTN_UNREACHABLE));
+    }
+
+    private void runDhcp6PacketWithNoPrefixAvailStatusCodeTest(boolean shouldReplyWithAdvertise)
+            throws Exception {
+        prepareDhcp6PdTest();
+        Dhcp6Packet packet = getNextDhcp6Packet(PACKET_TIMEOUT_MS);
+        assertTrue(packet instanceof Dhcp6SolicitPacket);
+
+        final PrefixDelegation pd = new PrefixDelegation(packet.getIaId(), 0 /* t1 */, 0 /* t2 */,
+                new ArrayList<IaPrefixOption>() /* ipos */, Dhcp6Packet.STATUS_NO_PREFIX_AVAIL);
+        final ByteBuffer iapd = pd.build();
+        if (shouldReplyWithAdvertise) {
+            mPacketReader.sendResponse(buildDhcp6Advertise(packet, iapd.array(), mClientMac,
+                    (Inet6Address) mClientIpAddress));
+        } else {
+            mPacketReader.sendResponse(buildDhcp6Reply(packet, iapd.array(), mClientMac,
+                    (Inet6Address) mClientIpAddress, true /* rapidCommit */));
+        }
+
+        // Check if client will ignore Advertise or Reply for Rapid Commit Solicit and
+        // retransmit Solicit.
+        packet = getNextDhcp6Packet(PACKET_TIMEOUT_MS);
+        assertTrue(packet instanceof Dhcp6SolicitPacket);
+    }
+
+    @Test
+    public void testDhcp6AdvertiseWithNoPrefixAvailStatusCode() throws Exception {
+        // Advertise
+        runDhcp6PacketWithNoPrefixAvailStatusCodeTest(true /* shouldReplyWithAdvertise */);
+    }
+
+    @Test
+    public void testDhcp6ReplyForRapidCommitSolicitWithNoPrefixAvailStatusCode() throws Exception {
+        // Reply
+        runDhcp6PacketWithNoPrefixAvailStatusCodeTest(false /* shouldReplyWithAdvertise */);
+    }
+
+    @Test
+    public void testDhcp6ReplyForRequestWithNoPrefixAvailStatusCode() throws Exception {
+        prepareDhcp6PdTest();
+        Dhcp6Packet packet = getNextDhcp6Packet(PACKET_TIMEOUT_MS);
+        assertTrue(packet instanceof Dhcp6SolicitPacket);
+
+        final IpPrefix prefix = new IpPrefix("2001:db8:1::/64");
+        final IaPrefixOption ipo = buildIaPrefixOption(prefix, 4500 /* preferred */,
+                7200 /* valid */);
+        PrefixDelegation pd = new PrefixDelegation(packet.getIaId(), 1000 /* t1 */,
+                2000 /* t2 */, Arrays.asList(ipo));
+        ByteBuffer iapd = pd.build();
+        mPacketReader.sendResponse(buildDhcp6Advertise(packet, iapd.array(), mClientMac,
+                (Inet6Address) mClientIpAddress));
+
+        packet = getNextDhcp6Packet(PACKET_TIMEOUT_MS);
+        assertTrue(packet instanceof Dhcp6RequestPacket);
+
+        // Reply for Request with NoPrefixAvail status code. Not sure if this is reasonable in
+        // practice, but Server can do everything it wants.
+        pd = new PrefixDelegation(packet.getIaId(), 0 /* t1 */, 0 /* t2 */,
+                new ArrayList<IaPrefixOption>() /* ipos */, Dhcp6Packet.STATUS_NO_PREFIX_AVAIL);
+        iapd = pd.build();
+        mPacketReader.sendResponse(buildDhcp6Reply(packet, iapd.array(), mClientMac,
+                (Inet6Address) mClientIpAddress, false /* rapidCommit */));
+
+        // Check if client will ignore Reply for Request with NoPrefixAvail status code, and
+        // rollback to SolicitState.
+        packet = getNextDhcp6Packet(PACKET_TIMEOUT_MS);
+        assertTrue(packet instanceof Dhcp6SolicitPacket);
+    }
+
+    @Test
+    @SignatureRequiredTest(reason = "Need to mock the DHCP6 renew/rebind alarms")
+    public void testDhcp6ReplyForRenewWithNoPrefixAvailStatusCode() throws Exception {
+        prepareDhcp6PdRenewTest();
+
+        final InOrder inOrder = inOrder(mAlarm);
+        final Handler handler = mDependencies.mDhcp6Client.getHandler();
+        final OnAlarmListener renewAlarm = expectAlarmSet(inOrder, "RENEW", 3600, handler);
+
+        handler.post(() -> renewAlarm.onAlarm());
+        HandlerUtils.waitForIdle(handler, TEST_TIMEOUT_MS);
+
+        Dhcp6Packet packet = getNextDhcp6Packet();
+        assertTrue(packet instanceof Dhcp6RenewPacket);
+
+        // Reply with normal IA_PD.
+        final IpPrefix prefix = new IpPrefix("2001:db8:1::/64");
+        final IaPrefixOption ipo = buildIaPrefixOption(prefix, 4500 /* preferred */,
+                7200 /* valid */);
+        PrefixDelegation pd = new PrefixDelegation(packet.getIaId(), 1000 /* t1 */,
+                2000 /* t2 */, Arrays.asList(ipo));
+        ByteBuffer iapd = pd.build();
+        mPacketReader.sendResponse(buildDhcp6Reply(packet, iapd.array(), mClientMac,
+                (Inet6Address) mClientIpAddress, false /* rapidCommit */));
+        HandlerUtils.waitForIdle(handler, TEST_TIMEOUT_MS);
+
+        // Trigger another Renew message.
+        handler.post(() -> renewAlarm.onAlarm());
+        HandlerUtils.waitForIdle(handler, TEST_TIMEOUT_MS);
+
+        packet = getNextDhcp6Packet();
+        assertTrue(packet instanceof Dhcp6RenewPacket);
+
+        // Reply for Renew with NoPrefixAvail status code, check if client will retransmit the
+        // Renew message.
+        pd = new PrefixDelegation(packet.getIaId(), 3600 /* t1 */, 4500 /* t2 */,
+                new ArrayList<IaPrefixOption>(0) /* ipos */, Dhcp6Packet.STATUS_NO_PREFIX_AVAIL);
+        iapd = pd.build();
+        mPacketReader.sendResponse(buildDhcp6Reply(packet, iapd.array(), mClientMac,
+                (Inet6Address) mClientIpAddress, false /* rapidCommit */));
+
+        packet = getNextDhcp6Packet(TEST_WAIT_RENEW_REBIND_RETRANSMIT_MS);
+        assertTrue(packet instanceof Dhcp6RenewPacket);
+    }
+
+    @Test
+    @SignatureRequiredTest(reason = "Need to mock the DHCP6 renew/rebind alarms")
+    public void testDhcp6ReplyForRebindWithNoPrefixAvailStatusCode() throws Exception {
+        prepareDhcp6PdRenewTest();
+
+        final InOrder inOrder = inOrder(mAlarm);
+        final Handler handler = mDependencies.mDhcp6Client.getHandler();
+        final OnAlarmListener renewAlarm = expectAlarmSet(inOrder, "RENEW", 3600, handler);
+        final OnAlarmListener rebindAlarm = expectAlarmSet(inOrder, "REBIND", 4500, handler);
+
+        handler.post(() -> renewAlarm.onAlarm());
+        HandlerUtils.waitForIdle(handler, TEST_TIMEOUT_MS);
+
+        Dhcp6Packet packet = getNextDhcp6Packet();
+        assertTrue(packet instanceof Dhcp6RenewPacket);
+
+        handler.post(() -> rebindAlarm.onAlarm());
+        HandlerUtils.waitForIdle(handler, TEST_TIMEOUT_MS);
+
+        packet = getNextDhcp6Packet();
+        assertTrue(packet instanceof Dhcp6RebindPacket);
+
+        // Reply with normal IA_PD.
+        final IpPrefix prefix = new IpPrefix("2001:db8:1::/64");
+        final IaPrefixOption ipo = buildIaPrefixOption(prefix, 4500 /* preferred */,
+                7200 /* valid */);
+        PrefixDelegation pd = new PrefixDelegation(packet.getIaId(), 1000 /* t1 */,
+                2000 /* t2 */, Arrays.asList(ipo));
+        ByteBuffer iapd = pd.build();
+        mPacketReader.sendResponse(buildDhcp6Reply(packet, iapd.array(), mClientMac,
+                (Inet6Address) mClientIpAddress, false /* rapidCommit */));
+        HandlerUtils.waitForIdle(handler, TEST_TIMEOUT_MS);
+
+        // Trigger another Rebind message.
+        handler.post(() -> renewAlarm.onAlarm());
+        HandlerUtils.waitForIdle(handler, TEST_TIMEOUT_MS);
+
+        packet = getNextDhcp6Packet();
+        assertTrue(packet instanceof Dhcp6RenewPacket);
+
+        handler.post(() -> rebindAlarm.onAlarm());
+        HandlerUtils.waitForIdle(handler, TEST_TIMEOUT_MS);
+
+        packet = getNextDhcp6Packet();
+        assertTrue(packet instanceof Dhcp6RebindPacket);
+
+        // Reply for Rebind with NoPrefixAvail status code, check if client will retransmit the
+        // Rebind message.
+        pd = new PrefixDelegation(packet.getIaId(), 3600 /* t1 */,
+                4500 /* t2 */, new ArrayList<IaPrefixOption>(0) /* ipos */,
+                Dhcp6Packet.STATUS_NO_PREFIX_AVAIL);
+        iapd = pd.build();
+        mPacketReader.sendResponse(buildDhcp6Reply(packet, iapd.array(), mClientMac,
+                (Inet6Address) mClientIpAddress, false /* rapidCommit */));
+
+        packet = getNextDhcp6Packet(TEST_WAIT_RENEW_REBIND_RETRANSMIT_MS);
+        assertTrue(packet instanceof Dhcp6RebindPacket);
+    }
+
+    @Test
     @SignatureRequiredTest(reason = "InterfaceParams.getByName requires CAP_NET_ADMIN")
     public void testSendRtmDelAddressMethod() throws Exception {
         ProvisioningConfiguration config = new ProvisioningConfiguration.Builder()
diff --git a/tests/unit/Android.bp b/tests/unit/Android.bp
index 464e4a1..ea29714 100644
--- a/tests/unit/Android.bp
+++ b/tests/unit/Android.bp
@@ -21,7 +21,10 @@
 java_defaults {
     name: "NetworkStackTestsDefaults",
     platform_apis: true,
-    srcs: ["src/**/*.java", "src/**/*.kt"],
+    srcs: [
+        "src/**/*.java",
+        "src/**/*.kt",
+    ],
     resource_dirs: ["res"],
     static_libs: [
         "androidx.test.ext.junit",
@@ -39,7 +42,7 @@
     ],
     defaults: [
         "framework-connectivity-test-defaults",
-        "libnetworkstackutilsjni_deps"
+        "libnetworkstackutilsjni_deps",
     ],
     jni_libs: [
         // For mockito extended
@@ -66,6 +69,9 @@
     static_libs: ["NetworkStackApiCurrentLib"],
     compile_multilib: "both", // Workaround for b/147785146 for mainline-presubmit
     jarjar_rules: ":NetworkStackJarJarRules",
+    lint: {
+        baseline_filename: "lint-baseline.xml",
+    },
 }
 
 // Library containing the unit tests. This is used by the coverage test target to pull in the
@@ -76,18 +82,24 @@
     min_sdk_version: "30",
     defaults: ["NetworkStackTestsDefaults"],
     static_libs: ["NetworkStackApiStableLib"],
-    lint: { test: true },
+    lint: {
+        test: true,
+        baseline_filename: "lint-baseline.xml",
+    },
     visibility: [
         "//packages/modules/NetworkStack/tests/integration",
         "//packages/modules/Connectivity/tests:__subpackages__",
         "//packages/modules/Connectivity/Tethering/tests:__subpackages__",
-    ]
+    ],
 }
 
 android_test {
     name: "NetworkStackTests",
     min_sdk_version: "30",
-    test_suites: ["general-tests", "mts"],
+    test_suites: [
+        "general-tests",
+        "mts",
+    ],
     defaults: [
         "NetworkStackTestsDefaults",
         "connectivity-mainline-presubmit-java-defaults",
@@ -95,6 +107,9 @@
     static_libs: ["NetworkStackApiStableLib"],
     compile_multilib: "both",
     jarjar_rules: ":NetworkStackJarJarRules",
+    lint: {
+        baseline_filename: "lint-baseline.xml",
+    },
 }
 
 // Additional dependencies of libnetworkstackutilsjni that are not provided by the system when
diff --git a/tests/unit/src/android/net/apf/ApfTest.java b/tests/unit/src/android/net/apf/ApfTest.java
index 0b61e04..4e1187b 100644
--- a/tests/unit/src/android/net/apf/ApfTest.java
+++ b/tests/unit/src/android/net/apf/ApfTest.java
@@ -16,6 +16,7 @@
 
 package android.net.apf;
 
+import static android.net.apf.ApfGenerator.APF_VERSION_4;
 import static android.net.apf.ApfGenerator.Register.R0;
 import static android.net.apf.ApfGenerator.Register.R1;
 import static android.net.apf.ApfJniUtils.compareBpfApf;
@@ -631,7 +632,7 @@
         // Test jump if bytes not equal.
         gen = new ApfGenerator(MIN_APF_VERSION);
         gen.addLoadImmediate(R0, 1);
-        gen.addJumpIfBytesNotEqual(R0, new byte[]{123}, gen.DROP_LABEL);
+        gen.addJumpIfBytesAtR0NotEqual(new byte[]{123}, gen.DROP_LABEL);
         program = gen.generate();
         assertEquals(6, program.length);
         assertEquals((13 << 3) | (1 << 1) | 0, program[0]);
@@ -643,20 +644,20 @@
         assertDrop(program, new byte[MIN_PKT_SIZE], 0);
         gen = new ApfGenerator(MIN_APF_VERSION);
         gen.addLoadImmediate(R0, 1);
-        gen.addJumpIfBytesNotEqual(R0, new byte[]{123}, gen.DROP_LABEL);
+        gen.addJumpIfBytesAtR0NotEqual(new byte[]{123}, gen.DROP_LABEL);
         byte[] packet123 = {0,123,0,0,0,0,0,0,0,0,0,0,0,0,0};
         assertPass(gen, packet123, 0);
         gen = new ApfGenerator(MIN_APF_VERSION);
-        gen.addJumpIfBytesNotEqual(R0, new byte[]{123}, gen.DROP_LABEL);
+        gen.addJumpIfBytesAtR0NotEqual(new byte[]{123}, gen.DROP_LABEL);
         assertDrop(gen, packet123, 0);
         gen = new ApfGenerator(MIN_APF_VERSION);
         gen.addLoadImmediate(R0, 1);
-        gen.addJumpIfBytesNotEqual(R0, new byte[]{1,2,30,4,5}, gen.DROP_LABEL);
+        gen.addJumpIfBytesAtR0NotEqual(new byte[]{1, 2, 30, 4, 5}, gen.DROP_LABEL);
         byte[] packet12345 = {0,1,2,3,4,5,0,0,0,0,0,0,0,0,0};
         assertDrop(gen, packet12345, 0);
         gen = new ApfGenerator(MIN_APF_VERSION);
         gen.addLoadImmediate(R0, 1);
-        gen.addJumpIfBytesNotEqual(R0, new byte[]{1,2,3,4,5}, gen.DROP_LABEL);
+        gen.addJumpIfBytesAtR0NotEqual(new byte[]{1, 2, 3, 4, 5}, gen.DROP_LABEL);
         assertPass(gen, packet12345, 0);
     }
 
@@ -747,23 +748,23 @@
         ApfGenerator gen;
 
         // Load data with no offset: lddw R0, [0 + r1]
-        gen = new ApfGenerator(3);
+        gen = new ApfGenerator(APF_VERSION_4);
         gen.addLoadData(R0, 0);
         assertProgramEquals(new byte[]{LDDW_OP | SIZE0}, gen.generate());
 
         // Store data with 8bit negative offset: lddw r0, [-42 + r1]
-        gen = new ApfGenerator(3);
+        gen = new ApfGenerator(APF_VERSION_4);
         gen.addStoreData(R0, -42);
         assertProgramEquals(new byte[]{STDW_OP | SIZE8, -42}, gen.generate());
 
         // Store data to R1 with 16bit negative offset: stdw r1, [-0x1122 + r0]
-        gen = new ApfGenerator(3);
+        gen = new ApfGenerator(APF_VERSION_4);
         gen.addStoreData(R1, -0x1122);
         assertProgramEquals(new byte[]{STDW_OP | SIZE16 | R1_REG, (byte)0xEE, (byte)0xDE},
                 gen.generate());
 
         // Load data to R1 with 32bit negative offset: lddw r1, [0xDEADBEEF + r0]
-        gen = new ApfGenerator(3);
+        gen = new ApfGenerator(APF_VERSION_4);
         gen.addLoadData(R1, 0xDEADBEEF);
         assertProgramEquals(
                 new byte[]{LDDW_OP | SIZE32 | R1_REG,
@@ -781,12 +782,12 @@
         byte[] expected_data = data.clone();
 
         // No memory access instructions: should leave the data segment untouched.
-        ApfGenerator gen = new ApfGenerator(3);
+        ApfGenerator gen = new ApfGenerator(APF_VERSION_4);
         assertDataMemoryContents(PASS, gen.generate(), packet, data, expected_data);
 
         // Expect value 0x87654321 to be stored starting from address -11 from the end of the
         // data buffer, in big-endian order.
-        gen = new ApfGenerator(3);
+        gen = new ApfGenerator(APF_VERSION_4);
         gen.addLoadImmediate(R0, 0x87654321);
         gen.addLoadImmediate(R1, -5);
         gen.addStoreData(R0, -6);  // -5 + -6 = -11 (offset +5 with data_len=16)
@@ -803,7 +804,7 @@
     @Test
     public void testApfDataRead() throws IllegalInstructionException, Exception {
         // Program that DROPs if address 10 (-6) contains 0x87654321.
-        ApfGenerator gen = new ApfGenerator(3);
+        ApfGenerator gen = new ApfGenerator(APF_VERSION_4);
         gen.addLoadImmediate(R1, 1000);
         gen.addLoadData(R0, -1006);  // 1000 + -1006 = -6 (offset +10 with data_len=16)
         gen.addJumpIfR0Equals(0x87654321, gen.DROP_LABEL);
@@ -832,7 +833,7 @@
      */
     @Test
     public void testApfDataReadModifyWrite() throws IllegalInstructionException, Exception {
-        ApfGenerator gen = new ApfGenerator(3);
+        ApfGenerator gen = new ApfGenerator(APF_VERSION_4);
         gen.addLoadImmediate(R1, -22);
         gen.addLoadData(R0, 0);  // Load from address 32 -22 + 0 = 10
         gen.addAdd(0x78453412);  // 87654321 + 78453412 = FFAA7733
@@ -859,35 +860,35 @@
         byte[] expected_data = data;
 
         // Program that DROPs unconditionally. This is our the baseline.
-        ApfGenerator gen = new ApfGenerator(3);
+        ApfGenerator gen = new ApfGenerator(APF_VERSION_4);
         gen.addLoadImmediate(R0, 3);
         gen.addLoadData(R1, 7);
         gen.addJump(gen.DROP_LABEL);
         assertDataMemoryContents(DROP, gen.generate(), packet, data, expected_data);
 
         // Same program as before, but this time we're trying to load past the end of the data.
-        gen = new ApfGenerator(3);
+        gen = new ApfGenerator(APF_VERSION_4);
         gen.addLoadImmediate(R0, 20);
         gen.addLoadData(R1, 15);  // 20 + 15 > 32
         gen.addJump(gen.DROP_LABEL);  // Not reached.
         assertDataMemoryContents(PASS, gen.generate(), packet, data, expected_data);
 
         // Subtracting an immediate should work...
-        gen = new ApfGenerator(3);
+        gen = new ApfGenerator(APF_VERSION_4);
         gen.addLoadImmediate(R0, 20);
         gen.addLoadData(R1, -4);
         gen.addJump(gen.DROP_LABEL);
         assertDataMemoryContents(DROP, gen.generate(), packet, data, expected_data);
 
         // ...and underflowing simply wraps around to the end of the buffer...
-        gen = new ApfGenerator(3);
+        gen = new ApfGenerator(APF_VERSION_4);
         gen.addLoadImmediate(R0, 20);
         gen.addLoadData(R1, -30);
         gen.addJump(gen.DROP_LABEL);
         assertDataMemoryContents(DROP, gen.generate(), packet, data, expected_data);
 
         // ...but doesn't allow accesses before the start of the buffer
-        gen = new ApfGenerator(3);
+        gen = new ApfGenerator(APF_VERSION_4);
         gen.addLoadImmediate(R0, 20);
         gen.addLoadData(R1, -1000);
         gen.addJump(gen.DROP_LABEL);  // Not reached.
diff --git a/tests/unit/src/android/net/apf/ApfV5Test.kt b/tests/unit/src/android/net/apf/ApfV5Test.kt
index 1b74c3e..dc8b7b6 100644
--- a/tests/unit/src/android/net/apf/ApfV5Test.kt
+++ b/tests/unit/src/android/net/apf/ApfV5Test.kt
@@ -16,6 +16,8 @@
 package android.net.apf
 
 import android.net.apf.ApfGenerator.IllegalInstructionException
+import android.net.apf.ApfGenerator.Register.R0
+import android.net.apf.ApfGenerator.Register.R1
 import androidx.test.filters.SmallTest
 import androidx.test.runner.AndroidJUnit4
 import java.lang.IllegalArgumentException
@@ -39,13 +41,50 @@
         assertFailsWith<IllegalInstructionException> { gen.addCountAndPass(1000) }
         assertFailsWith<IllegalInstructionException> { gen.addTransmit() }
         assertFailsWith<IllegalInstructionException> { gen.addDiscard() }
+        assertFailsWith<IllegalInstructionException> { gen.addAllocateR0() }
+        assertFailsWith<IllegalInstructionException> { gen.addAllocate(100) }
+        assertFailsWith<IllegalInstructionException> { gen.addData(ByteArray(3) { 0x01 }) }
+        assertFailsWith<IllegalInstructionException> { gen.addWriteU8(100) }
+        assertFailsWith<IllegalInstructionException> { gen.addWriteU16(100) }
+        assertFailsWith<IllegalInstructionException> { gen.addWriteU32(100) }
+        assertFailsWith<IllegalInstructionException> { gen.addPacketCopy(100, 100) }
+        assertFailsWith<IllegalInstructionException> { gen.addDataCopy(100, 100) }
+        assertFailsWith<IllegalInstructionException> { gen.addWriteU8(R0) }
+        assertFailsWith<IllegalInstructionException> { gen.addWriteU16(R0) }
+        assertFailsWith<IllegalInstructionException> { gen.addWriteU32(R0) }
+        assertFailsWith<IllegalInstructionException> { gen.addWriteU8(R1) }
+        assertFailsWith<IllegalInstructionException> { gen.addWriteU16(R1) }
+        assertFailsWith<IllegalInstructionException> { gen.addWriteU32(R1) }
+        assertFailsWith<IllegalInstructionException> { gen.addPacketCopyFromR0LenR1() }
+        assertFailsWith<IllegalInstructionException> { gen.addDataCopyFromR0LenR1() }
+        assertFailsWith<IllegalInstructionException> { gen.addPacketCopyFromR0(10) }
+        assertFailsWith<IllegalInstructionException> { gen.addDataCopyFromR0(10) }
+        assertFailsWith<IllegalInstructionException> {
+            gen.addJumpIfBytesAtR0Equal(byteArrayOf('a'.code.toByte()), ApfGenerator.DROP_LABEL) }
     }
 
     @Test
-    fun testApfInstructionArgumentCheck() {
+    fun testDataInstructionMustComeFirst() {
         var gen = ApfGenerator(ApfGenerator.MIN_APF_VERSION_IN_DEV)
-        assertFailsWith<IllegalArgumentException> { gen.addCountAndPass(0) }
-        assertFailsWith<IllegalArgumentException> { gen.addCountAndDrop(0) }
+        gen.addAllocateR0()
+        assertFailsWith<IllegalInstructionException> { gen.addData(ByteArray(3) { 0x01 }) }
+    }
+
+    @Test
+    fun testApfInstructionEncodingSizeCheck() {
+        var gen = ApfGenerator(ApfGenerator.MIN_APF_VERSION_IN_DEV)
+        assertFailsWith<IllegalArgumentException> { gen.addAllocate(65536) }
+        assertFailsWith<IllegalArgumentException> { gen.addAllocate(-1) }
+        assertFailsWith<IllegalArgumentException> { gen.addDataCopy(-1, 1) }
+        assertFailsWith<IllegalArgumentException> { gen.addPacketCopy(-1, 1) }
+        assertFailsWith<IllegalArgumentException> { gen.addDataCopy(1, 256) }
+        assertFailsWith<IllegalArgumentException> { gen.addPacketCopy(1, 256) }
+        assertFailsWith<IllegalArgumentException> { gen.addDataCopy(1, -1) }
+        assertFailsWith<IllegalArgumentException> { gen.addPacketCopy(1, -1) }
+        assertFailsWith<IllegalArgumentException> { gen.addPacketCopyFromR0(256) }
+        assertFailsWith<IllegalArgumentException> { gen.addDataCopyFromR0(256) }
+        assertFailsWith<IllegalArgumentException> { gen.addPacketCopyFromR0(-1) }
+        assertFailsWith<IllegalArgumentException> { gen.addDataCopyFromR0(-1) }
     }
 
     @Test
@@ -81,10 +120,18 @@
                         0x03, 0xe8.toByte()), program)
 
         gen = ApfGenerator(ApfGenerator.MIN_APF_VERSION_IN_DEV)
-        gen.addAlloc(ApfGenerator.Register.R0)
+        gen.addAllocateR0()
+        gen.addAllocate(1500)
         program = gen.generate()
-        assertContentEquals(byteArrayOf(encodeInstruction(21, 1, 0), 36), program)
-        assertContentEquals(arrayOf("       0: alloc r0"), ApfJniUtils.disassembleApf(program))
+        // encoding ALLOC opcode: opcode=21(EXT opcode number), imm=36(TRANS opcode number).
+        // R=0 means length stored in R0. R=1 means the length stored in imm1.
+        assertContentEquals(byteArrayOf(
+                encodeInstruction(opcode = 21, immLength = 1, register = 0), 36,
+                encodeInstruction(opcode = 21, immLength = 1, register = 1), 36, 0x05,
+                0xDC.toByte()),
+        program)
+        // TODO: add back disassembling test check after we update the apf_disassembler
+        // assertContentEquals(arrayOf("       0: alloc"), ApfJniUtils.disassembleApf(program))
 
         gen = ApfGenerator(ApfGenerator.MIN_APF_VERSION_IN_DEV)
         gen.addTransmit()
@@ -100,63 +147,113 @@
         // TODO: add back disassembling test check after we update the apf_disassembler
         // assertContentEquals(arrayOf("       0: trans"), ApfJniUtils.disassembleApf(program))
 
-        // TODO: add back when support write opcode
-//        gen = ApfGenerator(ApfGenerator.MIN_APF_VERSION_IN_DEV)
-//        gen.addWrite(0x01, 1)
-//        gen.addWrite(0x0102, 2)
-//        gen.addWrite(0x01020304, 4)
-//        program = gen.generate()
-//        assertContentEquals(byteArrayOf(
-//                encodeInstruction(24, 1, 0), 0x01,
-//                encodeInstruction(24, 2, 0), 0x01, 0x02,
-//                encodeInstruction(24, 4, 0), 0x01, 0x02, 0x03, 0x04
-//        ), program)
-//        assertContentEquals(arrayOf(
-//                "       0: write 0x01",
-//                "       2: write 0x0102",
-//                "       5: write 0x01020304"), ApfJniUtils.disassembleApf(program))
-//
-//        gen = ApfGenerator(ApfGenerator.MIN_APF_VERSION_IN_DEV)
-//        gen.addWrite(ApfGenerator.Register.R0, 1)
-//        gen.addWrite(ApfGenerator.Register.R0, 2)
-//        gen.addWrite(ApfGenerator.Register.R0, 4)
-//        program = gen.generate()
-//        assertContentEquals(byteArrayOf(
-//                encodeInstruction(21, 1, 0), 38,
-//                encodeInstruction(21, 1, 0), 39,
-//                encodeInstruction(21, 1, 0), 40
-//        ), program)
-//        assertContentEquals(arrayOf(
-//                "       0: write r0, 1",
-//                "       2: write r0, 2",
-//                "       4: write r0, 4"), ApfJniUtils.disassembleApf(program))
+        gen = ApfGenerator(ApfGenerator.MIN_APF_VERSION_IN_DEV)
+        val largeByteArray = ByteArray(256) { 0x01 }
+        gen.addData(largeByteArray)
+        program = gen.generate()
+        // encoding DATA opcode: opcode=14(JMP), R=1
+        assertContentEquals(byteArrayOf(
+                encodeInstruction(opcode = 14, immLength = 2, register = 1), 0x01, 0x00) +
+                largeByteArray, program)
 
-        // TODO: add back when we properly support copy opcode
-//        gen = ApfGenerator(ApfGenerator.MIN_APF_VERSION_IN_DEV)
-//        gen.addDataCopy(1, 5)
-//        gen.addPacketCopy(1000, 255)
-//        program = gen.generate()
-//        assertContentEquals(byteArrayOf(
-//                encodeInstruction(25, 1, 1), 1, 5,
-//                encodeInstruction(25, 2, 0),
-//                0x03.toByte(), 0xe8.toByte(), 0xff.toByte(),
-//        ), program)
+        gen = ApfGenerator(ApfGenerator.MIN_APF_VERSION_IN_DEV)
+        gen.addWriteU8(0x01)
+        gen.addWriteU16(0x0102)
+        gen.addWriteU32(0x01020304)
+        gen.addWriteU8(0x00)
+        gen.addWriteU8(0x80)
+        gen.addWriteU16(0x0000)
+        gen.addWriteU16(0x8000)
+        gen.addWriteU32(0x00000000)
+        gen.addWriteU32(0x80000000)
+        program = gen.generate()
+        assertContentEquals(byteArrayOf(
+                encodeInstruction(24, 1, 0), 0x01,
+                encodeInstruction(24, 2, 0), 0x01, 0x02,
+                encodeInstruction(24, 4, 0), 0x01, 0x02, 0x03, 0x04,
+                encodeInstruction(24, 1, 0), 0x00,
+                encodeInstruction(24, 1, 0), 0x80.toByte(),
+                encodeInstruction(24, 2, 0), 0x00, 0x00,
+                encodeInstruction(24, 2, 0), 0x80.toByte(), 0x00,
+                encodeInstruction(24, 4, 0), 0x00, 0x00, 0x00, 0x00,
+                encodeInstruction(24, 4, 0), 0x80.toByte(), 0x00, 0x00,
+                0x00), program)
+        assertContentEquals(arrayOf(
+                "       0: write 0x01",
+                "       2: write 0x0102",
+                "       5: write 0x01020304",
+                "      10: write 0x00",
+                "      12: write 0x80",
+                "      14: write 0x0000",
+                "      17: write 0x8000",
+                "      20: write 0x00000000",
+                "      25: write 0x80000000"),
+        ApfJniUtils.disassembleApf(program))
+
+        gen = ApfGenerator(ApfGenerator.MIN_APF_VERSION_IN_DEV)
+        gen.addWriteU8(R0)
+        gen.addWriteU16(R0)
+        gen.addWriteU32(R0)
+        gen.addWriteU8(R1)
+        gen.addWriteU16(R1)
+        gen.addWriteU32(R1)
+        program = gen.generate()
+        assertContentEquals(byteArrayOf(
+                encodeInstruction(21, 1, 0), 38,
+                encodeInstruction(21, 1, 0), 39,
+                encodeInstruction(21, 1, 0), 40,
+                encodeInstruction(21, 1, 1), 38,
+                encodeInstruction(21, 1, 1), 39,
+                encodeInstruction(21, 1, 1), 40
+        ), program)
+        // TODO: add back disassembling test check after we update the apf_disassembler
 //        assertContentEquals(arrayOf(
-//                "       0: dcopy 1, 5",
+//                "       0: ewrite1 r0",
+//                "       2: ewrite2 r0",
+//                "       4: ewrite4 r0",
+//                "       6: ewrite1 r1",
+//                "       8: ewrite2 r1",
+//                "      10: ewrite4 r1"), ApfJniUtils.disassembleApf(program))
+
+        gen = ApfGenerator(ApfGenerator.MIN_APF_VERSION_IN_DEV)
+        gen.addDataCopy(0, 10)
+        gen.addDataCopy(1, 5)
+        gen.addPacketCopy(1000, 255)
+        program = gen.generate()
+        assertContentEquals(byteArrayOf(
+                encodeInstruction(25, 0, 1), 10,
+                encodeInstruction(25, 1, 1), 1, 5,
+                encodeInstruction(25, 2, 0),
+                0x03.toByte(), 0xe8.toByte(), 0xff.toByte(),
+        ), program)
+        // TODO: add back disassembling test check after we update the apf_disassembler
+//        assertContentEquals(arrayOf(
+//                "       0: dcopy 0, 5",
 //                "       3: pcopy 1000, 255"), ApfJniUtils.disassembleApf(program))
-//
-//        gen = ApfGenerator(ApfGenerator.MIN_APF_VERSION_IN_DEV)
-//        gen.addDataCopy(ApfGenerator.Register.R1, 0, 5)
-//        gen.addPacketCopy(ApfGenerator.Register.R0, 1000, 255)
-//        program = gen.generate()
-//        assertContentEquals(byteArrayOf(
-//                encodeInstruction(21, 1, 1), 42, 0, 5,
-//                encodeInstruction(21, 2, 0),
-//                0, 41, 0x03.toByte(), 0xe8.toByte(), 0xff.toByte()
-//        ), program)
+
+        gen = ApfGenerator(ApfGenerator.MIN_APF_VERSION_IN_DEV)
+        gen.addPacketCopyFromR0LenR1()
+        gen.addPacketCopyFromR0(5)
+        gen.addDataCopyFromR0LenR1()
+        gen.addDataCopyFromR0(5)
+        program = gen.generate()
+        assertContentEquals(byteArrayOf(
+                encodeInstruction(21, 1, 1), 41,
+                encodeInstruction(21, 1, 0), 41, 5,
+                encodeInstruction(21, 1, 1), 42,
+                encodeInstruction(21, 1, 0), 42, 5,
+        ), program)
+        // TODO: add back the following test case when implementing EPKTCOPY, EDATACOPY opcodes.
 //        assertContentEquals(arrayOf(
 //                "       0: dcopy [r1+0], 5",
 //                "       4: pcopy [r0+1000], 255"), ApfJniUtils.disassembleApf(program))
+
+        gen = ApfGenerator(ApfGenerator.MIN_APF_VERSION_IN_DEV)
+        gen.addJumpIfBytesAtR0Equal(byteArrayOf('a'.code.toByte()), ApfGenerator.DROP_LABEL)
+        program = gen.generate()
+        assertContentEquals(
+                byteArrayOf(encodeInstruction(opcode = 20, immLength = 1, register = 1),
+                        1, 1, 'a'.code.toByte()), program)
     }
 
     private fun encodeInstruction(opcode: Int, immLength: Int, register: Int): Byte {
diff --git a/tests/unit/src/android/net/dhcp6/Dhcp6PacketTest.kt b/tests/unit/src/android/net/dhcp6/Dhcp6PacketTest.kt
index b5e806f..32cf464 100644
--- a/tests/unit/src/android/net/dhcp6/Dhcp6PacketTest.kt
+++ b/tests/unit/src/android/net/dhcp6/Dhcp6PacketTest.kt
@@ -220,4 +220,168 @@
         assertEquals(423, packet.prefixDelegation.minimalPreferredLifetime)
         assertEquals(43200, packet.prefixDelegation.minimalValidLifetime)
     }
+
+    @Test
+    fun testStatusCodeOptionWithStatusMessage() {
+        val replyHex =
+            // Reply, Transaction ID
+            "07000A47" +
+            // server identifier option(option_len=10)
+            "0002000A0003000186C9B26AED4D" +
+            // client identifier option(option_len=12)
+            "0001000C0003001B02FBBAFFFEB7BC71" +
+            // SOL_MAX_RT (don't support this option yet)
+            "005200040000003c" +
+            // Rapid Commit
+            "000e0000" +
+            // DNS recursive server (don't support this opton yet)
+            "00170010fdfd9ed6795000000000000000000001" +
+            // IA_PD option (t1=t2=0, empty prefix)
+            "0019000c000000000000000000000000" +
+            // Status code option: status code=NoPrefixAvail
+            "000d00150006" +
+            // Status code option: status message="no prefix available"
+            "6e6f2070726566697820617661696c61626c65"
+        val bytes = HexDump.hexStringToByteArray(replyHex)
+        val packet = Dhcp6Packet.decode(bytes, bytes.size)
+        assertTrue(packet is Dhcp6ReplyPacket)
+        assertEquals(0, packet.mPrefixDelegation.iaid)
+        assertEquals(0, packet.mPrefixDelegation.t1)
+        assertEquals(0, packet.mPrefixDelegation.t2)
+        assertEquals(Dhcp6Packet.STATUS_NO_PREFIX_AVAIL, packet.mStatusCode)
+    }
+
+    @Test
+    fun testStatusCodeOptionWithoutStatusMessage() {
+        val replyHex =
+            // Reply, Transaction ID
+            "07000A47" +
+            // server identifier option(option_len=10)
+            "0002000A0003000186C9B26AED4D" +
+            // client identifier option(option_len=12)
+            "0001000C0003001B02FBBAFFFEB7BC71" +
+            // SOL_MAX_RT (don't support this option yet)
+            "005200040000003c" +
+            // Rapid Commit
+            "000e0000" +
+            // DNS recursive server (don't support this opton yet)
+            "00170010fdfd9ed6795000000000000000000001" +
+            // IA_PD option (t1=t2=0, empty prefix)
+            "0019000c000000000000000000000000" +
+            // Status code option: status code=NoPrefixAvail
+            "000d00020006"
+        val bytes = HexDump.hexStringToByteArray(replyHex)
+        val packet = Dhcp6Packet.decode(bytes, bytes.size)
+        assertTrue(packet is Dhcp6ReplyPacket)
+        assertEquals(0, packet.mPrefixDelegation.iaid)
+        assertEquals(0, packet.mPrefixDelegation.t1)
+        assertEquals(0, packet.mPrefixDelegation.t2)
+        assertEquals(Dhcp6Packet.STATUS_NO_PREFIX_AVAIL, packet.mStatusCode)
+    }
+
+    @Test
+    fun testStatusCodeOptionInIaPdWithStatusMessage() {
+        val replyHex =
+            // Reply, Transaction ID
+            "07000A47" +
+            // server identifier option(option_len=10)
+            "0002000A0003000186C9B26AED4D" +
+            // client identifier option(option_len=12)
+            "0001000C0003001B02FBBAFFFEB7BC71" +
+            // SOL_MAX_RT (don't support this option yet)
+            "005200040000003c" +
+            // Rapid Commit
+            "000e0000" +
+            // DNS recursive server (don't support this opton yet)
+            "00170010fdfd9ed6795000000000000000000001" +
+            // IA_PD option (t1=t2=0, status code=NoPrefixAvail,
+            // status message="no prefix available")
+            "00190025000000000000000000000000000d00150006" +
+            "6e6f2070726566697820617661696c61626c65"
+        val bytes = HexDump.hexStringToByteArray(replyHex)
+        val packet = Dhcp6Packet.decode(bytes, bytes.size)
+        assertTrue(packet is Dhcp6ReplyPacket)
+        assertEquals(0, packet.mPrefixDelegation.iaid)
+        assertEquals(0, packet.mPrefixDelegation.t1)
+        assertEquals(0, packet.mPrefixDelegation.t2)
+        assertEquals(Dhcp6Packet.STATUS_NO_PREFIX_AVAIL, packet.mPrefixDelegation.statusCode)
+    }
+
+    @Test
+    fun testStatusCodeOptionInIaPdWithoutStatusMessage() {
+        val replyHex =
+            // Reply, Transaction ID
+            "07000A47" +
+            // server identifier option(option_len=10)
+            "0002000A0003000186C9B26AED4D" +
+            // client identifier option(option_len=12)
+            "0001000C0003001B02FBBAFFFEB7BC71" +
+            // SOL_MAX_RT (don't support this option yet)
+            "005200040000003c" +
+            // Rapid Commit
+            "000e0000" +
+            // DNS recursive server (don't support this opton yet)
+            "00170010fdfd9ed6795000000000000000000001" +
+            // IA_PD option (t1=t2=0, status code=NoPrefixAvail)
+            "00190012000000000000000000000000000d00020006"
+        val bytes = HexDump.hexStringToByteArray(replyHex)
+        val packet = Dhcp6Packet.decode(bytes, bytes.size)
+        assertTrue(packet is Dhcp6ReplyPacket)
+        assertEquals(0, packet.mPrefixDelegation.iaid)
+        assertEquals(0, packet.mPrefixDelegation.t1)
+        assertEquals(0, packet.mPrefixDelegation.t2)
+        assertEquals(Dhcp6Packet.STATUS_NO_PREFIX_AVAIL, packet.mPrefixDelegation.statusCode)
+    }
+
+    @Test
+    fun testStatusCodeOptionWithTruncatedStatusMessage() {
+        val replyHex =
+            // Reply, Transaction ID
+            "07000A47" +
+            // server identifier option(option_len=10)
+            "0002000A0003000186C9B26AED4D" +
+            // client identifier option(option_len=12)
+            "0001000C0003001B02FBBAFFFEB7BC71" +
+            // SOL_MAX_RT (don't support this option yet)
+            "005200040000003c" +
+            // Rapid Commit
+            "000e0000" +
+            // DNS recursive server (don't support this opton yet)
+            "00170010fdfd9ed6795000000000000000000001" +
+            // Status code option: len=21, status code=NoPrefixAvail
+            "000d00150006" +
+            // Status code option: truncated status message="no prefix available"
+            "6e6f2070726566697820617661696c6162"
+        val bytes = HexDump.hexStringToByteArray(replyHex)
+        assertThrows(Dhcp6Packet.ParseException::class.java) {
+            Dhcp6Packet.decode(bytes, bytes.size)
+        }
+    }
+
+    @Test
+    fun testStatusCodeOptionInIaPdWithTruncatedStatusMessage() {
+        val replyHex =
+            // Reply, Transaction ID
+            "07000A47" +
+            // server identifier option(option_len=10)
+            "0002000A0003000186C9B26AED4D" +
+            // client identifier option(option_len=12)
+            "0001000C0003001B02FBBAFFFEB7BC71" +
+            // SOL_MAX_RT (don't support this option yet)
+            "005200040000003c" +
+            // Rapid Commit
+            "000e0000" +
+            // DNS recursive server (don't support this opton yet)
+            "00170010fdfd9ed6795000000000000000000001" +
+            // IA_PD option (t1=t2=0, empty prefix)
+            "00190025000000000000000000000000" +
+            // Status code option: len=21, status code=NoPrefixAvail
+            "000d00150006" +
+            // truncated status message="no prefix available")
+            "6e6f2070726566697820617661696c6162"
+        val bytes = HexDump.hexStringToByteArray(replyHex)
+        assertThrows(Dhcp6Packet.ParseException::class.java) {
+            Dhcp6Packet.decode(bytes, bytes.size)
+        }
+    }
 }
diff --git a/tests/unit/src/android/net/ip/IpReachabilityMonitorTest.kt b/tests/unit/src/android/net/ip/IpReachabilityMonitorTest.kt
index 6471f3a..4d57df5 100644
--- a/tests/unit/src/android/net/ip/IpReachabilityMonitorTest.kt
+++ b/tests/unit/src/android/net/ip/IpReachabilityMonitorTest.kt
@@ -56,7 +56,6 @@
 import com.android.net.module.util.netlink.StructNdMsg.NUD_REACHABLE
 import com.android.net.module.util.netlink.StructNdMsg.NUD_STALE
 import com.android.networkstack.metrics.IpReachabilityMonitorMetrics
-import com.android.networkstack.util.NetworkStackUtils.IP_REACHABILITY_MCAST_RESOLICIT_VERSION
 import com.android.testutils.makeNewNeighMessage
 import com.android.testutils.waitForIdle
 import java.io.FileDescriptor
@@ -259,8 +258,6 @@
         }.`when`(dependencies).makeIpNeighborMonitor(any(), any(), any())
         doReturn(mIpReachabilityMonitorMetrics)
                 .`when`(dependencies).getIpReachabilityMonitorMetrics()
-        doReturn(true).`when`(dependencies).isFeatureNotChickenedOut(any(),
-                eq(IP_REACHABILITY_MCAST_RESOLICIT_VERSION))
 
         val monitorFuture = CompletableFuture<IpReachabilityMonitor>()
         // IpReachabilityMonitor needs to be started from the handler thread