diff --git a/framework-t/Android.bp b/framework-t/Android.bp index ffa2857334..9520ef60b4 100644 --- a/framework-t/Android.bp +++ b/framework-t/Android.bp @@ -82,6 +82,17 @@ filegroup { visibility: ["//packages/modules/Connectivity:__subpackages__"], } +// The filegroup lists files that are necessary for verifying building mdns as a standalone, +// for use with service-connectivity-mdns-standalone-build-test +filegroup { + name: "framework-connectivity-t-mdns-standalone-build-sources", + srcs: [ + "src/android/net/nsd/OffloadEngine.java", + "src/android/net/nsd/OffloadServiceInfo.java", + ], + visibility: ["//packages/modules/Connectivity:__subpackages__"], +} + java_library { name: "framework-connectivity-t-pre-jarjar", defaults: ["framework-connectivity-t-defaults"], diff --git a/framework-t/src/android/net/nsd/INsdServiceConnector.aidl b/framework-t/src/android/net/nsd/INsdServiceConnector.aidl index 55331540c0..e671db15e8 100644 --- a/framework-t/src/android/net/nsd/INsdServiceConnector.aidl +++ b/framework-t/src/android/net/nsd/INsdServiceConnector.aidl @@ -17,6 +17,7 @@ package android.net.nsd; import android.net.nsd.INsdManagerCallback; +import android.net.nsd.IOffloadEngine; import android.net.nsd.NsdServiceInfo; import android.os.Messenger; @@ -35,4 +36,6 @@ interface INsdServiceConnector { void stopResolution(int listenerKey); void registerServiceInfoCallback(int listenerKey, in NsdServiceInfo serviceInfo); void unregisterServiceInfoCallback(int listenerKey); + void registerOffloadEngine(String ifaceName, in IOffloadEngine cb, long offloadCapabilities, long offloadType); + void unregisterOffloadEngine(in IOffloadEngine cb); } \ No newline at end of file diff --git a/framework-t/src/android/net/nsd/IOffloadEngine.aidl b/framework-t/src/android/net/nsd/IOffloadEngine.aidl new file mode 100644 index 0000000000..379c2e0bc4 --- /dev/null +++ b/framework-t/src/android/net/nsd/IOffloadEngine.aidl @@ -0,0 +1,28 @@ +/** + * Copyright (c) 2023, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net.nsd; + +import android.net.nsd.OffloadServiceInfo; + +/** + * Callbacks from NsdService to inform providers of packet offload. + * @hide + */ +oneway interface IOffloadEngine { + void onOffloadServiceUpdated(in OffloadServiceInfo info); + void onOffloadServiceRemoved(in OffloadServiceInfo info); +} diff --git a/framework-t/src/android/net/nsd/NsdManager.java b/framework-t/src/android/net/nsd/NsdManager.java index 2930cbdf3c..934f185e50 100644 --- a/framework-t/src/android/net/nsd/NsdManager.java +++ b/framework-t/src/android/net/nsd/NsdManager.java @@ -16,6 +16,9 @@ package android.net.nsd; +import static android.Manifest.permission.NETWORK_SETTINGS; +import static android.Manifest.permission.NETWORK_STACK; +import static android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK; import static android.net.connectivity.ConnectivityCompatChanges.ENABLE_PLATFORM_MDNS_BACKEND; import static android.net.connectivity.ConnectivityCompatChanges.RUN_NATIVE_NSD_ONLY_IF_LEGACY_APPS_T_AND_LATER; @@ -45,9 +48,11 @@ import android.util.SparseArray; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; +import com.android.net.module.util.CollectionUtils; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.util.ArrayList; import java.util.Objects; import java.util.concurrent.Executor; @@ -246,6 +251,10 @@ public final class NsdManager { public static final int UNREGISTER_SERVICE_CALLBACK = 31; /** @hide */ public static final int UNREGISTER_SERVICE_CALLBACK_SUCCEEDED = 32; + /** @hide */ + public static final int REGISTER_OFFLOAD_ENGINE = 33; + /** @hide */ + public static final int UNREGISTER_OFFLOAD_ENGINE = 34; /** Dns based service discovery protocol */ public static final int PROTOCOL_DNS_SD = 0x0001; @@ -313,8 +322,107 @@ public final class NsdManager { private final ArrayMap mPerNetworkDiscoveryMap = new ArrayMap<>(); + @GuardedBy("mOffloadEngines") + private final ArrayList mOffloadEngines = new ArrayList<>(); private final ServiceHandler mHandler; + private static class OffloadEngineProxy extends IOffloadEngine.Stub { + private final Executor mExecutor; + private final OffloadEngine mEngine; + + private OffloadEngineProxy(@NonNull Executor executor, @NonNull OffloadEngine appCb) { + mExecutor = executor; + mEngine = appCb; + } + + @Override + public void onOffloadServiceUpdated(OffloadServiceInfo info) { + mExecutor.execute(() -> mEngine.onOffloadServiceUpdated(info)); + } + + @Override + public void onOffloadServiceRemoved(OffloadServiceInfo info) { + mExecutor.execute(() -> mEngine.onOffloadServiceRemoved(info)); + } + } + + /** + * Registers an OffloadEngine with NsdManager. + * + * A caller can register itself as an OffloadEngine if it supports mDns hardware offload. + * The caller must implement the {@link OffloadEngine} interface and update hardware offload + * state property when the {@link OffloadEngine#onOffloadServiceUpdated} and + * {@link OffloadEngine#onOffloadServiceRemoved} callback are called. Multiple engines may be + * registered for the same interface, and that the same engine cannot be registered twice. + * + * @param ifaceName indicates which network interface the hardware offload runs on + * @param offloadType the type of offload that the offload engine support + * @param offloadCapability the capabilities of the offload engine + * @param executor the executor on which to receive the offload callbacks + * @param engine the OffloadEngine that will receive the offload callbacks + * @throws IllegalStateException if the engine is already registered. + * + * @hide + */ + //@SystemApi + @RequiresPermission(anyOf = {NETWORK_SETTINGS, PERMISSION_MAINLINE_NETWORK_STACK, + NETWORK_STACK}) + public void registerOffloadEngine(@NonNull String ifaceName, + @OffloadEngine.OffloadType long offloadType, + @OffloadEngine.OffloadCapability long offloadCapability, @NonNull Executor executor, + @NonNull OffloadEngine engine) { + Objects.requireNonNull(ifaceName); + Objects.requireNonNull(executor); + Objects.requireNonNull(engine); + final OffloadEngineProxy cbImpl = new OffloadEngineProxy(executor, engine); + synchronized (mOffloadEngines) { + if (CollectionUtils.contains(mOffloadEngines, impl -> impl.mEngine == engine)) { + throw new IllegalStateException("This engine is already registered"); + } + mOffloadEngines.add(cbImpl); + } + try { + mService.registerOffloadEngine(ifaceName, cbImpl, offloadCapability, offloadType); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + } + + + /** + * Unregisters an OffloadEngine from NsdService. + * + * A caller can unregister itself as an OffloadEngine when it doesn't want to receive the + * callback anymore. The OffloadEngine must have been previously registered with the system + * using the {@link NsdManager#registerOffloadEngine} method. + * + * @param engine OffloadEngine object to be removed from NsdService + * @throws IllegalStateException if the engine is not registered. + * + * @hide + */ + //@SystemApi + @RequiresPermission(anyOf = {NETWORK_SETTINGS, PERMISSION_MAINLINE_NETWORK_STACK, + NETWORK_STACK}) + public void unregisterOffloadEngine(@NonNull OffloadEngine engine) { + Objects.requireNonNull(engine); + final OffloadEngineProxy cbImpl; + synchronized (mOffloadEngines) { + final int index = CollectionUtils.indexOf(mOffloadEngines, + impl -> impl.mEngine == engine); + if (index < 0) { + throw new IllegalStateException("This engine is not registered"); + } + cbImpl = mOffloadEngines.remove(index); + } + + try { + mService.unregisterOffloadEngine(cbImpl); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + } + private class PerNetworkDiscoveryTracker { final String mServiceType; final int mProtocolType; diff --git a/framework-t/src/android/net/nsd/OffloadEngine.java b/framework-t/src/android/net/nsd/OffloadEngine.java new file mode 100644 index 0000000000..d9397252d6 --- /dev/null +++ b/framework-t/src/android/net/nsd/OffloadEngine.java @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net.nsd; + +import android.annotation.LongDef; +import android.annotation.NonNull; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * OffloadEngine is an interface for mDns hardware offloading. + * + * An offloading engine can interact with the firmware code to instruct the hardware to + * offload some of mDns network traffic before it reached android OS. This can improve the + * power consumption performance of the host system by not always waking up the OS to handle + * the mDns packet when the device is in low power mode. + * + * @hide + */ +//@SystemApi +public interface OffloadEngine { + /** + * Indicates that the OffloadEngine can generate replies to mDns queries. + * + * @see OffloadServiceInfo#getOffloadPayload() + */ + int OFFLOAD_TYPE_REPLY = 1; + /** + * Indicates that the OffloadEngine can filter and drop mDns queries. + */ + int OFFLOAD_TYPE_FILTER_QUERIES = 1 << 1; + /** + * Indicates that the OffloadEngine can filter and drop mDns replies. It can allow mDns packets + * to be received even when no app holds a {@link android.net.wifi.WifiManager.MulticastLock}. + */ + int OFFLOAD_TYPE_FILTER_REPLIES = 1 << 2; + + /** + * Indicates that the OffloadEngine can bypass multicast lock. + */ + int OFFLOAD_CAPABILITY_BYPASS_MULTICAST_LOCK = 1; + + @Retention(RetentionPolicy.SOURCE) + @LongDef(flag = true, prefix = {"OFFLOAD_TYPE"}, value = { + OFFLOAD_TYPE_REPLY, + OFFLOAD_TYPE_FILTER_QUERIES, + OFFLOAD_TYPE_FILTER_REPLIES, + }) + @interface OffloadType {} + + @Retention(RetentionPolicy.SOURCE) + @LongDef(flag = true, prefix = {"OFFLOAD_CAPABILITY"}, value = { + OFFLOAD_CAPABILITY_BYPASS_MULTICAST_LOCK + }) + @interface OffloadCapability {} + + /** + * To be called when the OffloadServiceInfo is added or updated. + * + * @param info The OffloadServiceInfo to add or update. + */ + void onOffloadServiceUpdated(@NonNull OffloadServiceInfo info); + + /** + * To be called when the OffloadServiceInfo is removed. + * + * @param info The OffloadServiceInfo to remove. + */ + void onOffloadServiceRemoved(@NonNull OffloadServiceInfo info); +} diff --git a/framework-t/src/android/net/nsd/OffloadServiceInfo.java b/framework-t/src/android/net/nsd/OffloadServiceInfo.java new file mode 100644 index 0000000000..4aec7207d3 --- /dev/null +++ b/framework-t/src/android/net/nsd/OffloadServiceInfo.java @@ -0,0 +1,313 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net.nsd; + +import android.annotation.IntRange; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.Parcel; +import android.os.Parcelable; + +import com.android.net.module.util.HexDump; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Objects; + +/** + * The OffloadServiceInfo class contains all the necessary information the OffloadEngine needs to + * know about how to offload an mDns service. The OffloadServiceInfo is keyed on + * {@link OffloadServiceInfo.Key} which is a (serviceName, serviceType) pair. + * + * @hide + */ +// @SystemApi +public final class OffloadServiceInfo implements Parcelable { + @NonNull + private final Key mKey; + @NonNull + private final String mHostname; + @NonNull final List mSubtypes; + @Nullable + private final byte[] mOffloadPayload; + private final int mPriority; + private final long mOffloadType; + + /** + * Creates a new OffloadServiceInfo object with the specified parameters. + * + * @param key The key of the service. + * @param subtypes The list of subTypes of the service. + * @param hostname The name of the host offering the service. It is meaningful only when + * offloadType contains OFFLOAD_REPLY. + * @param offloadPayload The raw udp payload for hardware offloading. + * @param priority The priority of the service, @see #getPriority. + * @param offloadType The type of the service offload, @see #getOffloadType. + */ + public OffloadServiceInfo(@NonNull Key key, + @NonNull List subtypes, @NonNull String hostname, + @Nullable byte[] offloadPayload, + @IntRange(from = 0, to = Integer.MAX_VALUE) int priority, + @OffloadEngine.OffloadType long offloadType) { + Objects.requireNonNull(key); + Objects.requireNonNull(subtypes); + Objects.requireNonNull(hostname); + mKey = key; + mSubtypes = subtypes; + mHostname = hostname; + mOffloadPayload = offloadPayload; + mPriority = priority; + mOffloadType = offloadType; + } + + /** + * Creates a new OffloadServiceInfo object from a Parcel. + * + * @param in The Parcel to read the object from. + * + * @hide + */ + public OffloadServiceInfo(@NonNull Parcel in) { + mKey = in.readParcelable(Key.class.getClassLoader(), + Key.class); + mSubtypes = in.createStringArrayList(); + mHostname = in.readString(); + mOffloadPayload = in.createByteArray(); + mPriority = in.readInt(); + mOffloadType = in.readLong(); + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeParcelable(mKey, flags); + dest.writeStringList(mSubtypes); + dest.writeString(mHostname); + dest.writeByteArray(mOffloadPayload); + dest.writeInt(mPriority); + dest.writeLong(mOffloadType); + } + + @Override + public int describeContents() { + return 0; + } + + @NonNull + public static final Creator CREATOR = new Creator() { + @Override + public OffloadServiceInfo createFromParcel(Parcel in) { + return new OffloadServiceInfo(in); + } + + @Override + public OffloadServiceInfo[] newArray(int size) { + return new OffloadServiceInfo[size]; + } + }; + + /** + * Get the {@link Key}. + */ + @NonNull + public Key getKey() { + return mKey; + } + + /** + * Get the host name. (e.g. "Android.local" ) + */ + @NonNull + public String getHostname() { + return mHostname; + } + + /** + * Get the service subtypes. (e.g. ["_ann"] ) + */ + @NonNull + public List getSubtypes() { + return Collections.unmodifiableList(mSubtypes); + } + + /** + * Get the raw udp payload that the OffloadEngine can use to directly reply the incoming query. + *

