diff --git a/framework-t/src/android/net/nsd/NsdManager.java b/framework-t/src/android/net/nsd/NsdManager.java index d34038459f..45def3674c 100644 --- a/framework-t/src/android/net/nsd/NsdManager.java +++ b/framework-t/src/android/net/nsd/NsdManager.java @@ -243,9 +243,7 @@ public final class NsdManager { public static final int UNREGISTER_CLIENT = 22; /** @hide */ - public static final int MDNS_MONITORING_SOCKETS_CLEANUP = 23; - /** @hide */ - public static final int MDNS_DISCOVERY_MANAGER_EVENT = 24; + public static final int MDNS_DISCOVERY_MANAGER_EVENT = 23; /** Dns based service discovery protocol */ public static final int PROTOCOL_DNS_SD = 0x0001; diff --git a/service-t/src/com/android/server/NsdService.java b/service-t/src/com/android/server/NsdService.java index 36c3cd48e9..84b9f12312 100644 --- a/service-t/src/com/android/server/NsdService.java +++ b/service-t/src/com/android/server/NsdService.java @@ -27,6 +27,7 @@ import android.content.Context; import android.content.Intent; import android.net.ConnectivityManager; import android.net.INetd; +import android.net.InetAddresses; import android.net.LinkProperties; import android.net.Network; import android.net.mdns.aidl.DiscoveryInfo; @@ -75,6 +76,7 @@ import java.net.SocketException; import java.net.UnknownHostException; import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -196,6 +198,21 @@ public class NsdService extends INsdManager.Stub { } } + private class ResolutionListener extends MdnsListener { + + ResolutionListener(int clientId, int transactionId, @NonNull NsdServiceInfo reqServiceInfo, + @NonNull String listenServiceType) { + super(clientId, transactionId, reqServiceInfo, listenServiceType); + } + + @Override + public void onServiceFound(MdnsServiceInfo serviceInfo) { + mNsdStateMachine.sendMessage(MDNS_DISCOVERY_MANAGER_EVENT, mTransactionId, + NsdManager.RESOLVE_SERVICE_SUCCEEDED, + new MdnsEvent(mClientId, mReqServiceInfo.getServiceType(), serviceInfo)); + } + } + /** * Data class of mdns service callback information. */ @@ -382,9 +399,6 @@ public class NsdService extends INsdManager.Stub { maybeStartDaemon(); } break; - case NsdManager.MDNS_MONITORING_SOCKETS_CLEANUP: - maybeStopMonitoringSockets(); - break; default: Log.e(TAG, "Unhandled " + msg); return NOT_HANDLED; @@ -439,7 +453,6 @@ public class NsdService extends INsdManager.Stub { clientInfo.mClientIds.put(clientId, transactionId); clientInfo.mListeners.put(clientId, listener); mIdToClientInfoMap.put(transactionId, clientInfo); - removeMessages(NsdManager.MDNS_MONITORING_SOCKETS_CLEANUP); } private void removeListenerMap(int clientId, int transactionId, ClientInfo clientInfo) { @@ -472,7 +485,7 @@ public class NsdService extends INsdManager.Stub { if (!matcher.matches()) return null; return matcher.group(1) == null ? serviceType + ".local" - : matcher.group(1) + "._sub" + matcher.group(2) + ".local"; + : matcher.group(1) + "_sub." + matcher.group(2) + ".local"; } @Override @@ -482,7 +495,7 @@ public class NsdService extends INsdManager.Stub { final int clientId = msg.arg2; final ListenerArgs args; switch (msg.what) { - case NsdManager.DISCOVER_SERVICES: + case NsdManager.DISCOVER_SERVICES: { if (DBG) Log.d(TAG, "Discover services"); args = (ListenerArgs) msg.obj; clientInfo = mClients.get(args.connector); @@ -536,6 +549,7 @@ public class NsdService extends INsdManager.Stub { } } break; + } case NsdManager.STOP_DISCOVERY: if (DBG) Log.d(TAG, "Stop service discovery"); args = (ListenerArgs) msg.obj; @@ -626,7 +640,7 @@ public class NsdService extends INsdManager.Stub { clientId, NsdManager.FAILURE_INTERNAL_ERROR); } break; - case NsdManager.RESOLVE_SERVICE: + case NsdManager.RESOLVE_SERVICE: { if (DBG) Log.d(TAG, "Resolve service"); args = (ListenerArgs) msg.obj; clientInfo = mClients.get(args.connector); @@ -638,22 +652,43 @@ public class NsdService extends INsdManager.Stub { break; } - if (clientInfo.mResolvedService != null) { - clientInfo.onResolveServiceFailed( - clientId, NsdManager.FAILURE_ALREADY_ACTIVE); - break; - } - - maybeStartDaemon(); + final NsdServiceInfo info = args.serviceInfo; id = getUniqueId(); - if (resolveService(id, args.serviceInfo)) { - clientInfo.mResolvedService = new NsdServiceInfo(); - storeRequestMap(clientId, id, clientInfo, msg.what); + if (mMdnsDiscoveryManager != null) { + final String serviceType = constructServiceType(info.getServiceType()); + if (serviceType == null) { + clientInfo.onResolveServiceFailed(clientId, + NsdManager.FAILURE_INTERNAL_ERROR); + break; + } + + maybeStartMonitoringSockets(); + final MdnsListener listener = + new ResolutionListener(clientId, id, info, serviceType); + final MdnsSearchOptions options = MdnsSearchOptions.newBuilder() + .setNetwork(info.getNetwork()) + .setIsPassiveMode(true) + .build(); + mMdnsDiscoveryManager.registerListener(serviceType, listener, options); + storeListenerMap(clientId, id, listener, clientInfo); } else { - clientInfo.onResolveServiceFailed( - clientId, NsdManager.FAILURE_INTERNAL_ERROR); + if (clientInfo.mResolvedService != null) { + clientInfo.onResolveServiceFailed( + clientId, NsdManager.FAILURE_ALREADY_ACTIVE); + break; + } + + maybeStartDaemon(); + if (resolveService(id, args.serviceInfo)) { + clientInfo.mResolvedService = new NsdServiceInfo(); + storeRequestMap(clientId, id, clientInfo, msg.what); + } else { + clientInfo.onResolveServiceFailed( + clientId, NsdManager.FAILURE_INTERNAL_ERROR); + } } break; + } case MDNS_SERVICE_EVENT: if (!handleMDnsServiceEvent(msg.arg1, msg.arg2, msg.obj)) { return NOT_HANDLED; @@ -863,6 +898,43 @@ public class NsdService extends INsdManager.Stub { case NsdManager.SERVICE_LOST: clientInfo.onServiceLost(clientId, info); break; + case NsdManager.RESOLVE_SERVICE_SUCCEEDED: { + final MdnsServiceInfo serviceInfo = event.mMdnsServiceInfo; + // Add '.' in front of the service type that aligns with historical behavior + info.setServiceType("." + event.mRequestedServiceType); + info.setPort(serviceInfo.getPort()); + + Map attrs = serviceInfo.getAttributes(); + for (Map.Entry kv : attrs.entrySet()) { + final String key = kv.getKey(); + try { + info.setAttribute(key, serviceInfo.getAttributeAsBytes(key)); + } catch (IllegalArgumentException e) { + Log.e(TAG, "Invalid attribute", e); + } + } + try { + if (serviceInfo.getIpv4Address() != null) { + info.setHost(InetAddresses.parseNumericAddress( + serviceInfo.getIpv4Address())); + } else { + info.setHost(InetAddresses.parseNumericAddress( + serviceInfo.getIpv6Address())); + } + clientInfo.onResolveServiceSucceeded(clientId, info); + } catch (IllegalArgumentException e) { + Log.wtf(TAG, "Invalid address in RESOLVE_SERVICE_SUCCEEDED", e); + clientInfo.onResolveServiceFailed( + clientId, NsdManager.FAILURE_INTERNAL_ERROR); + } + + // Unregister the listener immediately like IMDnsEventListener design + final MdnsListener listener = clientInfo.mListeners.get(clientId); + mMdnsDiscoveryManager.unregisterListener( + listener.getListenedServiceType(), listener); + removeListenerMap(clientId, transactionId, clientInfo); + break; + } default: return false; } diff --git a/tests/unit/java/com/android/server/NsdServiceTest.java b/tests/unit/java/com/android/server/NsdServiceTest.java index a1c865f5be..1bd49a50ae 100644 --- a/tests/unit/java/com/android/server/NsdServiceTest.java +++ b/tests/unit/java/com/android/server/NsdServiceTest.java @@ -23,9 +23,11 @@ import static com.android.testutils.ContextUtils.mockService; import static libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges; import static libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges; +import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.argThat; @@ -91,6 +93,7 @@ import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import java.net.UnknownHostException; import java.util.LinkedList; import java.util.List; import java.util.Queue; @@ -111,6 +114,8 @@ public class NsdServiceTest { private static final String DOMAIN_NAME = "mytestdevice.local"; private static final int PORT = 2201; private static final int IFACE_IDX_ANY = 0; + private static final String IPV4_ADDRESS = "192.0.2.0"; + private static final String IPV6_ADDRESS = "2001:db8::"; // Records INsdManagerCallback created when NsdService#connect is called. // Only accessed on the test thread, since NsdService#connect is called by the NsdManager @@ -613,8 +618,8 @@ public class NsdServiceTest { List.of(), /* subtypes */ new String[] {"android", "local"}, /* hostName */ 12345, /* port */ - "192.0.2.0", /* ipv4Address */ - "2001:db8::", /* ipv6Address */ + IPV4_ADDRESS, + IPV6_ADDRESS, List.of(), /* textStrings */ List.of(), /* textEntries */ 1234, /* interfaceIndex */ @@ -682,6 +687,61 @@ public class NsdServiceTest { .onStartDiscoveryFailed(serviceTypeWithoutTcpOrUdpEnding, FAILURE_INTERNAL_ERROR); } + @Test + public void testResolutionWithMdnsDiscoveryManager() throws UnknownHostException { + makeServiceWithMdnsDiscoveryManagerEnabled(); + + final NsdManager client = connectClient(mService); + final ResolveListener resolveListener = mock(ResolveListener.class); + final Network network = new Network(999); + final String serviceType = "_nsd._service._tcp"; + final String constructedServiceType = "_nsd._sub._service._tcp.local"; + final ArgumentCaptor listenerCaptor = + ArgumentCaptor.forClass(MdnsServiceBrowserListener.class); + final NsdServiceInfo request = new NsdServiceInfo(SERVICE_NAME, serviceType); + request.setNetwork(network); + client.resolveService(request, resolveListener); + waitForIdle(); + verify(mSocketProvider).startMonitoringSockets(); + verify(mDiscoveryManager).registerListener(eq(constructedServiceType), + listenerCaptor.capture(), argThat(options -> network.equals(options.getNetwork()))); + + final MdnsServiceBrowserListener listener = listenerCaptor.getValue(); + final MdnsServiceInfo mdnsServiceInfo = new MdnsServiceInfo( + SERVICE_NAME, + constructedServiceType.split("\\."), + List.of(), /* subtypes */ + new String[]{"android", "local"}, /* hostName */ + PORT, + IPV4_ADDRESS, + IPV6_ADDRESS, + List.of() /* textStrings */, + List.of(MdnsServiceInfo.TextEntry.fromBytes(new byte[]{ + 'k', 'e', 'y', '=', (byte) 0xFF, (byte) 0xFE})) /* textEntries */, + 1234, + network); + + // Verify onServiceFound callback + listener.onServiceFound(mdnsServiceInfo); + final ArgumentCaptor infoCaptor = + ArgumentCaptor.forClass(NsdServiceInfo.class); + verify(resolveListener, timeout(TIMEOUT_MS)).onServiceResolved(infoCaptor.capture()); + final NsdServiceInfo info = infoCaptor.getValue(); + assertEquals(SERVICE_NAME, info.getServiceName()); + assertEquals("." + serviceType, info.getServiceType()); + assertEquals(PORT, info.getPort()); + assertTrue(info.getAttributes().containsKey("key")); + assertEquals(1, info.getAttributes().size()); + assertArrayEquals(new byte[]{(byte) 0xFF, (byte) 0xFE}, info.getAttributes().get("key")); + assertEquals(InetAddresses.parseNumericAddress(IPV4_ADDRESS), info.getHost()); + assertEquals(network, info.getNetwork()); + + // Verify the listener has been unregistered. + verify(mDiscoveryManager, timeout(TIMEOUT_MS)) + .unregisterListener(eq(constructedServiceType), any()); + verify(mSocketProvider, timeout(CLEANUP_DELAY_MS + TIMEOUT_MS)).stopMonitoringSockets(); + } + private void waitForIdle() { HandlerUtils.waitForIdle(mHandler, TIMEOUT_MS); }