diff options
author | Neil Fuller <nfuller@google.com> | 2014-02-12 09:39:58 +0000 |
---|---|---|
committer | Gerrit Code Review <noreply-gerritcodereview@google.com> | 2014-02-12 09:39:59 +0000 |
commit | 28261e443646049e8278ee0b64a2df9aa50f3b43 (patch) | |
tree | bfea6495cb5163714e3618e227eb6e038579f109 /luni | |
parent | 1bdb39d7af546f432f74e31c9c601fb30406166d (diff) | |
parent | ff81b740a338ba964e0ba1c40d925fb2ceb37bf2 (diff) | |
download | libcore-28261e443646049e8278ee0b64a2df9aa50f3b43.zip libcore-28261e443646049e8278ee0b64a2df9aa50f3b43.tar.gz libcore-28261e443646049e8278ee0b64a2df9aa50f3b43.tar.bz2 |
Merge "Adding additional methods to NetworkChannel."
Diffstat (limited to 'luni')
15 files changed, 1543 insertions, 24 deletions
diff --git a/luni/src/main/java/java/net/MulticastSocket.java b/luni/src/main/java/java/net/MulticastSocket.java index 6f4a582..970468a 100644 --- a/luni/src/main/java/java/net/MulticastSocket.java +++ b/luni/src/main/java/java/net/MulticastSocket.java @@ -351,7 +351,8 @@ public class MulticastSocket extends DatagramSocket { /** * Disables multicast loopback if {@code disable == true}. * See {@link SocketOptions#IP_MULTICAST_LOOP}, and note that the sense of this is the - * opposite of the underlying Unix {@code IP_MULTICAST_LOOP}. + * opposite of the underlying Unix {@code IP_MULTICAST_LOOP}: true means disabled, false + * means enabled. * * @throws SocketException if an error occurs. */ diff --git a/luni/src/main/java/java/net/SocketOption.java b/luni/src/main/java/java/net/SocketOption.java new file mode 100644 index 0000000..3f65494 --- /dev/null +++ b/luni/src/main/java/java/net/SocketOption.java @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package java.net; + +/** + * An option associated with a socket. + * + * <p>See {@link java.nio.channels.NetworkChannel#setOption}, + * {@link java.nio.channels.NetworkChannel#getOption} and + * {@link java.nio.channels.NetworkChannel#supportedOptions} for methods that use SocketOption. + * + * <p>See {@link StandardSocketOptions} for valid SocketOptions. + * + * @param <T> the type of the value + * @since 1.7 + * @hide Until ready for a public API change + */ +public interface SocketOption<T> { + + /** + * Returns the name of the option. + */ + String name(); + + /** + * Returns the type of the value of the option. + */ + Class<T> type(); +} diff --git a/luni/src/main/java/java/net/SocketOptions.java b/luni/src/main/java/java/net/SocketOptions.java index e23fc97..d0df689 100644 --- a/luni/src/main/java/java/net/SocketOptions.java +++ b/luni/src/main/java/java/net/SocketOptions.java @@ -28,19 +28,27 @@ package java.net; */ public interface SocketOptions { /** - * Number of seconds to wait when closing a socket if there - * is still some buffered data to be sent. + * Number of seconds to wait when closing a socket if there is still some buffered data to be + * sent. * - * <p>If this option is set to 0, the TCP socket is closed forcefully and the - * call to {@code close} returns immediately. + * <p>The option can be set to disabled using {@link #setOption(int, Object)} with a value of + * {@code Boolean.FALSE}. * - * <p>If this option is set to a value greater than 0, the value is interpreted - * as the number of seconds to wait. If all data could be sent - * during this time, the socket is closed normally. Otherwise the connection will be - * closed forcefully. + * <p>If this option is set to 0, the TCP socket is closed forcefully and the call to + * {@code close} returns immediately. * - * <p>Valid values for this option are in the range 0 to 65535 inclusive. (Larger + * If this option is disabled, closing a socket will return immediately and the close will be + * handled in the background. + * + * <p>If this option is set to a value greater than 0, the value is interpreted as the number of + * seconds to wait. If all data could be sent during this time, the socket is closed normally. + * Otherwise the connection will be closed forcefully. + * + * <p>Valid numeric values for this option are in the range 0 to 65535 inclusive. (Larger * timeouts will be treated as 65535s timeouts; roughly 18 hours.) + * + * <p>This option is intended for use with sockets in blocking mode. The behavior of this option + * for non-blocking sockets is undefined. */ public static final int SO_LINGER = 128; @@ -54,16 +62,21 @@ public interface SocketOptions { public static final int SO_TIMEOUT = 4102; /** - * This boolean option specifies whether data is sent immediately on this socket. - * As a side-effect this could lead to low packet efficiency. The - * socket implementation uses the Nagle's algorithm to try to reach a higher - * packet efficiency if this option is disabled. + * This boolean option specifies whether data is sent immediately on this socket or buffered. + * <p> + * If set to {@code Boolean.TRUE} the Nagle algorithm is disabled and there is no buffering. + * This could lead to low packet efficiency. When set to {@code Boolean.FALSE} the the socket + * implementation uses buffering to try to reach a higher packet efficiency. + * + * <p>See <a href="http://www.ietf.org/rfc/rfc1122.txt">RFC 1122: Requirements for Internet + * Hosts -- Communication Layers</a> for more information about buffering and the Nagle + * algorithm. */ public static final int TCP_NODELAY = 1; /** * This is an IPv4-only socket option whose functionality is subsumed by - * {@link #IP_MULTICAST_IF} and not implemented on Android. + * {@link #IP_MULTICAST_IF2} and not implemented on Android. */ public static final int IP_MULTICAST_IF = 16; @@ -73,9 +86,18 @@ public interface SocketOptions { public static final int SO_BINDADDR = 15; /** - * This boolean option specifies whether a reuse of a local address is allowed even - * if another socket is not yet removed by the operating system. It's only - * available on a {@code MulticastSocket}. + * This boolean option specifies whether a reuse of a local address is allowed when another + * socket has not yet been removed by the operating system. + * + * <p>For connection-oriented sockets, if this option is disabled and if there is another socket + * in state TIME_WAIT on a given address then another socket binding to that address would fail. + * Setting this value after a socket is bound has no effect. + * + * <p>For datagram sockets this option determines whether several sockets can listen on the + * same address; when enabled each socket will receive a copy of the datagram. + * + * <p>See <a href="https://www.ietf.org/rfc/rfc793.txt">RFC 793: Transmission Control Protocol + * </a> for more information about socket re-use. */ public static final int SO_REUSEADDR = 4; @@ -93,11 +115,18 @@ public interface SocketOptions { * This is a hint to the kernel; the kernel may use a larger buffer. * * <p>For datagram sockets, packets larger than this value will be discarded. + * + * <p>See <a href="http://www.ietf.org/rfc/rfc1323.txt">RFC1323: TCP Extensions for High + * Performance</a> for more information about TCP/IP buffering. */ public static final int SO_RCVBUF = 4098; /** - * This boolean option specifies whether the kernel sends keepalive messages. + * This boolean option specifies whether the kernel sends keepalive messages on + * connection-oriented sockets. + * + * <p>See <a href="http://www.ietf.org/rfc/rfc1122.txt">RFC 1122: Requirements for Internet + * Hosts -- Communication Layers</a> for more information on keep-alive. */ public static final int SO_KEEPALIVE = 8; @@ -114,15 +143,18 @@ public interface SocketOptions { /** * This boolean option specifies whether the local loopback of multicast packets is - * enabled or disabled. This option is enabled by default on multicast - * sockets. Note that the sense of this option in Java is the - * <i>opposite</i> of the underlying Unix {@code IP_MULTICAST_LOOP}. - * See {@link MulticastSocket#setLoopbackMode}. + * enabled or disabled. This loopback is enabled by default on multicast sockets. + * + * <p>See <a href="http://tools.ietf.org/rfc/rfc1112.txt">RFC 1112: Host Extensions for IP + * Multicasting</a> for more information about IP multicast. + * + * <p>See {@link MulticastSocket#setLoopbackMode}. */ public static final int IP_MULTICAST_LOOP = 18; /** - * This boolean option can be used to enable broadcasting on datagram sockets. + * This boolean option can be used to enable or disable broadcasting on datagram sockets. This + * option must be enabled to send broadcast messages. The default value is false. */ public static final int SO_BROADCAST = 32; @@ -135,6 +167,9 @@ public interface SocketOptions { /** * This integer option sets the outgoing interface for multicast packets * using an interface index. + * + * <p>See <a href="http://tools.ietf.org/rfc/rfc1112.txt">RFC 1112: Host Extensions for IP + * Multicasting</a> for more information about IP multicast. */ public static final int IP_MULTICAST_IF2 = 31; diff --git a/luni/src/main/java/java/net/StandardSocketOptions.java b/luni/src/main/java/java/net/StandardSocketOptions.java new file mode 100644 index 0000000..3d10caf --- /dev/null +++ b/luni/src/main/java/java/net/StandardSocketOptions.java @@ -0,0 +1,407 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package java.net; + +import java.io.FileDescriptor; +import java.io.IOException; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +import libcore.io.IoBridge; + +/** + * Defines the set standard of socket options that can be supported by network channels. + * + * <p>See {@link java.nio.channels.NetworkChannel} for more information, particularly + * {@link java.nio.channels.NetworkChannel#supportedOptions()} for the options that are supported + * for each type of socket. + * + * @since 1.7 + * @hide Until ready for a public API change + */ +public final class StandardSocketOptions { + + /** + * The outgoing interface for multicast packets. + * + * <p>See {@link SocketOptions#IP_MULTICAST_IF2} for further documentation. + */ + public static final SocketOption<NetworkInterface> IP_MULTICAST_IF = + new NetworkInterfaceSocketOption("IP_MULTICAST_IF", SocketOptions.IP_MULTICAST_IF2); + + /** + * Whether the local loopback of multicast packets is enabled (true) or disabled (false). This + * option is enabled by default. + * + * <p>See {@link SocketOptions#IP_MULTICAST_LOOP} for further documentation. + */ + public static final SocketOption<Boolean> IP_MULTICAST_LOOP = + new BooleanSocketOption("IP_MULTICAST_LOOP", SocketOptions.IP_MULTICAST_LOOP); + + /** + * The time-to-live (TTL) for multicast packets. The value must be between 0 and 255 inclusive. + * A 0 value restricts the packet to the originating host. See also {@link #IP_MULTICAST_LOOP}. + * The default value is 1. + * + * <p>See <a href="http://tools.ietf.org/rfc/rfc1112.txt">RFC 1112: Host Extensions for IP + * Multicasting</a> for more information about IP multicast. + */ + public static final SocketOption<Integer> IP_MULTICAST_TTL = + new ByteRangeSocketOption("IP_MULTICAST_TTL", IoBridge.JAVA_IP_MULTICAST_TTL); + + /** + * The value for the type-of-service field of the IPv4 header, or the traffic class field of the + * IPv6 header. These correspond to the IP_TOS and IPV6_TCLASS socket options. These may be + * ignored by the underlying OS. Values must be between 0 and 255 inclusive. + * + * <p>See {@link SocketOptions#IP_TOS} for further documentation. + */ + public static final SocketOption<Integer> IP_TOS = + new ByteRangeSocketOption("IP_TOS", SocketOptions.IP_TOS); + + /** + * Whether broadcasting on datagram sockets is enabled or disabled. This option must be enabled to + * send broadcast messages. The default value is false. + * + * <p>See {@link SocketOptions#SO_BROADCAST} for further documentation. + */ + public static final SocketOption<Boolean> SO_BROADCAST = + new BooleanSocketOption("SO_BROADCAST", SocketOptions.SO_BROADCAST); + + /** + * Whether the kernel sends keepalive messages on connection-oriented sockets. + * + * <p>See {@link SocketOptions#SO_KEEPALIVE} for further documentation. + */ + public static final SocketOption<Boolean> SO_KEEPALIVE = + new BooleanSocketOption("SO_KEEPALIVE", SocketOptions.SO_KEEPALIVE); + + /** + * Number of seconds to wait when closing a socket if there is still some buffered data to be + * sent. + * + * <p>If this option is negative this option is disabled. This is the default value. If the value + * is 0 or positive it is enabled. + * + * <p>See {@link SocketOptions#SO_LINGER} for further documentation. + * + */ + public static final SocketOption<Integer> SO_LINGER = + new SocketOptionImpl<Integer>("SO_LINGER", Integer.class, SocketOptions.SO_LINGER) { + @Override + protected Object validateAndConvertValueBeforeSet( + FileDescriptor fd, Integer value) { + Object objectValue = super.validateAndConvertValueBeforeSet(fd, value); + if (value != null && value < 0) { + // IoBridge requires a "false" object to disable linger. + objectValue = Boolean.FALSE; + } + return objectValue; + } + + @Override + protected Integer validateAndConvertValueAfterGet(FileDescriptor fd, Object value) { + // IoBridge returns a "false" object to indicate that linger is disabled. + if (value != null && value instanceof Boolean) { + value = -1; + } + return super.validateAndConvertValueAfterGet(fd, value); + } + }; + + /** + * The size in bytes of a socket's receive buffer. This must be an integer greater than 0. + * This is a hint to the kernel; the kernel may use a larger buffer. + * + * <p>See {@link SocketOptions#SO_RCVBUF} for further documentation. + */ + public static final SocketOption<Integer> SO_RCVBUF = + new PositiveIntegerSocketOption("SO_RCVBUF", SocketOptions.SO_RCVBUF); + + /** + * Whether a reuse of a local address is allowed when another socket has not yet been removed by + * the operating system. + * + * <p>See {@link SocketOptions#SO_REUSEADDR} for further documentation. + */ + public static final SocketOption<Boolean> SO_REUSEADDR = + new BooleanSocketOption("SO_REUSEADDR", SocketOptions.SO_REUSEADDR); + + /** + * The size in bytes of a socket's send buffer. This must be an integer greater than 0. + * This is a hint to the kernel; the kernel may use a larger buffer. + * + * <p>See {@link SocketOptions#SO_SNDBUF} for further documentation. + */ + public static final SocketOption<Integer> SO_SNDBUF = + new PositiveIntegerSocketOption("SO_SNDBUF", SocketOptions.SO_SNDBUF); + + /** + * Specifies whether data is sent immediately on this socket or buffered. + * + * <p>See {@link SocketOptions#TCP_NODELAY} for further documentation. + */ + public static final SocketOption<Boolean> TCP_NODELAY = + new BooleanSocketOption("TCP_NODELAY", SocketOptions.TCP_NODELAY); + + /** + * The set of supported options for UDP sockets. + * + * @hide internal use only + */ + public static final Set<SocketOption<?>> DATAGRAM_SOCKET_OPTIONS; + + static { + HashSet<SocketOption<?>> mutableSet = new HashSet<SocketOption<?>>(8); + mutableSet.add(IP_MULTICAST_IF); + mutableSet.add(IP_MULTICAST_LOOP); + mutableSet.add(IP_MULTICAST_TTL); + mutableSet.add(IP_TOS); + mutableSet.add(SO_BROADCAST); + mutableSet.add(SO_REUSEADDR); + mutableSet.add(SO_RCVBUF); + mutableSet.add(SO_SNDBUF); + DATAGRAM_SOCKET_OPTIONS = Collections.unmodifiableSet(mutableSet); + } + + /** + * The set of supported options for TCP sockets. + * + * @hide internal use only + */ + public static final Set<SocketOption<?>> SOCKET_OPTIONS; + + static { + HashSet<SocketOption<?>> mutableSet = new HashSet<SocketOption<?>>(7); + mutableSet.add(IP_TOS); + mutableSet.add(SO_KEEPALIVE); + mutableSet.add(SO_LINGER); + mutableSet.add(TCP_NODELAY); + mutableSet.add(SO_RCVBUF); + mutableSet.add(SO_REUSEADDR); + mutableSet.add(SO_SNDBUF); + SOCKET_OPTIONS = Collections.unmodifiableSet(mutableSet); + } + + /** + * The set of supported options for TCP server sockets. + * + * @hide internal use only + */ + public static final Set<SocketOption<?>> SERVER_SOCKET_OPTIONS; + + static { + HashSet<SocketOption<?>> mutableSet = new HashSet<SocketOption<?>>(2); + mutableSet.add(SO_RCVBUF); + mutableSet.add(SO_REUSEADDR); + SERVER_SOCKET_OPTIONS = Collections.unmodifiableSet(mutableSet); + } + + /** + * A base class for SocketOption objects that passes the values to/from {@link IoBridge} as they + * are. For use with simple types like Integer and Boolean, and can be extended for more + * validation / type conversion. + * + * @hide internal use only + */ + public static class SocketOptionImpl<T> implements SocketOption<T> { + + protected final String name; + + private final Class<T> type; + + protected final int socketOption; + + public SocketOptionImpl(String name, Class<T> type, int socketOption) { + this.name = name; + this.type = type; + this.socketOption = socketOption; + } + + @Override + public String name() { + return name; + } + + @Override + public Class<T> type() { + return type; + } + + /** + * Sets the socket option of the file descriptor to value using IoBridge. + * + * @hide internal method + */ + public final void setValue(FileDescriptor fd, T value) throws IOException { + // Sanity check required because of type erasure. + if (value != null && !type.isAssignableFrom(value.getClass())) { + throw new AssertionError("Invalid type " + value + " of value for " + name); + } + Object objectValue = validateAndConvertValueBeforeSet(fd, value); + IoBridge.setSocketOption(fd, socketOption, objectValue); + } + + /** + * Throws IllegalArgumentException if the value is outside of the acceptable range. + * Subclasses can override to apply option-specific validate, and may also convert the value + * to a different type or value. The default implementation prevents null values and returns + * the value unchanged. + */ + protected Object validateAndConvertValueBeforeSet(FileDescriptor fd, T value) { + if (value == null) { + throw new IllegalArgumentException("value for " + name + " must not be null"); + } + return value; + } + + /** + * Gets the value of the socket option. + * + * @hide internal method + */ + public final T getValue(FileDescriptor fd) throws IOException { + Object value = IoBridge.getSocketOption(fd, socketOption); + T typedValue = validateAndConvertValueAfterGet(fd, value); + if (typedValue != null && !type.isAssignableFrom(typedValue.getClass())) { + // Sanity check required because of type erasure. + throw new AssertionError("Unexpected type of value returned for " + name); + } + return typedValue; + } + + /** + * Throws AssertionError if the value is outside of the acceptable range. + * Implementations may also convert the value to a different type or + * value. The default implementation does nothing. + */ + @SuppressWarnings("unchecked") + protected T validateAndConvertValueAfterGet(FileDescriptor fd, Object value) { + return (T) value; + } + } + + /** + * A SocketOption capable of setting / getting an boolean value. + */ + private static class BooleanSocketOption extends SocketOptionImpl<Boolean> { + + public BooleanSocketOption(String name, int socketOption) { + super(name, Boolean.class, socketOption); + } + } + + /** + * A SocketOption capable of setting / getting an network interface value. + */ + private static class NetworkInterfaceSocketOption extends SocketOptionImpl<NetworkInterface> { + + public NetworkInterfaceSocketOption(String name, int socketOption) { + super(name, NetworkInterface.class, socketOption); + } + + @Override + public Integer validateAndConvertValueBeforeSet(FileDescriptor fd, NetworkInterface value) { + if (value == null) { + throw new IllegalArgumentException("value for " + name + " must not be null"); + } + int nicIndex = value.getIndex(); + if (nicIndex == -1) { + throw new IllegalArgumentException("The NetworkInterface must have a valid index"); + } + return nicIndex; + } + + @Override + public NetworkInterface validateAndConvertValueAfterGet(FileDescriptor fd, Object value) { + if (value == null) { + return null; + } else if (!(value instanceof Integer)) { + throw new AssertionError("Unexpected type of value returned for " + name); + } + + int nicIndex = (Integer) value; + try { + return NetworkInterface.getByIndex(nicIndex); + } catch (SocketException e) { + throw new IllegalArgumentException( + "Unable to resolve NetworkInterface index: " + nicIndex, e); + } + } + } + + /** + * A SocketOption capable of setting / getting an integer in the range 0-255. + */ + private static class ByteRangeSocketOption extends SocketOptionImpl<Integer> { + + public ByteRangeSocketOption(String name, int socketOption) { + super(name, Integer.class, socketOption); + } + + @Override + protected Object validateAndConvertValueBeforeSet(FileDescriptor fd, Integer value) { + if (value == null || value < 0 || value > 255) { + throw new IllegalArgumentException(name + " must be >= 0 and <= 255, is " + value); + } + return value; + } + + @Override + protected Integer validateAndConvertValueAfterGet(FileDescriptor fd, Object value) { + if (!(value instanceof Integer)) { + throw new AssertionError("Unexpected value for option " + name + ": " + value); + } + int intValue = (Integer) value; + if (intValue < 0 || intValue > 255) { + throw new AssertionError("Unexpected value for option " + name + ": " + value); + } + return intValue; + } + } + + /** + * A SocketOption capable of setting / getting an integer in the range 1.. + */ + private static class PositiveIntegerSocketOption extends SocketOptionImpl<Integer> { + + public PositiveIntegerSocketOption(String name, int socketOption) { + super(name, Integer.class, socketOption); + } + + @Override + protected Integer validateAndConvertValueBeforeSet(FileDescriptor fd, Integer value) { + if (value < 1) { + throw new IllegalArgumentException(name + " value must be > 0"); + } + return value; + } + + @Override + protected Integer validateAndConvertValueAfterGet(FileDescriptor fd, Object value) { + if (!(value instanceof Integer)) { + throw new AssertionError("Unexpected value for option " + name + ": " + value); + } + int intValue = (Integer) value; + if (intValue < 1) { + throw new AssertionError("Unexpected value for option " + name + ": " + value); + } + return intValue; + } + } +} diff --git a/luni/src/main/java/java/nio/DatagramChannelImpl.java b/luni/src/main/java/java/nio/DatagramChannelImpl.java index 3c4a980..cb940db 100644 --- a/luni/src/main/java/java/nio/DatagramChannelImpl.java +++ b/luni/src/main/java/java/nio/DatagramChannelImpl.java @@ -30,6 +30,8 @@ import java.net.InetSocketAddress; import java.net.PlainDatagramSocketImpl; import java.net.SocketAddress; import java.net.SocketException; +import java.net.SocketOption; +import java.net.StandardSocketOptions; import java.nio.channels.AlreadyBoundException; import java.nio.channels.AlreadyConnectedException; import java.nio.channels.ClosedChannelException; @@ -39,6 +41,8 @@ import java.nio.channels.NotYetConnectedException; import java.nio.channels.UnsupportedAddressTypeException; import java.nio.channels.spi.SelectorProvider; import java.util.Arrays; +import java.util.Set; + import libcore.io.ErrnoException; import libcore.io.IoBridge; import libcore.io.IoUtils; @@ -155,6 +159,28 @@ class DatagramChannelImpl extends DatagramChannel implements FileDescriptorChann return isBound ? new InetSocketAddress(localAddress, localPort) : null; } + /** @hide Until ready for a public API change */ + @Override + public <T> T getOption(SocketOption<T> option) throws IOException { + return NioUtils.getSocketOption( + this, StandardSocketOptions.DATAGRAM_SOCKET_OPTIONS, option); + } + + /** @hide Until ready for a public API change */ + @Override + public <T> DatagramChannel setOption(SocketOption<T> option, T value) throws IOException { + checkOpen(); + NioUtils.setSocketOption( + this, StandardSocketOptions.DATAGRAM_SOCKET_OPTIONS, option, value); + return this; + } + + /** @hide Until ready for a public API change */ + @Override + public Set<SocketOption<?>> supportedOptions() { + return StandardSocketOptions.DATAGRAM_SOCKET_OPTIONS; + } + @Override synchronized public boolean isConnected() { return connected; diff --git a/luni/src/main/java/java/nio/NioUtils.java b/luni/src/main/java/java/nio/NioUtils.java index a1a46b6..34af76b 100644 --- a/luni/src/main/java/java/nio/NioUtils.java +++ b/luni/src/main/java/java/nio/NioUtils.java @@ -17,7 +17,12 @@ package java.nio; import java.io.FileDescriptor; +import java.io.IOException; +import java.net.SocketOption; +import java.net.StandardSocketOptions; +import java.nio.channels.ClosedChannelException; import java.nio.channels.FileChannel; +import java.util.Set; /** * @hide internal use only @@ -62,4 +67,66 @@ public final class NioUtils { public static int unsafeArrayOffset(ByteBuffer b) { return ((ByteArrayBuffer) b).arrayOffset; } + + /** + * Sets the supplied option on the channel to have the value if option is a member of + * allowedOptions. + * + * @throws IOException + * if the value could not be set due to IO errors. + * @throws IllegalArgumentException + * if the socket option or the value is invalid. + * @throws UnsupportedOperationException + * if the option is not a member of allowedOptions. + * @throws ClosedChannelException + * if the channel is closed + */ + public static <T> void setSocketOption( + FileDescriptorChannel channel, Set<SocketOption<?>> allowedOptions, + SocketOption<T> option, T value) + throws IOException { + + if (!(option instanceof StandardSocketOptions.SocketOptionImpl)) { + throw new IllegalArgumentException("SocketOption must come from StandardSocketOptions"); + } + if (!allowedOptions.contains(option)) { + throw new UnsupportedOperationException( + option + " is not supported for this type of socket"); + } + if (!channel.getFD().valid()) { + throw new ClosedChannelException(); + } + ((StandardSocketOptions.SocketOptionImpl<T>) option).setValue(channel.getFD(), value); + } + + /** + * Gets the supplied option from the channel if option is a member of allowedOptions. + * + * @throws IOException + * if the value could not be read due to IO errors. + * @throws IllegalArgumentException + * if the socket option is invalid. + * @throws UnsupportedOperationException + * if the option is not a member of allowedOptions. + * @throws ClosedChannelException + * if the channel is closed + */ + public static <T> T getSocketOption( + FileDescriptorChannel channel, Set<SocketOption<?>> allowedOptions, + SocketOption<T> option) + throws IOException { + + if (!(option instanceof StandardSocketOptions.SocketOptionImpl)) { + throw new IllegalArgumentException("SocketOption must come from StandardSocketOptions"); + } + if (!allowedOptions.contains(option)) { + throw new UnsupportedOperationException( + option + " is not supported for this type of socket"); + } + if (!channel.getFD().valid()) { + throw new ClosedChannelException(); + } + return ((StandardSocketOptions.SocketOptionImpl<T>) option).getValue(channel.getFD()); + } + } diff --git a/luni/src/main/java/java/nio/ServerSocketChannelImpl.java b/luni/src/main/java/java/nio/ServerSocketChannelImpl.java index 9ae282e..3d6cd22 100644 --- a/luni/src/main/java/java/nio/ServerSocketChannelImpl.java +++ b/luni/src/main/java/java/nio/ServerSocketChannelImpl.java @@ -23,7 +23,9 @@ import java.net.InetSocketAddress; import java.net.ServerSocket; import java.net.Socket; import java.net.SocketAddress; +import java.net.SocketOption; import java.net.SocketTimeoutException; +import java.net.StandardSocketOptions; import java.nio.channels.AlreadyBoundException; import java.nio.channels.ClosedChannelException; import java.nio.channels.IllegalBlockingModeException; @@ -32,6 +34,8 @@ import java.nio.channels.ServerSocketChannel; import java.nio.channels.SocketChannel; import java.nio.channels.UnsupportedAddressTypeException; import java.nio.channels.spi.SelectorProvider; +import java.util.Set; + import libcore.io.ErrnoException; import libcore.io.IoUtils; import static libcore.io.OsConstants.*; @@ -80,6 +84,25 @@ final class ServerSocketChannelImpl extends ServerSocketChannel implements FileD return socket.getLocalSocketAddress(); } + /** @hide Until ready for a public API change */ + @Override + public <T> T getOption(SocketOption<T> option) throws IOException { + return NioUtils.getSocketOption(this, StandardSocketOptions.SERVER_SOCKET_OPTIONS, option); + } + + /** @hide Until ready for a public API change */ + @Override + public <T> ServerSocketChannel setOption(SocketOption<T> option, T value) throws IOException { + NioUtils.setSocketOption(this, StandardSocketOptions.SERVER_SOCKET_OPTIONS, option, value); + return this; + } + + /** @hide Until ready for a public API change */ + @Override + public Set<SocketOption<?>> supportedOptions() { + return StandardSocketOptions.SERVER_SOCKET_OPTIONS; + } + @Override public SocketChannel accept() throws IOException { if (!isOpen()) { diff --git a/luni/src/main/java/java/nio/SocketChannelImpl.java b/luni/src/main/java/java/nio/SocketChannelImpl.java index 977c433..5d3db43 100644 --- a/luni/src/main/java/java/nio/SocketChannelImpl.java +++ b/luni/src/main/java/java/nio/SocketChannelImpl.java @@ -31,7 +31,9 @@ import java.net.PlainSocketImpl; import java.net.Socket; import java.net.SocketAddress; import java.net.SocketException; +import java.net.SocketOption; import java.net.SocketUtils; +import java.net.StandardSocketOptions; import java.nio.channels.AlreadyBoundException; import java.nio.channels.AlreadyConnectedException; import java.nio.channels.ClosedChannelException; @@ -44,6 +46,8 @@ import java.nio.channels.UnresolvedAddressException; import java.nio.channels.UnsupportedAddressTypeException; import java.nio.channels.spi.SelectorProvider; import java.util.Arrays; +import java.util.Set; + import libcore.io.ErrnoException; import libcore.io.Libcore; import libcore.io.IoBridge; @@ -192,6 +196,25 @@ class SocketChannelImpl extends SocketChannel implements FileDescriptorChannel { return isBound ? new InetSocketAddress(localAddress, localPort) : null; } + /** @hide Until ready for a public API change */ + @Override + public <T> T getOption(SocketOption<T> option) throws IOException { + return NioUtils.getSocketOption(this, StandardSocketOptions.SOCKET_OPTIONS, option); + } + + /** @hide Until ready for a public API change */ + @Override + public <T> SocketChannel setOption(SocketOption<T> option, T value) throws IOException { + NioUtils.setSocketOption(this, StandardSocketOptions.SOCKET_OPTIONS, option, value); + return this; + } + + /** @hide Until ready for a public API change */ + @Override + public Set<SocketOption<?>> supportedOptions() { + return StandardSocketOptions.SOCKET_OPTIONS; + } + @Override synchronized public boolean isConnected() { return status == SOCKET_STATUS_CONNECTED; diff --git a/luni/src/main/java/java/nio/channels/DatagramChannel.java b/luni/src/main/java/java/nio/channels/DatagramChannel.java index 2040b8e..2aa228d 100644 --- a/luni/src/main/java/java/nio/channels/DatagramChannel.java +++ b/luni/src/main/java/java/nio/channels/DatagramChannel.java @@ -20,9 +20,11 @@ package java.nio.channels; import java.io.IOException; import java.net.DatagramSocket; import java.net.SocketAddress; +import java.net.SocketOption; import java.nio.ByteBuffer; import java.nio.channels.spi.AbstractSelectableChannel; import java.nio.channels.spi.SelectorProvider; +import java.util.Set; /** * A {@code DatagramChannel} is a selectable channel that represents a partial @@ -104,6 +106,30 @@ public abstract class DatagramChannel extends AbstractSelectableChannel throw new UnsupportedOperationException("Subclasses must override this method"); } + /** @hide Until ready for a public API change */ + @Override + public <T> T getOption(SocketOption<T> option) throws IOException { + // This method was added for interoperability with Java 7, where it is abstract. It is + // concrete here to avoid breaking existing Android applications that extend this class. + throw new UnsupportedOperationException("Subclasses must override this method"); + } + + /** @hide Until ready for a public API change */ + @Override + public <T> DatagramChannel setOption(SocketOption<T> option, T value) throws IOException { + // This method was added for interoperability with Java 7, where it is abstract. It is + // concrete here to avoid breaking existing Android applications that extend this class. + throw new UnsupportedOperationException("Subclasses must override this method"); + } + + /** @hide Until ready for a public API change */ + @Override + public Set<SocketOption<?>> supportedOptions() { + // This method was added for interoperability with Java 7, where it is abstract. It is + // concrete here to avoid breaking existing Android applications that extend this class. + throw new UnsupportedOperationException("Subclasses must override this method"); + } + /** * Returns whether this channel's socket is connected or not. * diff --git a/luni/src/main/java/java/nio/channels/NetworkChannel.java b/luni/src/main/java/java/nio/channels/NetworkChannel.java index b3505b9..9b46e30 100644 --- a/luni/src/main/java/java/nio/channels/NetworkChannel.java +++ b/luni/src/main/java/java/nio/channels/NetworkChannel.java @@ -19,6 +19,8 @@ package java.nio.channels; import java.io.Closeable; import java.io.IOException; import java.net.SocketAddress; +import java.net.SocketOption; +import java.util.Set; /** * A common interface for channels that are backed by network sockets. @@ -65,4 +67,42 @@ public interface NetworkChannel extends AutoCloseable, Channel, Closeable { * @hide Until ready for a public API change */ SocketAddress getLocalAddress() throws IOException; + + /** + * Returns the value for the socket option. + * + * @throws UnsupportedOperationException + * if the option is not supported by the socket. + * @throws java.nio.channels.ClosedChannelException + * if the socket is closed + * @throws IOException + * if the value cannot be read. + * @hide Until ready for a public API change + * @see java.net.StandardSocketOptions + */ + <T> T getOption(SocketOption<T> option) throws IOException; + + /** + * Sets the value for the socket option. + * + * @return this NetworkChannel + * @throws UnsupportedOperationException + * if the option is not supported by the socket. + * @throws IllegalArgumentException + * if the value is not valid for the option. + * @throws java.nio.channels.ClosedChannelException + * if the socket is closed + * @throws IOException + * if the value cannot be written. + * @hide Until ready for a public API change + * @see java.net.StandardSocketOptions + */ + <T> NetworkChannel setOption(SocketOption<T> option, T value) throws IOException; + + /** + * Returns the set of socket options supported by this channel. + * + * @hide Until ready for a public API change + */ + Set<SocketOption<?>> supportedOptions(); } diff --git a/luni/src/main/java/java/nio/channels/ServerSocketChannel.java b/luni/src/main/java/java/nio/channels/ServerSocketChannel.java index aeb6d8f..c720451 100644 --- a/luni/src/main/java/java/nio/channels/ServerSocketChannel.java +++ b/luni/src/main/java/java/nio/channels/ServerSocketChannel.java @@ -20,8 +20,10 @@ package java.nio.channels; import java.io.IOException; import java.net.ServerSocket; import java.net.SocketAddress; +import java.net.SocketOption; import java.nio.channels.spi.AbstractSelectableChannel; import java.nio.channels.spi.SelectorProvider; +import java.util.Set; /** * A {@code ServerSocketChannel} is a partial abstraction of a selectable, @@ -129,6 +131,30 @@ public abstract class ServerSocketChannel extends AbstractSelectableChannel throw new UnsupportedOperationException("Subclasses must override this method"); } + /** @hide Until ready for a public API change */ + @Override + public <T> T getOption(SocketOption<T> option) throws IOException { + // This method was added for interoperability with Java 7, where it is abstract. It is + // concrete here to avoid breaking existing Android applications that extend this class. + throw new UnsupportedOperationException("Subclasses must override this method"); + } + + /** @hide Until ready for a public API change */ + @Override + public <T> ServerSocketChannel setOption(SocketOption<T> option, T value) throws IOException { + // This method was added for interoperability with Java 7, where it is abstract. It is + // concrete here to avoid breaking existing Android applications that extend this class. + throw new UnsupportedOperationException("Subclasses must override this method"); + } + + /** @hide Until ready for a public API change */ + @Override + public Set<SocketOption<?>> supportedOptions() { + // This method was added for interoperability with Java 7, where it is abstract. It is + // concrete here to avoid breaking existing Android applications that extend this class. + throw new UnsupportedOperationException("Subclasses must override this method"); + } + /** * Accepts a connection to this server-socket channel. * <p> diff --git a/luni/src/main/java/java/nio/channels/SocketChannel.java b/luni/src/main/java/java/nio/channels/SocketChannel.java index 12dfe38..a6d1551 100644 --- a/luni/src/main/java/java/nio/channels/SocketChannel.java +++ b/luni/src/main/java/java/nio/channels/SocketChannel.java @@ -20,9 +20,11 @@ package java.nio.channels; import java.io.IOException; import java.net.Socket; import java.net.SocketAddress; +import java.net.SocketOption; import java.nio.ByteBuffer; import java.nio.channels.spi.AbstractSelectableChannel; import java.nio.channels.spi.SelectorProvider; +import java.util.Set; /** * A {@code SocketChannel} is a selectable channel that provides a partial @@ -156,6 +158,30 @@ public abstract class SocketChannel extends AbstractSelectableChannel implements throw new UnsupportedOperationException("Subclasses must override this method"); } + /** @hide Until ready for a public API change */ + @Override + public <T> T getOption(SocketOption<T> option) throws IOException { + // This method was added for interoperability with Java 7, where it is abstract. It is + // concrete here to avoid breaking existing Android applications that extend this class. + throw new UnsupportedOperationException("Subclasses must override this method"); + } + + /** @hide Until ready for a public API change */ + @Override + public <T> SocketChannel setOption(SocketOption<T> option, T value) throws IOException { + // This method was added for interoperability with Java 7, where it is abstract. It is + // concrete here to avoid breaking existing Android applications that extend this class. + throw new UnsupportedOperationException("Subclasses must override this method"); + } + + /** @hide Until ready for a public API change */ + @Override + public Set<SocketOption<?>> supportedOptions() { + // This method was added for interoperability with Java 7, where it is abstract. It is + // concrete here to avoid breaking existing Android applications that extend this class. + throw new UnsupportedOperationException("Subclasses must override this method"); + } + /** * Indicates whether this channel's socket is connected. * diff --git a/luni/src/test/java/libcore/java/nio/channels/DatagramChannelTest.java b/luni/src/test/java/libcore/java/nio/channels/DatagramChannelTest.java index a0092d0..5927583 100644 --- a/luni/src/test/java/libcore/java/nio/channels/DatagramChannelTest.java +++ b/luni/src/test/java/libcore/java/nio/channels/DatagramChannelTest.java @@ -16,9 +16,18 @@ package libcore.java.nio.channels; +import java.io.IOException; import java.net.DatagramSocket; +import java.net.Inet4Address; +import java.net.InetSocketAddress; +import java.net.NetworkInterface; +import java.net.SocketOption; +import java.net.StandardSocketOptions; import java.nio.ByteBuffer; +import java.nio.channels.ClosedChannelException; import java.nio.channels.DatagramChannel; +import java.util.Enumeration; +import java.util.Set; public class DatagramChannelTest extends junit.framework.TestCase { public void test_read_intoReadOnlyByteArrays() throws Exception { @@ -79,4 +88,362 @@ public class DatagramChannelTest extends junit.framework.TestCase { dc.close(); } } + + public void test_supportedOptions() throws Exception { + DatagramChannel dc = DatagramChannel.open(); + Set<SocketOption<?>> options = dc.supportedOptions(); + + // Probe some values. This is not intended to be complete. + assertTrue(options.contains(StandardSocketOptions.SO_REUSEADDR)); + assertFalse(options.contains(StandardSocketOptions.TCP_NODELAY)); + } + + public void test_getOption_unsupportedOption() throws Exception { + DatagramChannel dc = DatagramChannel.open(); + try { + dc.getOption(StandardSocketOptions.TCP_NODELAY); + fail(); + } catch (UnsupportedOperationException expected) {} + + dc.close(); + } + + public void test_getOption_afterClose() throws Exception { + DatagramChannel dc = DatagramChannel.open(); + dc.close(); + + try { + dc.getOption(StandardSocketOptions.SO_RCVBUF); + fail(); + } catch (ClosedChannelException expected) {} + } + + public void test_setOption_afterClose() throws Exception { + DatagramChannel dc = DatagramChannel.open(); + dc.close(); + + try { + dc.setOption(StandardSocketOptions.SO_RCVBUF, 1234); + fail(); + } catch (ClosedChannelException expected) {} + } + + public void test_getOption_SO_RCVBUF_defaults() throws Exception { + DatagramChannel dc = DatagramChannel.open(); + + int value = dc.getOption(StandardSocketOptions.SO_RCVBUF); + assertTrue(value > 0); + assertEquals(value, dc.socket().getReceiveBufferSize()); + + dc.close(); + } + + public void test_setOption_SO_RCVBUF_afterOpen() throws Exception { + DatagramChannel dc = DatagramChannel.open(); + + trySetReceiveBufferSizeOption(dc); + + dc.close(); + } + + private static void trySetReceiveBufferSizeOption(DatagramChannel dc) throws IOException { + int initialValue = dc.getOption(StandardSocketOptions.SO_RCVBUF); + try { + dc.setOption(StandardSocketOptions.SO_RCVBUF, -1); + fail(); + } catch (IllegalArgumentException expected) {} + int actualValue = dc.getOption(StandardSocketOptions.SO_RCVBUF); + assertEquals(initialValue, actualValue); + assertEquals(initialValue, dc.socket().getReceiveBufferSize()); + + int newBufferSize = initialValue - 1; + dc.setOption(StandardSocketOptions.SO_RCVBUF, newBufferSize); + actualValue = dc.getOption(StandardSocketOptions.SO_RCVBUF); + // The Linux Kernel actually doubles the value it is given and may choose to ignore it. + // This assertion may be brittle. + assertTrue(actualValue != initialValue); + assertEquals(actualValue, dc.socket().getReceiveBufferSize()); + } + + public void test_getOption_SO_SNDBUF_defaults() throws Exception { + DatagramChannel dc = DatagramChannel.open(); + + int value = dc.getOption(StandardSocketOptions.SO_SNDBUF); + assertTrue(value > 0); + assertEquals(value, dc.socket().getSendBufferSize()); + + dc.close(); + } + + public void test_setOption_SO_SNDBUF_afterOpen() throws Exception { + DatagramChannel dc = DatagramChannel.open(); + + trySetSendBufferSizeOption(dc); + + dc.close(); + } + + private static void trySetSendBufferSizeOption(DatagramChannel dc) throws IOException { + int initialValue = dc.getOption(StandardSocketOptions.SO_SNDBUF); + try { + dc.setOption(StandardSocketOptions.SO_SNDBUF, -1); + fail(); + } catch (IllegalArgumentException expected) {} + int actualValue = dc.getOption(StandardSocketOptions.SO_SNDBUF); + assertEquals(initialValue, actualValue); + assertEquals(initialValue, dc.socket().getSendBufferSize()); + + int newBufferSize = initialValue - 1; + dc.setOption(StandardSocketOptions.SO_SNDBUF, newBufferSize); + actualValue = dc.getOption(StandardSocketOptions.SO_SNDBUF); + // The Linux Kernel actually doubles the value it is given and may choose to ignore it. + // This assertion may be brittle. + assertTrue(actualValue != initialValue); + assertEquals(actualValue, dc.socket().getSendBufferSize()); + } + + public void test_getOption_IP_MULTICAST_IF_defaults() throws Exception { + DatagramChannel dc = DatagramChannel.open(); + + NetworkInterface networkInterface = dc.getOption(StandardSocketOptions.IP_MULTICAST_IF); + assertNull(networkInterface); + + dc.close(); + } + + public void test_getOption_IP_MULTICAST_IF_nullCheck() throws Exception { + DatagramChannel dc = DatagramChannel.open(); + try { + dc.setOption(StandardSocketOptions.IP_MULTICAST_IF, null); + fail(); + } catch (IllegalArgumentException expected) {} + + dc.close(); + } + + public void test_setOption_IP_MULTICAST_IF_afterOpen() throws Exception { + DatagramChannel dc = DatagramChannel.open(); + + Enumeration<NetworkInterface> networkInterfaces = NetworkInterface.getNetworkInterfaces(); + assertTrue(networkInterfaces.hasMoreElements()); + while (networkInterfaces.hasMoreElements()) { + trySetNetworkInterfaceOption(dc, networkInterfaces.nextElement()); + } + + dc.close(); + } + + public void test_setOption_IP_MULTICAST_IF_afterBind() throws Exception { + DatagramChannel dc = DatagramChannel.open(); + dc.bind(new InetSocketAddress(Inet4Address.getLoopbackAddress(), 0)); + + Enumeration<NetworkInterface> networkInterfaces = NetworkInterface.getNetworkInterfaces(); + assertTrue(networkInterfaces.hasMoreElements()); + while (networkInterfaces.hasMoreElements()) { + trySetNetworkInterfaceOption(dc, networkInterfaces.nextElement()); + } + + dc.close(); + } + + private static void trySetNetworkInterfaceOption( + DatagramChannel dc, NetworkInterface networkInterface) throws IOException { + + NetworkInterface initialValue = dc.getOption(StandardSocketOptions.IP_MULTICAST_IF); + try { + dc.setOption(StandardSocketOptions.IP_MULTICAST_IF, null); + fail(); + } catch (IllegalArgumentException expected) {} + assertEquals(initialValue, dc.getOption(StandardSocketOptions.IP_MULTICAST_IF)); + + dc.setOption(StandardSocketOptions.IP_MULTICAST_IF, networkInterface); + NetworkInterface actualValue = + dc.getOption(StandardSocketOptions.IP_MULTICAST_IF); + assertEquals(networkInterface, actualValue); + } + + public void test_getOption_IP_MULTICAST_LOOP_defaults() throws Exception { + DatagramChannel dc = DatagramChannel.open(); + + assertTrue(dc.getOption(StandardSocketOptions.IP_MULTICAST_LOOP)); + + dc.close(); + } + + public void test_getOption_IP_MULTICAST_LOOP_nullCheck() throws Exception { + DatagramChannel dc = DatagramChannel.open(); + try { + dc.setOption(StandardSocketOptions.IP_MULTICAST_LOOP, null); + fail(); + } catch (IllegalArgumentException expected) {} + + dc.close(); + } + + public void test_setOption_IP_MULTICAST_LOOP_afterOpen() throws Exception { + DatagramChannel dc = DatagramChannel.open(); + assertTrue(dc.getOption(StandardSocketOptions.IP_MULTICAST_LOOP)); + + dc.setOption(StandardSocketOptions.IP_MULTICAST_LOOP, false); + assertFalse(dc.getOption(StandardSocketOptions.IP_MULTICAST_LOOP)); + + dc.close(); + } + + public void test_setOption_IP_MULTICAST_LOOP_afterBind() throws Exception { + DatagramChannel dc = DatagramChannel.open(); + dc.bind(new InetSocketAddress(Inet4Address.getLoopbackAddress(), 0)); + + assertTrue(dc.getOption(StandardSocketOptions.IP_MULTICAST_LOOP)); + + dc.setOption(StandardSocketOptions.IP_MULTICAST_LOOP, false); + assertFalse(dc.getOption(StandardSocketOptions.IP_MULTICAST_LOOP)); + + dc.close(); + } + + public void test_getOption_IP_MULTICAST_TTL_defaults() throws Exception { + DatagramChannel dc = DatagramChannel.open(); + + int value = dc.getOption(StandardSocketOptions.IP_MULTICAST_TTL); + assertEquals(1, value); + + dc.close(); + } + + public void test_setOption_IP_MULTICAST_TTL_afterOpen() throws Exception { + DatagramChannel dc = DatagramChannel.open(); + + trySetMulticastTtlOption(dc); + + dc.close(); + } + + private static void trySetMulticastTtlOption(DatagramChannel dc) throws IOException { + int initialValue = dc.getOption(StandardSocketOptions.IP_MULTICAST_TTL); + try { + dc.setOption(StandardSocketOptions.IP_MULTICAST_TTL, -1); + fail(); + } catch (IllegalArgumentException expected) {} + int actualValue = dc.getOption(StandardSocketOptions.IP_MULTICAST_TTL); + assertEquals(initialValue, actualValue); + + int newTtl = initialValue + 1; + dc.setOption(StandardSocketOptions.IP_MULTICAST_TTL, newTtl); + actualValue = dc.getOption(StandardSocketOptions.IP_MULTICAST_TTL); + assertEquals(newTtl, actualValue); + } + + public void test_setOption_IP_MULTICAST_TTL_afterBind() throws Exception { + DatagramChannel dc = DatagramChannel.open(); + dc.bind(null); + + trySetMulticastTtlOption(dc); + + dc.close(); + } + + public void test_getOption_SO_BROADCAST_defaults() throws Exception { + DatagramChannel dc = DatagramChannel.open(); + + assertFalse(dc.getOption(StandardSocketOptions.SO_BROADCAST)); + + dc.close(); + } + + public void test_setOption_SO_BROADCAST_afterOpen() throws Exception { + DatagramChannel dc = DatagramChannel.open(); + + trySetSoBroadcastOption(dc); + + dc.close(); + } + + private static void trySetSoBroadcastOption(DatagramChannel dc) throws IOException { + boolean initialValue = dc.getOption(StandardSocketOptions.SO_BROADCAST); + + dc.setOption(StandardSocketOptions.SO_BROADCAST, !initialValue); + boolean actualValue = dc.getOption(StandardSocketOptions.SO_BROADCAST); + assertEquals(!initialValue, actualValue); + } + + public void test_setOption_SO_BROADCAST_afterBind() throws Exception { + DatagramChannel dc = DatagramChannel.open(); + dc.bind(null); + + trySetSoBroadcastOption(dc); + + dc.close(); + } + + public void test_getOption_IP_TOS_defaults() throws Exception { + DatagramChannel dc = DatagramChannel.open(); + + int value = dc.getOption(StandardSocketOptions.IP_TOS); + assertEquals(0, value); + assertEquals(value, dc.socket().getTrafficClass()); + + dc.close(); + } + + public void test_setOption_IP_TOS_afterOpen() throws Exception { + DatagramChannel dc = DatagramChannel.open(); + + trySetTosOption(dc); + + dc.close(); + } + + private static void trySetTosOption(DatagramChannel dc) throws IOException { + int initialValue = dc.getOption(StandardSocketOptions.IP_TOS); + try { + dc.setOption(StandardSocketOptions.IP_TOS, -1); + fail(); + } catch (IllegalArgumentException expected) {} + assertEquals(initialValue, (int) dc.getOption(StandardSocketOptions.IP_TOS)); + assertEquals(initialValue, dc.socket().getTrafficClass()); + + try { + dc.setOption(StandardSocketOptions.IP_TOS, 256); + fail(); + } catch (IllegalArgumentException expected) {} + assertEquals(initialValue, (int) dc.getOption(StandardSocketOptions.IP_TOS)); + assertEquals(initialValue, dc.socket().getTrafficClass()); + + int newValue = (initialValue + 1) % 255; + dc.setOption(StandardSocketOptions.IP_TOS, newValue); + assertEquals(newValue, (int) dc.getOption(StandardSocketOptions.IP_TOS)); + assertEquals(newValue, dc.socket().getTrafficClass()); + } + + public void test_setOption_IP_TOS_afterBind() throws Exception { + DatagramChannel dc = DatagramChannel.open(); + dc.bind(null); + + trySetTosOption(dc); + + dc.close(); + } + + public void test_getOption_SO_REUSEADDR_defaults() throws Exception { + DatagramChannel dc = DatagramChannel.open(); + + boolean value = dc.getOption(StandardSocketOptions.SO_REUSEADDR); + assertFalse(value); + assertFalse(dc.socket().getReuseAddress()); + + dc.close(); + } + + public void test_setOption_SO_REUSEADDR_afterOpen() throws Exception { + DatagramChannel dc = DatagramChannel.open(); + + boolean initialValue = dc.getOption(StandardSocketOptions.SO_REUSEADDR); + dc.setOption(StandardSocketOptions.SO_REUSEADDR, !initialValue); + assertEquals(!initialValue, (boolean) dc.getOption(StandardSocketOptions.SO_REUSEADDR)); + assertEquals(!initialValue, dc.socket().getReuseAddress()); + + dc.close(); + } + } diff --git a/luni/src/test/java/libcore/java/nio/channels/ServerSocketChannelTest.java b/luni/src/test/java/libcore/java/nio/channels/ServerSocketChannelTest.java index bceb759..ed39d46 100644 --- a/luni/src/test/java/libcore/java/nio/channels/ServerSocketChannelTest.java +++ b/luni/src/test/java/libcore/java/nio/channels/ServerSocketChannelTest.java @@ -21,9 +21,13 @@ import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.NetworkInterface; import java.net.ServerSocket; +import java.net.SocketOption; +import java.net.StandardSocketOptions; +import java.nio.channels.ClosedChannelException; import java.nio.channels.ServerSocketChannel; import java.nio.channels.SocketChannel; import java.util.Enumeration; +import java.util.Set; public class ServerSocketChannelTest extends junit.framework.TestCase { // http://code.google.com/p/android/issues/detail?id=16579 @@ -127,4 +131,102 @@ public class ServerSocketChannelTest extends junit.framework.TestCase { return false; } } + + public void test_supportedOptions() throws Exception { + ServerSocketChannel ssc = ServerSocketChannel.open(); + Set<SocketOption<?>> options = ssc.supportedOptions(); + + // Probe some values. This is not intended to be complete. + assertTrue(options.contains(StandardSocketOptions.SO_REUSEADDR)); + assertFalse(options.contains(StandardSocketOptions.IP_MULTICAST_TTL)); + } + + public void test_getOption_unsupportedOption() throws Exception { + ServerSocketChannel ssc = ServerSocketChannel.open(); + try { + ssc.getOption(StandardSocketOptions.IP_MULTICAST_TTL); + fail(); + } catch (UnsupportedOperationException expected) {} + + ssc.close(); + } + + public void test_getOption_afterClose() throws Exception { + ServerSocketChannel ssc = ServerSocketChannel.open(); + ssc.close(); + + try { + ssc.getOption(StandardSocketOptions.SO_RCVBUF); + fail(); + } catch (ClosedChannelException expected) {} + } + + public void test_setOption_afterClose() throws Exception { + ServerSocketChannel ssc = ServerSocketChannel.open(); + ssc.close(); + + try { + ssc.setOption(StandardSocketOptions.SO_RCVBUF, 1234); + fail(); + } catch (ClosedChannelException expected) {} + } + + public void test_getOption_SO_RCVBUF_defaults() throws Exception { + ServerSocketChannel ssc = ServerSocketChannel.open(); + + int value = ssc.getOption(StandardSocketOptions.SO_RCVBUF); + assertTrue(value > 0); + assertEquals(value, ssc.socket().getReceiveBufferSize()); + + ssc.close(); + } + + public void test_setOption_SO_RCVBUF_afterOpen() throws Exception { + ServerSocketChannel ssc = ServerSocketChannel.open(); + + trySetReceiveBufferSizeOption(ssc); + + ssc.close(); + } + + private static void trySetReceiveBufferSizeOption(ServerSocketChannel ssc) throws IOException { + int initialValue = ssc.getOption(StandardSocketOptions.SO_RCVBUF); + try { + ssc.setOption(StandardSocketOptions.SO_RCVBUF, -1); + fail(); + } catch (IllegalArgumentException expected) {} + int actualValue = ssc.getOption(StandardSocketOptions.SO_RCVBUF); + assertEquals(initialValue, actualValue); + assertEquals(initialValue, ssc.socket().getReceiveBufferSize()); + + int newBufferSize = initialValue - 1; + ssc.setOption(StandardSocketOptions.SO_RCVBUF, newBufferSize); + actualValue = ssc.getOption(StandardSocketOptions.SO_RCVBUF); + // The Linux Kernel actually doubles the value it is given and may choose to ignore it. + // This assertion may be brittle. + assertTrue(actualValue != initialValue); + assertEquals(actualValue, ssc.socket().getReceiveBufferSize()); + } + + public void test_getOption_SO_REUSEADDR_defaults() throws Exception { + ServerSocketChannel ssc = ServerSocketChannel.open(); + + boolean value = ssc.getOption(StandardSocketOptions.SO_REUSEADDR); + assertTrue(value); + assertTrue(ssc.socket().getReuseAddress()); + + ssc.close(); + } + + public void test_setOption_SO_REUSEADDR_afterOpen() throws Exception { + ServerSocketChannel ssc = ServerSocketChannel.open(); + + boolean initialValue = ssc.getOption(StandardSocketOptions.SO_REUSEADDR); + ssc.setOption(StandardSocketOptions.SO_REUSEADDR, !initialValue); + assertEquals(!initialValue, (boolean) ssc.getOption(StandardSocketOptions.SO_REUSEADDR)); + assertEquals(!initialValue, ssc.socket().getReuseAddress()); + + ssc.close(); + } + } diff --git a/luni/src/test/java/libcore/java/nio/channels/SocketChannelTest.java b/luni/src/test/java/libcore/java/nio/channels/SocketChannelTest.java index 553e20c..118a168 100644 --- a/luni/src/test/java/libcore/java/nio/channels/SocketChannelTest.java +++ b/luni/src/test/java/libcore/java/nio/channels/SocketChannelTest.java @@ -24,11 +24,15 @@ import java.net.Socket; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.ServerSocket; +import java.net.SocketOption; +import java.net.StandardSocketOptions; import java.nio.ByteBuffer; import java.nio.channels.ClosedChannelException; import java.nio.channels.SocketChannel; import java.nio.channels.Selector; import java.nio.channels.SelectionKey; +import java.util.Set; + import tests.io.MockOs; import static libcore.io.OsConstants.*; @@ -256,4 +260,307 @@ public class SocketChannelTest extends junit.framework.TestCase { ss.close(); sc.close(); } + + public void test_supportedOptions() throws Exception { + SocketChannel sc = SocketChannel.open(); + Set<SocketOption<?>> options = sc.supportedOptions(); + + // Probe some values. This is not intended to be complete. + assertTrue(options.contains(StandardSocketOptions.SO_REUSEADDR)); + assertFalse(options.contains(StandardSocketOptions.IP_MULTICAST_TTL)); + } + + public void test_getOption_unsupportedOption() throws Exception { + SocketChannel sc = SocketChannel.open(); + try { + sc.getOption(StandardSocketOptions.IP_MULTICAST_TTL); + fail(); + } catch (UnsupportedOperationException expected) { + } + + sc.close(); + } + + public void test_getOption_afterClose() throws Exception { + SocketChannel sc = SocketChannel.open(); + sc.close(); + + try { + sc.getOption(StandardSocketOptions.SO_RCVBUF); + fail(); + } catch (ClosedChannelException expected) { + } + } + + public void test_setOption_afterClose() throws Exception { + SocketChannel sc = SocketChannel.open(); + sc.close(); + + try { + sc.setOption(StandardSocketOptions.SO_RCVBUF, 1234); + fail(); + } catch (ClosedChannelException expected) { + } + } + + public void test_getOption_SO_RCVBUF_defaults() throws Exception { + SocketChannel sc = SocketChannel.open(); + + int value = sc.getOption(StandardSocketOptions.SO_RCVBUF); + assertTrue(value > 0); + assertEquals(value, sc.socket().getReceiveBufferSize()); + + sc.close(); + } + + public void test_setOption_SO_RCVBUF_afterOpen() throws Exception { + SocketChannel sc = SocketChannel.open(); + + trySetReceiveBufferSizeOption(sc); + + sc.close(); + } + + private static void trySetReceiveBufferSizeOption(SocketChannel sc) throws IOException { + int initialValue = sc.getOption(StandardSocketOptions.SO_RCVBUF); + try { + sc.setOption(StandardSocketOptions.SO_RCVBUF, -1); + fail(); + } catch (IllegalArgumentException expected) { + } + int actualValue = sc.getOption(StandardSocketOptions.SO_RCVBUF); + assertEquals(initialValue, actualValue); + assertEquals(initialValue, sc.socket().getReceiveBufferSize()); + + int newBufferSize = initialValue - 1; + sc.setOption(StandardSocketOptions.SO_RCVBUF, newBufferSize); + actualValue = sc.getOption(StandardSocketOptions.SO_RCVBUF); + // The Linux Kernel actually doubles the value it is given and may choose to ignore it. + // This assertion may be brittle. + assertTrue(actualValue != initialValue); + assertEquals(actualValue, sc.socket().getReceiveBufferSize()); + } + + public void test_getOption_SO_SNDBUF_defaults() throws Exception { + SocketChannel sc = SocketChannel.open(); + + int bufferSize = sc.getOption(StandardSocketOptions.SO_SNDBUF); + assertTrue(bufferSize > 0); + assertEquals(bufferSize, sc.socket().getSendBufferSize()); + + sc.close(); + } + + public void test_setOption_SO_SNDBUF_afterOpen() throws Exception { + SocketChannel sc = SocketChannel.open(); + + trySetSendBufferSizeOption(sc); + + sc.close(); + } + + private static void trySetSendBufferSizeOption(SocketChannel sc) throws IOException { + int initialValue = sc.getOption(StandardSocketOptions.SO_SNDBUF); + try { + sc.setOption(StandardSocketOptions.SO_SNDBUF, -1); + fail(); + } catch (IllegalArgumentException expected) { + } + int actualValue = sc.getOption(StandardSocketOptions.SO_SNDBUF); + assertEquals(initialValue, actualValue); + assertEquals(initialValue, sc.socket().getSendBufferSize()); + + int newValue = initialValue - 1; + sc.setOption(StandardSocketOptions.SO_SNDBUF, newValue); + actualValue = sc.getOption(StandardSocketOptions.SO_SNDBUF); + // The Linux Kernel actually doubles the value it is given and may choose to ignore it. + // This assertion may be brittle. + assertTrue(actualValue != initialValue); + assertEquals(actualValue, sc.socket().getSendBufferSize()); + } + + public void test_getOption_SO_KEEPALIVE_defaults() throws Exception { + SocketChannel sc = SocketChannel.open(); + + assertFalse(sc.getOption(StandardSocketOptions.SO_KEEPALIVE)); + + sc.close(); + } + + public void test_setOption_SO_KEEPALIVE_afterOpen() throws Exception { + SocketChannel sc = SocketChannel.open(); + + trySetSoKeepaliveOption(sc); + + sc.close(); + } + + private static void trySetSoKeepaliveOption(SocketChannel sc) throws IOException { + boolean initialValue = sc.getOption(StandardSocketOptions.SO_KEEPALIVE); + + sc.setOption(StandardSocketOptions.SO_KEEPALIVE, !initialValue); + boolean actualValue = sc.getOption(StandardSocketOptions.SO_KEEPALIVE); + assertEquals(!initialValue, actualValue); + } + + public void test_setOption_SO_KEEPALIVE_afterBind() throws Exception { + SocketChannel sc = SocketChannel.open(); + sc.bind(null); + + trySetSoKeepaliveOption(sc); + + sc.close(); + } + + public void test_getOption_IP_TOS_defaults() throws Exception { + SocketChannel sc = SocketChannel.open(); + + int value = sc.getOption(StandardSocketOptions.IP_TOS); + assertEquals(0, value); + assertEquals(value, sc.socket().getTrafficClass()); + + sc.close(); + } + + public void test_setOption_IP_TOS_afterOpen() throws Exception { + SocketChannel sc = SocketChannel.open(); + + trySetTosOption(sc); + + sc.close(); + } + + private static void trySetTosOption(SocketChannel sc) throws IOException { + int initialValue = sc.getOption(StandardSocketOptions.IP_TOS); + try { + sc.setOption(StandardSocketOptions.IP_TOS, -1); + fail(); + } catch (IllegalArgumentException expected) { + } + assertEquals(initialValue, (int) sc.getOption(StandardSocketOptions.IP_TOS)); + assertEquals(initialValue, sc.socket().getTrafficClass()); + + try { + sc.setOption(StandardSocketOptions.IP_TOS, 256); + fail(); + } catch (IllegalArgumentException expected) { + } + assertEquals(initialValue, (int) sc.getOption(StandardSocketOptions.IP_TOS)); + assertEquals(initialValue, sc.socket().getTrafficClass()); + + int newValue = (initialValue + 1) % 255; + sc.setOption(StandardSocketOptions.IP_TOS, newValue); + assertEquals(newValue, (int) sc.getOption(StandardSocketOptions.IP_TOS)); + assertEquals(newValue, sc.socket().getTrafficClass()); + } + + public void test_setOption_IP_TOS_afterBind() throws Exception { + SocketChannel sc = SocketChannel.open(); + sc.bind(null); + + trySetTosOption(sc); + + sc.close(); + } + + public void test_getOption_SO_LINGER_defaults() throws Exception { + SocketChannel sc = SocketChannel.open(); + + int value = sc.getOption(StandardSocketOptions.SO_LINGER); + assertTrue(value < 0); + assertEquals(value, sc.socket().getSoLinger()); + + sc.close(); + } + + public void test_setOption_SO_LINGER_afterOpen() throws Exception { + SocketChannel sc = SocketChannel.open(); + + trySetLingerOption(sc); + + sc.close(); + } + + private static void trySetLingerOption(SocketChannel sc) throws IOException { + int initialValue = sc.getOption(StandardSocketOptions.SO_LINGER); + // Any negative value disables the setting, -1 is used to report SO_LINGER being disabled. + sc.setOption(StandardSocketOptions.SO_LINGER, -2); + int soLingerDisabled = -1; + assertEquals(soLingerDisabled, (int) sc.getOption(StandardSocketOptions.SO_LINGER)); + assertEquals(soLingerDisabled, sc.socket().getSoLinger()); + + sc.setOption(StandardSocketOptions.SO_LINGER, 65536); + assertEquals(65535, (int) sc.getOption(StandardSocketOptions.SO_LINGER)); + assertEquals(65535, sc.socket().getSoLinger()); + + int newValue = (initialValue + 1) % 65535; + sc.setOption(StandardSocketOptions.SO_LINGER, newValue); + assertEquals(newValue, (int) sc.getOption(StandardSocketOptions.SO_LINGER)); + assertEquals(newValue, sc.socket().getSoLinger()); + } + + public void test_setOption_SO_LINGER_afterBind() throws Exception { + SocketChannel sc = SocketChannel.open(); + sc.bind(null); + + trySetLingerOption(sc); + + sc.close(); + } + + public void test_getOption_SO_REUSEADDR_defaults() throws Exception { + SocketChannel sc = SocketChannel.open(); + + boolean value = sc.getOption(StandardSocketOptions.SO_REUSEADDR); + assertFalse(value); + assertFalse(sc.socket().getReuseAddress()); + + sc.close(); + } + + public void test_setOption_SO_REUSEADDR_afterOpen() throws Exception { + SocketChannel sc = SocketChannel.open(); + + boolean initialValue = sc.getOption(StandardSocketOptions.SO_REUSEADDR); + sc.setOption(StandardSocketOptions.SO_REUSEADDR, !initialValue); + assertEquals(!initialValue, (boolean) sc.getOption(StandardSocketOptions.SO_REUSEADDR)); + assertEquals(!initialValue, sc.socket().getReuseAddress()); + + sc.close(); + } + + public void test_getOption_TCP_NODELAY_defaults() throws Exception { + SocketChannel sc = SocketChannel.open(); + + boolean value = sc.getOption(StandardSocketOptions.TCP_NODELAY); + assertFalse(value); + assertFalse(sc.socket().getTcpNoDelay()); + + sc.close(); + } + + public void test_setOption_TCP_NODELAY_afterOpen() throws Exception { + SocketChannel sc = SocketChannel.open(); + + trySetNoDelay(sc); + + sc.close(); + } + + private static void trySetNoDelay(SocketChannel sc) throws IOException { + boolean initialValue = sc.getOption(StandardSocketOptions.TCP_NODELAY); + sc.setOption(StandardSocketOptions.TCP_NODELAY, !initialValue); + assertEquals(!initialValue, (boolean) sc.getOption(StandardSocketOptions.TCP_NODELAY)); + assertEquals(!initialValue, sc.socket().getTcpNoDelay()); + } + + public void test_setOption_TCP_NODELAY_afterBind() throws Exception { + SocketChannel sc = SocketChannel.open(); + sc.bind(null); + + trySetNoDelay(sc); + + sc.close(); + } + } |