1 /*
2  * Copyright (C) 2023 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 android.net;
18 
19 import android.annotation.IntDef;
20 import android.annotation.NonNull;
21 import android.annotation.Nullable;
22 import android.os.Parcel;
23 import android.os.Parcelable;
24 import android.text.TextUtils;
25 import android.util.ArraySet;
26 import android.util.Log;
27 
28 import java.lang.annotation.Retention;
29 import java.lang.annotation.RetentionPolicy;
30 import java.net.Inet6Address;
31 import java.net.UnknownHostException;
32 import java.util.Arrays;
33 import java.util.Collections;
34 import java.util.Objects;
35 import java.util.Set;
36 import java.util.StringJoiner;
37 
38 /**
39  * A class representing a configuration for multicast routing.
40  *
41  * Internal usage to Connectivity
42  * @hide
43  */
44 // @SystemApi(client = MODULE_LIBRARIES)
45 public final class MulticastRoutingConfig implements Parcelable {
46     private static final String TAG = MulticastRoutingConfig.class.getSimpleName();
47 
48     /** Do not forward any multicast packets. */
49     public static final int FORWARD_NONE = 0;
50     /**
51      * Forward only multicast packets with destination in the list of listening addresses.
52      * Ignore the min scope.
53      */
54     public static final int FORWARD_SELECTED = 1;
55     /**
56      * Forward all multicast packets with scope greater or equal than the min scope.
57      * Ignore the list of listening addresses.
58      */
59     public static final int FORWARD_WITH_MIN_SCOPE = 2;
60 
61     /** @hide */
62     @Retention(RetentionPolicy.SOURCE)
63     @IntDef(prefix = { "FORWARD_" }, value = {
64             FORWARD_NONE,
65             FORWARD_SELECTED,
66             FORWARD_WITH_MIN_SCOPE
67     })
68     public @interface MulticastForwardingMode {}
69 
70     /**
71      * Not a multicast scope, for configurations that do not use the min scope.
72      */
73     public static final int MULTICAST_SCOPE_NONE = -1;
74 
75     /** @hide */
76     public static final MulticastRoutingConfig CONFIG_FORWARD_NONE =
77             new MulticastRoutingConfig(FORWARD_NONE, MULTICAST_SCOPE_NONE, null);
78 
79     @MulticastForwardingMode
80     private final int mForwardingMode;
81 
82     private final int mMinScope;
83 
84     @NonNull
85     private final Set<Inet6Address> mListeningAddresses;
86 
MulticastRoutingConfig(@ulticastForwardingMode final int mode, final int scope, @Nullable final Set<Inet6Address> addresses)87     private MulticastRoutingConfig(@MulticastForwardingMode final int mode, final int scope,
88             @Nullable final Set<Inet6Address> addresses) {
89         mForwardingMode = mode;
90         mMinScope = scope;
91         if (null != addresses) {
92             mListeningAddresses = Collections.unmodifiableSet(new ArraySet<>(addresses));
93         } else {
94             mListeningAddresses = Collections.emptySet();
95         }
96     }
97 
98     /**
99      * Returns the forwarding mode.
100      */
101     @MulticastForwardingMode
getForwardingMode()102     public int getForwardingMode() {
103         return mForwardingMode;
104     }
105 
106     /**
107      * Returns the minimal group address scope that is allowed for forwarding.
108      * If the forwarding mode is not FORWARD_WITH_MIN_SCOPE, will be MULTICAST_SCOPE_NONE.
109      */
getMinimumScope()110     public int getMinimumScope() {
111         return mMinScope;
112     }
113 
114     /**
115      * Returns the list of group addresses listened by the outgoing interface.
116      * The list will be empty if the forwarding mode is not FORWARD_SELECTED.
117      */
118     @NonNull
getListeningAddresses()119     public Set<Inet6Address> getListeningAddresses() {
120         return mListeningAddresses;
121     }
122 
MulticastRoutingConfig(Parcel in)123     private MulticastRoutingConfig(Parcel in) {
124         mForwardingMode = in.readInt();
125         mMinScope = in.readInt();
126         final int count = in.readInt();
127         final ArraySet<Inet6Address> listeningAddresses = new ArraySet<>(count);
128         final byte[] buffer = new byte[16]; // Size of an Inet6Address
129         for (int i = 0; i < count; ++i) {
130             in.readByteArray(buffer);
131             try {
132                 listeningAddresses.add((Inet6Address) Inet6Address.getByAddress(buffer));
133             } catch (UnknownHostException e) {
134                 Log.wtf(TAG, "Can't read inet6address : " + Arrays.toString(buffer));
135             }
136         }
137         mListeningAddresses = Collections.unmodifiableSet(listeningAddresses);
138     }
139 
140     @Override
writeToParcel(@onNull Parcel dest, int flags)141     public void writeToParcel(@NonNull Parcel dest, int flags) {
142         dest.writeInt(mForwardingMode);
143         dest.writeInt(mMinScope);
144         dest.writeInt(mListeningAddresses.size());
145         for (final Inet6Address addr : mListeningAddresses) {
146             dest.writeByteArray(addr.getAddress());
147         }
148     }
149 
150     @Override
describeContents()151     public int describeContents() {
152         return 0;
153     }
154 
155     @NonNull
156     public static final Creator<MulticastRoutingConfig> CREATOR = new Creator<>() {
157         @Override
158         public MulticastRoutingConfig createFromParcel(@NonNull Parcel in) {
159             return new MulticastRoutingConfig(in);
160         }
161 
162         @Override
163         public MulticastRoutingConfig[] newArray(int size) {
164             return new MulticastRoutingConfig[size];
165         }
166     };
167 
forwardingModeToString(final int forwardingMode)168     private static String forwardingModeToString(final int forwardingMode) {
169         switch (forwardingMode) {
170             case FORWARD_NONE: return "NONE";
171             case FORWARD_SELECTED: return "SELECTED";
172             case FORWARD_WITH_MIN_SCOPE: return "WITH_MIN_SCOPE";
173             default: return "UNKNOWN";
174         }
175     }
176 
177     public static final class Builder {
178         @MulticastForwardingMode
179         private final int mForwardingMode;
180         private int mMinScope;
181         private final ArraySet<Inet6Address> mListeningAddresses;
182 
183         // The two constructors with runtime checks for the mode and scope are arguably
184         // less convenient than three static factory methods, but API guidelines mandates
185         // that Builders are built with a constructor and not factory methods.
186         /**
187          * Create a new builder for forwarding mode FORWARD_NONE or FORWARD_SELECTED.
188          *
189          * <p>On a Builder for FORWARD_NONE, no properties can be set.
190          * <p>On a Builder for FORWARD_SELECTED, listening addresses can be added and removed
191          * but the minimum scope can't be set.
192          *
193          * @param mode {@link #FORWARD_NONE} or {@link #FORWARD_SELECTED}. Any other
194          *             value will result in IllegalArgumentException.
195          * @see #Builder(int, int)
196          */
Builder(@ulticastForwardingMode final int mode)197         public Builder(@MulticastForwardingMode final int mode) {
198             if (FORWARD_NONE != mode && FORWARD_SELECTED != mode) {
199                 if (FORWARD_WITH_MIN_SCOPE == mode) {
200                     throw new IllegalArgumentException("FORWARD_WITH_MIN_SCOPE requires "
201                             + "passing the scope as a second argument");
202                 } else {
203                     throw new IllegalArgumentException("Unknown forwarding mode : " + mode);
204                 }
205             }
206             mForwardingMode = mode;
207             mMinScope = MULTICAST_SCOPE_NONE;
208             mListeningAddresses = new ArraySet<>();
209         }
210 
211         /**
212          * Create a new builder for forwarding mode FORWARD_WITH_MIN_SCOPE.
213          *
214          * <p>On this Builder the scope can be set with {@link #setMinimumScope}, but
215          * listening addresses can't be added or removed.
216          *
217          * @param mode Must be {@link #FORWARD_WITH_MIN_SCOPE}.
218          * @param scope the minimum scope for this multicast routing config.
219          * @see Builder#Builder(int)
220          */
Builder(@ulticastForwardingMode final int mode, int scope)221         public Builder(@MulticastForwardingMode final int mode, int scope) {
222             if (FORWARD_WITH_MIN_SCOPE != mode) {
223                 throw new IllegalArgumentException("Forwarding with a min scope must "
224                         + "use forward mode FORWARD_WITH_MIN_SCOPE");
225             }
226             mForwardingMode = mode;
227             mMinScope = scope;
228             mListeningAddresses = new ArraySet<>();
229         }
230 
231         /**
232          * Sets the minimum scope for this multicast routing config.
233          * This is only meaningful (indeed, allowed) for configs in FORWARD_WITH_MIN_SCOPE mode.
234          * @return this builder
235          */
236         @NonNull
setMinimumScope(final int scope)237         public Builder setMinimumScope(final int scope) {
238             if (FORWARD_WITH_MIN_SCOPE != mForwardingMode) {
239                 throw new IllegalArgumentException("Can't set the scope on a builder in mode "
240                         + modeToString(mForwardingMode));
241             }
242             mMinScope = scope;
243             return this;
244         }
245 
246         /**
247          * Add an address to the set of listening addresses.
248          *
249          * This is only meaningful (indeed, allowed) for configs in FORWARD_SELECTED mode.
250          * If this address was already added, this is a no-op.
251          * @return this builder
252          */
253         @NonNull
addListeningAddress(@onNull final Inet6Address address)254         public Builder addListeningAddress(@NonNull final Inet6Address address) {
255             if (FORWARD_SELECTED != mForwardingMode) {
256                 throw new IllegalArgumentException("Can't add an address on a builder in mode "
257                         + modeToString(mForwardingMode));
258             }
259             // TODO : should we check that this is a multicast address ?
260             mListeningAddresses.add(address);
261             return this;
262         }
263 
264         /**
265          * Remove an address from the set of listening addresses.
266          *
267          * This is only meaningful (indeed, allowed) for configs in FORWARD_SELECTED mode.
268          * If this address was not added, or was already removed, this is a no-op.
269          * @return this builder
270          */
271         @NonNull
clearListeningAddress(@onNull final Inet6Address address)272         public Builder clearListeningAddress(@NonNull final Inet6Address address) {
273             if (FORWARD_SELECTED != mForwardingMode) {
274                 throw new IllegalArgumentException("Can't remove an address on a builder in mode "
275                         + modeToString(mForwardingMode));
276             }
277             mListeningAddresses.remove(address);
278             return this;
279         }
280 
281         /**
282          * Build the config.
283          */
284         @NonNull
build()285         public MulticastRoutingConfig build() {
286             return new MulticastRoutingConfig(mForwardingMode, mMinScope, mListeningAddresses);
287         }
288     }
289 
modeToString(@ulticastForwardingMode final int mode)290     private static String modeToString(@MulticastForwardingMode final int mode) {
291         switch (mode) {
292             case FORWARD_NONE: return "FORWARD_NONE";
293             case FORWARD_SELECTED: return "FORWARD_SELECTED";
294             case FORWARD_WITH_MIN_SCOPE: return "FORWARD_WITH_MIN_SCOPE";
295             default: return "unknown multicast routing mode " + mode;
296         }
297     }
298 
equals(Object other)299     public boolean equals(Object other) {
300         if (other == this) {
301             return true;
302         } else if (!(other instanceof MulticastRoutingConfig)) {
303             return false;
304         } else {
305             final MulticastRoutingConfig otherConfig = (MulticastRoutingConfig) other;
306             return mForwardingMode == otherConfig.mForwardingMode
307                 && mMinScope == otherConfig.mMinScope
308                 && mListeningAddresses.equals(otherConfig.mListeningAddresses);
309         }
310     }
311 
hashCode()312     public int hashCode() {
313         return Objects.hash(mForwardingMode, mMinScope, mListeningAddresses);
314     }
315 
toString()316     public String toString() {
317         final StringJoiner resultJoiner = new StringJoiner(" ", "{", "}");
318 
319         resultJoiner.add("ForwardingMode:");
320         resultJoiner.add(modeToString(mForwardingMode));
321 
322         if (mForwardingMode == FORWARD_WITH_MIN_SCOPE) {
323             resultJoiner.add("MinScope:");
324             resultJoiner.add(Integer.toString(mMinScope));
325         }
326 
327         if (mForwardingMode == FORWARD_SELECTED && !mListeningAddresses.isEmpty()) {
328             resultJoiner.add("ListeningAddresses: [");
329             resultJoiner.add(TextUtils.join(",", mListeningAddresses));
330             resultJoiner.add("]");
331         }
332 
333         return resultJoiner.toString();
334     }
335 }
336