+ * It is null if the OffloadEngine can not handle transmit. The packet must be sent as-is when + * replying to query. + */ + @Nullable + public byte[] getOffloadPayload() { + if (mOffloadPayload == null) { + return null; + } else { + return mOffloadPayload.clone(); + } + } + + /** + * Get the offloadType. + *

+ * For example, if the {@link com.android.server.NsdService} requests the OffloadEngine to both + * filter the mDNS queries and replies, the {@link #mOffloadType} = + * ({@link OffloadEngine#OFFLOAD_TYPE_FILTER_QUERIES} | + * {@link OffloadEngine#OFFLOAD_TYPE_FILTER_REPLIES}). + */ + @OffloadEngine.OffloadType public long getOffloadType() { + return mOffloadType; + } + + /** + * Get the priority for the OffloadServiceInfo. + *

+ * When OffloadEngine don't have enough resource + * (e.g. not enough memory) to offload all the OffloadServiceInfo. The OffloadServiceInfo + * having lower priority values should be handled by the OffloadEngine first. + */ + public int getPriority() { + return mPriority; + } + + /** + * Only for debug purpose, the string can be long as the raw packet is dump in the string. + */ + @Override + public String toString() { + return String.format( + "OffloadServiceInfo{ mOffloadServiceInfoKey=%s, mHostName=%s, " + + "mOffloadPayload=%s, mPriority=%d, mOffloadType=%d, mSubTypes=%s }", + mKey, + mHostname, HexDump.dumpHexString(mOffloadPayload), mPriority, + mOffloadType, mSubtypes.toString()); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof OffloadServiceInfo)) return false; + OffloadServiceInfo that = (OffloadServiceInfo) o; + return mPriority == that.mPriority && mOffloadType == that.mOffloadType + && mKey.equals(that.mKey) + && mHostname.equals( + that.mHostname) && Arrays.equals(mOffloadPayload, + that.mOffloadPayload) + && mSubtypes.equals(that.mSubtypes); + } + + @Override + public int hashCode() { + int result = Objects.hash(mKey, mHostname, mPriority, + mOffloadType, mSubtypes); + result = 31 * result + Arrays.hashCode(mOffloadPayload); + return result; + } + + /** + * The {@link OffloadServiceInfo.Key} is the (serviceName, serviceType) pair. + */ + public static final class Key implements Parcelable { + @NonNull + private final String mServiceName; + @NonNull + private final String mServiceType; + + /** + * Creates a new OffloadServiceInfoKey object with the specified parameters. + * + * @param serviceName The name of the service. + * @param serviceType The type of the service. + */ + public Key(@NonNull String serviceName, @NonNull String serviceType) { + Objects.requireNonNull(serviceName); + Objects.requireNonNull(serviceType); + mServiceName = serviceName; + mServiceType = serviceType; + } + + /** + * Creates a new OffloadServiceInfoKey object from a Parcel. + * + * @param in The Parcel to read the object from. + * + * @hide + */ + public Key(@NonNull Parcel in) { + mServiceName = in.readString(); + mServiceType = in.readString(); + } + /** + * Get the service name. (e.g. "NsdChat") + */ + @NonNull + public String getServiceName() { + return mServiceName; + } + + /** + * Get the service type. (e.g. "_http._tcp.local" ) + */ + @NonNull + public String getServiceType() { + return mServiceType; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeString(mServiceName); + dest.writeString(mServiceType); + } + + @Override + public int describeContents() { + return 0; + } + + @NonNull + public static final Creator CREATOR = + new Creator() { + @Override + public Key createFromParcel(Parcel in) { + return new Key(in); + } + + @Override + public Key[] newArray(int size) { + return new Key[size]; + } + }; + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof Key)) return false; + Key that = (Key) o; + return Objects.equals(mServiceName, that.mServiceName) && Objects.equals( + mServiceType, that.mServiceType); + } + + @Override + public int hashCode() { + return Objects.hash(mServiceName, mServiceType); + } + + @Override + public String toString() { + return String.format("OffloadServiceInfoKey{ mServiceName=%s, mServiceType=%s }", + mServiceName, mServiceType); + } + } +} diff --git a/framework/aidl-export/android/net/nsd/OffloadServiceInfo.aidl b/framework/aidl-export/android/net/nsd/OffloadServiceInfo.aidl new file mode 100644 index 0000000000..aa7aef24fc --- /dev/null +++ b/framework/aidl-export/android/net/nsd/OffloadServiceInfo.aidl @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net.nsd; + +@JavaOnlyStableParcelable parcelable OffloadServiceInfo; \ No newline at end of file diff --git a/framework/jarjar-excludes.txt b/framework/jarjar-excludes.txt index 9b48d57f38..1ac5e8ec4a 100644 --- a/framework/jarjar-excludes.txt +++ b/framework/jarjar-excludes.txt @@ -27,4 +27,10 @@ android\.nearby\..+ # Don't touch anything that's already under android.net.http (cronet) # This is required since android.net.http contains api classes and hidden classes. # TODO: Remove this after hidden classes are moved to different package -android\.net\.http\..+ \ No newline at end of file +android\.net\.http\..+ + +# TODO: OffloadServiceInfo is being added as an API, but wasn't an API yet in the first module +# versions targeting U. Do not jarjar it such versions so that tests do not have to cover both +# cases. This will be removed in an upcoming change marking it as API. +android\.net\.nsd\.OffloadServiceInfo(\$.+)? +android\.net\.nsd\.OffloadEngine(\$.+)? diff --git a/service-t/Android.bp b/service-t/Android.bp index 7de749ca4d..c277cf68c0 100644 --- a/service-t/Android.bp +++ b/service-t/Android.bp @@ -88,8 +88,9 @@ java_library { name: "service-connectivity-mdns-standalone-build-test", sdk_version: "core_platform", srcs: [ - ":service-mdns-droidstubs", "src/com/android/server/connectivity/mdns/**/*.java", + ":framework-connectivity-t-mdns-standalone-build-sources", + ":service-mdns-droidstubs" ], exclude_srcs: [ "src/com/android/server/connectivity/mdns/internal/SocketNetlinkMonitor.java", diff --git a/service-t/src/com/android/server/NsdService.java b/service-t/src/com/android/server/NsdService.java index 745c5bc705..1a05d46057 100644 --- a/service-t/src/com/android/server/NsdService.java +++ b/service-t/src/com/android/server/NsdService.java @@ -16,6 +16,7 @@ package com.android.server; +import static android.Manifest.permission.NETWORK_SETTINGS; import static android.net.ConnectivityManager.NETID_UNSET; import static android.net.NetworkCapabilities.TRANSPORT_VPN; import static android.net.NetworkCapabilities.TRANSPORT_WIFI; @@ -46,9 +47,12 @@ import android.net.mdns.aidl.ResolutionInfo; import android.net.nsd.INsdManager; import android.net.nsd.INsdManagerCallback; import android.net.nsd.INsdServiceConnector; +import android.net.nsd.IOffloadEngine; import android.net.nsd.MDnsManager; import android.net.nsd.NsdManager; import android.net.nsd.NsdServiceInfo; +import android.net.nsd.OffloadEngine; +import android.net.nsd.OffloadServiceInfo; import android.net.wifi.WifiManager; import android.os.Binder; import android.os.Handler; @@ -56,6 +60,7 @@ import android.os.HandlerThread; import android.os.IBinder; import android.os.Looper; import android.os.Message; +import android.os.RemoteCallbackList; import android.os.RemoteException; import android.os.UserHandle; import android.provider.DeviceConfig; @@ -98,6 +103,7 @@ import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -215,6 +221,24 @@ public class NsdService extends INsdManager.Stub { // The number of client that ever connected. private int mClientNumberId = 1; + private final RemoteCallbackList mOffloadEngines = + new RemoteCallbackList<>(); + + private static class OffloadEngineInfo { + @NonNull final String mInterfaceName; + final long mOffloadCapabilities; + final long mOffloadType; + @NonNull final IOffloadEngine mOffloadEngine; + + OffloadEngineInfo(@NonNull IOffloadEngine offloadEngine, + @NonNull String interfaceName, long capabilities, long offloadType) { + this.mOffloadEngine = offloadEngine; + this.mInterfaceName = interfaceName; + this.mOffloadCapabilities = capabilities; + this.mOffloadType = offloadType; + } + } + private static class MdnsListener implements MdnsServiceBrowserListener { protected final int mClientRequestId; protected final int mTransactionId; @@ -719,6 +743,7 @@ public class NsdService extends INsdManager.Stub { final int transactionId; final int clientRequestId = msg.arg2; final ListenerArgs args; + final OffloadEngineInfo offloadEngineInfo; switch (msg.what) { case NsdManager.DISCOVER_SERVICES: { if (DBG) Log.d(TAG, "Discover services"); @@ -1114,6 +1139,16 @@ public class NsdService extends INsdManager.Stub { return NOT_HANDLED; } break; + case NsdManager.REGISTER_OFFLOAD_ENGINE: + offloadEngineInfo = (OffloadEngineInfo) msg.obj; + // TODO: Limits the number of registrations created by a given class. + mOffloadEngines.register(offloadEngineInfo.mOffloadEngine, + offloadEngineInfo); + // TODO: Sends all the existing OffloadServiceInfos back. + break; + case NsdManager.UNREGISTER_OFFLOAD_ENGINE: + mOffloadEngines.unregister((IOffloadEngine) msg.obj); + break; default: return NOT_HANDLED; } @@ -1771,7 +1806,42 @@ public class NsdService extends INsdManager.Stub { } } + private void sendOffloadServiceInfosUpdate(@NonNull String targetInterfaceName, + @NonNull OffloadServiceInfo offloadServiceInfo, boolean isRemove) { + final int count = mOffloadEngines.beginBroadcast(); + try { + for (int i = 0; i < count; i++) { + final OffloadEngineInfo offloadEngineInfo = + (OffloadEngineInfo) mOffloadEngines.getBroadcastCookie(i); + final String interfaceName = offloadEngineInfo.mInterfaceName; + if (!targetInterfaceName.equals(interfaceName) + || ((offloadEngineInfo.mOffloadType + & offloadServiceInfo.getOffloadType()) == 0)) { + continue; + } + try { + if (isRemove) { + mOffloadEngines.getBroadcastItem(i).onOffloadServiceRemoved( + offloadServiceInfo); + } else { + mOffloadEngines.getBroadcastItem(i).onOffloadServiceUpdated( + offloadServiceInfo); + } + } catch (RemoteException e) { + // Can happen in regular cases, do not log a stacktrace + Log.i(TAG, "Failed to send offload callback, remote died", e); + } + } + } finally { + mOffloadEngines.finishBroadcast(); + } + } + private class AdvertiserCallback implements MdnsAdvertiser.AdvertiserCallback { + // TODO: add a callback to notify when a service is being added on each interface (as soon + // as probing starts), and call mOffloadCallbacks. This callback is for + // OFFLOAD_CAPABILITY_FILTER_REPLIES offload type. + @Override public void onRegisterServiceSucceeded(int transactionId, NsdServiceInfo registeredInfo) { mServiceLogs.log("onRegisterServiceSucceeded: transactionId " + transactionId); @@ -1801,6 +1871,18 @@ public class NsdService extends INsdManager.Stub { request.calculateRequestDurationMs()); } + @Override + public void onOffloadStartOrUpdate(@NonNull String interfaceName, + @NonNull OffloadServiceInfo offloadServiceInfo) { + sendOffloadServiceInfosUpdate(interfaceName, offloadServiceInfo, false /* isRemove */); + } + + @Override + public void onOffloadStop(@NonNull String interfaceName, + @NonNull OffloadServiceInfo offloadServiceInfo) { + sendOffloadServiceInfosUpdate(interfaceName, offloadServiceInfo, true /* isRemove */); + } + private ClientInfo getClientInfoOrLog(int transactionId) { final ClientInfo clientInfo = mTransactionIdToClientInfoMap.get(transactionId); if (clientInfo == null) { @@ -1920,6 +2002,32 @@ public class NsdService extends INsdManager.Stub { public void binderDied() { mNsdStateMachine.sendMessage( mNsdStateMachine.obtainMessage(NsdManager.UNREGISTER_CLIENT, this)); + + } + + @Override + public void registerOffloadEngine(String ifaceName, IOffloadEngine cb, + @OffloadEngine.OffloadCapability long offloadCapabilities, + @OffloadEngine.OffloadType long offloadTypes) { + // TODO: Relax the permission because NETWORK_SETTINGS is a signature permission, and + // it may not be possible for all the callers of this API to have it. + PermissionUtils.enforceNetworkStackPermissionOr(mContext, NETWORK_SETTINGS); + Objects.requireNonNull(ifaceName); + Objects.requireNonNull(cb); + mNsdStateMachine.sendMessage( + mNsdStateMachine.obtainMessage(NsdManager.REGISTER_OFFLOAD_ENGINE, + new OffloadEngineInfo(cb, ifaceName, offloadCapabilities, + offloadTypes))); + } + + @Override + public void unregisterOffloadEngine(IOffloadEngine cb) { + // TODO: Relax the permission because NETWORK_SETTINGS is a signature permission, and + // it may not be possible for all the callers of this API to have it. + PermissionUtils.enforceNetworkStackPermissionOr(mContext, NETWORK_SETTINGS); + Objects.requireNonNull(cb); + mNsdStateMachine.sendMessage( + mNsdStateMachine.obtainMessage(NsdManager.UNREGISTER_OFFLOAD_ENGINE, cb)); } } @@ -2003,25 +2111,41 @@ public class NsdService extends INsdManager.Stub { return IFACE_IDX_ANY; } - final ConnectivityManager cm = mContext.getSystemService(ConnectivityManager.class); - if (cm == null) { - Log.wtf(TAG, "No ConnectivityManager for resolveService"); + String interfaceName = getNetworkInterfaceName(network); + if (interfaceName == null) { return IFACE_IDX_ANY; } - final LinkProperties lp = cm.getLinkProperties(network); - if (lp == null) return IFACE_IDX_ANY; + return getNetworkInterfaceIndexByName(interfaceName); + } + private String getNetworkInterfaceName(@Nullable Network network) { + if (network == null) { + return null; + } + final ConnectivityManager cm = mContext.getSystemService(ConnectivityManager.class); + if (cm == null) { + Log.wtf(TAG, "No ConnectivityManager"); + return null; + } + final LinkProperties lp = cm.getLinkProperties(network); + if (lp == null) { + return null; + } // Only resolve on non-stacked interfaces + return lp.getInterfaceName(); + } + + private int getNetworkInterfaceIndexByName(final String ifaceName) { final NetworkInterface iface; try { - iface = NetworkInterface.getByName(lp.getInterfaceName()); + iface = NetworkInterface.getByName(ifaceName); } catch (SocketException e) { Log.e(TAG, "Error querying interface", e); return IFACE_IDX_ANY; } if (iface == null) { - Log.e(TAG, "Interface not found: " + lp.getInterfaceName()); + Log.e(TAG, "Interface not found: " + ifaceName); return IFACE_IDX_ANY; } diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsAdvertiser.java b/service-t/src/com/android/server/connectivity/mdns/MdnsAdvertiser.java index 158d7a367e..1bc059d12d 100644 --- a/service-t/src/com/android/server/connectivity/mdns/MdnsAdvertiser.java +++ b/service-t/src/com/android/server/connectivity/mdns/MdnsAdvertiser.java @@ -24,15 +24,19 @@ import android.net.LinkAddress; import android.net.Network; import android.net.nsd.NsdManager; import android.net.nsd.NsdServiceInfo; +import android.net.nsd.OffloadEngine; +import android.net.nsd.OffloadServiceInfo; import android.os.Looper; import android.util.ArrayMap; import android.util.Log; import android.util.SparseArray; import com.android.internal.annotations.VisibleForTesting; +import com.android.net.module.util.CollectionUtils; import com.android.net.module.util.SharedLog; import com.android.server.connectivity.mdns.util.MdnsUtils; +import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.UUID; @@ -68,9 +72,10 @@ public class MdnsAdvertiser { new ArrayMap<>(); private final SparseArray mRegistrations = new SparseArray<>(); private final Dependencies mDeps; - private String[] mDeviceHostName; @NonNull private final SharedLog mSharedLog; + private final Map> mInterfaceOffloadServices = + new ArrayMap<>(); /** * Dependencies for {@link MdnsAdvertiser}, useful for testing. @@ -115,18 +120,32 @@ public class MdnsAdvertiser { private final MdnsInterfaceAdvertiser.Callback mInterfaceAdvertiserCb = new MdnsInterfaceAdvertiser.Callback() { @Override - public void onRegisterServiceSucceeded( + public void onServiceProbingSucceeded( @NonNull MdnsInterfaceAdvertiser advertiser, int serviceId) { - // Wait for all current interfaces to be done probing before notifying of success. - if (any(mAllAdvertisers, (k, a) -> a.isProbing(serviceId))) return; - // The service may still be unregistered/renamed if a conflict is found on a later added - // interface, or if a conflicting announcement/reply is detected (RFC6762 9.) - final Registration registration = mRegistrations.get(serviceId); if (registration == null) { Log.wtf(TAG, "Register succeeded for unknown registration"); return; } + + final String interfaceName = advertiser.getSocketInterfaceName(); + final List existingOffloadServiceInfoWrappers = + mInterfaceOffloadServices.computeIfAbsent( + interfaceName, k -> new ArrayList<>()); + // Remove existing offload services from cache for update. + existingOffloadServiceInfoWrappers.removeIf(item -> item.mServiceId == serviceId); + final OffloadServiceInfoWrapper newOffloadServiceInfoWrapper = createOffloadService( + serviceId, + registration); + existingOffloadServiceInfoWrappers.add(newOffloadServiceInfoWrapper); + mCb.onOffloadStartOrUpdate(interfaceName, + newOffloadServiceInfoWrapper.mOffloadServiceInfo); + + // Wait for all current interfaces to be done probing before notifying of success. + if (any(mAllAdvertisers, (k, a) -> a.isProbing(serviceId))) return; + // The service may still be unregistered/renamed if a conflict is found on a later added + // interface, or if a conflicting announcement/reply is detected (RFC6762 9.) + if (!registration.mNotifiedRegistrationSuccess) { mCb.onRegisterServiceSucceeded(serviceId, registration.getServiceInfo()); registration.mNotifiedRegistrationSuccess = true; @@ -148,7 +167,12 @@ public class MdnsAdvertiser { registration.mNotifiedRegistrationSuccess = false; // The service was done probing, just reset it to probing state (RFC6762 9.) - forAllAdvertisers(a -> a.restartProbingForConflict(serviceId)); + forAllAdvertisers(a -> { + if (!a.maybeRestartProbingForConflict(serviceId)) { + return; + } + maybeSendOffloadStop(a.getSocketInterfaceName(), serviceId); + }); return; } @@ -196,6 +220,22 @@ public class MdnsAdvertiser { registration.updateForConflict(newInfo, renameCount); } + private void maybeSendOffloadStop(final String interfaceName, int serviceId) { + final List existingOffloadServiceInfoWrappers = + mInterfaceOffloadServices.get(interfaceName); + if (existingOffloadServiceInfoWrappers == null) { + return; + } + // Stop the offloaded service by matching the service id + int idx = CollectionUtils.indexOf(existingOffloadServiceInfoWrappers, + item -> item.mServiceId == serviceId); + if (idx >= 0) { + mCb.onOffloadStop(interfaceName, + existingOffloadServiceInfoWrappers.get(idx).mOffloadServiceInfo); + existingOffloadServiceInfoWrappers.remove(idx); + } + } + /** * A request for a {@link MdnsInterfaceAdvertiser}. * @@ -221,7 +261,22 @@ public class MdnsAdvertiser { * @return true if this {@link InterfaceAdvertiserRequest} should now be deleted. */ boolean onAdvertiserDestroyed(@NonNull MdnsInterfaceSocket socket) { - mAdvertisers.remove(socket); + final MdnsInterfaceAdvertiser removedAdvertiser = mAdvertisers.remove(socket); + if (removedAdvertiser != null) { + final String interfaceName = removedAdvertiser.getSocketInterfaceName(); + // If the interface is destroyed, stop all hardware offloading on that interface. + final List offloadServiceInfoWrappers = + mInterfaceOffloadServices.remove( + interfaceName); + if (offloadServiceInfoWrappers != null) { + for (OffloadServiceInfoWrapper offloadServiceInfoWrapper : + offloadServiceInfoWrappers) { + mCb.onOffloadStop(interfaceName, + offloadServiceInfoWrapper.mOffloadServiceInfo); + } + } + } + if (mAdvertisers.size() == 0 && mPendingRegistrations.size() == 0) { // No advertiser is using sockets from this request anymore (in particular for exit // announcements), and there is no registration so newer sockets will not be @@ -282,7 +337,10 @@ public class MdnsAdvertiser { void removeService(int id) { mPendingRegistrations.remove(id); for (int i = 0; i < mAdvertisers.size(); i++) { - mAdvertisers.valueAt(i).removeService(id); + final MdnsInterfaceAdvertiser advertiser = mAdvertisers.valueAt(i); + advertiser.removeService(id); + + maybeSendOffloadStop(advertiser.getSocketInterfaceName(), id); } } @@ -325,6 +383,16 @@ public class MdnsAdvertiser { } } + private static class OffloadServiceInfoWrapper { + private final @NonNull OffloadServiceInfo mOffloadServiceInfo; + private final int mServiceId; + + OffloadServiceInfoWrapper(int serviceId, OffloadServiceInfo offloadServiceInfo) { + mOffloadServiceInfo = offloadServiceInfo; + mServiceId = serviceId; + } + } + private static class Registration { @NonNull final String mOriginalName; @@ -425,6 +493,24 @@ public class MdnsAdvertiser { // Unregistration is notified immediately as success in NsdService so no callback is needed // here. + + /** + * Called when a service is ready to be sent for hardware offloading. + * + * @param interfaceName the interface for sending the update to. + * @param offloadServiceInfo the offloading content. + */ + void onOffloadStartOrUpdate(@NonNull String interfaceName, + @NonNull OffloadServiceInfo offloadServiceInfo); + + /** + * Called when a service is removed or the MdnsInterfaceAdvertiser is destroyed. + * + * @param interfaceName the interface for sending the update to. + * @param offloadServiceInfo the offloading content. + */ + void onOffloadStop(@NonNull String interfaceName, + @NonNull OffloadServiceInfo offloadServiceInfo); } public MdnsAdvertiser(@NonNull Looper looper, @NonNull MdnsSocketProvider socketProvider, @@ -525,4 +611,28 @@ public class MdnsAdvertiser { return false; }); } + + private OffloadServiceInfoWrapper createOffloadService(int serviceId, + @NonNull Registration registration) { + final NsdServiceInfo nsdServiceInfo = registration.getServiceInfo(); + List subTypes = new ArrayList<>(); + String subType = registration.getSubtype(); + if (subType != null) { + subTypes.add(subType); + } + final OffloadServiceInfo offloadServiceInfo = new OffloadServiceInfo( + new OffloadServiceInfo.Key(nsdServiceInfo.getServiceName(), + nsdServiceInfo.getServiceType()), + subTypes, + String.join(".", mDeviceHostName), + null /* rawOffloadPacket */, + // TODO: define overlayable resources in + // ServiceConnectivityResources that set the priority based on + // service type. + 0 /* priority */, + // TODO: set the offloadType based on the callback timing. + OffloadEngine.OFFLOAD_TYPE_REPLY); + return new OffloadServiceInfoWrapper(serviceId, offloadServiceInfo); + } + } diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsInterfaceAdvertiser.java b/service-t/src/com/android/server/connectivity/mdns/MdnsInterfaceAdvertiser.java index 724a7045d1..c5177b70cc 100644 --- a/service-t/src/com/android/server/connectivity/mdns/MdnsInterfaceAdvertiser.java +++ b/service-t/src/com/android/server/connectivity/mdns/MdnsInterfaceAdvertiser.java @@ -73,7 +73,7 @@ public class MdnsInterfaceAdvertiser implements MulticastPacketReader.PacketHand /** * Called by the advertiser after it successfully registered a service, after probing. */ - void onRegisterServiceSucceeded(@NonNull MdnsInterfaceAdvertiser advertiser, int serviceId); + void onServiceProbingSucceeded(@NonNull MdnsInterfaceAdvertiser advertiser, int serviceId); /** * Called by the advertiser when a conflict was found, during or after probing. @@ -101,7 +101,7 @@ public class MdnsInterfaceAdvertiser implements MulticastPacketReader.PacketHand public void onFinished(MdnsProber.ProbingInfo info) { final MdnsAnnouncer.AnnouncementInfo announcementInfo; mSharedLog.i("Probing finished for service " + info.getServiceId()); - mCbHandler.post(() -> mCb.onRegisterServiceSucceeded( + mCbHandler.post(() -> mCb.onServiceProbingSucceeded( MdnsInterfaceAdvertiser.this, info.getServiceId())); try { announcementInfo = mRecordRepository.onProbingSucceeded(info); @@ -282,11 +282,12 @@ public class MdnsInterfaceAdvertiser implements MulticastPacketReader.PacketHand /** * Reset a service to the probing state due to a conflict found on the network. */ - public void restartProbingForConflict(int serviceId) { + public boolean maybeRestartProbingForConflict(int serviceId) { final MdnsProber.ProbingInfo probingInfo = mRecordRepository.setServiceProbing(serviceId); - if (probingInfo == null) return; + if (probingInfo == null) return false; mProber.restartForConflict(probingInfo); + return true; } /** @@ -346,4 +347,8 @@ public class MdnsInterfaceAdvertiser implements MulticastPacketReader.PacketHand if (answers == null) return; mReplySender.queueReply(answers); } + + public String getSocketInterfaceName() { + return mSocket.getInterface().getName(); + } } diff --git a/tests/cts/net/src/android/net/cts/NsdManagerTest.kt b/tests/cts/net/src/android/net/cts/NsdManagerTest.kt index 49620b057a..7731ac99f5 100644 --- a/tests/cts/net/src/android/net/cts/NsdManagerTest.kt +++ b/tests/cts/net/src/android/net/cts/NsdManagerTest.kt @@ -16,6 +16,7 @@ package android.net.cts import android.Manifest.permission.MANAGE_TEST_NETWORKS +import android.Manifest.permission.NETWORK_SETTINGS import android.app.compat.CompatChanges import android.net.ConnectivityManager import android.net.ConnectivityManager.NetworkCallback @@ -60,6 +61,8 @@ import android.net.nsd.NsdManager.DiscoveryListener import android.net.nsd.NsdManager.RegistrationListener import android.net.nsd.NsdManager.ResolveListener import android.net.nsd.NsdServiceInfo +import android.net.nsd.OffloadEngine +import android.net.nsd.OffloadServiceInfo import android.os.Build import android.os.Handler import android.os.HandlerThread @@ -353,6 +356,22 @@ class NsdManagerTest { } } + private class TestNsdOffloadEngine : OffloadEngine, + NsdRecord() { + sealed class OffloadEvent : NsdEvent { + data class AddOrUpdateEvent(val info: OffloadServiceInfo) : OffloadEvent() + data class RemoveEvent(val info: OffloadServiceInfo) : OffloadEvent() + } + + override fun onOffloadServiceUpdated(info: OffloadServiceInfo) { + add(OffloadEvent.AddOrUpdateEvent(info)) + } + + override fun onOffloadServiceRemoved(info: OffloadServiceInfo) { + add(OffloadEvent.RemoveEvent(info)) + } + } + @Before fun setUp() { handlerThread.start() @@ -858,6 +877,52 @@ class NsdManagerTest { } } + fun checkOffloadServiceInfo(serviceInfo: OffloadServiceInfo) { + assertEquals(serviceName, serviceInfo.key.serviceName) + assertEquals(serviceType, serviceInfo.key.serviceType) + assertEquals(listOf("_subtype"), serviceInfo.subtypes) + assertTrue(serviceInfo.hostname.startsWith("Android_")) + assertTrue(serviceInfo.hostname.endsWith("local")) + assertEquals(0, serviceInfo.priority) + assertEquals(OffloadEngine.OFFLOAD_TYPE_REPLY.toLong(), serviceInfo.offloadType) + } + + @Test + fun testNsdManager_registerOffloadEngine() { + val targetSdkVersion = context.packageManager + .getTargetSdkVersion(context.applicationInfo.packageName) + // The offload callbacks are only supported with the new backend, + // enabled with target SDK U+. + assumeTrue(isAtLeastU() || targetSdkVersion > Build.VERSION_CODES.TIRAMISU) + val offloadEngine = TestNsdOffloadEngine() + runAsShell(NETWORK_SETTINGS) { + nsdManager.registerOffloadEngine(testNetwork1.iface.interfaceName, + OffloadEngine.OFFLOAD_TYPE_REPLY.toLong(), + OffloadEngine.OFFLOAD_CAPABILITY_BYPASS_MULTICAST_LOCK.toLong(), + { it.run() }, offloadEngine) + } + + val si = NsdServiceInfo() + si.serviceType = "$serviceType,_subtype" + si.serviceName = serviceName + si.network = testNetwork1.network + si.port = 12345 + val record = NsdRegistrationRecord() + nsdManager.registerService(si, NsdManager.PROTOCOL_DNS_SD, record) + val addOrUpdateEvent = offloadEngine + .expectCallback() + checkOffloadServiceInfo(addOrUpdateEvent.info) + + nsdManager.unregisterService(record) + val unregisterEvent = offloadEngine + .expectCallback() + checkOffloadServiceInfo(unregisterEvent.info) + + runAsShell(NETWORK_SETTINGS) { + nsdManager.unregisterOffloadEngine(offloadEngine) + } + } + private fun checkConnectSocketToMdnsd(shouldFail: Boolean) { val discoveryRecord = NsdDiscoveryRecord() val localSocket = LocalSocket() diff --git a/tests/unit/java/com/android/server/connectivity/mdns/MdnsAdvertiserTest.kt b/tests/unit/java/com/android/server/connectivity/mdns/MdnsAdvertiserTest.kt index 6a0334fb19..9b38fea1e6 100644 --- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsAdvertiserTest.kt +++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsAdvertiserTest.kt @@ -20,6 +20,8 @@ import android.net.InetAddresses.parseNumericAddress import android.net.LinkAddress import android.net.Network import android.net.nsd.NsdServiceInfo +import android.net.nsd.OffloadEngine +import android.net.nsd.OffloadServiceInfo import android.os.Build import android.os.Handler import android.os.HandlerThread @@ -60,6 +62,8 @@ private val TEST_SOCKETKEY_1 = SocketKey(1001 /* interfaceIndex */) private val TEST_SOCKETKEY_2 = SocketKey(1002 /* interfaceIndex */) private val TEST_HOSTNAME = arrayOf("Android_test", "local") private const val TEST_SUBTYPE = "_subtype" +private val TEST_INTERFACE1 = "test_iface1" +private val TEST_INTERFACE2 = "test_iface2" private val SERVICE_1 = NsdServiceInfo("TestServiceName", "_advertisertest._tcp").apply { port = 12345 @@ -94,6 +98,24 @@ private val LONG_ALL_NETWORKS_SERVICE = network = null } +private val OFFLOAD_SERVICEINFO = OffloadServiceInfo( + OffloadServiceInfo.Key("TestServiceName", "_advertisertest._tcp"), + listOf(TEST_SUBTYPE), + "Android_test.local", + null, /* rawOffloadPacket */ + 0, /* priority */ + OffloadEngine.OFFLOAD_TYPE_REPLY.toLong() +) + +private val OFFLOAD_SERVICEINFO_NO_SUBTYPE = OffloadServiceInfo( + OffloadServiceInfo.Key("TestServiceName", "_advertisertest._tcp"), + listOf(), + "Android_test.local", + null, /* rawOffloadPacket */ + 0, /* priority */ + OffloadEngine.OFFLOAD_TYPE_REPLY.toLong() +) + @RunWith(DevSdkIgnoreRunner::class) @IgnoreUpTo(Build.VERSION_CODES.S_V2) class MdnsAdvertiserTest { @@ -123,6 +145,8 @@ class MdnsAdvertiserTest { doReturn(true).`when`(mockInterfaceAdvertiser2).isProbing(anyInt()) doReturn(createEmptyNetworkInterface()).`when`(mockSocket1).getInterface() doReturn(createEmptyNetworkInterface()).`when`(mockSocket2).getInterface() + doReturn(TEST_INTERFACE1).`when`(mockInterfaceAdvertiser1).socketInterfaceName + doReturn(TEST_INTERFACE2).`when`(mockInterfaceAdvertiser2).socketInterfaceName } @After @@ -160,12 +184,15 @@ class MdnsAdvertiserTest { ) doReturn(false).`when`(mockInterfaceAdvertiser1).isProbing(SERVICE_ID_1) - postSync { intAdvCbCaptor.value.onRegisterServiceSucceeded( + postSync { intAdvCbCaptor.value.onServiceProbingSucceeded( mockInterfaceAdvertiser1, SERVICE_ID_1) } verify(cb).onRegisterServiceSucceeded(eq(SERVICE_ID_1), argThat { it.matches(SERVICE_1) }) + verify(cb).onOffloadStartOrUpdate(eq(TEST_INTERFACE1), eq(OFFLOAD_SERVICEINFO_NO_SUBTYPE)) postSync { socketCb.onInterfaceDestroyed(TEST_SOCKETKEY_1, mockSocket1) } verify(mockInterfaceAdvertiser1).destroyNow() + postSync { intAdvCbCaptor.value.onDestroyed(mockSocket1) } + verify(cb).onOffloadStop(eq(TEST_INTERFACE1), eq(OFFLOAD_SERVICEINFO_NO_SUBTYPE)) } @Test @@ -195,14 +222,16 @@ class MdnsAdvertiserTest { anyInt(), eq(ALL_NETWORKS_SERVICE), eq(TEST_SUBTYPE)) doReturn(false).`when`(mockInterfaceAdvertiser1).isProbing(SERVICE_ID_1) - postSync { intAdvCbCaptor1.value.onRegisterServiceSucceeded( + postSync { intAdvCbCaptor1.value.onServiceProbingSucceeded( mockInterfaceAdvertiser1, SERVICE_ID_1) } + verify(cb).onOffloadStartOrUpdate(eq(TEST_INTERFACE1), eq(OFFLOAD_SERVICEINFO)) // Need both advertisers to finish probing and call onRegisterServiceSucceeded verify(cb, never()).onRegisterServiceSucceeded(anyInt(), any()) doReturn(false).`when`(mockInterfaceAdvertiser2).isProbing(SERVICE_ID_1) - postSync { intAdvCbCaptor2.value.onRegisterServiceSucceeded( + postSync { intAdvCbCaptor2.value.onServiceProbingSucceeded( mockInterfaceAdvertiser2, SERVICE_ID_1) } + verify(cb).onOffloadStartOrUpdate(eq(TEST_INTERFACE2), eq(OFFLOAD_SERVICEINFO)) verify(cb).onRegisterServiceSucceeded(eq(SERVICE_ID_1), argThat { it.matches(ALL_NETWORKS_SERVICE) }) @@ -210,6 +239,8 @@ class MdnsAdvertiserTest { postSync { advertiser.removeService(SERVICE_ID_1) } verify(mockInterfaceAdvertiser1).removeService(SERVICE_ID_1) verify(mockInterfaceAdvertiser2).removeService(SERVICE_ID_1) + verify(cb).onOffloadStop(eq(TEST_INTERFACE1), eq(OFFLOAD_SERVICEINFO)) + verify(cb).onOffloadStop(eq(TEST_INTERFACE2), eq(OFFLOAD_SERVICEINFO)) // Interface advertisers call onDestroyed after sending exit announcements postSync { intAdvCbCaptor1.value.onDestroyed(mockSocket1) } @@ -285,12 +316,12 @@ class MdnsAdvertiserTest { argThat { it.matches(expectedCaseInsensitiveRenamed) }, eq(null)) doReturn(false).`when`(mockInterfaceAdvertiser1).isProbing(SERVICE_ID_1) - postSync { intAdvCbCaptor.value.onRegisterServiceSucceeded( + postSync { intAdvCbCaptor.value.onServiceProbingSucceeded( mockInterfaceAdvertiser1, SERVICE_ID_1) } verify(cb).onRegisterServiceSucceeded(eq(SERVICE_ID_1), argThat { it.matches(SERVICE_1) }) doReturn(false).`when`(mockInterfaceAdvertiser1).isProbing(SERVICE_ID_2) - postSync { intAdvCbCaptor.value.onRegisterServiceSucceeded( + postSync { intAdvCbCaptor.value.onServiceProbingSucceeded( mockInterfaceAdvertiser1, SERVICE_ID_2) } verify(cb).onRegisterServiceSucceeded(eq(SERVICE_ID_2), argThat { it.matches(expectedRenamed) }) diff --git a/tests/unit/java/com/android/server/connectivity/mdns/MdnsInterfaceAdvertiserTest.kt b/tests/unit/java/com/android/server/connectivity/mdns/MdnsInterfaceAdvertiserTest.kt index dd458b812f..732ae2a33e 100644 --- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsInterfaceAdvertiserTest.kt +++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsInterfaceAdvertiserTest.kt @@ -150,7 +150,7 @@ class MdnsInterfaceAdvertiserTest { 0L /* initialDelayMs */) thread.waitForIdle(TIMEOUT_MS) - verify(cb).onRegisterServiceSucceeded(advertiser, TEST_SERVICE_ID_1) + verify(cb).onServiceProbingSucceeded(advertiser, TEST_SERVICE_ID_1) // Remove the service: expect exit announcements val testExitInfo = mock(ExitAnnouncementInfo::class.java) @@ -256,7 +256,7 @@ class MdnsInterfaceAdvertiserTest { val mockProbingInfo = mock(ProbingInfo::class.java) doReturn(mockProbingInfo).`when`(repository).setServiceProbing(TEST_SERVICE_ID_1) - advertiser.restartProbingForConflict(TEST_SERVICE_ID_1) + advertiser.maybeRestartProbingForConflict(TEST_SERVICE_ID_1) verify(prober).restartForConflict(mockProbingInfo) }