From e832ab3c91f01cdb1bd618ffe4a8e00505264d22 Mon Sep 17 00:00:00 2001 From: Michael Koch Date: Thu, 3 Oct 2002 11:23:33 +0000 Subject: [PATCH] 2002-09-30 Michael Koch * java/net/DatagramSocket.java (receive): Check with SecurityManager AFTER the packet is received, check if connected to multicast address, documentation added. (send): Only check SecurityManager if connected, check address of packet to send. (connect): Implemented, documentation added. * java/net/Inet6Address.java: New file (not added yet to Makefile.am). * java/net/InetSocketAddress.java (whole file): Reindented. (hostname): New attribute. (InetSocketAddress): Initialize new attribute. (getAddress): Documentation added. (getHostName): Documentation added. (getPort): Documentation added. (hashCode): Documentation added. (isUnresolved): Documentation added. (toString): Conform to output of JDK 1.4.1, documentation added. * java/net/MulticastSocket.java (joinGroup): Removed FIXME, documentation added. (leaveGroup): Removed FIXME, documentation added. (send): Documentation added. * java/net/Socket.java (inputShutdown): New variable. (outputShutdown): New variable. (Socket): Initialize new variables. (getRemoteSocketAddress): Check if connected. (shutdownInput): Set new variable. (shutdownOutput): Set new variable. (isConnected): New method. (isClosed): New method. (isInputShutdown): New method. (isOutputShutdown): New method. * java/net/URLStreamHandler.java (URLStreamHandler): New method. (openConnection): Added documentation. (parseURL): Added documentation. (getHostAddress): New method. (getDefaultPort): New method. From-SVN: r57772 --- libjava/ChangeLog | 41 ++++ libjava/java/net/DatagramSocket.java | 39 +++- libjava/java/net/Inet6Address.java | 272 ++++++++++++++++++++++++ libjava/java/net/InetSocketAddress.java | 255 ++++++++++++---------- libjava/java/net/MulticastSocket.java | 17 +- libjava/java/net/Socket.java | 53 ++++- libjava/java/net/URLStreamHandler.java | 51 ++++- 7 files changed, 602 insertions(+), 126 deletions(-) create mode 100644 libjava/java/net/Inet6Address.java diff --git a/libjava/ChangeLog b/libjava/ChangeLog index 9d3b83655ab..4e7110f17d6 100644 --- a/libjava/ChangeLog +++ b/libjava/ChangeLog @@ -1,3 +1,44 @@ +2002-09-30 Michael Koch + + * java/net/DatagramSocket.java + (receive): Check with SecurityManager AFTER the packet is received, + check if connected to multicast address, documentation added. + (send): Only check SecurityManager if connected, check address of + packet to send. + (connect): Implemented, documentation added. + * java/net/Inet6Address.java: New file (not added yet to Makefile.am). + * java/net/InetSocketAddress.java + (whole file): Reindented. + (hostname): New attribute. + (InetSocketAddress): Initialize new attribute. + (getAddress): Documentation added. + (getHostName): Documentation added. + (getPort): Documentation added. + (hashCode): Documentation added. + (isUnresolved): Documentation added. + (toString): Conform to output of JDK 1.4.1, documentation added. + * java/net/MulticastSocket.java + (joinGroup): Removed FIXME, documentation added. + (leaveGroup): Removed FIXME, documentation added. + (send): Documentation added. + * java/net/Socket.java + (inputShutdown): New variable. + (outputShutdown): New variable. + (Socket): Initialize new variables. + (getRemoteSocketAddress): Check if connected. + (shutdownInput): Set new variable. + (shutdownOutput): Set new variable. + (isConnected): New method. + (isClosed): New method. + (isInputShutdown): New method. + (isOutputShutdown): New method. + * java/net/URLStreamHandler.java + (URLStreamHandler): New method. + (openConnection): Added documentation. + (parseURL): Added documentation. + (getHostAddress): New method. + (getDefaultPort): New method. + 2002-10-02 Tom Tromey * java/rmi/activation/ActivationDesc.java, diff --git a/libjava/java/net/DatagramSocket.java b/libjava/java/net/DatagramSocket.java index dfbce3bcf7d..b2f2ca11ce3 100644 --- a/libjava/java/net/DatagramSocket.java +++ b/libjava/java/net/DatagramSocket.java @@ -290,20 +290,26 @@ public class DatagramSocket * exception will be thrown * @exception IllegalBlockingModeException If this socket has an associated * channel, and the channel is in non-blocking mode + * @exception SecurityException If a security manager exists and its + * checkAccept ethod doesn't allow the receive */ public synchronized void receive(DatagramPacket p) throws IOException { - SecurityManager s = System.getSecurityManager(); - if (s != null) - s.checkAccept (p.getAddress().getHostName (), p.getPort ()); - if (impl == null) throw new IOException ("Cannot initialize Socket implementation"); + if (remoteAddress != null && remoteAddress.isMulticastAddress ()) + throw new IOException ( + "Socket connected to a multicast address my not receive"); + if (ch != null && !ch.isBlocking ()) throw new IllegalBlockingModeException (); impl.receive(p); + + SecurityManager s = System.getSecurityManager(); + if (s != null && isConnected ()) + s.checkAccept (p.getAddress().getHostName (), p.getPort ()); } /** @@ -324,7 +330,7 @@ public class DatagramSocket { // JDK1.2: Don't do security checks if socket is connected; see jdk1.2 api. SecurityManager s = System.getSecurityManager(); - if (s != null) + if (s != null && !isConnected ()) { InetAddress addr = p.getAddress(); if (addr.isMulticastAddress()) @@ -332,6 +338,14 @@ public class DatagramSocket else s.checkConnect(addr.getHostAddress(), p.getPort()); } + + if (isConnected ()) + { + if (p.getAddress () != null && (remoteAddress != p.getAddress () || + remotePort != p.getPort ())) + throw new IllegalArgumentException ( + "DatagramPacket address does not match remote address" ); + } // FIXME: if this is a subclass of MulticastSocket, // use getTimeToLive for TTL val. @@ -376,7 +390,20 @@ public class DatagramSocket public void connect(InetAddress address, int port) throws SocketException { - //impl.connect(address, port); + if (address == null) + throw new IllegalArgumentException ("Address may not be null"); + + if (port < 1 || port > 65535) + throw new IllegalArgumentException ("Port number is illegal"); + + SecurityManager s = System.getSecurityManager(); + if (s != null) + s.checkAccept(address.getHostName (), port); + + impl.connect (address, port); + + remoteAddress = address; + remotePort = port; } /** diff --git a/libjava/java/net/Inet6Address.java b/libjava/java/net/Inet6Address.java new file mode 100644 index 00000000000..de496b148ee --- /dev/null +++ b/libjava/java/net/Inet6Address.java @@ -0,0 +1,272 @@ +/* Inet6Address.java + Copyright (C) 2002 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package java.net; + +import java.io.IOException; + +/** + * @author Michael Koch + * @date August 3, 2002. + */ + +/* + * Written using on-line Java Platform 1.4 API Specification and + * RFC 1884 (http://www.ietf.org/rfc/rfc1884.txt) + * Status: Believed complete and correct. + */ + +public final class Inet6Address extends InetAddress +{ + static final long serialVersionUID = 6880410070516793377L; + + /** + * Needed for serialization + */ + byte[] ipaddress; + + /** + * Create an Inet6Address object + * + * @param addr The IP address + * @param host The hostname + */ + protected Inet6Address (byte[] addr, String host) + { + super (null, host); + this.ipaddress = addr; + } + + /** + * Utility routine to check if the InetAddress is an IP multicast address + * + * @since 1.1 + */ + public boolean isMulticastAddress () + { + return ipaddress [0] == 0xFF; + } + + /** + * Utility routine to check if the InetAddress in a wildcard address + * + * @since 1.4 + */ + public boolean isAnyLocalAddress () + { + byte[] anylocal = { 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0 }; + + return ipaddress == anylocal; + } + + /** + * Utility routine to check if the InetAddress is a loopback address + * + * @since 1.4 + */ + public boolean isLoopbackAddress () + { + byte[] loopback = { 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1 }; + + return ipaddress == loopback; + } + + /** + * Utility routine to check if the InetAddress is an link local address + * + * @since 1.4 + */ + public boolean isLinkLocalAddress () + { + return ipaddress [0] == 0xFA; + } + + /** + * Utility routine to check if the InetAddress is a site local address + * + * @since 1.4 + */ + public boolean isSiteLocalAddress () + { + return ipaddress [0] == 0xFB; + } + + /** + * Utility routine to check if the multicast address has global scope + * + * @since 1.4 + */ + public boolean isMCGlobal () + { + if (!isMulticastAddress ()) + return false; + + return (ipaddress [1] & 0x0F) == 0xE; + } + + /** + * Utility routine to check if the multicast address has node scope + * + * @since 1.4 + */ + public boolean isMCNodeLocal () + { + if (!isMulticastAddress ()) + return false; + + return (ipaddress [1] & 0x0F) == 0x1; + } + + /** + * Utility routine to check if the multicast address has link scope + * + * @since 1.4 + */ + public boolean isMCLinkLocal () + { + if (!isMulticastAddress ()) + return false; + + return (ipaddress [1] & 0x0F) == 0x2; + } + + /** + * Utility routine to check if the multicast address has site scope + * + * @since 1.4 + */ + public boolean isMCSiteLocal () + { + if (!isMulticastAddress ()) + return false; + + return (ipaddress [1] & 0x0F) == 0x5; + } + + /** + * Utility routine to check if the multicast address has organization scope + * + * @since 1.4 + */ + public boolean isMCOrgLocal () + { + if (!isMulticastAddress ()) + return false; + + return (ipaddress [1] & 0x0F) == 0x8; + } + + /** + * Returns the raw IP address of this InetAddress object. The result is in + * network byte order: the highest order byte of the address is i + * n getAddress()[0] + */ + public byte[] getAddress () + { + return ipaddress; + } + + /** + * Returns the IP address string in textual presentation + */ + public String getHostAddress () + { + StringBuffer sbuf = new StringBuffer (40); + + for (int i = 0; i < 16; i += 2) + { + int x = ((ipaddress [i] & 0xFF) << 8) | (ipaddress [i + 1] & 0xFF); + boolean empty = sbuf.length () == 0; + + if (empty) + { + if (i > 0) + sbuf.append ("::"); + } + else + sbuf.append (':'); + + if (x != 0 || i >= 14) + sbuf.append (Integer.toHexString (x)); + } + + return sbuf.toString (); + } + + /** + * Returns a hashcode for this IP address + */ + public int hashCode () + { + return super.hashCode (); + } + + /** + * Compares this object against the specified object + */ + public boolean equals (Object obj) + { + if (obj == null || ! (obj instanceof Inet6Address)) + return false; + + Inet6Address tmp = (Inet6Address) obj; + + return super.equals (tmp) + && this.ipaddress == tmp.ipaddress; + } + + /** + * Utility routine to check if the InetAddress is an + * IPv4 compatible IPv6 address + * + * @since 1.4 + */ + public boolean isIPv4CompatibleAddress () + { + if (ipaddress [0] != 0x00 || ipaddress [1] != 0x00 || + ipaddress [2] != 0x00 || ipaddress [3] != 0x00 || + ipaddress [4] != 0x00 || ipaddress [5] != 0x00 || + ipaddress [6] != 0x00 || ipaddress [7] != 0x00 || + ipaddress [8] != 0x00 || ipaddress [9] != 0x00 || + ipaddress [10] != 0x00 || ipaddress [11] != 0x00) + return false; + + return true; + } +} diff --git a/libjava/java/net/InetSocketAddress.java b/libjava/java/net/InetSocketAddress.java index 20ebbfaec5a..1f932a95b5f 100644 --- a/libjava/java/net/InetSocketAddress.java +++ b/libjava/java/net/InetSocketAddress.java @@ -47,119 +47,154 @@ package java.net; public class InetSocketAddress extends SocketAddress { - InetAddress addr; - int port; + String hostname; + InetAddress addr; + int port; - /** - * Constructs an InetSocketAddress instance. - * - * @param addr Address of the socket - * @param port Port if the socket - * - * @exception IllegalArgumentException If the port number is illegel - */ - public InetSocketAddress(InetAddress addr, int port) - throws IllegalArgumentException - { - if (port < 0 || port > 65535) - throw new IllegalArgumentException(); + /** + * Constructs an InetSocketAddress instance. + * + * @param addr Address of the socket + * @param port Port if the socket + * + * @exception IllegalArgumentException If the port number is illegal + */ + public InetSocketAddress(InetAddress addr, int port) + throws IllegalArgumentException + { + if (port < 0 || port > 65535) + throw new IllegalArgumentException(); - this.addr = addr; - this.port = port; - } - - /** - * Constructs an InetSocketAddress instance. - * - * @param port Port if the socket - * - * @exception IllegalArgumentException If the port number is illegal - */ - public InetSocketAddress(int port) - throws IllegalArgumentException - { - if (port < 0 || port > 65535) - throw new IllegalArgumentException(); - - this.port = port; - try { - this.addr = InetAddress.getLocalHost(); - } catch (Exception e) { - } - } - - - /** - * Constructs an InetSocketAddress instance. - * - * @param addr Address of the socket - * @param port Port if the socket - * - * @exception IllegalArgumentException If the port number is illegal - */ - public InetSocketAddress(String hostname, int port) - throws IllegalArgumentException - { - if (port < 0 || port > 65535) - throw new IllegalArgumentException(); - - this.port = port; - try { - this.addr = InetAddress.getByName(hostname); - } catch (Exception e) { - } - } + this.addr = addr; + this.port = port; + + try + { + this.hostname = addr.getHostName (); + } + catch (UnknownHostException e) + { + this.hostname = ""; + } + } + + /** + * Constructs an InetSocketAddress instance. + * + * @param port Port if the socket + * + * @exception IllegalArgumentException If the port number is illegal + */ + public InetSocketAddress(int port) + throws IllegalArgumentException + { + if (port < 0 || port > 65535) + throw new IllegalArgumentException(); + + this.port = port; + + try + { + byte[] any = { 0, 0, 0, 0 }; + this.addr = InetAddress.getByAddress (any); + this.hostname = "0.0.0.0"; + } + catch (UnknownHostException e) + { + this.addr = null; + this.hostname = ""; + } + } + + + /** + * Constructs an InetSocketAddress instance. + * + * @param addr Address of the socket + * @param port Port if the socket + * + * @exception IllegalArgumentException If the port number is illegal + */ + public InetSocketAddress(String hostname, int port) + throws IllegalArgumentException + { + if (port < 0 || port > 65535) + throw new IllegalArgumentException(); + + this.port = port; + this.hostname = hostname; + + try + { + this.addr = InetAddress.getByName(hostname); + } + catch (Exception e) // UnknownHostException, SecurityException + { + this.addr = null; + } + } - /** - * Test if obj is a InetSocketAddress and - * has the same address & port - */ - public final boolean equals(Object obj) - { - if (obj instanceof InetSocketAddress) - { - InetSocketAddress a = (InetSocketAddress) obj; - return addr.equals(a.addr) && a.port == port; - } - return false; - } - - public final InetAddress getAddress() - { - return addr; - } - - public final String getHostName() - { - return addr.getHostName(); - } - - public final int getPort() - { - return port; - } + /** + * Test if obj is a InetSocketAddress and + * has the same address and port + */ + public final boolean equals (Object obj) + { + if (obj instanceof InetSocketAddress) + { + InetSocketAddress a = (InetSocketAddress) obj; + return addr.equals(a.addr) && a.port == port; + } + + return false; + } + + /** + * Returns the InetAddress or + * null if its unresolved + */ + public final InetAddress getAddress() + { + return addr; + } + + /** + * Returns hostname + */ + public final String getHostName() + { + return hostname; + } + + /** + * Returns the port + */ + public final int getPort() + { + return port; + } - /** - * TODO: see what sun does here. - */ - public final int hashCode() - { - return port + addr.hashCode(); - } - - /** - * TODO: see what sun does here. - */ - public final boolean isUnresolved() - { - return addr == null; - } + /** + * Returns the hashcode of the InetSocketAddress + */ + public final int hashCode() + { + return port + addr.hashCode(); + } + + /** + * Checks wether the address has been resolved or not + */ + public final boolean isUnresolved() + { + return addr == null; + } - /** - * TODO: see what sun does here. - */ - public String toString() - { - return "SA:"+addr+":"+port; - } + /** + * Returns the InetSocketAddress as string + */ + public String toString() + { + return addr + ":" + port; + } } diff --git a/libjava/java/net/MulticastSocket.java b/libjava/java/net/MulticastSocket.java index b3582605a57..04d07351ab6 100644 --- a/libjava/java/net/MulticastSocket.java +++ b/libjava/java/net/MulticastSocket.java @@ -252,7 +252,8 @@ public class MulticastSocket extends DatagramSocket * @param addr The address of the group to join * * @exception IOException If an error occurs - * @exception SecurityException FIXME + * @exception SecurityException If a security manager exists and its + * checkMulticast method doesn't allow the operation */ public void joinGroup(InetAddress mcastaddr) throws IOException { @@ -272,7 +273,8 @@ public class MulticastSocket extends DatagramSocket * @param addr The address of the group to leave * * @exception IOException If an error occurs - * @exception SecurityException FIXME + * @exception SecurityException If a security manager exists and its + * checkMulticast method doesn't allow the operation */ public void leaveGroup(InetAddress mcastaddr) throws IOException { @@ -296,7 +298,8 @@ public class MulticastSocket extends DatagramSocket * * @exception IOException If an error occurs * @exception IllegalArgumentException If address type is not supported - * @exception SecurityException FIXME + * @exception SecurityException If a security manager exists and its + * checkMulticast method doesn't allow the operation * * @see MulticastSocket:setInterface * @see MulticastSocket:setNetworkInterface @@ -314,7 +317,6 @@ public class MulticastSocket extends DatagramSocket if (! tmp.getAddress ().isMulticastAddress ()) throw new IOException ("Not a Multicast address"); - // FIXME: check if this check is sufficient. Do we need to check the port ? SecurityManager s = System.getSecurityManager (); if (s != null) s.checkMulticast (tmp.getAddress ()); @@ -331,7 +333,8 @@ public class MulticastSocket extends DatagramSocket * * @exception IOException If an error occurs * @exception IllegalArgumentException If address type is not supported - * @exception SecurityException FIXME + * @exception SecurityException If a security manager exists and its + * checkMulticast method doesn't allow the operation * * @see MulticastSocket:setInterface * @see MulticastSocket:setNetworkInterface @@ -346,7 +349,6 @@ public class MulticastSocket extends DatagramSocket if (! tmp.getAddress ().isMulticastAddress ()) throw new IOException ("Not a Multicast address"); - // FIXME: do we need to check the port too, or is this sufficient ? SecurityManager s = System.getSecurityManager (); if (s != null) s.checkMulticast (tmp.getAddress ()); @@ -363,7 +365,8 @@ public class MulticastSocket extends DatagramSocket * @param ttl The TTL for this packet * * @exception IOException If an error occurs - * @exception SecurityException FIXME + * @exception SecurityException If a security manager exists and its + * checkConnect or checkMulticast method doesn't allow the operation */ public synchronized void send(DatagramPacket p, byte ttl) throws IOException { diff --git a/libjava/java/net/Socket.java b/libjava/java/net/Socket.java index 82265dd32f9..b900801a16b 100644 --- a/libjava/java/net/Socket.java +++ b/libjava/java/net/Socket.java @@ -80,6 +80,9 @@ public class Socket */ SocketImpl impl; + private boolean inputShutdown; + private boolean outputShutdown; + SocketChannel ch; // this field must have been set if created by SocketChannel // Constructors @@ -97,6 +100,9 @@ public class Socket impl = factory.createSocketImpl(); else impl = new PlainSocketImpl(); + + inputShutdown = false; + outputShutdown = false; } /** @@ -118,6 +124,8 @@ public class Socket protected Socket (SocketImpl impl) throws SocketException { this.impl = impl; + this.inputShutdown = false; + this.outputShutdown = false; } /** @@ -264,6 +272,9 @@ public class Socket boolean stream) throws IOException { this(); + this.inputShutdown = false; + this.outputShutdown = false; + if (impl == null) throw new IOException("Cannot initialize Socket implementation"); @@ -457,6 +468,9 @@ public class Socket */ public SocketAddress getRemoteSocketAddress() { + if (!isConnected ()) + return null; + return new InetSocketAddress (impl.getInetAddress (), impl.getPort ()); } @@ -886,10 +900,12 @@ public class Socket * * @exception IOException If an error occurs. */ - public void shutdownInput() throws IOException + public void shutdownInput() throws IOException { if (impl != null) impl.shutdownInput(); + + inputShutdown = true; } /** @@ -901,6 +917,8 @@ public class Socket { if (impl != null) impl.shutdownOutput(); + + outputShutdown = true; } /** @@ -993,6 +1011,14 @@ public class Socket impl.setOption (SocketOptions.IP_TOS, new Integer (tc)); } + /** + * Checks if the socket is connected + */ + public boolean isConnected () + { + return impl.getInetAddress () != null; + } + /** * Checks if the socket is already bound. */ @@ -1000,4 +1026,29 @@ public class Socket { return getLocalAddress () != null; } + + /** + * Checks if the socket is closed. + */ + public boolean isClosed () + { + // FIXME: implement this. + return false; + } + + /** + * Checks if the socket's input stream is shutdown + */ + public boolean isInputShutdown () + { + return inputShutdown; + } + + /** + * Checks if the socket's output stream is shutdown + */ + public boolean isOutputShutdown () + { + return outputShutdown; + } } diff --git a/libjava/java/net/URLStreamHandler.java b/libjava/java/net/URLStreamHandler.java index cade4f3d066..9c6ba258dc7 100644 --- a/libjava/java/net/URLStreamHandler.java +++ b/libjava/java/net/URLStreamHandler.java @@ -25,6 +25,19 @@ import java.io.IOException; public abstract class URLStreamHandler { + /** + * Creates a URLStreamHander + */ + public URLStreamHandler () + { + } + + /** + * Opens a connection to the object referenced by the URL argument. + * This method should be overridden by a subclass. + * + * @exception IOException If an error occurs + */ protected abstract URLConnection openConnection(URL u) throws IOException; @@ -33,8 +46,12 @@ public abstract class URLStreamHandler * * @param u The URL to parse * @param spec The specification to use - * @param start FIXME - * @param limit FIXME + * @param start The character index at which to begin parsing. This is just + * past the ':' (if there is one) that specifies the determination of the + * protocol name + * @param limit The character position to stop parsing at. This is the end + * of the string or the position of the "#" character, if present. All + * information after the sharp sign indicates an anchor */ protected void parseURL(URL u, String spec, int start, int limit) { @@ -206,6 +223,36 @@ public abstract class URLStreamHandler u.set(protocol, host, port, authority, userInfo, path, query, ref); } + /** + * Get the IP address of our host. An empty host field or a DNS failure will + * result in a null return. + */ + protected InetAddress getHostAddress (URL url) + { + String hostname = url.getHost (); + + if (hostname == "") + return null; + + try + { + return InetAddress.getByName (hostname); + } + catch (UnknownHostException e) + { + return null; + } + } + + /** + * Returns the default port for a URL parsed by this handler. This method is + * meant to be overidden by handlers with default port numbers. + */ + protected int getDefaultPort () + { + return -1; + } + /** * Converts an URL of a specific protocol to a string * -- 2.30.2