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.io.ObjectInputStream;
22import java.io.ObjectOutputStream;
23import java.io.ObjectStreamField;
24import java.util.Arrays;
25import java.util.Enumeration;
26import static android.system.OsConstants.*;
27
28/**
29 * An IPv6 address. See {@link InetAddress}.
30 */
31public final class Inet6Address extends InetAddress {
32
33    private static final long serialVersionUID = 6880410070516793377L;
34
35    /**
36     * @hide
37     */
38    public static final InetAddress ANY =
39            new Inet6Address(new byte[] {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, null, 0);
40
41    /**
42     * @hide
43     */
44    public static final InetAddress LOOPBACK =
45            new Inet6Address(new byte[] {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 },
46                    "ip6-localhost", 0);
47
48    private boolean scope_id_set;
49    private int scope_id;
50
51    private boolean scope_ifname_set;
52    private String ifname;
53
54    /**
55     * Constructs an {@code InetAddress} representing the {@code address} and
56     * {@code name} and {@code scope_id}.
57     *
58     * @param address
59     *            the network address.
60     * @param name
61     *            the name associated with the address.
62     * @param scope_id
63     *            the scope id for link- or site-local addresses.
64     */
65    Inet6Address(byte[] ipaddress, String hostName, int scope_id) {
66        super(AF_INET6, ipaddress, hostName);
67        this.scope_id = scope_id;
68        this.scope_id_set = (scope_id != 0);
69    }
70
71    /**
72     * Constructs an IPv6 address according to the given {@code host}, {@code
73     * addr} and {@code scope_id}.
74     *
75     * @param host
76     *            the host name associated with the address.
77     * @param addr
78     *            the network address.
79     * @param scope_id
80     *            the scope id for link- or site-local addresses.
81     * @return the Inet6Address instance representing the IP address.
82     * @throws UnknownHostException
83     *             if the address is null or has an invalid length.
84     */
85    public static Inet6Address getByAddress(String host, byte[] addr, int scope_id)
86            throws UnknownHostException {
87        if (addr == null || addr.length != 16) {
88            throw new UnknownHostException("Not an IPv6 address: " + Arrays.toString(addr));
89        }
90        if (scope_id < 0) {
91            scope_id = 0;
92        }
93        // TODO: should we clone 'addr'?
94        return new Inet6Address(addr, host, scope_id);
95    }
96
97    /**
98     * Gets an IPv6 address instance according to the given {@code host},
99     * {@code addr} and {@code nif}. {@code scope_id} is set according to the
100     * given {@code nif} and the {@code addr} type (for example site-local or
101     * link-local).
102     *
103     * @param host
104     *            the hostname associated with the address.
105     * @param addr
106     *            the network address.
107     * @param nif
108     *            the network interface that this address is associated with.
109     * @return the Inet6Address instance representing the IP address.
110     * @throws UnknownHostException
111     *             if the address is {@code null} or has an invalid length or
112     *             the interface doesn't have a numeric scope id for the given
113     *             address type.
114     */
115    public static Inet6Address getByAddress(String host, byte[] addr,
116            NetworkInterface nif) throws UnknownHostException {
117
118        Inet6Address address = Inet6Address.getByAddress(host, addr, 0);
119
120        // if nif is null, nothing needs to be set.
121        if (nif == null) {
122            return address;
123        }
124
125        // find the first address which matches the type addr,
126        // then set the scope_id and ifname.
127        Enumeration<InetAddress> addressList = nif.getInetAddresses();
128        while (addressList.hasMoreElements()) {
129            InetAddress ia = addressList.nextElement();
130            if (ia.getAddress().length == 16) {
131                Inet6Address v6ia = (Inet6Address) ia;
132                boolean isSameType = v6ia.compareLocalType(address);
133                if (isSameType) {
134                    address.scope_id_set = true;
135                    address.scope_id = v6ia.scope_id;
136                    address.scope_ifname_set = true;
137                    address.ifname = nif.getName();
138                    break;
139                }
140            }
141        }
142        // if no address matches the type of addr, throws an
143        // UnknownHostException.
144        if (!address.scope_id_set) {
145            throw new UnknownHostException("Scope id not found for address: " + Arrays.toString(addr));
146        }
147        return address;
148    }
149
150    /**
151     * Returns {@code true} if one of following cases applies:
152     * <p>
153     * <ol>
154     *  <li>both addresses are site local</li>
155     *  <li>both addresses are link local</li>
156     *  <li>{@code ia} is neither site local nor link local</li>
157     * </ol>
158     */
159    private boolean compareLocalType(Inet6Address ia) {
160        if (ia.isSiteLocalAddress() && isSiteLocalAddress()) {
161            return true;
162        }
163        if (ia.isLinkLocalAddress() && isLinkLocalAddress()) {
164            return true;
165        }
166        if (!ia.isSiteLocalAddress() && !ia.isLinkLocalAddress()) {
167            return true;
168        }
169        return false;
170    }
171
172    @Override public boolean isAnyLocalAddress() {
173        return Arrays.equals(ipaddress, Inet6Address.ANY.ipaddress);
174    }
175
176    /**
177     * Returns whether this IPv6 address is an IPv4-compatible address or not.
178     * An IPv4-compatible address has the prefix {@code ::/96} and is a deprecated
179     * and no-longer used equivalent of the modern IPv4-mapped IPv6 addresses.
180     */
181    public boolean isIPv4CompatibleAddress() {
182        for (int i = 0; i < 12; i++) {
183            if (ipaddress[i] != 0) {
184                return false;
185            }
186        }
187        return true;
188    }
189
190    @Override public boolean isLinkLocalAddress() {
191        return ((ipaddress[0] & 0xff) == 0xfe) && ((ipaddress[1] & 0xc0) == 0x80); // fe80:/10
192    }
193
194    @Override public boolean isLoopbackAddress() {
195        return Arrays.equals(ipaddress, Inet6Address.LOOPBACK.ipaddress);
196    }
197
198    @Override public boolean isMCGlobal() {
199        return ((ipaddress[0] & 0xff) == 0xff) && ((ipaddress[1] & 0x0f) == 0x0e); // ffxe:/16
200    }
201
202    @Override public boolean isMCLinkLocal() {
203        return ((ipaddress[0] & 0xff) == 0xff) && ((ipaddress[1] & 0x0f) == 0x02); // ffx2:/16
204    }
205
206    @Override public boolean isMCNodeLocal() {
207        return ((ipaddress[0] & 0xff) == 0xff) && ((ipaddress[1] & 0x0f) == 0x01); // ffx1:/16
208    }
209
210    @Override public boolean isMCOrgLocal() {
211        return ((ipaddress[0] & 0xff) == 0xff) && ((ipaddress[1] & 0x0f) == 0x08); // ffx8:/16
212    }
213
214    @Override public boolean isMCSiteLocal() {
215        return ((ipaddress[0] & 0xff) == 0xff) && ((ipaddress[1] & 0x0f) == 0x05); // ffx5:/16
216    }
217
218    @Override public boolean isMulticastAddress() {
219        return ((ipaddress[0] & 0xff) == 0xff); // ff::/8
220    }
221
222    @Override public boolean isSiteLocalAddress() {
223        return ((ipaddress[0] & 0xff) == 0xfe) && ((ipaddress[1] & 0xc0) == 0xc0); // fec0:/10
224    }
225
226    /**
227     * Returns the scope id if this address is scoped to an interface, 0 otherwise.
228     */
229    public int getScopeId() {
230        return scope_id_set ? scope_id : 0;
231    }
232
233    /**
234     * Returns the network interface if this address is instanced with a scoped
235     * network interface, null otherwise.
236     */
237    public NetworkInterface getScopedInterface() {
238        try {
239            return (scope_ifname_set && ifname != null) ? NetworkInterface.getByName(ifname) : null;
240        } catch (SocketException ex) {
241            return null;
242        }
243    }
244
245    private static final ObjectStreamField[] serialPersistentFields = {
246        new ObjectStreamField("ipaddress", byte[].class),
247        new ObjectStreamField("scope_id", int.class),
248        new ObjectStreamField("scope_id_set", boolean.class),
249        new ObjectStreamField("scope_ifname_set", boolean.class),
250        new ObjectStreamField("ifname", String.class),
251    };
252
253    private void writeObject(ObjectOutputStream stream) throws IOException {
254        ObjectOutputStream.PutField fields = stream.putFields();
255        if (ipaddress == null) {
256            fields.put("ipaddress", null);
257        } else {
258            fields.put("ipaddress", ipaddress);
259        }
260
261        fields.put("scope_id", scope_id);
262        fields.put("scope_id_set", scope_id_set);
263        fields.put("scope_ifname_set", scope_ifname_set);
264        fields.put("ifname", ifname);
265        stream.writeFields();
266    }
267
268    private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException {
269        ObjectInputStream.GetField fields = stream.readFields();
270        ipaddress = (byte[]) fields.get("ipaddress", null);
271        scope_id = fields.get("scope_id", 0);
272        scope_id_set = fields.get("scope_id_set", false);
273        ifname = (String) fields.get("ifname", null);
274        scope_ifname_set = fields.get("scope_ifname_set", false);
275    }
276
277    @Override public String toString() {
278        if (ifname != null) {
279            return super.toString() + "%" + ifname;
280        }
281        if (scope_id != 0) {
282            return super.toString() + "%" + scope_id;
283        }
284        return super.toString();
285    }
286}
287