1/*
2 *  Licensed to the Apache Software Foundation (ASF) under one or more
3 *  contributor license agreements.  See the NOTICE file distributed with
4 *  this work for additional information regarding copyright ownership.
5 *  The ASF licenses this file to You under the Apache License, Version 2.0
6 *  (the "License"); you may not use this file except in compliance with
7 *  the License.  You may obtain a copy of the License at
8 *
9 *     http://www.apache.org/licenses/LICENSE-2.0
10 *
11 *  Unless required by applicable law or agreed to in writing, software
12 *  distributed under the License is distributed on an "AS IS" BASIS,
13 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 *  See the License for the specific language governing permissions and
15 *  limitations under the License.
16 */
17
18package java.net;
19
20import java.io.IOException;
21import java.util.Enumeration;
22import libcore.io.IoUtils;
23
24/**
25 * This class implements a multicast socket for sending and receiving IP
26 * multicast datagram packets.
27 *
28 * @see DatagramSocket
29 */
30public class MulticastSocket extends DatagramSocket {
31    /**
32     * Stores the address supplied to setInterface so we can return it from getInterface. The
33     * translation to an interface index is lossy because an interface can have multiple addresses.
34     */
35    private InetAddress setAddress;
36
37    /**
38     * Constructs a multicast socket, bound to any available port on the
39     * local host.
40     *
41     * @throws IOException if an error occurs.
42     */
43    public MulticastSocket() throws IOException {
44        setReuseAddress(true);
45    }
46
47    /**
48     * Constructs a multicast socket, bound to the specified {@code port} on the
49     * local host.
50     *
51     * @throws IOException if an error occurs.
52     */
53    public MulticastSocket(int port) throws IOException {
54        super(port);
55        setReuseAddress(true);
56    }
57
58    /**
59     * Constructs a {@code MulticastSocket} bound to the address and port specified by
60     * {@code localAddress}, or an unbound {@code MulticastSocket} if {@code localAddress == null}.
61     *
62     * @throws IllegalArgumentException if {@code localAddress} is not supported (because it's not
63     * an {@code InetSocketAddress}, say).
64     * @throws IOException if an error occurs.
65     */
66    public MulticastSocket(SocketAddress localAddress) throws IOException {
67        super(localAddress);
68        setReuseAddress(true);
69    }
70
71    /**
72     * Returns an address of the outgoing network interface used by this socket. To avoid
73     * inherent unpredictability, new code should use {@link #getNetworkInterface} instead.
74     *
75     * @throws SocketException if an error occurs.
76     */
77    public InetAddress getInterface() throws SocketException {
78        checkOpen();
79        if (setAddress != null) {
80            return setAddress;
81        }
82        InetAddress ipvXaddress = (InetAddress) impl.getOption(SocketOptions.IP_MULTICAST_IF);
83        if (ipvXaddress.isAnyLocalAddress()) {
84            // the address was not set at the IPv4 level so check the IPv6
85            // level
86            NetworkInterface theInterface = getNetworkInterface();
87            if (theInterface != null) {
88                Enumeration<InetAddress> addresses = theInterface.getInetAddresses();
89                if (addresses != null) {
90                    while (addresses.hasMoreElements()) {
91                        InetAddress nextAddress = addresses.nextElement();
92                        if (nextAddress instanceof Inet6Address) {
93                            return nextAddress;
94                        }
95                    }
96                }
97            }
98        }
99        return ipvXaddress;
100    }
101
102    /**
103     * Returns the outgoing network interface used by this socket.
104     *
105     * @throws SocketException if an error occurs.
106     */
107    public NetworkInterface getNetworkInterface() throws SocketException {
108        checkOpen();
109        int index = (Integer) impl.getOption(SocketOptions.IP_MULTICAST_IF2);
110        if (index != 0) {
111            return NetworkInterface.getByIndex(index);
112        }
113        return NetworkInterface.forUnboundMulticastSocket();
114    }
115
116    /**
117     * Returns the time-to-live (TTL) for multicast packets sent on this socket.
118     *
119     * @throws IOException if an error occurs.
120     */
121    public int getTimeToLive() throws IOException {
122        checkOpen();
123        return impl.getTimeToLive();
124    }
125
126    /**
127     * Returns the time-to-live (TTL) for multicast packets sent on this socket.
128     *
129     * @throws IOException if an error occurs.
130     * @deprecated Replaced by {@link #getTimeToLive}
131     */
132    @Deprecated
133    public byte getTTL() throws IOException {
134        checkOpen();
135        return impl.getTTL();
136    }
137
138    /**
139     * Adds this socket to the specified multicast group. A socket must join a
140     * group before data may be received. A socket may be a member of multiple
141     * groups but may join any group only once.
142     *
143     * @param groupAddr
144     *            the multicast group to be joined.
145     * @throws IOException if an error occurs.
146     */
147    public void joinGroup(InetAddress groupAddr) throws IOException {
148        checkJoinOrLeave(groupAddr);
149        impl.join(groupAddr);
150    }
151
152    /**
153     * Adds this socket to the specified multicast group. A socket must join a
154     * group before data may be received. A socket may be a member of multiple
155     * groups but may join any group only once.
156     *
157     * @param groupAddress
158     *            the multicast group to be joined.
159     * @param netInterface
160     *            the network interface on which the datagram packets will be
161     *            received.
162     * @throws IOException
163     *                if the specified address is not a multicast address.
164     * @throws IllegalArgumentException
165     *                if no multicast group is specified.
166     */
167    public void joinGroup(SocketAddress groupAddress, NetworkInterface netInterface) throws IOException {
168        checkJoinOrLeave(groupAddress, netInterface);
169        impl.joinGroup(groupAddress, netInterface);
170    }
171
172    /**
173     * Removes this socket from the specified multicast group.
174     *
175     * @param groupAddr
176     *            the multicast group to be left.
177     * @throws NullPointerException
178     *                if {@code groupAddr} is {@code null}.
179     * @throws IOException
180     *                if the specified group address is not a multicast address.
181     */
182    public void leaveGroup(InetAddress groupAddr) throws IOException {
183        checkJoinOrLeave(groupAddr);
184        impl.leave(groupAddr);
185    }
186
187    /**
188     * Removes this socket from the specified multicast group.
189     *
190     * @param groupAddress
191     *            the multicast group to be left.
192     * @param netInterface
193     *            the network interface on which the addresses should be
194     *            dropped.
195     * @throws IOException
196     *                if the specified group address is not a multicast address.
197     * @throws IllegalArgumentException
198     *                if {@code groupAddress} is {@code null}.
199     */
200    public void leaveGroup(SocketAddress groupAddress, NetworkInterface netInterface) throws IOException {
201        checkJoinOrLeave(groupAddress, netInterface);
202        impl.leaveGroup(groupAddress, netInterface);
203    }
204
205    private void checkJoinOrLeave(SocketAddress groupAddress, NetworkInterface netInterface) throws IOException {
206        checkOpen();
207        if (groupAddress == null) {
208            throw new IllegalArgumentException("groupAddress == null");
209        }
210
211        if (netInterface != null && !netInterface.getInetAddresses().hasMoreElements()) {
212            throw new SocketException("No address associated with interface: " + netInterface);
213        }
214
215        if (!(groupAddress instanceof InetSocketAddress)) {
216            throw new IllegalArgumentException("Group address not an InetSocketAddress: " +
217                    groupAddress.getClass());
218        }
219
220        InetAddress groupAddr = ((InetSocketAddress) groupAddress).getAddress();
221        if (groupAddr == null) {
222            throw new SocketException("Group address has no address: " + groupAddress);
223        }
224
225        if (!groupAddr.isMulticastAddress()) {
226            throw new IOException("Not a multicast group: " + groupAddr);
227        }
228    }
229
230    private void checkJoinOrLeave(InetAddress groupAddr) throws IOException {
231        checkOpen();
232        if (!groupAddr.isMulticastAddress()) {
233            throw new IOException("Not a multicast group: " + groupAddr);
234        }
235    }
236
237    /**
238     * Sends the given {@code packet} on this socket, using the given {@code ttl}. This method is
239     * deprecated because it modifies the TTL socket option for this socket twice on each call.
240     *
241     * @throws IOException if an error occurs.
242     * @deprecated use {@link #setTimeToLive}.
243     */
244    @Deprecated
245    public void send(DatagramPacket packet, byte ttl) throws IOException {
246        checkOpen();
247        InetAddress packAddr = packet.getAddress();
248        int currTTL = getTimeToLive();
249        if (packAddr.isMulticastAddress() && (byte) currTTL != ttl) {
250            try {
251                setTimeToLive(ttl & 0xff);
252                impl.send(packet);
253            } finally {
254                setTimeToLive(currTTL);
255            }
256        } else {
257            impl.send(packet);
258        }
259    }
260
261    /**
262     * Sets the outgoing network interface used by this socket. The interface used is the first
263     * interface found to have the given {@code address}. To avoid inherent unpredictability,
264     * new code should use {@link #getNetworkInterface} instead.
265     *
266     * @throws SocketException if an error occurs.
267     */
268    public void setInterface(InetAddress address) throws SocketException {
269        checkOpen();
270        if (address == null) {
271            throw new NullPointerException("address == null");
272        }
273
274        NetworkInterface networkInterface = NetworkInterface.getByInetAddress(address);
275        if (networkInterface == null) {
276            throw new SocketException("Address not associated with an interface: " + address);
277        }
278        impl.setOption(SocketOptions.IP_MULTICAST_IF2, networkInterface.getIndex());
279        this.setAddress = address;
280    }
281
282    /**
283     * Sets the outgoing network interface used by this socket to the given
284     * {@code networkInterface}.
285     *
286     * @throws SocketException if an error occurs.
287     */
288    public void setNetworkInterface(NetworkInterface networkInterface) throws SocketException {
289        checkOpen();
290        if (networkInterface == null) {
291            throw new SocketException("networkInterface == null");
292        }
293
294        impl.setOption(SocketOptions.IP_MULTICAST_IF2, networkInterface.getIndex());
295        this.setAddress = null;
296    }
297
298    /**
299     * Sets the time-to-live (TTL) for multicast packets sent on this socket.
300     * Valid TTL values are between 0 and 255 inclusive.
301     *
302     * @throws IOException if an error occurs.
303     */
304    public void setTimeToLive(int ttl) throws IOException {
305        checkOpen();
306        if (ttl < 0 || ttl > 255) {
307            throw new IllegalArgumentException("TimeToLive out of bounds: " + ttl);
308        }
309        impl.setTimeToLive(ttl);
310    }
311
312    /**
313     * Sets the time-to-live (TTL) for multicast packets sent on this socket.
314     * Valid TTL values are between 0 and 255 inclusive.
315     *
316     * @throws IOException if an error occurs.
317     * @deprecated Replaced by {@link #setTimeToLive}
318     */
319    @Deprecated
320    public void setTTL(byte ttl) throws IOException {
321        checkOpen();
322        impl.setTTL(ttl);
323    }
324
325    @Override
326    synchronized void createSocket(int aPort, InetAddress addr) throws SocketException {
327        impl = factory != null ? factory.createDatagramSocketImpl() : new PlainDatagramSocketImpl();
328        impl.create();
329        try {
330            impl.setOption(SocketOptions.SO_REUSEADDR, Boolean.TRUE);
331            impl.bind(aPort, addr);
332            isBound = true;
333        } catch (SocketException e) {
334            close();
335            throw e;
336        }
337    }
338
339    /**
340     * Returns true if multicast loopback is <i>disabled</i>.
341     * See {@link SocketOptions#IP_MULTICAST_LOOP}, and note that the sense of this is the
342     * opposite of the underlying Unix {@code IP_MULTICAST_LOOP}.
343     *
344     * @throws SocketException if an error occurs.
345     */
346    public boolean getLoopbackMode() throws SocketException {
347        checkOpen();
348        return !((Boolean) impl.getOption(SocketOptions.IP_MULTICAST_LOOP)).booleanValue();
349    }
350
351    /**
352     * Disables multicast loopback if {@code disable == true}.
353     * See {@link SocketOptions#IP_MULTICAST_LOOP}, and note that the sense of this is the
354     * opposite of the underlying Unix {@code IP_MULTICAST_LOOP}.
355     *
356     * @throws SocketException if an error occurs.
357     */
358    public void setLoopbackMode(boolean disable) throws SocketException {
359        checkOpen();
360        impl.setOption(SocketOptions.IP_MULTICAST_LOOP, Boolean.valueOf(!disable));
361    }
362}
363