1 /*
2  * Copyright (C) 2021 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 android.annotation.NonNull;
20 import android.annotation.Nullable;
21 import android.net.Network;
22 import android.os.Parcel;
23 import android.os.Parcelable;
24 import android.text.TextUtils;
25 
26 import com.android.server.connectivity.mdns.util.MdnsUtils;
27 
28 import java.util.ArrayList;
29 import java.util.Collection;
30 import java.util.List;
31 import java.util.Objects;
32 import java.util.Set;
33 
34 /**
35  * API configuration parameters for searching the mDNS service.
36  *
37  * <p>Use {@link MdnsSearchOptions.Builder} to create {@link MdnsSearchOptions}.
38  *
39  * @hide
40  */
41 public class MdnsSearchOptions implements Parcelable {
42     // Passive query mode scans less frequently in order to conserve battery and produce less
43     // network traffic.
44     public static final int PASSIVE_QUERY_MODE = 0;
45     // Active query mode scans frequently.
46     public static final int ACTIVE_QUERY_MODE = 1;
47     // Aggressive query mode scans more frequently than the active mode at first, and sends both
48     // unicast and multicast queries simultaneously, but in long sessions it eventually sends as
49     // many queries as the PASSIVE mode.
50     public static final int AGGRESSIVE_QUERY_MODE = 2;
51 
52     /** @hide */
53     public static final Parcelable.Creator<MdnsSearchOptions> CREATOR =
54             new Parcelable.Creator<MdnsSearchOptions>() {
55                 @Override
56                 public MdnsSearchOptions createFromParcel(Parcel source) {
57                     return new MdnsSearchOptions(
58                             source.createStringArrayList(),
59                             source.readInt(),
60                             source.readInt() == 1,
61                             source.readParcelable(null),
62                             source.readInt(),
63                             source.readString(),
64                             source.readInt() == 1,
65                             source.readInt());
66                 }
67 
68                 @Override
69                 public MdnsSearchOptions[] newArray(int size) {
70                     return new MdnsSearchOptions[size];
71                 }
72             };
73     private static MdnsSearchOptions defaultOptions;
74     private final List<String> subtypes;
75     @Nullable
76     private final String resolveInstanceName;
77     private final int queryMode;
78     private final boolean onlyUseIpv6OnIpv6OnlyNetworks;
79     private final int numOfQueriesBeforeBackoff;
80     private final boolean removeExpiredService;
81     // The target network for searching. Null network means search on all possible interfaces.
82     @Nullable private final Network mNetwork;
83     // If the target interface does not have a Network, set to the interface index, otherwise unset.
84     private final int mInterfaceIndex;
85 
86     /** Parcelable constructs for a {@link MdnsSearchOptions}. */
MdnsSearchOptions( List<String> subtypes, int queryMode, boolean removeExpiredService, @Nullable Network network, int interfaceIndex, @Nullable String resolveInstanceName, boolean onlyUseIpv6OnIpv6OnlyNetworks, int numOfQueriesBeforeBackoff)87     MdnsSearchOptions(
88             List<String> subtypes,
89             int queryMode,
90             boolean removeExpiredService,
91             @Nullable Network network,
92             int interfaceIndex,
93             @Nullable String resolveInstanceName,
94             boolean onlyUseIpv6OnIpv6OnlyNetworks,
95             int numOfQueriesBeforeBackoff) {
96         this.subtypes = new ArrayList<>();
97         if (subtypes != null) {
98             this.subtypes.addAll(subtypes);
99         }
100         this.queryMode = queryMode;
101         this.onlyUseIpv6OnIpv6OnlyNetworks = onlyUseIpv6OnIpv6OnlyNetworks;
102         this.numOfQueriesBeforeBackoff = numOfQueriesBeforeBackoff;
103         this.removeExpiredService = removeExpiredService;
104         mNetwork = network;
105         mInterfaceIndex = interfaceIndex;
106         this.resolveInstanceName = resolveInstanceName;
107     }
108 
109     /** Returns a {@link Builder} for {@link MdnsSearchOptions}. */
newBuilder()110     public static Builder newBuilder() {
111         return new Builder();
112     }
113 
114     /** Returns a default search options. */
getDefaultOptions()115     public static synchronized MdnsSearchOptions getDefaultOptions() {
116         if (defaultOptions == null) {
117             defaultOptions = newBuilder().build();
118         }
119         return defaultOptions;
120     }
121 
122     /** @return the list of subtypes to search. */
getSubtypes()123     public List<String> getSubtypes() {
124         return subtypes;
125     }
126 
127     /**
128      * @return the current query mode.
129      */
getQueryMode()130     public int getQueryMode() {
131         return queryMode;
132     }
133 
134     /**
135      * @return {@code true} if only the IPv4 mDNS host should be queried on network that supports
136      * both IPv6 as well as IPv4. On an IPv6-only network, this is ignored.
137      */
onlyUseIpv6OnIpv6OnlyNetworks()138     public boolean onlyUseIpv6OnIpv6OnlyNetworks() {
139         return onlyUseIpv6OnIpv6OnlyNetworks;
140     }
141 
142     /**
143      *  Returns number of queries should be executed before backoff mode is enabled.
144      *  The default number is 3 if it is not set.
145      */
numOfQueriesBeforeBackoff()146     public int numOfQueriesBeforeBackoff() {
147         return numOfQueriesBeforeBackoff;
148     }
149 
150     /** Returns {@code true} if service will be removed after its TTL expires. */
removeExpiredService()151     public boolean removeExpiredService() {
152         return removeExpiredService;
153     }
154 
155     /**
156      * Returns the network which the mdns query should target.
157      *
158      * @return the target network or null to search on all possible interfaces.
159      */
160     @Nullable
getNetwork()161     public Network getNetwork() {
162         return mNetwork;
163     }
164 
165 
166     /**
167      * Returns the interface index which the mdns query should target.
168      *
169      * This is only set when the service is to be searched on an interface that does not have a
170      * Network, in which case {@link #getNetwork()} returns null.
171      * The interface index as per {@link java.net.NetworkInterface#getIndex}, or 0 if unset.
172      */
getInterfaceIndex()173     public int getInterfaceIndex() {
174         return mInterfaceIndex;
175     }
176 
177     /**
178      * If non-null, queries should try to resolve all records of this specific service, rather than
179      * discovering all services.
180      */
181     @Nullable
getResolveInstanceName()182     public String getResolveInstanceName() {
183         return resolveInstanceName;
184     }
185 
186     @Override
describeContents()187     public int describeContents() {
188         return 0;
189     }
190 
191     @Override
writeToParcel(Parcel out, int flags)192     public void writeToParcel(Parcel out, int flags) {
193         out.writeStringList(subtypes);
194         out.writeInt(queryMode);
195         out.writeInt(removeExpiredService ? 1 : 0);
196         out.writeParcelable(mNetwork, 0);
197         out.writeInt(mInterfaceIndex);
198         out.writeString(resolveInstanceName);
199         out.writeInt(onlyUseIpv6OnIpv6OnlyNetworks ? 1 : 0);
200         out.writeInt(numOfQueriesBeforeBackoff);
201     }
202 
203     /** A builder to create {@link MdnsSearchOptions}. */
204     public static final class Builder {
205         private final Set<String> subtypes;
206         private int queryMode = PASSIVE_QUERY_MODE;
207         private boolean onlyUseIpv6OnIpv6OnlyNetworks = false;
208         private int numOfQueriesBeforeBackoff = 3;
209         private boolean removeExpiredService;
210         private Network mNetwork;
211         private int mInterfaceIndex;
212         private String resolveInstanceName;
213 
Builder()214         private Builder() {
215             subtypes = MdnsUtils.newSet();
216         }
217 
218         /**
219          * Adds a subtype to search.
220          *
221          * @param subtype the subtype to add.
222          */
addSubtype(@onNull String subtype)223         public Builder addSubtype(@NonNull String subtype) {
224             if (TextUtils.isEmpty(subtype)) {
225                 throw new IllegalArgumentException("Empty subtype");
226             }
227             subtypes.add(subtype);
228             return this;
229         }
230 
231         /**
232          * Adds a set of subtypes to search.
233          *
234          * @param subtypes The list of subtypes to add.
235          */
addSubtypes(@onNull Collection<String> subtypes)236         public Builder addSubtypes(@NonNull Collection<String> subtypes) {
237             this.subtypes.addAll(Objects.requireNonNull(subtypes));
238             return this;
239         }
240 
241         /**
242          * Sets which query mode should be used.
243          *
244          * @param queryMode the query mode should be used.
245          */
setQueryMode(int queryMode)246         public Builder setQueryMode(int queryMode) {
247             this.queryMode = queryMode;
248             return this;
249         }
250 
251         /**
252          * Sets if only the IPv4 mDNS host should be queried on a network that is both IPv4 & IPv6.
253          * On an IPv6-only network, this is ignored.
254          */
setOnlyUseIpv6OnIpv6OnlyNetworks(boolean onlyUseIpv6OnIpv6OnlyNetworks)255         public Builder setOnlyUseIpv6OnIpv6OnlyNetworks(boolean onlyUseIpv6OnIpv6OnlyNetworks) {
256             this.onlyUseIpv6OnIpv6OnlyNetworks = onlyUseIpv6OnIpv6OnlyNetworks;
257             return this;
258         }
259 
260         /**
261          * Sets if the query backoff mode should be turned on.
262          */
setNumOfQueriesBeforeBackoff(int numOfQueriesBeforeBackoff)263         public Builder setNumOfQueriesBeforeBackoff(int numOfQueriesBeforeBackoff) {
264             this.numOfQueriesBeforeBackoff = numOfQueriesBeforeBackoff;
265             return this;
266         }
267 
268         /**
269          * Sets if the service should be removed after TTL.
270          *
271          * @param removeExpiredService If set to {@code true}, the service will be removed after TTL
272          */
setRemoveExpiredService(boolean removeExpiredService)273         public Builder setRemoveExpiredService(boolean removeExpiredService) {
274             this.removeExpiredService = removeExpiredService;
275             return this;
276         }
277 
278         /**
279          * Sets if the mdns query should target on specific network.
280          *
281          * @param network the mdns query will target on given network.
282          */
setNetwork(Network network)283         public Builder setNetwork(Network network) {
284             mNetwork = network;
285             return this;
286         }
287 
288         /**
289          * Set the instance name to resolve.
290          *
291          * If non-null, queries should try to resolve all records of this specific service,
292          * rather than discovering all services.
293          * @param name The instance name.
294          */
setResolveInstanceName(String name)295         public Builder setResolveInstanceName(String name) {
296             resolveInstanceName = name;
297             return this;
298         }
299 
300         /**
301          * Set the interface index to use for the query, if not querying on a {@link Network}.
302          *
303          * @see MdnsSearchOptions#getInterfaceIndex()
304          */
setInterfaceIndex(int index)305         public Builder setInterfaceIndex(int index) {
306             mInterfaceIndex = index;
307             return this;
308         }
309 
310         /** Builds a {@link MdnsSearchOptions} with the arguments supplied to this builder. */
build()311         public MdnsSearchOptions build() {
312             return new MdnsSearchOptions(
313                     new ArrayList<>(subtypes),
314                     queryMode,
315                     removeExpiredService,
316                     mNetwork,
317                     mInterfaceIndex,
318                     resolveInstanceName,
319                     onlyUseIpv6OnIpv6OnlyNetworks,
320                     numOfQueriesBeforeBackoff);
321         }
322     }
323 }