Implement exit announcements

Build ExitAnnouncementInfo in MdnsRecordRepository.exitService. Use a
separate class for AnnouncementInfo and ExitAnnouncementInfo, so
announcement callbacks can differentiate each case.

Bug: 241738458
Test: atest
Change-Id: I3b1ad1bef3dc1514479d7c789ef06b6a7de02e59
This commit is contained in:
Remi NGUYEN VAN
2023-01-12 20:30:31 +09:00
parent bdc2d50c49
commit e4bd27f919
6 changed files with 232 additions and 42 deletions

View File

@@ -22,6 +22,7 @@ import android.os.HandlerThread
import android.os.SystemClock
import com.android.internal.util.HexDump
import com.android.server.connectivity.mdns.MdnsAnnouncer.AnnouncementInfo
import com.android.server.connectivity.mdns.MdnsAnnouncer.BaseAnnouncementInfo
import com.android.server.connectivity.mdns.MdnsRecordRepository.getReverseDnsAddress
import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo
import com.android.testutils.DevSdkIgnoreRunner
@@ -67,7 +68,7 @@ class MdnsAnnouncerTest {
private class TestAnnouncementInfo(
announcedRecords: List<MdnsRecord>,
additionalRecords: List<MdnsRecord>
) : AnnouncementInfo(announcedRecords, additionalRecords) {
) : AnnouncementInfo(1 /* serviceId */, announcedRecords, additionalRecords) {
override fun getDelayMs(nextIndex: Int) =
if (nextIndex < FIRST_ANNOUNCES_COUNT) {
FIRST_ANNOUNCES_DELAY
@@ -81,7 +82,7 @@ class MdnsAnnouncerTest {
val replySender = MdnsReplySender(thread.looper, socket, buffer)
@Suppress("UNCHECKED_CAST")
val cb = mock(MdnsPacketRepeater.PacketRepeaterCallback::class.java)
as MdnsPacketRepeater.PacketRepeaterCallback<AnnouncementInfo>
as MdnsPacketRepeater.PacketRepeaterCallback<BaseAnnouncementInfo>
val announcer = MdnsAnnouncer("testiface", thread.looper, replySender, cb)
/*
The expected packet replicates records announced when registering a service, as observed in

View File

@@ -22,6 +22,8 @@ import android.net.nsd.NsdServiceInfo
import android.os.Build
import android.os.HandlerThread
import com.android.server.connectivity.mdns.MdnsAnnouncer.AnnouncementInfo
import com.android.server.connectivity.mdns.MdnsAnnouncer.BaseAnnouncementInfo
import com.android.server.connectivity.mdns.MdnsAnnouncer.ExitAnnouncementInfo
import com.android.server.connectivity.mdns.MdnsInterfaceAdvertiser.EXIT_ANNOUNCEMENT_DELAY_MS
import com.android.server.connectivity.mdns.MdnsPacketRepeater.PacketRepeaterCallback
import com.android.server.connectivity.mdns.MdnsProber.ProbingInfo
@@ -35,8 +37,10 @@ import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
import org.mockito.Mockito.any
import org.mockito.Mockito.anyInt
import org.mockito.Mockito.doAnswer
import org.mockito.Mockito.doReturn
import org.mockito.Mockito.mock
import org.mockito.Mockito.times
import org.mockito.Mockito.verify
private const val LOG_TAG = "testlogtag"
@@ -66,7 +70,7 @@ class MdnsInterfaceAdvertiserTest {
private val probeCbCaptor = ArgumentCaptor.forClass(PacketRepeaterCallback::class.java)
as ArgumentCaptor<PacketRepeaterCallback<ProbingInfo>>
private val announceCbCaptor = ArgumentCaptor.forClass(PacketRepeaterCallback::class.java)
as ArgumentCaptor<PacketRepeaterCallback<AnnouncementInfo>>
as ArgumentCaptor<PacketRepeaterCallback<BaseAnnouncementInfo>>
private val probeCb get() = probeCbCaptor.value
private val announceCb get() = announceCbCaptor.value
@@ -82,7 +86,21 @@ class MdnsInterfaceAdvertiserTest {
doReturn(announcer).`when`(deps).makeMdnsAnnouncer(any(), any(), any(), any())
doReturn(prober).`when`(deps).makeMdnsProber(any(), any(), any(), any())
doReturn(-1).`when`(repository).addService(anyInt(), any())
val knownServices = mutableSetOf<Int>()
doAnswer { inv ->
knownServices.add(inv.getArgument(0))
-1
}.`when`(repository).addService(anyInt(), any())
doAnswer { inv ->
knownServices.remove(inv.getArgument(0))
null
}.`when`(repository).removeService(anyInt())
doAnswer {
knownServices.toIntArray().also { knownServices.clear() }
}.`when`(repository).clearServices()
doAnswer { inv ->
knownServices.contains(inv.getArgument(0))
}.`when`(repository).hasActiveService(anyInt())
thread.start()
advertiser.start()
@@ -97,18 +115,7 @@ class MdnsInterfaceAdvertiserTest {
@Test
fun testAddRemoveService() {
val testProbingInfo = mock(ProbingInfo::class.java)
doReturn(TEST_SERVICE_ID_1).`when`(testProbingInfo).serviceId
doReturn(testProbingInfo).`when`(repository).setServiceProbing(TEST_SERVICE_ID_1)
advertiser.addService(TEST_SERVICE_ID_1, TEST_SERVICE_1)
verify(repository).addService(TEST_SERVICE_ID_1, TEST_SERVICE_1)
verify(prober).startProbing(testProbingInfo)
// Simulate probing success: continues to announcing
val testAnnouncementInfo = mock(AnnouncementInfo::class.java)
doReturn(testAnnouncementInfo).`when`(repository).onProbingSucceeded(testProbingInfo)
probeCb.onFinished(testProbingInfo)
val testAnnouncementInfo = addServiceAndFinishProbing(TEST_SERVICE_ID_1, TEST_SERVICE_1)
verify(announcer).startSending(TEST_SERVICE_ID_1, testAnnouncementInfo,
0L /* initialDelayMs */)
@@ -117,7 +124,7 @@ class MdnsInterfaceAdvertiserTest {
verify(cb).onRegisterServiceSucceeded(advertiser, TEST_SERVICE_ID_1)
// Remove the service: expect exit announcements
val testExitInfo = mock(AnnouncementInfo::class.java)
val testExitInfo = mock(ExitAnnouncementInfo::class.java)
doReturn(testExitInfo).`when`(repository).exitService(TEST_SERVICE_ID_1)
advertiser.removeService(TEST_SERVICE_ID_1)
@@ -125,7 +132,45 @@ class MdnsInterfaceAdvertiserTest {
verify(announcer).stop(TEST_SERVICE_ID_1)
verify(announcer).startSending(TEST_SERVICE_ID_1, testExitInfo, EXIT_ANNOUNCEMENT_DELAY_MS)
// TODO: after exit announcements are implemented, verify that announceCb.onFinished causes
// cb.onDestroyed to be called.
// Exit announcements finish: the advertiser has no left service and destroys itself
announceCb.onFinished(testExitInfo)
thread.waitForIdle(TIMEOUT_MS)
verify(cb).onDestroyed(socket)
}
@Test
fun testDoubleRemove() {
addServiceAndFinishProbing(TEST_SERVICE_ID_1, TEST_SERVICE_1)
val testExitInfo = mock(ExitAnnouncementInfo::class.java)
doReturn(testExitInfo).`when`(repository).exitService(TEST_SERVICE_ID_1)
advertiser.removeService(TEST_SERVICE_ID_1)
verify(prober).stop(TEST_SERVICE_ID_1)
verify(announcer).stop(TEST_SERVICE_ID_1)
verify(announcer).startSending(TEST_SERVICE_ID_1, testExitInfo, EXIT_ANNOUNCEMENT_DELAY_MS)
doReturn(false).`when`(repository).hasActiveService(TEST_SERVICE_ID_1)
advertiser.removeService(TEST_SERVICE_ID_1)
// Prober, announcer were still stopped only one time
verify(prober, times(1)).stop(TEST_SERVICE_ID_1)
verify(announcer, times(1)).stop(TEST_SERVICE_ID_1)
}
private fun addServiceAndFinishProbing(serviceId: Int, serviceInfo: NsdServiceInfo):
AnnouncementInfo {
val testProbingInfo = mock(ProbingInfo::class.java)
doReturn(serviceId).`when`(testProbingInfo).serviceId
doReturn(testProbingInfo).`when`(repository).setServiceProbing(serviceId)
advertiser.addService(serviceId, serviceInfo)
verify(repository).addService(serviceId, serviceInfo)
verify(prober).startProbing(testProbingInfo)
// Simulate probing success: continues to announcing
val testAnnouncementInfo = mock(AnnouncementInfo::class.java)
doReturn(testAnnouncementInfo).`when`(repository).onProbingSucceeded(testProbingInfo)
probeCb.onFinished(testProbingInfo)
return testAnnouncementInfo
}
}

View File

@@ -30,6 +30,7 @@ import java.util.Collections
import kotlin.test.assertContentEquals
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
import kotlin.test.assertFalse
import kotlin.test.assertNotNull
import kotlin.test.assertTrue
import org.junit.After
@@ -129,10 +130,63 @@ class MdnsRecordRepositoryTest {
}
}
@Test
fun testHasActiveService() {
val repository = MdnsRecordRepository(thread.looper, deps)
assertFalse(repository.hasActiveService(TEST_SERVICE_ID_1))
repository.addService(TEST_SERVICE_ID_1, TEST_SERVICE_1)
assertTrue(repository.hasActiveService(TEST_SERVICE_ID_1))
val probingInfo = repository.setServiceProbing(TEST_SERVICE_ID_1)
repository.onProbingSucceeded(probingInfo)
repository.onAdvertisementSent(TEST_SERVICE_ID_1)
assertTrue(repository.hasActiveService(TEST_SERVICE_ID_1))
repository.exitService(TEST_SERVICE_ID_1)
assertFalse(repository.hasActiveService(TEST_SERVICE_ID_1))
}
@Test
fun testExitAnnouncements() {
val repository = MdnsRecordRepository(thread.looper, deps)
repository.updateAddresses(TEST_ADDRESSES)
repository.addService(TEST_SERVICE_ID_1, TEST_SERVICE_1)
val probingInfo = repository.setServiceProbing(TEST_SERVICE_ID_1)
repository.onProbingSucceeded(probingInfo)
repository.onAdvertisementSent(TEST_SERVICE_ID_1)
val exitAnnouncement = repository.exitService(TEST_SERVICE_ID_1)
assertNotNull(exitAnnouncement)
assertEquals(1, repository.servicesCount)
val packet = exitAnnouncement.getPacket(0)
assertEquals(0x8400 /* response, authoritative */, packet.flags)
assertEquals(0, packet.questions.size)
assertEquals(0, packet.authorityRecords.size)
assertEquals(0, packet.additionalRecords.size)
assertContentEquals(listOf(
MdnsPointerRecord(
arrayOf("_testservice", "_tcp", "local"),
0L /* receiptTimeMillis */,
true /* cacheFlush */,
0L /* ttlMillis */,
arrayOf("MyTestService", "_testservice", "_tcp", "local"))
), packet.answers)
repository.removeService(TEST_SERVICE_ID_1)
assertEquals(0, repository.servicesCount)
}
@Test
fun testExitingServiceReAdded() {
val repository = MdnsRecordRepository(thread.looper, deps)
repository.addService(TEST_SERVICE_ID_1, TEST_SERVICE_1)
val probingInfo = repository.setServiceProbing(TEST_SERVICE_ID_1)
repository.onProbingSucceeded(probingInfo)
repository.onAdvertisementSent(TEST_SERVICE_ID_1)
repository.exitService(TEST_SERVICE_ID_1)
assertEquals(TEST_SERVICE_ID_1, repository.addService(TEST_SERVICE_ID_2, TEST_SERVICE_1))
@@ -262,7 +316,7 @@ class MdnsRecordRepositoryTest {
@Test
fun testGetReverseDnsAddress() {
val expectedV6 = "1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.B.D.1.0.0.2.ip6.arpa"
val expectedV6 = "1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.B.D.0.1.0.0.2.ip6.arpa"
.split(".").toTypedArray()
assertContentEquals(expectedV6, getReverseDnsAddress(parseNumericAddress("2001:db8::1")))
val expectedV4 = "123.2.0.192.in-addr.arpa".split(".").toTypedArray()