Loading packages/Tethering/tests/unit/src/com/android/networkstack/tethering/BpfCoordinatorTest.java +231 −0 Original line number Diff line number Diff line Loading @@ -31,10 +31,16 @@ import static com.android.networkstack.tethering.BpfCoordinator.StatsType; import static com.android.networkstack.tethering.BpfCoordinator.StatsType.STATS_PER_IFACE; import static com.android.networkstack.tethering.BpfCoordinator.StatsType.STATS_PER_UID; import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertNotNull; import static junit.framework.Assert.fail; import static org.mockito.Matchers.anyInt; import static org.mockito.Matchers.anyLong; import static org.mockito.Matchers.anyString; import static org.mockito.Mockito.argThat; import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; Loading @@ -42,8 +48,12 @@ import static org.mockito.Mockito.when; import android.annotation.NonNull; import android.app.usage.NetworkStatsManager; import android.net.INetd; import android.net.InetAddresses; import android.net.MacAddress; import android.net.NetworkStats; import android.net.TetherOffloadRuleParcel; import android.net.TetherStatsParcel; import android.net.ip.IpServer; import android.net.util.SharedLog; import android.os.Handler; import android.os.test.TestLooper; Loading @@ -51,22 +61,37 @@ import android.os.test.TestLooper; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; import com.android.networkstack.tethering.BpfCoordinator.Ipv6ForwardingRule; import com.android.testutils.TestableNetworkStatsProviderCbBinder; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.ArgumentMatcher; import org.mockito.InOrder; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import java.net.Inet6Address; import java.net.InetAddress; import java.util.ArrayList; import java.util.Arrays; @RunWith(AndroidJUnit4.class) @SmallTest public class BpfCoordinatorTest { private static final int DOWNSTREAM_IFINDEX = 10; private static final MacAddress DOWNSTREAM_MAC = MacAddress.ALL_ZEROS_ADDRESS; private static final InetAddress NEIGH_A = InetAddresses.parseNumericAddress("2001:db8::1"); private static final InetAddress NEIGH_B = InetAddresses.parseNumericAddress("2001:db8::2"); private static final MacAddress MAC_A = MacAddress.fromString("00:00:00:00:00:0a"); private static final MacAddress MAC_B = MacAddress.fromString("11:22:33:00:00:0b"); @Mock private NetworkStatsManager mStatsManager; @Mock private INetd mNetd; @Mock private IpServer mIpServer; // Late init since methods must be called by the thread that created this object. private TestableNetworkStatsProviderCbBinder mTetherStatsProviderCb; private BpfCoordinator.BpfTetherStatsProvider mTetherStatsProvider; Loading Loading @@ -243,4 +268,210 @@ public class BpfCoordinatorTest { waitForIdle(); mTetherStatsProviderCb.assertNoCallback(); } // The custom ArgumentMatcher simply comes from IpServerTest. // TODO: move both of them into a common utility class for reusing the code. private static class TetherOffloadRuleParcelMatcher implements ArgumentMatcher<TetherOffloadRuleParcel> { public final int upstreamIfindex; public final int downstreamIfindex; public final Inet6Address address; public final MacAddress srcMac; public final MacAddress dstMac; TetherOffloadRuleParcelMatcher(@NonNull Ipv6ForwardingRule rule) { upstreamIfindex = rule.upstreamIfindex; downstreamIfindex = rule.downstreamIfindex; address = rule.address; srcMac = rule.srcMac; dstMac = rule.dstMac; } public boolean matches(@NonNull TetherOffloadRuleParcel parcel) { return upstreamIfindex == parcel.inputInterfaceIndex && (downstreamIfindex == parcel.outputInterfaceIndex) && Arrays.equals(address.getAddress(), parcel.destination) && (128 == parcel.prefixLength) && Arrays.equals(srcMac.toByteArray(), parcel.srcL2Address) && Arrays.equals(dstMac.toByteArray(), parcel.dstL2Address); } public String toString() { return String.format("TetherOffloadRuleParcelMatcher(%d, %d, %s, %s, %s", upstreamIfindex, downstreamIfindex, address.getHostAddress(), srcMac, dstMac); } } @NonNull private TetherOffloadRuleParcel matches(@NonNull Ipv6ForwardingRule rule) { return argThat(new TetherOffloadRuleParcelMatcher(rule)); } @NonNull private static Ipv6ForwardingRule buildTestForwardingRule( int upstreamIfindex, @NonNull InetAddress address, @NonNull MacAddress dstMac) { return new Ipv6ForwardingRule(upstreamIfindex, DOWNSTREAM_IFINDEX, (Inet6Address) address, DOWNSTREAM_MAC, dstMac); } @Test public void testSetDataLimit() throws Exception { setupFunctioningNetdInterface(); final BpfCoordinator coordinator = makeBpfCoordinator(); final String mobileIface = "rmnet_data0"; final Integer mobileIfIndex = 100; coordinator.addUpstreamNameToLookupTable(mobileIfIndex, mobileIface); // [1] Default limit. // Set the unlimited quota as default if the service has never applied a data limit for a // given upstream. Note that the data limit only be applied on an upstream which has rules. final Ipv6ForwardingRule rule = buildTestForwardingRule(mobileIfIndex, NEIGH_A, MAC_A); final InOrder inOrder = inOrder(mNetd); coordinator.tetherOffloadRuleAdd(mIpServer, rule); inOrder.verify(mNetd).tetherOffloadRuleAdd(matches(rule)); inOrder.verify(mNetd).tetherOffloadSetInterfaceQuota(mobileIfIndex, QUOTA_UNLIMITED); inOrder.verifyNoMoreInteractions(); // [2] Specific limit. // Applying the data limit boundary {min, max, infinity} on current upstream. for (final long quota : new long[] {0, Long.MAX_VALUE, QUOTA_UNLIMITED}) { mTetherStatsProvider.onSetLimit(mobileIface, quota); waitForIdle(); inOrder.verify(mNetd).tetherOffloadSetInterfaceQuota(mobileIfIndex, quota); inOrder.verifyNoMoreInteractions(); } // [3] Invalid limit. // The valid range of quota is 0..max_int64 or -1 (unlimited). final long invalidLimit = Long.MIN_VALUE; try { mTetherStatsProvider.onSetLimit(mobileIface, invalidLimit); waitForIdle(); fail("No exception thrown for invalid limit " + invalidLimit + "."); } catch (IllegalArgumentException expected) { assertEquals(expected.getMessage(), "invalid quota value " + invalidLimit); } } // TODO: Test the case in which the rules are changed from different IpServer objects. @Test public void testSetDataLimitOnRuleChange() throws Exception { setupFunctioningNetdInterface(); final BpfCoordinator coordinator = makeBpfCoordinator(); final String mobileIface = "rmnet_data0"; final Integer mobileIfIndex = 100; coordinator.addUpstreamNameToLookupTable(mobileIfIndex, mobileIface); // Applying a data limit to the current upstream does not take any immediate action. // The data limit could be only set on an upstream which has rules. final long limit = 12345; final InOrder inOrder = inOrder(mNetd); mTetherStatsProvider.onSetLimit(mobileIface, limit); waitForIdle(); inOrder.verify(mNetd, never()).tetherOffloadSetInterfaceQuota(anyInt(), anyLong()); // Adding the first rule on current upstream immediately sends the quota to netd. final Ipv6ForwardingRule ruleA = buildTestForwardingRule(mobileIfIndex, NEIGH_A, MAC_A); coordinator.tetherOffloadRuleAdd(mIpServer, ruleA); inOrder.verify(mNetd).tetherOffloadRuleAdd(matches(ruleA)); inOrder.verify(mNetd).tetherOffloadSetInterfaceQuota(mobileIfIndex, limit); inOrder.verifyNoMoreInteractions(); // Adding the second rule on current upstream does not send the quota to netd. final Ipv6ForwardingRule ruleB = buildTestForwardingRule(mobileIfIndex, NEIGH_B, MAC_B); coordinator.tetherOffloadRuleAdd(mIpServer, ruleB); inOrder.verify(mNetd).tetherOffloadRuleAdd(matches(ruleB)); inOrder.verify(mNetd, never()).tetherOffloadSetInterfaceQuota(anyInt(), anyLong()); // Removing the second rule on current upstream does not send the quota to netd. coordinator.tetherOffloadRuleRemove(mIpServer, ruleB); inOrder.verify(mNetd).tetherOffloadRuleRemove(matches(ruleB)); inOrder.verify(mNetd, never()).tetherOffloadSetInterfaceQuota(anyInt(), anyLong()); // Removing the last rule on current upstream immediately sends the cleanup stuff to netd. when(mNetd.tetherOffloadGetAndClearStats(mobileIfIndex)) .thenReturn(buildTestTetherStatsParcel(mobileIfIndex, 0, 0, 0, 0)); coordinator.tetherOffloadRuleRemove(mIpServer, ruleA); inOrder.verify(mNetd).tetherOffloadRuleRemove(matches(ruleA)); inOrder.verify(mNetd).tetherOffloadGetAndClearStats(mobileIfIndex); inOrder.verifyNoMoreInteractions(); } @Test public void testTetherOffloadRuleUpdateAndClear() throws Exception { setupFunctioningNetdInterface(); final BpfCoordinator coordinator = makeBpfCoordinator(); final String ethIface = "eth1"; final String mobileIface = "rmnet_data0"; final Integer ethIfIndex = 100; final Integer mobileIfIndex = 101; coordinator.addUpstreamNameToLookupTable(ethIfIndex, ethIface); coordinator.addUpstreamNameToLookupTable(mobileIfIndex, mobileIface); final InOrder inOrder = inOrder(mNetd); // Before the rule test, here are the additional actions while the rules are changed. // - After adding the first rule on a given upstream, the coordinator adds a data limit. // If the service has never applied the data limit, set an unlimited quota as default. // - After removing the last rule on a given upstream, the coordinator gets the last stats. // Then, it clears the stats and the limit entry from BPF maps. // See tetherOffloadRule{Add, Remove, Clear, Clean}. // [1] Adding rules on the upstream Ethernet. // Note that the default data limit is applied after the first rule is added. final Ipv6ForwardingRule ethernetRuleA = buildTestForwardingRule( ethIfIndex, NEIGH_A, MAC_A); final Ipv6ForwardingRule ethernetRuleB = buildTestForwardingRule( ethIfIndex, NEIGH_B, MAC_B); coordinator.tetherOffloadRuleAdd(mIpServer, ethernetRuleA); inOrder.verify(mNetd).tetherOffloadRuleAdd(matches(ethernetRuleA)); inOrder.verify(mNetd).tetherOffloadSetInterfaceQuota(ethIfIndex, QUOTA_UNLIMITED); coordinator.tetherOffloadRuleAdd(mIpServer, ethernetRuleB); inOrder.verify(mNetd).tetherOffloadRuleAdd(matches(ethernetRuleB)); // [2] Update the existing rules from Ethernet to cellular. final Ipv6ForwardingRule mobileRuleA = buildTestForwardingRule( mobileIfIndex, NEIGH_A, MAC_A); final Ipv6ForwardingRule mobileRuleB = buildTestForwardingRule( mobileIfIndex, NEIGH_B, MAC_B); when(mNetd.tetherOffloadGetAndClearStats(ethIfIndex)) .thenReturn(buildTestTetherStatsParcel(ethIfIndex, 10, 20, 30, 40)); // Update the existing rules for upstream changes. The rules are removed and re-added one // by one for updating upstream interface index by #tetherOffloadRuleUpdate. coordinator.tetherOffloadRuleUpdate(mIpServer, mobileIfIndex); inOrder.verify(mNetd).tetherOffloadRuleRemove(matches(ethernetRuleA)); inOrder.verify(mNetd).tetherOffloadRuleAdd(matches(mobileRuleA)); inOrder.verify(mNetd).tetherOffloadSetInterfaceQuota(mobileIfIndex, QUOTA_UNLIMITED); inOrder.verify(mNetd).tetherOffloadRuleRemove(matches(ethernetRuleB)); inOrder.verify(mNetd).tetherOffloadGetAndClearStats(ethIfIndex); inOrder.verify(mNetd).tetherOffloadRuleAdd(matches(mobileRuleB)); // [3] Clear all rules for a given IpServer. when(mNetd.tetherOffloadGetAndClearStats(mobileIfIndex)) .thenReturn(buildTestTetherStatsParcel(mobileIfIndex, 50, 60, 70, 80)); coordinator.tetherOffloadRuleClear(mIpServer); inOrder.verify(mNetd).tetherOffloadRuleRemove(matches(mobileRuleA)); inOrder.verify(mNetd).tetherOffloadRuleRemove(matches(mobileRuleB)); inOrder.verify(mNetd).tetherOffloadGetAndClearStats(mobileIfIndex); // [4] Force pushing stats update to verify that the last diff of stats is reported on all // upstreams. mTetherStatsProvider.pushTetherStats(); mTetherStatsProviderCb.expectNotifyStatsUpdated( new NetworkStats(0L, 2) .addEntry(buildTestEntry(STATS_PER_IFACE, ethIface, 10, 20, 30, 40)) .addEntry(buildTestEntry(STATS_PER_IFACE, mobileIface, 50, 60, 70, 80)), new NetworkStats(0L, 2) .addEntry(buildTestEntry(STATS_PER_UID, ethIface, 10, 20, 30, 40)) .addEntry(buildTestEntry(STATS_PER_UID, mobileIface, 50, 60, 70, 80))); } } Loading
packages/Tethering/tests/unit/src/com/android/networkstack/tethering/BpfCoordinatorTest.java +231 −0 Original line number Diff line number Diff line Loading @@ -31,10 +31,16 @@ import static com.android.networkstack.tethering.BpfCoordinator.StatsType; import static com.android.networkstack.tethering.BpfCoordinator.StatsType.STATS_PER_IFACE; import static com.android.networkstack.tethering.BpfCoordinator.StatsType.STATS_PER_UID; import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertNotNull; import static junit.framework.Assert.fail; import static org.mockito.Matchers.anyInt; import static org.mockito.Matchers.anyLong; import static org.mockito.Matchers.anyString; import static org.mockito.Mockito.argThat; import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; Loading @@ -42,8 +48,12 @@ import static org.mockito.Mockito.when; import android.annotation.NonNull; import android.app.usage.NetworkStatsManager; import android.net.INetd; import android.net.InetAddresses; import android.net.MacAddress; import android.net.NetworkStats; import android.net.TetherOffloadRuleParcel; import android.net.TetherStatsParcel; import android.net.ip.IpServer; import android.net.util.SharedLog; import android.os.Handler; import android.os.test.TestLooper; Loading @@ -51,22 +61,37 @@ import android.os.test.TestLooper; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; import com.android.networkstack.tethering.BpfCoordinator.Ipv6ForwardingRule; import com.android.testutils.TestableNetworkStatsProviderCbBinder; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.ArgumentMatcher; import org.mockito.InOrder; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import java.net.Inet6Address; import java.net.InetAddress; import java.util.ArrayList; import java.util.Arrays; @RunWith(AndroidJUnit4.class) @SmallTest public class BpfCoordinatorTest { private static final int DOWNSTREAM_IFINDEX = 10; private static final MacAddress DOWNSTREAM_MAC = MacAddress.ALL_ZEROS_ADDRESS; private static final InetAddress NEIGH_A = InetAddresses.parseNumericAddress("2001:db8::1"); private static final InetAddress NEIGH_B = InetAddresses.parseNumericAddress("2001:db8::2"); private static final MacAddress MAC_A = MacAddress.fromString("00:00:00:00:00:0a"); private static final MacAddress MAC_B = MacAddress.fromString("11:22:33:00:00:0b"); @Mock private NetworkStatsManager mStatsManager; @Mock private INetd mNetd; @Mock private IpServer mIpServer; // Late init since methods must be called by the thread that created this object. private TestableNetworkStatsProviderCbBinder mTetherStatsProviderCb; private BpfCoordinator.BpfTetherStatsProvider mTetherStatsProvider; Loading Loading @@ -243,4 +268,210 @@ public class BpfCoordinatorTest { waitForIdle(); mTetherStatsProviderCb.assertNoCallback(); } // The custom ArgumentMatcher simply comes from IpServerTest. // TODO: move both of them into a common utility class for reusing the code. private static class TetherOffloadRuleParcelMatcher implements ArgumentMatcher<TetherOffloadRuleParcel> { public final int upstreamIfindex; public final int downstreamIfindex; public final Inet6Address address; public final MacAddress srcMac; public final MacAddress dstMac; TetherOffloadRuleParcelMatcher(@NonNull Ipv6ForwardingRule rule) { upstreamIfindex = rule.upstreamIfindex; downstreamIfindex = rule.downstreamIfindex; address = rule.address; srcMac = rule.srcMac; dstMac = rule.dstMac; } public boolean matches(@NonNull TetherOffloadRuleParcel parcel) { return upstreamIfindex == parcel.inputInterfaceIndex && (downstreamIfindex == parcel.outputInterfaceIndex) && Arrays.equals(address.getAddress(), parcel.destination) && (128 == parcel.prefixLength) && Arrays.equals(srcMac.toByteArray(), parcel.srcL2Address) && Arrays.equals(dstMac.toByteArray(), parcel.dstL2Address); } public String toString() { return String.format("TetherOffloadRuleParcelMatcher(%d, %d, %s, %s, %s", upstreamIfindex, downstreamIfindex, address.getHostAddress(), srcMac, dstMac); } } @NonNull private TetherOffloadRuleParcel matches(@NonNull Ipv6ForwardingRule rule) { return argThat(new TetherOffloadRuleParcelMatcher(rule)); } @NonNull private static Ipv6ForwardingRule buildTestForwardingRule( int upstreamIfindex, @NonNull InetAddress address, @NonNull MacAddress dstMac) { return new Ipv6ForwardingRule(upstreamIfindex, DOWNSTREAM_IFINDEX, (Inet6Address) address, DOWNSTREAM_MAC, dstMac); } @Test public void testSetDataLimit() throws Exception { setupFunctioningNetdInterface(); final BpfCoordinator coordinator = makeBpfCoordinator(); final String mobileIface = "rmnet_data0"; final Integer mobileIfIndex = 100; coordinator.addUpstreamNameToLookupTable(mobileIfIndex, mobileIface); // [1] Default limit. // Set the unlimited quota as default if the service has never applied a data limit for a // given upstream. Note that the data limit only be applied on an upstream which has rules. final Ipv6ForwardingRule rule = buildTestForwardingRule(mobileIfIndex, NEIGH_A, MAC_A); final InOrder inOrder = inOrder(mNetd); coordinator.tetherOffloadRuleAdd(mIpServer, rule); inOrder.verify(mNetd).tetherOffloadRuleAdd(matches(rule)); inOrder.verify(mNetd).tetherOffloadSetInterfaceQuota(mobileIfIndex, QUOTA_UNLIMITED); inOrder.verifyNoMoreInteractions(); // [2] Specific limit. // Applying the data limit boundary {min, max, infinity} on current upstream. for (final long quota : new long[] {0, Long.MAX_VALUE, QUOTA_UNLIMITED}) { mTetherStatsProvider.onSetLimit(mobileIface, quota); waitForIdle(); inOrder.verify(mNetd).tetherOffloadSetInterfaceQuota(mobileIfIndex, quota); inOrder.verifyNoMoreInteractions(); } // [3] Invalid limit. // The valid range of quota is 0..max_int64 or -1 (unlimited). final long invalidLimit = Long.MIN_VALUE; try { mTetherStatsProvider.onSetLimit(mobileIface, invalidLimit); waitForIdle(); fail("No exception thrown for invalid limit " + invalidLimit + "."); } catch (IllegalArgumentException expected) { assertEquals(expected.getMessage(), "invalid quota value " + invalidLimit); } } // TODO: Test the case in which the rules are changed from different IpServer objects. @Test public void testSetDataLimitOnRuleChange() throws Exception { setupFunctioningNetdInterface(); final BpfCoordinator coordinator = makeBpfCoordinator(); final String mobileIface = "rmnet_data0"; final Integer mobileIfIndex = 100; coordinator.addUpstreamNameToLookupTable(mobileIfIndex, mobileIface); // Applying a data limit to the current upstream does not take any immediate action. // The data limit could be only set on an upstream which has rules. final long limit = 12345; final InOrder inOrder = inOrder(mNetd); mTetherStatsProvider.onSetLimit(mobileIface, limit); waitForIdle(); inOrder.verify(mNetd, never()).tetherOffloadSetInterfaceQuota(anyInt(), anyLong()); // Adding the first rule on current upstream immediately sends the quota to netd. final Ipv6ForwardingRule ruleA = buildTestForwardingRule(mobileIfIndex, NEIGH_A, MAC_A); coordinator.tetherOffloadRuleAdd(mIpServer, ruleA); inOrder.verify(mNetd).tetherOffloadRuleAdd(matches(ruleA)); inOrder.verify(mNetd).tetherOffloadSetInterfaceQuota(mobileIfIndex, limit); inOrder.verifyNoMoreInteractions(); // Adding the second rule on current upstream does not send the quota to netd. final Ipv6ForwardingRule ruleB = buildTestForwardingRule(mobileIfIndex, NEIGH_B, MAC_B); coordinator.tetherOffloadRuleAdd(mIpServer, ruleB); inOrder.verify(mNetd).tetherOffloadRuleAdd(matches(ruleB)); inOrder.verify(mNetd, never()).tetherOffloadSetInterfaceQuota(anyInt(), anyLong()); // Removing the second rule on current upstream does not send the quota to netd. coordinator.tetherOffloadRuleRemove(mIpServer, ruleB); inOrder.verify(mNetd).tetherOffloadRuleRemove(matches(ruleB)); inOrder.verify(mNetd, never()).tetherOffloadSetInterfaceQuota(anyInt(), anyLong()); // Removing the last rule on current upstream immediately sends the cleanup stuff to netd. when(mNetd.tetherOffloadGetAndClearStats(mobileIfIndex)) .thenReturn(buildTestTetherStatsParcel(mobileIfIndex, 0, 0, 0, 0)); coordinator.tetherOffloadRuleRemove(mIpServer, ruleA); inOrder.verify(mNetd).tetherOffloadRuleRemove(matches(ruleA)); inOrder.verify(mNetd).tetherOffloadGetAndClearStats(mobileIfIndex); inOrder.verifyNoMoreInteractions(); } @Test public void testTetherOffloadRuleUpdateAndClear() throws Exception { setupFunctioningNetdInterface(); final BpfCoordinator coordinator = makeBpfCoordinator(); final String ethIface = "eth1"; final String mobileIface = "rmnet_data0"; final Integer ethIfIndex = 100; final Integer mobileIfIndex = 101; coordinator.addUpstreamNameToLookupTable(ethIfIndex, ethIface); coordinator.addUpstreamNameToLookupTable(mobileIfIndex, mobileIface); final InOrder inOrder = inOrder(mNetd); // Before the rule test, here are the additional actions while the rules are changed. // - After adding the first rule on a given upstream, the coordinator adds a data limit. // If the service has never applied the data limit, set an unlimited quota as default. // - After removing the last rule on a given upstream, the coordinator gets the last stats. // Then, it clears the stats and the limit entry from BPF maps. // See tetherOffloadRule{Add, Remove, Clear, Clean}. // [1] Adding rules on the upstream Ethernet. // Note that the default data limit is applied after the first rule is added. final Ipv6ForwardingRule ethernetRuleA = buildTestForwardingRule( ethIfIndex, NEIGH_A, MAC_A); final Ipv6ForwardingRule ethernetRuleB = buildTestForwardingRule( ethIfIndex, NEIGH_B, MAC_B); coordinator.tetherOffloadRuleAdd(mIpServer, ethernetRuleA); inOrder.verify(mNetd).tetherOffloadRuleAdd(matches(ethernetRuleA)); inOrder.verify(mNetd).tetherOffloadSetInterfaceQuota(ethIfIndex, QUOTA_UNLIMITED); coordinator.tetherOffloadRuleAdd(mIpServer, ethernetRuleB); inOrder.verify(mNetd).tetherOffloadRuleAdd(matches(ethernetRuleB)); // [2] Update the existing rules from Ethernet to cellular. final Ipv6ForwardingRule mobileRuleA = buildTestForwardingRule( mobileIfIndex, NEIGH_A, MAC_A); final Ipv6ForwardingRule mobileRuleB = buildTestForwardingRule( mobileIfIndex, NEIGH_B, MAC_B); when(mNetd.tetherOffloadGetAndClearStats(ethIfIndex)) .thenReturn(buildTestTetherStatsParcel(ethIfIndex, 10, 20, 30, 40)); // Update the existing rules for upstream changes. The rules are removed and re-added one // by one for updating upstream interface index by #tetherOffloadRuleUpdate. coordinator.tetherOffloadRuleUpdate(mIpServer, mobileIfIndex); inOrder.verify(mNetd).tetherOffloadRuleRemove(matches(ethernetRuleA)); inOrder.verify(mNetd).tetherOffloadRuleAdd(matches(mobileRuleA)); inOrder.verify(mNetd).tetherOffloadSetInterfaceQuota(mobileIfIndex, QUOTA_UNLIMITED); inOrder.verify(mNetd).tetherOffloadRuleRemove(matches(ethernetRuleB)); inOrder.verify(mNetd).tetherOffloadGetAndClearStats(ethIfIndex); inOrder.verify(mNetd).tetherOffloadRuleAdd(matches(mobileRuleB)); // [3] Clear all rules for a given IpServer. when(mNetd.tetherOffloadGetAndClearStats(mobileIfIndex)) .thenReturn(buildTestTetherStatsParcel(mobileIfIndex, 50, 60, 70, 80)); coordinator.tetherOffloadRuleClear(mIpServer); inOrder.verify(mNetd).tetherOffloadRuleRemove(matches(mobileRuleA)); inOrder.verify(mNetd).tetherOffloadRuleRemove(matches(mobileRuleB)); inOrder.verify(mNetd).tetherOffloadGetAndClearStats(mobileIfIndex); // [4] Force pushing stats update to verify that the last diff of stats is reported on all // upstreams. mTetherStatsProvider.pushTetherStats(); mTetherStatsProviderCb.expectNotifyStatsUpdated( new NetworkStats(0L, 2) .addEntry(buildTestEntry(STATS_PER_IFACE, ethIface, 10, 20, 30, 40)) .addEntry(buildTestEntry(STATS_PER_IFACE, mobileIface, 50, 60, 70, 80)), new NetworkStats(0L, 2) .addEntry(buildTestEntry(STATS_PER_UID, ethIface, 10, 20, 30, 40)) .addEntry(buildTestEntry(STATS_PER_UID, mobileIface, 50, 60, 70, 80))); } }