1 /*
   2  * Copyright (c) 2009, 2011, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 package sun.nio.ch.sctp;
  26 
  27 import java.net.SocketAddress;
  28 import java.net.InetSocketAddress;
  29 import java.net.InetAddress;
  30 import java.io.FileDescriptor;
  31 import java.io.IOException;
  32 import java.util.Collections;
  33 import java.util.Set;
  34 import java.util.HashSet;
  35 import java.nio.channels.SelectionKey;
  36 import java.nio.channels.ClosedChannelException;
  37 import java.nio.channels.NotYetBoundException;
  38 import java.nio.channels.spi.SelectorProvider;
  39 import com.sun.nio.sctp.IllegalUnbindException;
  40 import com.sun.nio.sctp.SctpChannel;
  41 import com.sun.nio.sctp.SctpServerChannel;
  42 import com.sun.nio.sctp.SctpSocketOption;
  43 import com.sun.nio.sctp.SctpStandardSocketOptions;
  44 import sun.nio.ch.DirectBuffer;
  45 import sun.nio.ch.NativeThread;
  46 import sun.nio.ch.IOStatus;
  47 import sun.nio.ch.IOUtil;
  48 import sun.nio.ch.Net;
  49 import sun.nio.ch.PollArrayWrapper;
  50 import sun.nio.ch.SelChImpl;
  51 import sun.nio.ch.SelectionKeyImpl;
  52 import sun.nio.ch.Util;
  53 
  54 /**
  55  * An implementation of SctpServerChannel
  56  */
  57 public class SctpServerChannelImpl extends SctpServerChannel
  58     implements SelChImpl
  59 {
  60     private final FileDescriptor fd;
  61 
  62     private final int fdVal;
  63 
  64     /* IDs of native thread doing accept, for signalling */
  65     private volatile long thread = 0;
  66 
  67     /* Lock held by thread currently blocked in this channel */
  68     private final Object lock = new Object();
  69 
  70     /* Lock held by any thread that modifies the state fields declared below
  71      * DO NOT invoke a blocking I/O operation while holding this lock! */
  72     private final Object stateLock = new Object();
  73 
  74     private enum ChannelState {
  75         UNINITIALIZED,
  76         INUSE,
  77         KILLPENDING,
  78         KILLED,
  79     }
  80     /* -- The following fields are protected by stateLock -- */
  81     private ChannelState state = ChannelState.UNINITIALIZED;
  82 
  83     /* Binding: Once bound the port will remain constant. */
  84     int port = -1;
  85     private HashSet<InetSocketAddress> localAddresses = new HashSet<InetSocketAddress>();
  86     /* Has the channel been bound to the wildcard address */
  87     private boolean wildcard; /* false */
  88 
  89     /* -- End of fields protected by stateLock -- */
  90 
  91     /**
  92      * Initializes a new instance of this class.
  93      */
  94     public SctpServerChannelImpl(SelectorProvider provider)
  95             throws IOException {
  96         //TODO: update provider remove public modifier
  97         super(provider);
  98         this.fd = SctpNet.socket(true);
  99         this.fdVal = IOUtil.fdVal(fd);
 100         this.state = ChannelState.INUSE;
 101     }
 102 
 103     @Override
 104     public SctpServerChannel bind(SocketAddress local, int backlog)
 105             throws IOException {
 106         synchronized (lock) {
 107             synchronized (stateLock) {
 108                 if (!isOpen())
 109                     throw new ClosedChannelException();
 110                 if (isBound())
 111                     SctpNet.throwAlreadyBoundException();
 112 
 113                 InetSocketAddress isa = (local == null) ?
 114                     new InetSocketAddress(0) : Net.checkAddress(local);
 115                 SecurityManager sm = System.getSecurityManager();
 116                 if (sm != null)
 117                     sm.checkListen(isa.getPort());
 118                 Net.bind(fd, isa.getAddress(), isa.getPort());
 119 
 120                 InetSocketAddress boundIsa = Net.localAddress(fd);
 121                 port = boundIsa.getPort();
 122                 localAddresses.add(isa);
 123                     if (isa.getAddress().isAnyLocalAddress())
 124                         wildcard = true;
 125 
 126                 SctpNet.listen(fdVal, backlog < 1 ? 50 : backlog);
 127             }
 128         }
 129         return this;
 130     }
 131 
 132     @Override
 133     public SctpServerChannel bindAddress(InetAddress address)
 134             throws IOException {
 135         return bindUnbindAddress(address, true);
 136     }
 137 
 138     @Override
 139     public SctpServerChannel unbindAddress(InetAddress address)
 140             throws IOException {
 141         return bindUnbindAddress(address, false);
 142     }
 143 
 144     private SctpServerChannel bindUnbindAddress(InetAddress address, boolean add)
 145             throws IOException {
 146         if (address == null)
 147             throw new IllegalArgumentException();
 148 
 149         synchronized (lock) {
 150             synchronized (stateLock) {
 151                 if (!isOpen())
 152                     throw new ClosedChannelException();
 153                 if (!isBound())
 154                     throw new NotYetBoundException();
 155                 if (wildcard)
 156                     throw new IllegalStateException(
 157                             "Cannot add or remove addresses from a channel that is bound to the wildcard address");
 158                 if (address.isAnyLocalAddress())
 159                     throw new IllegalArgumentException(
 160                             "Cannot add or remove the wildcard address");
 161                 if (add) {
 162                     for (InetSocketAddress addr : localAddresses) {
 163                         if (addr.getAddress().equals(address)) {
 164                             SctpNet.throwAlreadyBoundException();
 165                         }
 166                     }
 167                 } else { /*removing */
 168                     /* Verify that there is more than one address
 169                      * and that address is already bound */
 170                     if (localAddresses.size() <= 1)
 171                         throw new IllegalUnbindException("Cannot remove address from a channel with only one address bound");
 172                     boolean foundAddress = false;
 173                     for (InetSocketAddress addr : localAddresses) {
 174                         if (addr.getAddress().equals(address)) {
 175                             foundAddress = true;
 176                             break;
 177                         }
 178                     }
 179                     if (!foundAddress )
 180                         throw new IllegalUnbindException("Cannot remove address from a channel that is not bound to that address");
 181                 }
 182 
 183                 SctpNet.bindx(fdVal, new InetAddress[]{address}, port, add);
 184 
 185                 /* Update our internal Set to reflect the addition/removal */
 186                 if (add)
 187                     localAddresses.add(new InetSocketAddress(address, port));
 188                 else {
 189                     for (InetSocketAddress addr : localAddresses) {
 190                         if (addr.getAddress().equals(address)) {
 191                             localAddresses.remove(addr);
 192                             break;
 193                         }
 194                     }
 195                 }
 196             }
 197         }
 198         return this;
 199     }
 200 
 201     private boolean isBound() {
 202         synchronized (stateLock) {
 203             return port == -1 ? false : true;
 204         }
 205     }
 206 
 207     private void acceptCleanup() throws IOException {
 208         synchronized (stateLock) {
 209             thread = 0;
 210             if (state == ChannelState.KILLPENDING)
 211                 kill();
 212         }
 213     }
 214 
 215     @Override
 216     public SctpChannel accept() throws IOException {
 217         synchronized (lock) {
 218             if (!isOpen())
 219                 throw new ClosedChannelException();
 220             if (!isBound())
 221                 throw new NotYetBoundException();
 222             SctpChannel sc = null;
 223 
 224             int n = 0;
 225             FileDescriptor newfd = new FileDescriptor();
 226             InetSocketAddress[] isaa = new InetSocketAddress[1];
 227 
 228             try {
 229                 begin();
 230                 if (!isOpen())
 231                     return null;
 232                 thread = NativeThread.current();
 233                 for (;;) {
 234                     n = accept0(fd, newfd, isaa);
 235                     if ((n == IOStatus.INTERRUPTED) && isOpen())
 236                         continue;
 237                     break;
 238                 }
 239             } finally {
 240                 acceptCleanup();
 241                 end(n > 0);
 242                 assert IOStatus.check(n);
 243             }
 244 
 245             if (n < 1)
 246                 return null;
 247 
 248             IOUtil.configureBlocking(newfd, true);
 249             InetSocketAddress isa = isaa[0];
 250             sc = new SctpChannelImpl(provider(), newfd);
 251 
 252             SecurityManager sm = System.getSecurityManager();
 253             if (sm != null)
 254                 sm.checkAccept(isa.getAddress().getHostAddress(),
 255                                isa.getPort());
 256 
 257             return sc;
 258         }
 259     }
 260 
 261     @Override
 262     protected void implConfigureBlocking(boolean block) throws IOException {
 263         IOUtil.configureBlocking(fd, block);
 264     }
 265 
 266     @Override
 267     public void implCloseSelectableChannel() throws IOException {
 268         synchronized (stateLock) {
 269             SctpNet.preClose(fdVal);
 270             if (thread != 0)
 271                 NativeThread.signal(thread);
 272             if (!isRegistered())
 273                 kill();
 274         }
 275     }
 276 
 277     @Override
 278     public void kill() throws IOException {
 279         synchronized (stateLock) {
 280             if (state == ChannelState.KILLED)
 281                 return;
 282             if (state == ChannelState.UNINITIALIZED) {
 283                 state = ChannelState.KILLED;
 284                 return;
 285             }
 286             assert !isOpen() && !isRegistered();
 287 
 288             // Postpone the kill if there is a thread in accept
 289             if (thread == 0) {
 290                 SctpNet.close(fdVal);
 291                 state = ChannelState.KILLED;
 292             } else {
 293                 state = ChannelState.KILLPENDING;
 294             }
 295         }
 296     }
 297 
 298     @Override
 299     public FileDescriptor getFD() {
 300         return fd;
 301     }
 302 
 303     @Override
 304     public int getFDVal() {
 305         return fdVal;
 306     }
 307 
 308     /**
 309      * Translates native poll revent ops into a ready operation ops
 310      */
 311     private boolean translateReadyOps(int ops, int initialOps,
 312                                      SelectionKeyImpl sk) {
 313         int intOps = sk.nioInterestOps();
 314         int oldOps = sk.nioReadyOps();
 315         int newOps = initialOps;
 316 
 317         if ((ops & PollArrayWrapper.POLLNVAL) != 0) {
 318             /* This should only happen if this channel is pre-closed while a
 319              * selection operation is in progress
 320              * ## Throw an error if this channel has not been pre-closed */
 321             return false;
 322         }
 323 
 324         if ((ops & (PollArrayWrapper.POLLERR
 325                     | PollArrayWrapper.POLLHUP)) != 0) {
 326             newOps = intOps;
 327             sk.nioReadyOps(newOps);
 328             return (newOps & ~oldOps) != 0;
 329         }
 330 
 331         if (((ops & PollArrayWrapper.POLLIN) != 0) &&
 332             ((intOps & SelectionKey.OP_ACCEPT) != 0))
 333                 newOps |= SelectionKey.OP_ACCEPT;
 334 
 335         sk.nioReadyOps(newOps);
 336         return (newOps & ~oldOps) != 0;
 337     }
 338 
 339     @Override
 340     public boolean translateAndUpdateReadyOps(int ops, SelectionKeyImpl sk) {
 341         return translateReadyOps(ops, sk.nioReadyOps(), sk);
 342     }
 343 
 344     @Override
 345     public boolean translateAndSetReadyOps(int ops, SelectionKeyImpl sk) {
 346         return translateReadyOps(ops, 0, sk);
 347     }
 348 
 349     @Override
 350     public void translateAndSetInterestOps(int ops, SelectionKeyImpl sk) {
 351         int newOps = 0;
 352 
 353         /* Translate ops */
 354         if ((ops & SelectionKey.OP_ACCEPT) != 0)
 355             newOps |= PollArrayWrapper.POLLIN;
 356         /* Place ops into pollfd array */
 357         sk.selector.putEventOps(sk, newOps);
 358 
 359     }
 360 
 361     @Override
 362     public <T> SctpServerChannel setOption(SctpSocketOption<T> name, T value)
 363             throws IOException {
 364         if (name == null)
 365             throw new NullPointerException();
 366         if (!supportedOptions().contains(name))
 367             throw new UnsupportedOperationException("'" + name + "' not supported");
 368 
 369         synchronized (stateLock) {
 370             if (!isOpen())
 371                 throw new ClosedChannelException();
 372 
 373             SctpNet.setSocketOption(fdVal, name, value, 0 /*oneToOne*/);
 374             return this;
 375         }
 376     }
 377 
 378     @Override
 379     @SuppressWarnings("unchecked")
 380     public <T> T getOption(SctpSocketOption<T> name) throws IOException {
 381         if (name == null)
 382             throw new NullPointerException();
 383         if (!supportedOptions().contains(name))
 384             throw new UnsupportedOperationException("'" + name + "' not supported");
 385 
 386         synchronized (stateLock) {
 387             if (!isOpen())
 388                 throw new ClosedChannelException();
 389 
 390             return (T) SctpNet.getSocketOption(fdVal, name, 0 /*oneToOne*/);
 391         }
 392     }
 393 
 394     private static class DefaultOptionsHolder {
 395         static final Set<SctpSocketOption<?>> defaultOptions = defaultOptions();
 396 
 397         private static Set<SctpSocketOption<?>> defaultOptions() {
 398             HashSet<SctpSocketOption<?>> set = new HashSet<SctpSocketOption<?>>(1);
 399             set.add(SctpStandardSocketOptions.SCTP_INIT_MAXSTREAMS);
 400             return Collections.unmodifiableSet(set);
 401         }
 402     }
 403 
 404     @Override
 405     public final Set<SctpSocketOption<?>> supportedOptions() {
 406         return DefaultOptionsHolder.defaultOptions;
 407     }
 408 
 409     @Override
 410     public Set<SocketAddress> getAllLocalAddresses()
 411             throws IOException {
 412         synchronized (stateLock) {
 413             if (!isOpen())
 414                 throw new ClosedChannelException();
 415             if (!isBound())
 416                 return Collections.emptySet();
 417 
 418             return SctpNet.getLocalAddresses(fdVal);
 419         }
 420     }
 421 
 422     /* Native */
 423     private static native void initIDs();
 424 
 425     private static native int accept0(FileDescriptor ssfd,
 426         FileDescriptor newfd, InetSocketAddress[] isaa) throws IOException;
 427 
 428     static {
 429         Util.load();   // loads nio & net native libraries
 430         java.security.AccessController.doPrivileged(
 431             new java.security.PrivilegedAction<Void>() {
 432                 public Void run() {
 433                     System.loadLibrary("sctp");
 434                     return null;
 435                 }
 436             });
 437         initIDs();
 438     }
 439 }