1 /*
2  * Copyright (C) 2018 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.net.dhcp;
18 
19 import static android.net.InetAddresses.parseNumericAddress;
20 import static android.net.dhcp.DhcpPacket.DHCP_HOST_NAME;
21 import static android.net.dhcp.DhcpPacket.ENCAP_BOOTP;
22 import static android.net.dhcp.DhcpPacket.INADDR_ANY;
23 import static android.net.dhcp.DhcpPacket.INADDR_BROADCAST;
24 import static android.net.dhcp.DhcpServer.CMD_RECEIVE_PACKET;
25 import static android.net.dhcp.IDhcpServer.STATUS_SUCCESS;
26 
27 import static com.android.net.module.util.Inet4AddressUtils.inet4AddressToIntHTH;
28 import static com.android.networkstack.util.NetworkStackUtils.DHCP_RAPID_COMMIT_VERSION;
29 
30 import static junit.framework.Assert.assertEquals;
31 import static junit.framework.Assert.assertFalse;
32 import static junit.framework.Assert.assertNotNull;
33 import static junit.framework.Assert.assertTrue;
34 
35 import static org.mockito.ArgumentMatchers.any;
36 import static org.mockito.ArgumentMatchers.eq;
37 import static org.mockito.ArgumentMatchers.isNull;
38 import static org.mockito.Mockito.doNothing;
39 import static org.mockito.Mockito.never;
40 import static org.mockito.Mockito.times;
41 import static org.mockito.Mockito.verify;
42 import static org.mockito.Mockito.when;
43 
44 import android.annotation.NonNull;
45 import android.annotation.Nullable;
46 import android.content.Context;
47 import android.net.INetworkStackStatusCallback;
48 import android.net.IpPrefix;
49 import android.net.LinkAddress;
50 import android.net.MacAddress;
51 import android.net.dhcp.DhcpLeaseRepository.InvalidAddressException;
52 import android.net.dhcp.DhcpLeaseRepository.OutOfAddressesException;
53 import android.net.dhcp.DhcpServer.Clock;
54 import android.net.dhcp.DhcpServer.Dependencies;
55 import android.os.ConditionVariable;
56 import android.testing.AndroidTestingRunner;
57 
58 import androidx.test.filters.SmallTest;
59 
60 import com.android.net.module.util.Inet4AddressUtils;
61 import com.android.net.module.util.SharedLog;
62 import com.android.testutils.HandlerUtils;
63 
64 import org.junit.After;
65 import org.junit.Before;
66 import org.junit.Test;
67 import org.junit.runner.RunWith;
68 import org.mockito.ArgumentCaptor;
69 import org.mockito.Captor;
70 import org.mockito.Mock;
71 import org.mockito.MockitoAnnotations;
72 
73 import java.net.Inet4Address;
74 import java.nio.ByteBuffer;
75 import java.util.Arrays;
76 import java.util.Collection;
77 import java.util.Collections;
78 import java.util.HashSet;
79 import java.util.Set;
80 
81 @RunWith(AndroidTestingRunner.class)
82 @SmallTest
83 public class DhcpServerTest {
84     private static final String TEST_IFACE = "testiface";
85 
86     private static final Inet4Address TEST_SERVER_ADDR = parseAddr("192.168.0.2");
87     private static final int TEST_PREFIX_LENGTH = 20;
88     private static final LinkAddress TEST_SERVER_LINKADDR = new LinkAddress(
89             TEST_SERVER_ADDR, TEST_PREFIX_LENGTH);
90     private static final Set<Inet4Address> TEST_DEFAULT_ROUTERS = new HashSet<>(
91             Arrays.asList(parseAddr("192.168.0.123"), parseAddr("192.168.0.124")));
92     private static final Set<Inet4Address> TEST_DNS_SERVERS = new HashSet<>(
93             Arrays.asList(parseAddr("192.168.0.126"), parseAddr("192.168.0.127")));
94     private static final Set<Inet4Address> TEST_EXCLUDED_ADDRS = new HashSet<>(
95             Arrays.asList(parseAddr("192.168.0.200"), parseAddr("192.168.0.201")));
96     private static final long TEST_LEASE_TIME_SECS = 3600L;
97     private static final int TEST_MTU = 1500;
98     private static final String TEST_HOSTNAME = "testhostname";
99 
100     private static final int TEST_TRANSACTION_ID = 123;
101     private static final byte[] TEST_CLIENT_MAC_BYTES = new byte [] { 1, 2, 3, 4, 5, 6 };
102     private static final MacAddress TEST_CLIENT_MAC = MacAddress.fromBytes(TEST_CLIENT_MAC_BYTES);
103     private static final Inet4Address TEST_CLIENT_ADDR = parseAddr("192.168.0.42");
104 
105     private static final long TEST_CLOCK_TIME = 1234L;
106     private static final int TEST_LEASE_EXPTIME_SECS = 3600;
107     private static final DhcpLease TEST_LEASE = new DhcpLease(null, TEST_CLIENT_MAC,
108             TEST_CLIENT_ADDR, TEST_PREFIX_LENGTH, TEST_LEASE_EXPTIME_SECS * 1000L + TEST_CLOCK_TIME,
109             null /* hostname */);
110     private static final DhcpLease TEST_LEASE_WITH_HOSTNAME = new DhcpLease(null, TEST_CLIENT_MAC,
111             TEST_CLIENT_ADDR, TEST_PREFIX_LENGTH, TEST_LEASE_EXPTIME_SECS * 1000L + TEST_CLOCK_TIME,
112             TEST_HOSTNAME);
113     private static final int TEST_TIMEOUT_MS = 10000;
114 
115     @NonNull @Mock
116     private Context mContext;
117     @NonNull @Mock
118     private Dependencies mDeps;
119     @NonNull @Mock
120     private DhcpLeaseRepository mRepository;
121     @NonNull @Mock
122     private Clock mClock;
123     @NonNull @Mock
124     private DhcpPacketListener mPacketListener;
125     @NonNull @Mock
126     private IDhcpEventCallbacks mEventCallbacks;
127 
128     @NonNull @Captor
129     private ArgumentCaptor<ByteBuffer> mSentPacketCaptor;
130     @NonNull @Captor
131     private ArgumentCaptor<Inet4Address> mResponseDstAddrCaptor;
132 
133     @NonNull
134     private MyDhcpServer mServer;
135 
136     @Nullable
137     private String mPrevShareClassloaderProp;
138 
139     private class MyDhcpServer extends DhcpServer {
140         private final ConditionVariable mCv = new ConditionVariable(false);
141 
MyDhcpServer(Context context, String ifName, DhcpServingParams params, SharedLog log, Dependencies deps)142         MyDhcpServer(Context context, String ifName, DhcpServingParams params, SharedLog log,
143                 Dependencies deps) {
144             super(context, ifName, params, log, deps);
145         }
146 
147         @Override
onQuitting()148         protected void onQuitting() {
149             super.onQuitting();
150             mCv.open();
151         }
152 
waitForShutdown()153         public void waitForShutdown() {
154             assertTrue(mCv.block(TEST_TIMEOUT_MS));
155         }
156     }
157 
158     private final INetworkStackStatusCallback mAssertSuccessCallback =
159             new INetworkStackStatusCallback.Stub() {
160         @Override
161         public void onStatusAvailable(int statusCode) {
162             assertEquals(STATUS_SUCCESS, statusCode);
163         }
164 
165         @Override
166         public int getInterfaceVersion() {
167             return this.VERSION;
168         }
169 
170         @Override
171         public String getInterfaceHash() {
172             return this.HASH;
173         }
174     };
175 
makeServingParams()176     private DhcpServingParams makeServingParams() throws Exception {
177         return new DhcpServingParams.Builder()
178                 .setDefaultRouters(TEST_DEFAULT_ROUTERS)
179                 .setDhcpLeaseTimeSecs(TEST_LEASE_TIME_SECS)
180                 .setDnsServers(TEST_DNS_SERVERS)
181                 .setServerAddr(TEST_SERVER_LINKADDR)
182                 .setLinkMtu(TEST_MTU)
183                 .setExcludedAddrs(TEST_EXCLUDED_ADDRS)
184                 .setChangePrefixOnDecline(false)
185                 .build();
186     }
187 
startServer()188     private void startServer() throws Exception {
189         mServer.start(mAssertSuccessCallback);
190         HandlerUtils.waitForIdle(mServer.getHandler(), TEST_TIMEOUT_MS);
191     }
192 
193     @Before
setUp()194     public void setUp() throws Exception {
195         MockitoAnnotations.initMocks(this);
196 
197         when(mDeps.makeLeaseRepository(any(), any(), any())).thenReturn(mRepository);
198         when(mDeps.makeClock()).thenReturn(mClock);
199         when(mDeps.makePacketListener(any())).thenReturn(mPacketListener);
200         when(mDeps.isFeatureNotChickenedOut(eq(mContext), eq(DHCP_RAPID_COMMIT_VERSION)))
201                 .thenReturn(true);
202         doNothing().when(mDeps)
203                 .sendPacket(any(), mSentPacketCaptor.capture(), mResponseDstAddrCaptor.capture());
204         when(mClock.elapsedRealtime()).thenReturn(TEST_CLOCK_TIME);
205         when(mPacketListener.start()).thenReturn(true);
206 
207         mServer = new MyDhcpServer(mContext, TEST_IFACE, makeServingParams(),
208                 new SharedLog(DhcpServerTest.class.getSimpleName()), mDeps);
209     }
210 
211     @After
tearDown()212     public void tearDown() throws Exception {
213         verify(mRepository, never()).addLeaseCallbacks(eq(null));
214         mServer.stop(mAssertSuccessCallback);
215         mServer.waitForShutdown();
216         verify(mPacketListener, times(1)).stop();
217     }
218 
219     @Test
testStart()220     public void testStart() throws Exception {
221         startServer();
222 
223         verify(mPacketListener, times(1)).start();
224     }
225 
226     @Test
testStartWithCallbacks()227     public void testStartWithCallbacks() throws Exception {
228         mServer.start(mAssertSuccessCallback, mEventCallbacks);
229         HandlerUtils.waitForIdle(mServer.getHandler(), TEST_TIMEOUT_MS);
230         verify(mRepository).addLeaseCallbacks(eq(mEventCallbacks));
231     }
232 
233     @Test
testDiscover()234     public void testDiscover() throws Exception {
235         startServer();
236 
237         // TODO: refactor packet construction to eliminate unnecessary/confusing/duplicate fields
238         when(mRepository.getOffer(isNull() /* clientId */, eq(TEST_CLIENT_MAC),
239                 eq(INADDR_ANY) /* relayAddr */, isNull() /* reqAddr */, isNull() /* hostname */))
240                 .thenReturn(TEST_LEASE);
241 
242         final DhcpDiscoverPacket discover = new DhcpDiscoverPacket(TEST_TRANSACTION_ID,
243                 (short) 0 /* secs */, INADDR_ANY /* relayIp */, TEST_CLIENT_MAC_BYTES,
244                 false /* broadcast */, INADDR_ANY /* srcIp */, false /* rapidCommit */);
245         mServer.sendMessage(CMD_RECEIVE_PACKET, discover);
246         HandlerUtils.waitForIdle(mServer.getHandler(), TEST_TIMEOUT_MS);
247 
248         assertResponseSentTo(TEST_CLIENT_ADDR);
249         final DhcpOfferPacket packet = assertOffer(getPacket());
250         assertMatchesTestLease(packet);
251     }
252 
253     @Test
testDiscover_RapidCommit()254     public void testDiscover_RapidCommit() throws Exception {
255         startServer();
256 
257         when(mDeps.isFeatureNotChickenedOut(eq(mContext), eq(DHCP_RAPID_COMMIT_VERSION)))
258                 .thenReturn(true);
259         when(mRepository.getCommittedLease(isNull() /* clientId */, eq(TEST_CLIENT_MAC),
260                 eq(INADDR_ANY) /* relayAddr */, isNull() /* hostname */)).thenReturn(TEST_LEASE);
261 
262         final DhcpDiscoverPacket discover = new DhcpDiscoverPacket(TEST_TRANSACTION_ID,
263                 (short) 0 /* secs */, INADDR_ANY /* relayIp */, TEST_CLIENT_MAC_BYTES,
264                 false /* broadcast */, INADDR_ANY /* srcIp */, true /* rapidCommit */);
265         mServer.sendMessage(CMD_RECEIVE_PACKET, discover);
266         HandlerUtils.waitForIdle(mServer.getHandler(), TEST_TIMEOUT_MS);
267 
268         assertResponseSentTo(TEST_CLIENT_ADDR);
269         final DhcpAckPacket packet = assertAck(getPacket());
270         assertMatchesTestLease(packet);
271     }
272 
273     @Test
testDiscover_OutOfAddresses()274     public void testDiscover_OutOfAddresses() throws Exception {
275         startServer();
276 
277         when(mRepository.getOffer(isNull() /* clientId */, eq(TEST_CLIENT_MAC),
278                 eq(INADDR_ANY) /* relayAddr */, isNull() /* reqAddr */, isNull() /* hostname */))
279                 .thenThrow(new OutOfAddressesException("Test exception"));
280 
281         final DhcpDiscoverPacket discover = new DhcpDiscoverPacket(TEST_TRANSACTION_ID,
282                 (short) 0 /* secs */, INADDR_ANY /* relayIp */, TEST_CLIENT_MAC_BYTES,
283                 false /* broadcast */, INADDR_ANY /* srcIp */, false /* rapidCommit */);
284         mServer.sendMessage(CMD_RECEIVE_PACKET, discover);
285         HandlerUtils.waitForIdle(mServer.getHandler(), TEST_TIMEOUT_MS);
286 
287         assertResponseSentTo(INADDR_BROADCAST);
288         final DhcpNakPacket packet = assertNak(getPacket());
289         assertMatchesClient(packet);
290     }
291 
makeRequestSelectingPacket()292     private DhcpRequestPacket makeRequestSelectingPacket() {
293         final DhcpRequestPacket request = new DhcpRequestPacket(TEST_TRANSACTION_ID,
294                 (short) 0 /* secs */, INADDR_ANY /* clientIp */, INADDR_ANY /* relayIp */,
295                 TEST_CLIENT_MAC_BYTES, false /* broadcast */);
296         request.mServerIdentifier = TEST_SERVER_ADDR;
297         request.mRequestedIp = TEST_CLIENT_ADDR;
298         return request;
299     }
300 
301     @Test
testRequest_Selecting_Ack()302     public void testRequest_Selecting_Ack() throws Exception {
303         startServer();
304 
305         when(mRepository.requestLease(isNull() /* clientId */, eq(TEST_CLIENT_MAC),
306                 eq(INADDR_ANY) /* clientAddr */, eq(INADDR_ANY) /* relayAddr */,
307                 eq(TEST_CLIENT_ADDR) /* reqAddr */, eq(true) /* sidSet */, eq(TEST_HOSTNAME)))
308                 .thenReturn(TEST_LEASE_WITH_HOSTNAME);
309 
310         final DhcpRequestPacket request = makeRequestSelectingPacket();
311         request.mHostName = TEST_HOSTNAME;
312         request.mRequestedParams = new byte[] { DHCP_HOST_NAME };
313         mServer.sendMessage(CMD_RECEIVE_PACKET, request);
314         HandlerUtils.waitForIdle(mServer.getHandler(), TEST_TIMEOUT_MS);
315 
316         assertResponseSentTo(TEST_CLIENT_ADDR);
317         final DhcpAckPacket packet = assertAck(getPacket());
318         assertMatchesTestLease(packet, TEST_HOSTNAME);
319     }
320 
321     @Test
testRequest_Selecting_Nak()322     public void testRequest_Selecting_Nak() throws Exception {
323         startServer();
324 
325         when(mRepository.requestLease(isNull(), eq(TEST_CLIENT_MAC),
326                 eq(INADDR_ANY) /* clientAddr */, eq(INADDR_ANY) /* relayAddr */,
327                 eq(TEST_CLIENT_ADDR) /* reqAddr */, eq(true) /* sidSet */, isNull() /* hostname */))
328                 .thenThrow(new InvalidAddressException("Test error"));
329 
330         final DhcpRequestPacket request = makeRequestSelectingPacket();
331         mServer.sendMessage(CMD_RECEIVE_PACKET, request);
332         HandlerUtils.waitForIdle(mServer.getHandler(), TEST_TIMEOUT_MS);
333 
334         assertResponseSentTo(INADDR_BROADCAST);
335         final DhcpNakPacket packet = assertNak(getPacket());
336         assertMatchesClient(packet);
337     }
338 
339     @Test
testRelease()340     public void testRelease() throws Exception {
341         startServer();
342 
343         final DhcpReleasePacket release = new DhcpReleasePacket(TEST_TRANSACTION_ID,
344                 TEST_SERVER_ADDR, TEST_CLIENT_ADDR,
345                 INADDR_ANY /* relayIp */, TEST_CLIENT_MAC_BYTES);
346         mServer.sendMessage(CMD_RECEIVE_PACKET, release);
347         HandlerUtils.waitForIdle(mServer.getHandler(), TEST_TIMEOUT_MS);
348 
349         verify(mRepository, times(1))
350                 .releaseLease(isNull(), eq(TEST_CLIENT_MAC), eq(TEST_CLIENT_ADDR));
351     }
352 
353     @Test
testDecline_LeaseDoesNotExist()354     public void testDecline_LeaseDoesNotExist() throws Exception {
355         when(mRepository.markAndReleaseDeclinedLease(isNull(), eq(TEST_CLIENT_MAC),
356                 eq(TEST_CLIENT_ADDR))).thenReturn(false);
357 
358         startServer();
359         runOnReceivedDeclinePacket();
360         verify(mEventCallbacks, never()).onNewPrefixRequest(any());
361     }
362 
runOnReceivedDeclinePacket()363     private void runOnReceivedDeclinePacket() throws Exception {
364         when(mRepository.getCommittedLeases()).thenReturn(
365                 Arrays.asList(new DhcpLease(null, TEST_CLIENT_MAC, TEST_CLIENT_ADDR,
366                         TEST_PREFIX_LENGTH, TEST_LEASE_EXPTIME_SECS * 1000L + TEST_CLOCK_TIME,
367                         TEST_HOSTNAME)));
368         final DhcpDeclinePacket decline = new DhcpDeclinePacket(TEST_TRANSACTION_ID,
369                 (short) 0 /* secs */, INADDR_ANY /* clientIp */, INADDR_ANY /* yourIp */,
370                 INADDR_ANY /* nextIp */, INADDR_ANY /* relayIp */, TEST_CLIENT_MAC_BYTES,
371                 TEST_CLIENT_ADDR /* requestedIp */, TEST_SERVER_ADDR /* serverIdentifier */);
372         mServer.sendMessage(CMD_RECEIVE_PACKET, decline);
373         HandlerUtils.waitForIdle(mServer.getHandler(), TEST_TIMEOUT_MS);
374 
375         verify(mRepository).markAndReleaseDeclinedLease(isNull(), eq(TEST_CLIENT_MAC),
376                 eq(TEST_CLIENT_ADDR));
377     }
378 
toIntArray(@onNull Collection<Inet4Address> addrs)379     private int[] toIntArray(@NonNull Collection<Inet4Address> addrs) {
380         return addrs.stream().mapToInt(Inet4AddressUtils::inet4AddressToIntHTH).toArray();
381     }
382 
updateServingParams(Set<Inet4Address> defaultRouters, Set<Inet4Address> dnsServers, Set<Inet4Address> excludedAddrs, LinkAddress serverAddr, boolean changePrefixOnDecline)383     private void updateServingParams(Set<Inet4Address> defaultRouters,
384             Set<Inet4Address> dnsServers, Set<Inet4Address> excludedAddrs, LinkAddress serverAddr,
385             boolean changePrefixOnDecline) throws Exception {
386         final DhcpServingParamsParcel params = new DhcpServingParamsParcel();
387         params.serverAddr = inet4AddressToIntHTH((Inet4Address) serverAddr.getAddress());
388         params.serverAddrPrefixLength = serverAddr.getPrefixLength();
389         params.defaultRouters = toIntArray(defaultRouters);
390         params.dnsServers = toIntArray(dnsServers);
391         params.excludedAddrs = toIntArray(excludedAddrs);
392         params.dhcpLeaseTimeSecs = TEST_LEASE_EXPTIME_SECS;
393         params.linkMtu = TEST_MTU;
394         params.metered = true;
395         params.changePrefixOnDecline = changePrefixOnDecline;
396 
397         mServer.updateParams(params, mAssertSuccessCallback);
398         HandlerUtils.waitForIdle(mServer.getHandler(), TEST_TIMEOUT_MS);
399     }
400 
401     @Test
testChangePrefixOnDecline()402     public void testChangePrefixOnDecline() throws Exception {
403         when(mRepository.markAndReleaseDeclinedLease(isNull(), eq(TEST_CLIENT_MAC),
404                 eq(TEST_CLIENT_ADDR))).thenReturn(true);
405 
406         mServer.start(mAssertSuccessCallback, mEventCallbacks);
407         HandlerUtils.waitForIdle(mServer.getHandler(), TEST_TIMEOUT_MS);
408         verify(mRepository).addLeaseCallbacks(eq(mEventCallbacks));
409 
410         // Enable changePrefixOnDecline
411         updateServingParams(TEST_DEFAULT_ROUTERS, TEST_DNS_SERVERS, TEST_EXCLUDED_ADDRS,
412                 TEST_SERVER_LINKADDR, true /* changePrefixOnDecline */);
413 
414         runOnReceivedDeclinePacket();
415         final IpPrefix servingPrefix = DhcpServingParams.makeIpPrefix(TEST_SERVER_LINKADDR);
416         verify(mEventCallbacks).onNewPrefixRequest(eq(servingPrefix));
417 
418         final Inet4Address serverAddr = parseAddr("192.168.51.129");
419         final LinkAddress srvLinkAddr = new LinkAddress(serverAddr, 24);
420         final Set<Inet4Address> srvAddr = new HashSet<>(Collections.singletonList(serverAddr));
421         final Set<Inet4Address> excludedAddrs = new HashSet<>(
422                 Arrays.asList(parseAddr("192.168.51.200"), parseAddr("192.168.51.201")));
423 
424         // Simulate IpServer updates the serving params with a new prefix.
425         updateServingParams(srvAddr, srvAddr, excludedAddrs, srvLinkAddr,
426                 true /* changePrefixOnDecline */);
427 
428         final Inet4Address clientAddr = parseAddr("192.168.51.42");
429         final DhcpLease lease = new DhcpLease(null, TEST_CLIENT_MAC,
430                 clientAddr, 24 /*prefixLen*/, TEST_LEASE_EXPTIME_SECS * 1000L + TEST_CLOCK_TIME,
431                 null /* hostname */);
432         when(mRepository.getOffer(isNull() /* clientId */, eq(TEST_CLIENT_MAC),
433                 eq(INADDR_ANY) /* relayAddr */, isNull() /* reqAddr */, isNull() /* hostname */))
434                 .thenReturn(lease);
435 
436         // Test discover packet
437         final DhcpDiscoverPacket discover = new DhcpDiscoverPacket(TEST_TRANSACTION_ID,
438                 (short) 0 /* secs */, INADDR_ANY /* relayIp */, TEST_CLIENT_MAC_BYTES,
439                 false /* broadcast */, INADDR_ANY /* srcIp */, false /* rapidCommit */);
440         mServer.sendMessage(CMD_RECEIVE_PACKET, discover);
441         HandlerUtils.waitForIdle(mServer.getHandler(), TEST_TIMEOUT_MS);
442         assertResponseSentTo(clientAddr);
443         final DhcpOfferPacket packet = assertOffer(getPacket());
444         assertMatchesLease(packet, serverAddr, clientAddr, null);
445     }
446 
447     /* TODO: add more tests once packet construction is refactored, including:
448      *  - usage of giaddr
449      *  - usage of broadcast bit
450      *  - other request states (init-reboot/renewing/rebinding)
451      */
452 
assertMatchesLease(@onNull DhcpPacket packet, @NonNull Inet4Address srvAddr, @NonNull Inet4Address clientAddr, @Nullable String hostname)453     private void assertMatchesLease(@NonNull DhcpPacket packet, @NonNull Inet4Address srvAddr,
454             @NonNull Inet4Address clientAddr, @Nullable String hostname) {
455         assertMatchesClient(packet);
456         assertFalse(packet.hasExplicitClientId());
457         assertEquals(srvAddr, packet.mServerIdentifier);
458         assertEquals(clientAddr, packet.mYourIp);
459         assertNotNull(packet.mLeaseTime);
460         assertEquals(TEST_LEASE_EXPTIME_SECS, (int) packet.mLeaseTime);
461         assertEquals(hostname, packet.mHostName);
462     }
463 
assertMatchesTestLease(@onNull DhcpPacket packet, @Nullable String hostname)464     private void assertMatchesTestLease(@NonNull DhcpPacket packet, @Nullable String hostname) {
465         assertMatchesLease(packet, TEST_SERVER_ADDR, TEST_CLIENT_ADDR, hostname);
466     }
467 
assertMatchesTestLease(@onNull DhcpPacket packet)468     private void assertMatchesTestLease(@NonNull DhcpPacket packet) {
469         assertMatchesTestLease(packet, null);
470     }
471 
assertMatchesClient(@onNull DhcpPacket packet)472     private void assertMatchesClient(@NonNull DhcpPacket packet) {
473         assertEquals(TEST_TRANSACTION_ID, packet.mTransId);
474         assertEquals(TEST_CLIENT_MAC, MacAddress.fromBytes(packet.mClientMac));
475     }
476 
assertResponseSentTo(@onNull Inet4Address addr)477     private void assertResponseSentTo(@NonNull Inet4Address addr) {
478         assertEquals(addr, mResponseDstAddrCaptor.getValue());
479     }
480 
assertNak(@ullable DhcpPacket packet)481     private static DhcpNakPacket assertNak(@Nullable DhcpPacket packet) {
482         assertTrue(packet instanceof DhcpNakPacket);
483         return (DhcpNakPacket) packet;
484     }
485 
assertAck(@ullable DhcpPacket packet)486     private static DhcpAckPacket assertAck(@Nullable DhcpPacket packet) {
487         assertTrue(packet instanceof DhcpAckPacket);
488         return (DhcpAckPacket) packet;
489     }
490 
assertOffer(@ullable DhcpPacket packet)491     private static DhcpOfferPacket assertOffer(@Nullable DhcpPacket packet) {
492         assertTrue(packet instanceof DhcpOfferPacket);
493         return (DhcpOfferPacket) packet;
494     }
495 
getPacket()496     private DhcpPacket getPacket() throws Exception {
497         verify(mDeps, times(1)).sendPacket(any(), any(), any());
498         return DhcpPacket.decodeFullPacket(mSentPacketCaptor.getValue(), ENCAP_BOOTP,
499                 new byte[0] /* optionsToSkip */);
500     }
501 
parseAddr(@ullable String inet4Addr)502     private static Inet4Address parseAddr(@Nullable String inet4Addr) {
503         return (Inet4Address) parseNumericAddress(inet4Addr);
504     }
505 }
506