• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2022 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 com.android.server.connectivity.mdns;
18 
19 import static com.android.server.connectivity.mdns.MdnsConstants.NO_PACKET;
20 import static com.android.server.connectivity.mdns.MdnsInterfaceAdvertiser.CONFLICT_HOST;
21 import static com.android.server.connectivity.mdns.MdnsInterfaceAdvertiser.CONFLICT_SERVICE;
22 import static com.android.server.connectivity.mdns.MdnsRecord.MAX_LABEL_LENGTH;
23 
24 import android.annotation.NonNull;
25 import android.annotation.Nullable;
26 import android.annotation.RequiresApi;
27 import android.content.Context;
28 import android.net.LinkAddress;
29 import android.net.Network;
30 import android.net.nsd.NsdManager;
31 import android.net.nsd.NsdServiceInfo;
32 import android.net.nsd.OffloadEngine;
33 import android.net.nsd.OffloadServiceInfo;
34 import android.os.Build;
35 import android.os.Looper;
36 import android.text.TextUtils;
37 import android.util.ArrayMap;
38 import android.util.ArraySet;
39 import android.util.Log;
40 import android.util.SparseArray;
41 
42 import com.android.connectivity.resources.R;
43 import com.android.internal.annotations.VisibleForTesting;
44 import com.android.net.module.util.CollectionUtils;
45 import com.android.net.module.util.DnsUtils;
46 import com.android.net.module.util.SharedLog;
47 import com.android.server.connectivity.ConnectivityResources;
48 import com.android.server.connectivity.mdns.util.MdnsUtils;
49 
50 import java.security.SecureRandom;
51 import java.util.ArrayList;
52 import java.util.Collections;
53 import java.util.List;
54 import java.util.Map;
55 import java.util.Objects;
56 import java.util.Set;
57 import java.util.UUID;
58 import java.util.function.BiPredicate;
59 import java.util.function.Consumer;
60 
61 /**
62  * MdnsAdvertiser manages advertising services per {@link com.android.server.NsdService} requests.
63  *
64  * All methods except the constructor must be called on the looper thread.
65  */
66 @RequiresApi(Build.VERSION_CODES.TIRAMISU)
67 public class MdnsAdvertiser {
68     private static final String TAG = MdnsAdvertiser.class.getSimpleName();
69     static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG);
70 
71     // Top-level domain for link-local queries, as per RFC6762 3.
72     private static final String LOCAL_TLD = "local";
73 
74 
75     private final Looper mLooper;
76     private final AdvertiserCallback mCb;
77 
78     // Max-sized buffers to be used as temporary buffer to read/build packets. May be used by
79     // multiple components, but only for self-contained operations in the looper thread, so not
80     // concurrently.
81     // TODO: set according to MTU. 1300 should fit for ethernet MTU 1500 with some overhead.
82     private final byte[] mPacketCreationBuffer = new byte[1300];
83 
84     private final MdnsSocketProvider mSocketProvider;
85     private final ArrayMap<Network, InterfaceAdvertiserRequest> mAdvertiserRequests =
86             new ArrayMap<>();
87     private final ArrayMap<MdnsInterfaceSocket, MdnsInterfaceAdvertiser> mAllAdvertisers =
88             new ArrayMap<>();
89     private final SparseArray<Registration> mRegistrations = new SparseArray<>();
90     private final Dependencies mDeps;
91     private String[] mDeviceHostName;
92     @NonNull private final SharedLog mSharedLog;
93     private final Map<String, List<OffloadServiceInfoWrapper>> mInterfaceOffloadServices =
94             new ArrayMap<>();
95     private final MdnsFeatureFlags mMdnsFeatureFlags;
96     private final Map<String, Integer> mServiceTypeToOffloadPriority;
97     private final ArraySet<String> mOffloadServiceTypeDenyList;
98 
99     /**
100      * Dependencies for {@link MdnsAdvertiser}, useful for testing.
101      */
102     @VisibleForTesting
103     public static class Dependencies {
104         /**
105          * @see MdnsInterfaceAdvertiser
106          */
makeAdvertiser(@onNull MdnsInterfaceSocket socket, @NonNull List<LinkAddress> initialAddresses, @NonNull Looper looper, @NonNull byte[] packetCreationBuffer, @NonNull MdnsInterfaceAdvertiser.Callback cb, @NonNull String[] deviceHostName, @NonNull SharedLog sharedLog, @NonNull MdnsFeatureFlags mdnsFeatureFlags)107         public MdnsInterfaceAdvertiser makeAdvertiser(@NonNull MdnsInterfaceSocket socket,
108                 @NonNull List<LinkAddress> initialAddresses,
109                 @NonNull Looper looper, @NonNull byte[] packetCreationBuffer,
110                 @NonNull MdnsInterfaceAdvertiser.Callback cb,
111                 @NonNull String[] deviceHostName,
112                 @NonNull SharedLog sharedLog,
113                 @NonNull MdnsFeatureFlags mdnsFeatureFlags) {
114             // Note NetworkInterface is final and not mockable
115             return new MdnsInterfaceAdvertiser(socket, initialAddresses, looper,
116                     packetCreationBuffer, cb, deviceHostName, sharedLog, mdnsFeatureFlags);
117         }
118 
119         /**
120          * Generates a unique hostname to be used by the device.
121          */
122         @NonNull
generateHostname(boolean useShortFormat)123         public String[] generateHostname(boolean useShortFormat) {
124             // Generate a very-probably-unique hostname. This allows minimizing possible conflicts
125             // to the point that probing for it is no longer necessary (as per RFC6762 8.1 last
126             // paragraph), and does not leak more information than what could already be obtained by
127             // looking at the mDNS packets source address.
128             // This differs from historical behavior that just used "Android.local" for many
129             // devices, creating a lot of conflicts.
130             // Having a different hostname per interface is an acceptable option as per RFC6762 14.
131             // This hostname will change every time the interface is reconnected, so this does not
132             // allow tracking the device.
133             if (useShortFormat) {
134                 // A short hostname helps reduce the size of APF mDNS filtering programs, and
135                 // is necessary for compatibility with some Matter 1.0 devices which assumed
136                 // 16 characters is the maximum length.
137                 // Generate a hostname matching Android_[0-9A-Z]{8}, which has 36^8 possibilities.
138                 // Even with 100 devices advertising the probability of collision is around 2E-9,
139                 // which is negligible.
140                 final SecureRandom sr = new SecureRandom();
141                 final String allowedChars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
142                 final StringBuilder sb = new StringBuilder(8);
143                 for (int i = 0; i < 8; i++) {
144                     sb.append(allowedChars.charAt(sr.nextInt(allowedChars.length())));
145                 }
146                 return new String[]{ "Android_" + sb.toString(), LOCAL_TLD };
147             } else {
148                 return new String[]{
149                         "Android_" + UUID.randomUUID().toString().replace("-", ""), LOCAL_TLD};
150             }
151         }
152     }
153 
154     /**
155      * Gets the current status of the OffloadServiceInfos per interface.
156      * @param interfaceName the target interfaceName
157      * @return the list of current offloaded services.
158      */
159     @NonNull
getAllInterfaceOffloadServiceInfos( @onNull String interfaceName)160     public List<OffloadServiceInfoWrapper> getAllInterfaceOffloadServiceInfos(
161             @NonNull String interfaceName) {
162         return mInterfaceOffloadServices.getOrDefault(interfaceName, Collections.emptyList());
163     }
164 
isInOffloadDenyList(@onNull String serviceType)165     private boolean isInOffloadDenyList(@NonNull String serviceType) {
166         for (int i = 0; i < mOffloadServiceTypeDenyList.size(); ++i) {
167             final String denyListServiceType = mOffloadServiceTypeDenyList.valueAt(i);
168             if (DnsUtils.equalsIgnoreDnsCase(serviceType, denyListServiceType)) {
169                 return true;
170             }
171         }
172         return false;
173     }
174 
175     private final MdnsInterfaceAdvertiser.Callback mInterfaceAdvertiserCb =
176             new MdnsInterfaceAdvertiser.Callback() {
177         @Override
178         public void onServiceProbingSucceeded(
179                 @NonNull MdnsInterfaceAdvertiser advertiser, int serviceId) {
180             final Registration registration = mRegistrations.get(serviceId);
181             if (registration == null) {
182                 mSharedLog.wtf("Register succeeded for unknown registration");
183                 return;
184             }
185             if (mMdnsFeatureFlags.mIsMdnsOffloadFeatureEnabled
186                     // TODO: Enable offload when the serviceInfo contains a custom host.
187                     && TextUtils.isEmpty(registration.getServiceInfo().getHostname())) {
188                 final String serviceType = registration.getServiceInfo().getServiceType();
189                 if (isInOffloadDenyList(serviceType)) {
190                     mSharedLog.i("Offload denied for service type: " + serviceType);
191                 } else {
192                     final String interfaceName = advertiser.getSocketInterfaceName();
193                     final List<OffloadServiceInfoWrapper> existingOffloadServiceInfoWrappers =
194                             mInterfaceOffloadServices.computeIfAbsent(interfaceName,
195                                     k -> new ArrayList<>());
196                     // Remove existing offload services from cache for update.
197                     existingOffloadServiceInfoWrappers.removeIf(
198                             item -> item.mServiceId == serviceId);
199 
200                     byte[] rawOffloadPacket = advertiser.getRawOffloadPayload(serviceId);
201                     final OffloadServiceInfoWrapper newOffloadServiceInfoWrapper =
202                             createOffloadService(serviceId, registration, rawOffloadPacket);
203                     existingOffloadServiceInfoWrappers.add(newOffloadServiceInfoWrapper);
204                     mCb.onOffloadStartOrUpdate(interfaceName,
205                             newOffloadServiceInfoWrapper.mOffloadServiceInfo);
206                 }
207             }
208 
209             // Wait for all current interfaces to be done probing before notifying of success.
210             if (any(mAllAdvertisers, (k, a) -> a.isProbing(serviceId))) return;
211             // The service may still be unregistered/renamed if a conflict is found on a later added
212             // interface, or if a conflicting announcement/reply is detected (RFC6762 9.)
213 
214             if (!registration.mNotifiedRegistrationSuccess) {
215                 mCb.onRegisterServiceSucceeded(serviceId, registration.getServiceInfo());
216                 registration.mNotifiedRegistrationSuccess = true;
217             }
218         }
219 
220         @Override
221         public void onServiceConflict(@NonNull MdnsInterfaceAdvertiser advertiser, int serviceId,
222                 int conflictType) {
223             mSharedLog.i("Found conflict, restarted probing for service "
224                     + serviceId + " "
225                     + conflictType);
226 
227             final Registration registration = mRegistrations.get(serviceId);
228             if (registration == null) return;
229             if (registration.mNotifiedRegistrationSuccess) {
230                 // TODO: consider notifying clients that the service is no longer registered with
231                 // the old name (back to probing). The legacy implementation did not send any
232                 // callback though; it only sent onServiceRegistered after re-probing finishes
233                 // (with the old, conflicting, actually not used name as argument... The new
234                 // implementation will send callbacks with the new name).
235                 registration.mNotifiedRegistrationSuccess = false;
236                 registration.mConflictAfterProbingCount++;
237 
238                 // The service was done probing, just reset it to probing state (RFC6762 9.)
239                 forAllAdvertisers(a -> {
240                     if (!a.maybeRestartProbingForConflict(serviceId)) {
241                         return;
242                     }
243                     if (mMdnsFeatureFlags.mIsMdnsOffloadFeatureEnabled) {
244                         maybeSendOffloadStop(a.getSocketInterfaceName(), serviceId);
245                     }
246                 });
247                 return;
248             }
249 
250             if ((conflictType & CONFLICT_SERVICE) != 0) {
251                 // Service conflict was found during probing; rename once to find a name that has no
252                 // conflict
253                 registration.updateForServiceConflict(
254                         registration.makeNewServiceInfoForServiceConflict(1 /* renameCount */),
255                         1 /* renameCount */);
256             }
257 
258             if ((conflictType & CONFLICT_HOST) != 0) {
259                 // Host conflict was found during probing; rename once to find a name that has no
260                 // conflict
261                 registration.updateForHostConflict(
262                         registration.makeNewServiceInfoForHostConflict(1 /* renameCount */),
263                         1 /* renameCount */);
264             }
265 
266             registration.mConflictDuringProbingCount++;
267 
268             // Keep renaming if the new name conflicts in local registrations
269             updateRegistrationUntilNoConflict((net, adv) -> adv.hasRegistration(registration),
270                     registration);
271 
272             // Update advertisers to use the new name
273             forAllAdvertisers(a -> a.renameServiceForConflict(
274                     serviceId, registration.getServiceInfo()));
275         }
276 
277         @Override
278         public void onAllServicesRemoved(@NonNull MdnsInterfaceSocket socket) {
279             if (DBG) { mSharedLog.i("onAllServicesRemoved: " + socket); }
280             // Try destroying the advertiser if all services has been removed
281             destroyAdvertiser(socket, false /* interfaceDestroyed */);
282         }
283     };
284 
hasAnyServiceConflict( @onNull BiPredicate<Network, InterfaceAdvertiserRequest> applicableAdvertiserFilter, @NonNull NsdServiceInfo newInfo, @NonNull Registration originalRegistration)285     private boolean hasAnyServiceConflict(
286             @NonNull BiPredicate<Network, InterfaceAdvertiserRequest> applicableAdvertiserFilter,
287             @NonNull NsdServiceInfo newInfo,
288             @NonNull Registration originalRegistration) {
289         return any(
290                 mAdvertiserRequests,
291                 (network, adv) ->
292                         applicableAdvertiserFilter.test(network, adv)
293                                 && adv.hasServiceConflict(newInfo, originalRegistration));
294     }
295 
hasAnyHostConflict( @onNull BiPredicate<Network, InterfaceAdvertiserRequest> applicableAdvertiserFilter, @NonNull NsdServiceInfo newInfo, int clientUid)296     private boolean hasAnyHostConflict(
297             @NonNull BiPredicate<Network, InterfaceAdvertiserRequest> applicableAdvertiserFilter,
298             @NonNull NsdServiceInfo newInfo,
299             int clientUid) {
300         // Check if it conflicts with custom hosts.
301         if (any(
302                 mAdvertiserRequests,
303                 (network, adv) ->
304                         applicableAdvertiserFilter.test(network, adv)
305                                 && adv.hasHostConflict(newInfo, clientUid))) {
306             return true;
307         }
308         // Check if it conflicts with the default hostname.
309         return DnsUtils.equalsIgnoreDnsCase(newInfo.getHostname(), mDeviceHostName[0]);
310     }
311 
updateRegistrationUntilNoConflict( @onNull BiPredicate<Network, InterfaceAdvertiserRequest> applicableAdvertiserFilter, @NonNull Registration registration)312     private void updateRegistrationUntilNoConflict(
313             @NonNull BiPredicate<Network, InterfaceAdvertiserRequest> applicableAdvertiserFilter,
314             @NonNull Registration registration) {
315         NsdServiceInfo newInfo = registration.getServiceInfo();
316 
317         int renameServiceCount = 0;
318         while (hasAnyServiceConflict(applicableAdvertiserFilter, newInfo, registration)) {
319             renameServiceCount++;
320             newInfo = registration.makeNewServiceInfoForServiceConflict(renameServiceCount);
321         }
322         registration.updateForServiceConflict(newInfo, renameServiceCount);
323 
324         if (!TextUtils.isEmpty(registration.getServiceInfo().getHostname())) {
325             int renameHostCount = 0;
326             while (hasAnyHostConflict(
327                     applicableAdvertiserFilter, newInfo, registration.mClientUid)) {
328                 renameHostCount++;
329                 newInfo = registration.makeNewServiceInfoForHostConflict(renameHostCount);
330             }
331             registration.updateForHostConflict(newInfo, renameHostCount);
332         }
333     }
334 
maybeSendOffloadStop(final String interfaceName, int serviceId)335     private void maybeSendOffloadStop(final String interfaceName, int serviceId) {
336         final List<OffloadServiceInfoWrapper> existingOffloadServiceInfoWrappers =
337                 mInterfaceOffloadServices.get(interfaceName);
338         if (existingOffloadServiceInfoWrappers == null) {
339             return;
340         }
341         // Stop the offloaded service by matching the service id
342         int idx = CollectionUtils.indexOf(existingOffloadServiceInfoWrappers,
343                 item -> item.mServiceId == serviceId);
344         if (idx >= 0) {
345             mCb.onOffloadStop(interfaceName,
346                     existingOffloadServiceInfoWrappers.get(idx).mOffloadServiceInfo);
347             existingOffloadServiceInfoWrappers.remove(idx);
348         }
349     }
350 
351     /**
352      * Destroys the advertiser for the interface indicated by {@code socket}.
353      *
354      * {@code interfaceDestroyed} should be set to {@code true} if this method is called because
355      * the associated interface has been destroyed.
356      */
destroyAdvertiser(MdnsInterfaceSocket socket, boolean interfaceDestroyed)357     private void destroyAdvertiser(MdnsInterfaceSocket socket, boolean interfaceDestroyed) {
358         InterfaceAdvertiserRequest advertiserRequest;
359 
360         MdnsInterfaceAdvertiser advertiser = mAllAdvertisers.remove(socket);
361         if (advertiser != null) {
362             advertiser.destroyNow();
363             if (DBG) { mSharedLog.i("MdnsInterfaceAdvertiser is destroyed: " + advertiser); }
364         }
365 
366         for (int i = mAdvertiserRequests.size() - 1; i >= 0; i--) {
367             advertiserRequest = mAdvertiserRequests.valueAt(i);
368             if (advertiserRequest.onAdvertiserDestroyed(socket, interfaceDestroyed)) {
369                 if (DBG) { mSharedLog.i("AdvertiserRequest is removed: " + advertiserRequest); }
370                 mAdvertiserRequests.removeAt(i);
371             }
372         }
373     }
374 
375     /**
376      * A request for a {@link MdnsInterfaceAdvertiser}.
377      *
378      * This class tracks services to be advertised on all sockets provided via a registered
379      * {@link MdnsSocketProvider.SocketCallback}.
380      */
381     private class InterfaceAdvertiserRequest implements MdnsSocketProvider.SocketCallback {
382         /** Registrations to add to newer MdnsInterfaceAdvertisers when sockets are created. */
383         @NonNull
384         private final SparseArray<Registration> mPendingRegistrations = new SparseArray<>();
385         @NonNull
386         private final ArrayMap<MdnsInterfaceSocket, MdnsInterfaceAdvertiser> mAdvertisers =
387                 new ArrayMap<>();
388 
InterfaceAdvertiserRequest(@ullable Network requestedNetwork)389         InterfaceAdvertiserRequest(@Nullable Network requestedNetwork) {
390             mSocketProvider.requestSocket(requestedNetwork, this);
391         }
392 
393         /**
394          * Called when the interface advertiser associated with {@code socket} has been destroyed.
395          *
396          * {@code interfaceDestroyed} should be set to {@code true} if this method is called because
397          * the associated interface has been destroyed.
398          *
399          * @return true if the {@link InterfaceAdvertiserRequest} should now be deleted
400          */
onAdvertiserDestroyed( @onNull MdnsInterfaceSocket socket, boolean interfaceDestroyed)401         boolean onAdvertiserDestroyed(
402                 @NonNull MdnsInterfaceSocket socket, boolean interfaceDestroyed) {
403             final MdnsInterfaceAdvertiser removedAdvertiser = mAdvertisers.remove(socket);
404             if (removedAdvertiser != null
405                     && !interfaceDestroyed && mPendingRegistrations.size() > 0) {
406                 mSharedLog.wtf(
407                         "unexpected onAdvertiserDestroyed() when there are pending registrations");
408             }
409 
410             if (mMdnsFeatureFlags.mIsMdnsOffloadFeatureEnabled && removedAdvertiser != null) {
411                 final String interfaceName = removedAdvertiser.getSocketInterfaceName();
412                 // If the interface is destroyed, stop all hardware offloading on that
413                 // interface.
414                 final List<OffloadServiceInfoWrapper> offloadServiceInfoWrappers =
415                         mInterfaceOffloadServices.remove(interfaceName);
416                 if (offloadServiceInfoWrappers != null) {
417                     for (OffloadServiceInfoWrapper offloadServiceInfoWrapper :
418                             offloadServiceInfoWrappers) {
419                         mCb.onOffloadStop(interfaceName,
420                                 offloadServiceInfoWrapper.mOffloadServiceInfo);
421                     }
422                 }
423             }
424 
425             if (mAdvertisers.size() == 0 && mPendingRegistrations.size() == 0) {
426                 // No advertiser is using sockets from this request anymore (in particular for exit
427                 // announcements), and there is no registration so newer sockets will not be
428                 // necessary, so the request can be unregistered.
429                 mSocketProvider.unrequestSocket(this);
430                 return true;
431             }
432             return false;
433         }
434 
435         /**
436          * Return whether this {@link InterfaceAdvertiserRequest} has the given registration.
437          */
hasRegistration(@onNull Registration registration)438         boolean hasRegistration(@NonNull Registration registration) {
439             return mPendingRegistrations.indexOfValue(registration) >= 0;
440         }
441 
442         /**
443          * Return whether using the proposed new {@link NsdServiceInfo} to add a registration would
444          * cause a conflict of the service in this {@link InterfaceAdvertiserRequest}.
445          */
hasServiceConflict( @onNull NsdServiceInfo newInfo, @NonNull Registration originalRegistration)446         boolean hasServiceConflict(
447                 @NonNull NsdServiceInfo newInfo, @NonNull Registration originalRegistration) {
448             return getConflictingRegistrationDueToService(newInfo, originalRegistration) >= 0;
449         }
450 
451         /**
452          * Return whether using the proposed new {@link NsdServiceInfo} to add a registration would
453          * cause a conflict of the host in this {@link InterfaceAdvertiserRequest}.
454          *
455          * @param clientUid UID of the user who wants to advertise the serviceInfo.
456          */
hasHostConflict(@onNull NsdServiceInfo newInfo, int clientUid)457         boolean hasHostConflict(@NonNull NsdServiceInfo newInfo, int clientUid) {
458             return getConflictingRegistrationDueToHost(newInfo, clientUid) >= 0;
459         }
460 
461         /** Get the ID of a conflicting registration due to service, or -1 if none. */
getConflictingRegistrationDueToService( @onNull NsdServiceInfo info, @NonNull Registration originalRegistration)462         int getConflictingRegistrationDueToService(
463                 @NonNull NsdServiceInfo info, @NonNull Registration originalRegistration) {
464             if (TextUtils.isEmpty(info.getServiceName())) {
465                 return -1;
466             }
467             for (int i = 0; i < mPendingRegistrations.size(); i++) {
468                 // Never conflict with itself
469                 if (mPendingRegistrations.valueAt(i) == originalRegistration) {
470                     continue;
471                 }
472                 final NsdServiceInfo other = mPendingRegistrations.valueAt(i).getServiceInfo();
473                 if (DnsUtils.equalsIgnoreDnsCase(info.getServiceName(), other.getServiceName())
474                         && DnsUtils.equalsIgnoreDnsCase(info.getServiceType(),
475                         other.getServiceType())) {
476                     return mPendingRegistrations.keyAt(i);
477                 }
478             }
479             return -1;
480         }
481 
482         /**
483          * Get the ID of a conflicting registration due to host, or -1 if none.
484          *
485          * <p>If there's already another registration with the same hostname requested by another
486          * UID, this is a conflict.
487          *
488          * <p>If there're two registrations both containing address records using the same hostname,
489          * this is a conflict.
490          */
getConflictingRegistrationDueToHost(@onNull NsdServiceInfo info, int clientUid)491         int getConflictingRegistrationDueToHost(@NonNull NsdServiceInfo info, int clientUid) {
492             if (TextUtils.isEmpty(info.getHostname())) {
493                 return -1;
494             }
495             for (int i = 0; i < mPendingRegistrations.size(); i++) {
496                 final Registration otherRegistration = mPendingRegistrations.valueAt(i);
497                 final NsdServiceInfo otherInfo = otherRegistration.getServiceInfo();
498                 final int otherServiceId = mPendingRegistrations.keyAt(i);
499                 if (clientUid != otherRegistration.mClientUid
500                         && DnsUtils.equalsIgnoreDnsCase(
501                                 info.getHostname(), otherInfo.getHostname())) {
502                     return otherServiceId;
503                 }
504                 if (!info.getHostAddresses().isEmpty()
505                         && !otherInfo.getHostAddresses().isEmpty()
506                         && DnsUtils.equalsIgnoreDnsCase(
507                                 info.getHostname(), otherInfo.getHostname())) {
508                     return otherServiceId;
509                 }
510             }
511             return -1;
512         }
513 
514         /**
515          * Add a service to advertise.
516          *
517          * <p>Conflicts must be checked via {@link #getConflictingRegistrationDueToService} and
518          * {@link #getConflictingRegistrationDueToHost} before attempting to add.
519          */
addService(int id, @NonNull Registration registration)520         void addService(int id, @NonNull Registration registration) {
521             mPendingRegistrations.put(id, registration);
522             for (int i = 0; i < mAdvertisers.size(); i++) {
523                 try {
524                     mAdvertisers.valueAt(i).addService(id, registration.getServiceInfo(),
525                             registration.getAdvertisingOptions());
526                 } catch (NameConflictException e) {
527                     mSharedLog.wtf("Name conflict adding services that should have unique names",
528                             e);
529                 }
530             }
531         }
532 
533         /**
534          * Update an already registered service.
535          * The caller is expected to check that the service being updated doesn't change its name
536          */
updateService(int id, @NonNull Registration registration)537         void updateService(int id, @NonNull Registration registration) {
538             mPendingRegistrations.put(id, registration);
539             for (int i = 0; i < mAdvertisers.size(); i++) {
540                 mAdvertisers.valueAt(i).updateService(
541                         id, registration.getServiceInfo().getSubtypes());
542             }
543         }
544 
removeService(int id)545         void removeService(int id) {
546             mPendingRegistrations.remove(id);
547             for (int i = 0; i < mAdvertisers.size(); i++) {
548                 final MdnsInterfaceAdvertiser advertiser = mAdvertisers.valueAt(i);
549                 advertiser.removeService(id);
550 
551                 if (mMdnsFeatureFlags.mIsMdnsOffloadFeatureEnabled) {
552                     maybeSendOffloadStop(advertiser.getSocketInterfaceName(), id);
553                 }
554             }
555         }
556 
getServiceRepliedRequestsCount(int id)557         int getServiceRepliedRequestsCount(int id) {
558             int repliedRequestsCount = NO_PACKET;
559             for (int i = 0; i < mAdvertisers.size(); i++) {
560                 repliedRequestsCount += mAdvertisers.valueAt(i).getServiceRepliedRequestsCount(id);
561             }
562             return repliedRequestsCount;
563         }
564 
getSentPacketCount(int id)565         int getSentPacketCount(int id) {
566             int sentPacketCount = NO_PACKET;
567             for (int i = 0; i < mAdvertisers.size(); i++) {
568                 sentPacketCount += mAdvertisers.valueAt(i).getSentPacketCount(id);
569             }
570             return sentPacketCount;
571         }
572 
573         @Override
onSocketCreated(@onNull SocketKey socketKey, @NonNull MdnsInterfaceSocket socket, @NonNull List<LinkAddress> addresses)574         public void onSocketCreated(@NonNull SocketKey socketKey,
575                 @NonNull MdnsInterfaceSocket socket,
576                 @NonNull List<LinkAddress> addresses) {
577             MdnsInterfaceAdvertiser advertiser = mAllAdvertisers.get(socket);
578             if (advertiser == null) {
579                 advertiser = mDeps.makeAdvertiser(socket, addresses, mLooper, mPacketCreationBuffer,
580                         mInterfaceAdvertiserCb, mDeviceHostName,
581                         mSharedLog.forSubComponent(socket.getInterface().getName()),
582                         mMdnsFeatureFlags);
583                 mAllAdvertisers.put(socket, advertiser);
584                 advertiser.start();
585             }
586             mAdvertisers.put(socket, advertiser);
587             for (int i = 0; i < mPendingRegistrations.size(); i++) {
588                 final Registration registration = mPendingRegistrations.valueAt(i);
589                 try {
590                     advertiser.addService(mPendingRegistrations.keyAt(i),
591                             registration.getServiceInfo(), registration.getAdvertisingOptions());
592                 } catch (NameConflictException e) {
593                     mSharedLog.wtf("Name conflict adding services that should have unique names",
594                             e);
595                 }
596             }
597         }
598 
599         @Override
onInterfaceDestroyed(@onNull SocketKey socketKey, @NonNull MdnsInterfaceSocket socket)600         public void onInterfaceDestroyed(@NonNull SocketKey socketKey,
601                 @NonNull MdnsInterfaceSocket socket) {
602             final MdnsInterfaceAdvertiser advertiser = mAdvertisers.get(socket);
603             if (advertiser != null) destroyAdvertiser(socket, true /* interfaceDestroyed */);
604         }
605 
606         @Override
onAddressesChanged(@onNull SocketKey socketKey, @NonNull MdnsInterfaceSocket socket, @NonNull List<LinkAddress> addresses)607         public void onAddressesChanged(@NonNull SocketKey socketKey,
608                 @NonNull MdnsInterfaceSocket socket, @NonNull List<LinkAddress> addresses) {
609             final MdnsInterfaceAdvertiser advertiser = mAdvertisers.get(socket);
610             if (advertiser == null)  {
611                 return;
612             }
613             advertiser.updateAddresses(addresses);
614 
615             if (mMdnsFeatureFlags.mIsMdnsOffloadFeatureEnabled) {
616                 // Update address should trigger offload packet update.
617                 final String interfaceName = advertiser.getSocketInterfaceName();
618                 final List<OffloadServiceInfoWrapper> existingOffloadServiceInfoWrappers =
619                         mInterfaceOffloadServices.get(interfaceName);
620                 if (existingOffloadServiceInfoWrappers == null) {
621                     return;
622                 }
623                 final List<OffloadServiceInfoWrapper> updatedOffloadServiceInfoWrappers =
624                         new ArrayList<>(existingOffloadServiceInfoWrappers.size());
625                 for (OffloadServiceInfoWrapper oldWrapper : existingOffloadServiceInfoWrappers) {
626                     OffloadServiceInfoWrapper newWrapper = new OffloadServiceInfoWrapper(
627                             oldWrapper.mServiceId,
628                             oldWrapper.mOffloadServiceInfo.withOffloadPayload(
629                                     advertiser.getRawOffloadPayload(oldWrapper.mServiceId))
630                     );
631                     updatedOffloadServiceInfoWrappers.add(newWrapper);
632                     mCb.onOffloadStartOrUpdate(interfaceName, newWrapper.mOffloadServiceInfo);
633                 }
634                 mInterfaceOffloadServices.put(interfaceName, updatedOffloadServiceInfoWrappers);
635             }
636         }
637     }
638 
639     /**
640      * The wrapper class for OffloadServiceInfo including the serviceId.
641      */
642     public static class OffloadServiceInfoWrapper {
643         public final @NonNull OffloadServiceInfo mOffloadServiceInfo;
644         public final int mServiceId;
645 
OffloadServiceInfoWrapper(int serviceId, OffloadServiceInfo offloadServiceInfo)646         OffloadServiceInfoWrapper(int serviceId, OffloadServiceInfo offloadServiceInfo) {
647             mOffloadServiceInfo = offloadServiceInfo;
648             mServiceId = serviceId;
649         }
650     }
651 
652     private static class Registration {
653         @Nullable
654         final String mOriginalServiceName;
655         @Nullable
656         final String mOriginalHostname;
657         boolean mNotifiedRegistrationSuccess;
658         private int mServiceNameConflictCount;
659         private int mHostnameConflictCount;
660         @NonNull
661         private NsdServiceInfo mServiceInfo;
662         final int mClientUid;
663         private final MdnsAdvertisingOptions mAdvertisingOptions;
664         int mConflictDuringProbingCount;
665         int mConflictAfterProbingCount;
666 
Registration(@onNull NsdServiceInfo serviceInfo, int clientUid, @NonNull MdnsAdvertisingOptions advertisingOptions)667         private Registration(@NonNull NsdServiceInfo serviceInfo, int clientUid,
668                 @NonNull MdnsAdvertisingOptions advertisingOptions) {
669             this.mOriginalServiceName = serviceInfo.getServiceName();
670             this.mOriginalHostname = serviceInfo.getHostname();
671             this.mServiceInfo = serviceInfo;
672             this.mClientUid = clientUid;
673             this.mAdvertisingOptions = advertisingOptions;
674         }
675 
676         /** Check if the new {@link NsdServiceInfo} doesn't update any data other than subtypes. */
isSubtypeOnlyUpdate(@onNull NsdServiceInfo newInfo)677         public boolean isSubtypeOnlyUpdate(@NonNull NsdServiceInfo newInfo) {
678             return Objects.equals(newInfo.getServiceName(), mOriginalServiceName)
679                     && Objects.equals(newInfo.getServiceType(), mServiceInfo.getServiceType())
680                     && newInfo.getPort() == mServiceInfo.getPort()
681                     && Objects.equals(newInfo.getHostname(), mOriginalHostname)
682                     && Objects.equals(newInfo.getHostAddresses(), mServiceInfo.getHostAddresses())
683                     && Objects.equals(newInfo.getNetwork(), mServiceInfo.getNetwork());
684         }
685 
686         /**
687          * Update subTypes for the registration.
688          */
updateSubtypes(@onNull Set<String> subtypes)689         public void updateSubtypes(@NonNull Set<String> subtypes) {
690             mServiceInfo = new NsdServiceInfo(mServiceInfo);
691             mServiceInfo.setSubtypes(subtypes);
692         }
693 
694         /**
695          * Update the registration to use a different service name, after a conflict was found.
696          *
697          * @param newInfo New service info to use.
698          * @param renameCount How many renames were done before reaching the current name.
699          */
updateForServiceConflict(@onNull NsdServiceInfo newInfo, int renameCount)700         private void updateForServiceConflict(@NonNull NsdServiceInfo newInfo, int renameCount) {
701             mServiceNameConflictCount += renameCount;
702             mServiceInfo = newInfo;
703         }
704 
705         /**
706          * Update the registration to use a different host name, after a conflict was found.
707          *
708          * @param newInfo New service info to use.
709          * @param renameCount How many renames were done before reaching the current name.
710          */
updateForHostConflict(@onNull NsdServiceInfo newInfo, int renameCount)711         private void updateForHostConflict(@NonNull NsdServiceInfo newInfo, int renameCount) {
712             mHostnameConflictCount += renameCount;
713             mServiceInfo = newInfo;
714         }
715 
716         /**
717          * Make a new service name for the registration, after a conflict was found.
718          *
719          * If a name conflict was found during probing or because different advertising requests
720          * used the same name, the registration is attempted again with a new name (here using
721          * a number suffix, (1), (2) etc). Registration success is notified once probing succeeds
722          * with a new name. This matches legacy behavior based on mdnsresponder, and appendix D of
723          * RFC6763.
724          *
725          * @param renameCount How much to increase the number suffix for this conflict.
726          */
727         @NonNull
makeNewServiceInfoForServiceConflict(int renameCount)728         public NsdServiceInfo makeNewServiceInfoForServiceConflict(int renameCount) {
729             // In case of conflict choose a different service name. After the first conflict use
730             // "Name (2)", then "Name (3)" etc.
731             // TODO: use a hidden method in NsdServiceInfo once MdnsAdvertiser is moved to service-t
732             final NsdServiceInfo newInfo = new NsdServiceInfo(mServiceInfo);
733             newInfo.setServiceName(getUpdatedServiceName(renameCount));
734             return newInfo;
735         }
736 
737         /**
738          * Make a new hostname for the registration, after a conflict was found.
739          *
740          * <p>If a name conflict was found during probing or because different advertising requests
741          * used the same name, the registration is attempted again with a new name (here using a
742          * number suffix, -1, -2, etc). Registration success is notified once probing succeeds with
743          * a new name.
744          *
745          * @param renameCount How much to increase the number suffix for this conflict.
746          */
747         @NonNull
makeNewServiceInfoForHostConflict(int renameCount)748         public NsdServiceInfo makeNewServiceInfoForHostConflict(int renameCount) {
749             // In case of conflict choose a different hostname. After the first conflict use
750             // "Name-2", then "Name-3" etc.
751             final NsdServiceInfo newInfo = new NsdServiceInfo(mServiceInfo);
752             newInfo.setHostname(getUpdatedHostname(renameCount));
753             return newInfo;
754         }
755 
getUpdatedServiceName(int renameCount)756         private String getUpdatedServiceName(int renameCount) {
757             final String suffix = " (" + (mServiceNameConflictCount + renameCount + 1) + ")";
758             final String truncatedServiceName = MdnsUtils.truncateServiceName(mOriginalServiceName,
759                     MAX_LABEL_LENGTH - suffix.length());
760             return truncatedServiceName + suffix;
761         }
762 
getUpdatedHostname(int renameCount)763         private String getUpdatedHostname(int renameCount) {
764             final String suffix = "-" + (mHostnameConflictCount + renameCount + 1);
765             final String truncatedHostname =
766                     MdnsUtils.truncateServiceName(
767                             mOriginalHostname, MAX_LABEL_LENGTH - suffix.length());
768             return truncatedHostname + suffix;
769         }
770 
771         @NonNull
getServiceInfo()772         public NsdServiceInfo getServiceInfo() {
773             return mServiceInfo;
774         }
775 
776         @NonNull
getAdvertisingOptions()777         public MdnsAdvertisingOptions getAdvertisingOptions() {
778             return mAdvertisingOptions;
779         }
780     }
781 
782     /**
783      * Callbacks for advertising services.
784      *
785      * Every method is called on the MdnsAdvertiser looper thread.
786      */
787     public interface AdvertiserCallback {
788         /**
789          * Called when a service was successfully registered, after probing.
790          *
791          * @param serviceId ID of the service provided when registering.
792          * @param registeredInfo Registered info, which may be different from the requested info,
793          *                       after probing and possibly choosing alternative service names.
794          */
onRegisterServiceSucceeded(int serviceId, NsdServiceInfo registeredInfo)795         void onRegisterServiceSucceeded(int serviceId, NsdServiceInfo registeredInfo);
796 
797         /**
798          * Called when service registration failed.
799          *
800          * @param serviceId ID of the service provided when registering.
801          * @param errorCode One of {@code NsdManager.FAILURE_*}
802          */
onRegisterServiceFailed(int serviceId, int errorCode)803         void onRegisterServiceFailed(int serviceId, int errorCode);
804 
805         // Unregistration is notified immediately as success in NsdService so no callback is needed
806         // here.
807 
808         /**
809          * Called when a service is ready to be sent for hardware offloading.
810          *
811          * @param interfaceName the interface for sending the update to.
812          * @param offloadServiceInfo the offloading content.
813          */
onOffloadStartOrUpdate(@onNull String interfaceName, @NonNull OffloadServiceInfo offloadServiceInfo)814         void onOffloadStartOrUpdate(@NonNull String interfaceName,
815                 @NonNull OffloadServiceInfo offloadServiceInfo);
816 
817         /**
818          * Called when a service is removed or the MdnsInterfaceAdvertiser is destroyed.
819          *
820          * @param interfaceName the interface for sending the update to.
821          * @param offloadServiceInfo the offloading content.
822          */
onOffloadStop(@onNull String interfaceName, @NonNull OffloadServiceInfo offloadServiceInfo)823         void onOffloadStop(@NonNull String interfaceName,
824                 @NonNull OffloadServiceInfo offloadServiceInfo);
825     }
826 
827     /**
828      * Data class of avdverting metrics.
829      */
830     public static class AdvertiserMetrics {
831         public final int mRepliedRequestsCount;
832         public final int mSentPacketCount;
833         public final int mConflictDuringProbingCount;
834         public final int mConflictAfterProbingCount;
835 
AdvertiserMetrics(int repliedRequestsCount, int sentPacketCount, int conflictDuringProbingCount, int conflictAfterProbingCount)836         public AdvertiserMetrics(int repliedRequestsCount, int sentPacketCount,
837                 int conflictDuringProbingCount, int conflictAfterProbingCount) {
838             mRepliedRequestsCount = repliedRequestsCount;
839             mSentPacketCount = sentPacketCount;
840             mConflictDuringProbingCount = conflictDuringProbingCount;
841             mConflictAfterProbingCount = conflictAfterProbingCount;
842         }
843     }
844 
MdnsAdvertiser(@onNull Looper looper, @NonNull MdnsSocketProvider socketProvider, @NonNull AdvertiserCallback cb, @NonNull SharedLog sharedLog, @NonNull MdnsFeatureFlags mDnsFeatureFlags, @NonNull Context context)845     public MdnsAdvertiser(@NonNull Looper looper, @NonNull MdnsSocketProvider socketProvider,
846             @NonNull AdvertiserCallback cb, @NonNull SharedLog sharedLog,
847             @NonNull MdnsFeatureFlags mDnsFeatureFlags, @NonNull Context context) {
848         this(looper, socketProvider, cb, new Dependencies(), sharedLog, mDnsFeatureFlags,
849                 context);
850     }
851 
852     @VisibleForTesting
MdnsAdvertiser(@onNull Looper looper, @NonNull MdnsSocketProvider socketProvider, @NonNull AdvertiserCallback cb, @NonNull Dependencies deps, @NonNull SharedLog sharedLog, @NonNull MdnsFeatureFlags mDnsFeatureFlags, @NonNull Context context)853     MdnsAdvertiser(@NonNull Looper looper, @NonNull MdnsSocketProvider socketProvider,
854             @NonNull AdvertiserCallback cb, @NonNull Dependencies deps,
855             @NonNull SharedLog sharedLog, @NonNull MdnsFeatureFlags mDnsFeatureFlags,
856             @NonNull Context context) {
857         mLooper = looper;
858         mCb = cb;
859         mSocketProvider = socketProvider;
860         mDeps = deps;
861         mDeviceHostName = deps.generateHostname(mDnsFeatureFlags.isShortHostnamesEnabled());
862         mSharedLog = sharedLog;
863         mMdnsFeatureFlags = mDnsFeatureFlags;
864         final ConnectivityResources res = new ConnectivityResources(context);
865         mServiceTypeToOffloadPriority = parseOffloadPriorityList(
866                 res.get().getStringArray(R.array.config_nsdOffloadServicesPriority), sharedLog);
867         mOffloadServiceTypeDenyList = new ArraySet<>(
868                 res.get().getStringArray(R.array.config_nsdOffloadServicesDenyList));
869     }
870 
parseOffloadPriorityList( @onNull String[] resValues, SharedLog sharedLog)871     private static Map<String, Integer> parseOffloadPriorityList(
872             @NonNull String[] resValues, SharedLog sharedLog) {
873         final Map<String, Integer> priorities = new ArrayMap<>(resValues.length);
874         for (String entry : resValues) {
875             final String[] priorityAndType = entry.split(":", 2);
876             if (priorityAndType.length != 2) {
877                 sharedLog.wtf("Invalid config_nsdOffloadServicesPriority ignored: " + entry);
878                 continue;
879             }
880 
881             final int priority;
882             try {
883                 priority = Integer.parseInt(priorityAndType[0]);
884             } catch (NumberFormatException e) {
885                 sharedLog.wtf("Invalid priority in config_nsdOffloadServicesPriority: " + entry);
886                 continue;
887             }
888             priorities.put(DnsUtils.toDnsUpperCase(priorityAndType[1]), priority);
889         }
890         return priorities;
891     }
892 
checkThread()893     private void checkThread() {
894         if (Thread.currentThread() != mLooper.getThread()) {
895             throw new IllegalStateException("This must be called on the looper thread");
896         }
897     }
898 
899     /**
900      * Add or update a service to advertise.
901      *
902      * @param id A unique ID for the service.
903      * @param service The service info to advertise.
904      * @param advertisingOptions The advertising options.
905      * @param clientUid The UID who wants to advertise the service.
906      */
addOrUpdateService(int id, NsdServiceInfo service, MdnsAdvertisingOptions advertisingOptions, int clientUid)907     public void addOrUpdateService(int id, NsdServiceInfo service,
908             MdnsAdvertisingOptions advertisingOptions, int clientUid) {
909         checkThread();
910         final Registration existingRegistration = mRegistrations.get(id);
911         final Network network = service.getNetwork();
912         final Set<String> subtypes = service.getSubtypes();
913         Registration registration;
914         if (advertisingOptions.isOnlyUpdate()) {
915             if (existingRegistration == null) {
916                 mSharedLog.e("Update non existing registration for " + service);
917                 mCb.onRegisterServiceFailed(id, NsdManager.FAILURE_INTERNAL_ERROR);
918                 return;
919             }
920             if (!(existingRegistration.isSubtypeOnlyUpdate(service))) {
921                 mSharedLog.e("Update request can only update subType, serviceInfo: " + service
922                         + ", existing serviceInfo: " + existingRegistration.getServiceInfo());
923                 mCb.onRegisterServiceFailed(id, NsdManager.FAILURE_INTERNAL_ERROR);
924                 return;
925 
926             }
927             mSharedLog.i("Update service " + service + " with ID " + id + " and subtypes "
928                     + subtypes + " advertisingOptions " + advertisingOptions);
929             registration = existingRegistration;
930             registration.updateSubtypes(subtypes);
931         } else {
932             if (existingRegistration != null) {
933                 mSharedLog.e("Adding duplicate registration for " + service);
934                 // TODO (b/264986328): add a more specific error code
935                 mCb.onRegisterServiceFailed(id, NsdManager.FAILURE_INTERNAL_ERROR);
936                 return;
937             }
938             mSharedLog.i("Adding service " + service + " with ID " + id + " and subtypes "
939                     + subtypes + " advertisingOptions " + advertisingOptions);
940             registration = new Registration(service, clientUid, advertisingOptions);
941             final BiPredicate<Network, InterfaceAdvertiserRequest> checkConflictFilter;
942             if (network == null) {
943                 // If registering on all networks, no advertiser must have conflicts
944                 checkConflictFilter = (net, adv) -> true;
945             } else {
946                 // If registering on one network, the matching network advertiser and the one
947                 // for all networks must not have conflicts
948                 checkConflictFilter = (net, adv) -> net == null || network.equals(net);
949             }
950             updateRegistrationUntilNoConflict(checkConflictFilter, registration);
951         }
952 
953         InterfaceAdvertiserRequest advertiser = mAdvertiserRequests.get(network);
954         if (advertiser == null) {
955             advertiser = new InterfaceAdvertiserRequest(network);
956             mAdvertiserRequests.put(network, advertiser);
957         }
958         if (advertisingOptions.isOnlyUpdate()) {
959             advertiser.updateService(id, registration);
960         } else {
961             advertiser.addService(id, registration);
962         }
963         mRegistrations.put(id, registration);
964     }
965 
966     /**
967      * Remove a previously added service.
968      * @param id ID used when registering.
969      */
removeService(int id)970     public void removeService(int id) {
971         checkThread();
972         if (!mRegistrations.contains(id)) return;
973         mSharedLog.i("Removing service with ID " + id);
974         for (int i = mAdvertiserRequests.size() - 1; i >= 0; i--) {
975             final InterfaceAdvertiserRequest advertiser = mAdvertiserRequests.valueAt(i);
976             advertiser.removeService(id);
977         }
978         mRegistrations.remove(id);
979         // Regenerates host name when registrations removed.
980         if (mRegistrations.size() == 0) {
981             mDeviceHostName = mDeps.generateHostname(mMdnsFeatureFlags.isShortHostnamesEnabled());
982         }
983     }
984 
985     /**
986      * Get advertising metrics.
987      *
988      * @param id ID used when registering.
989      * @return The advertising metrics includes replied requests count, send packet count, conflict
990      *         count during/after probing.
991      */
getAdvertiserMetrics(int id)992     public AdvertiserMetrics getAdvertiserMetrics(int id) {
993         checkThread();
994         final Registration registration = mRegistrations.get(id);
995         if (registration == null) {
996             return new AdvertiserMetrics(
997                     NO_PACKET /* repliedRequestsCount */,
998                     NO_PACKET /* sentPacketCount */,
999                     0 /* conflictDuringProbingCount */,
1000                     0 /* conflictAfterProbingCount */);
1001         }
1002         int repliedRequestsCount = NO_PACKET;
1003         int sentPacketCount = NO_PACKET;
1004         for (int i = 0; i < mAdvertiserRequests.size(); i++) {
1005             repliedRequestsCount +=
1006                     mAdvertiserRequests.valueAt(i).getServiceRepliedRequestsCount(id);
1007             sentPacketCount += mAdvertiserRequests.valueAt(i).getSentPacketCount(id);
1008         }
1009         return new AdvertiserMetrics(repliedRequestsCount, sentPacketCount,
1010                 registration.mConflictDuringProbingCount, registration.mConflictAfterProbingCount);
1011     }
1012 
any(@onNull ArrayMap<K, V> map, @NonNull BiPredicate<K, V> predicate)1013     private static <K, V> boolean any(@NonNull ArrayMap<K, V> map,
1014             @NonNull BiPredicate<K, V> predicate) {
1015         for (int i = 0; i < map.size(); i++) {
1016             if (predicate.test(map.keyAt(i), map.valueAt(i))) {
1017                 return true;
1018             }
1019         }
1020         return false;
1021     }
1022 
forAllAdvertisers(@onNull Consumer<MdnsInterfaceAdvertiser> consumer)1023     private void forAllAdvertisers(@NonNull Consumer<MdnsInterfaceAdvertiser> consumer) {
1024         any(mAllAdvertisers, (socket, advertiser) -> {
1025             consumer.accept(advertiser);
1026             return false;
1027         });
1028     }
1029 
getOffloadSubtype(@onNull NsdServiceInfo nsdServiceInfo)1030     private List<String> getOffloadSubtype(@NonNull NsdServiceInfo nsdServiceInfo) {
1031         // Workaround: Google Cast doesn't announce subtypes per DNS-SD/mDNS spec.
1032         // Thus, subtypes aren't offloaded; only "_googlecast._tcp" is.
1033         // Subtype responses occur when hardware offload is off.
1034         // This solution works because Google Cast doesn't follow the intended usage of subtypes in
1035         // the spec, as it always discovers for both the subtype+base type, and only uses the mDNS
1036         // subtype as an optimization.
1037         if (nsdServiceInfo.getServiceType().equals("_googlecast._tcp")) {
1038             return new ArrayList<>();
1039         }
1040         return new ArrayList<>(nsdServiceInfo.getSubtypes());
1041     }
1042 
createOffloadService(int serviceId, @NonNull Registration registration, byte[] rawOffloadPacket)1043     private OffloadServiceInfoWrapper createOffloadService(int serviceId,
1044             @NonNull Registration registration, byte[] rawOffloadPacket) {
1045         final NsdServiceInfo nsdServiceInfo = registration.getServiceInfo();
1046         final Integer mapPriority = mServiceTypeToOffloadPriority.get(
1047                 DnsUtils.toDnsUpperCase(nsdServiceInfo.getServiceType()));
1048         // Higher values of priority are less prioritized
1049         final int priority = mapPriority == null ? Integer.MAX_VALUE : mapPriority;
1050         final OffloadServiceInfo offloadServiceInfo = new OffloadServiceInfo(
1051                 new OffloadServiceInfo.Key(nsdServiceInfo.getServiceName(),
1052                         nsdServiceInfo.getServiceType()),
1053                 getOffloadSubtype(nsdServiceInfo),
1054                 String.join(".", mDeviceHostName),
1055                 rawOffloadPacket,
1056                 priority,
1057                 // TODO: set the offloadType based on the callback timing.
1058                 OffloadEngine.OFFLOAD_TYPE_REPLY);
1059         return new OffloadServiceInfoWrapper(serviceId, offloadServiceInfo);
1060     }
1061 }
1062