content: /*
   * Copyright (c) 2010-2019 Nathan Rajlich
   *
   *  Permission is hereby granted, free of charge, to any person
   *  obtaining a copy of this software and associated documentation
   *  files (the "Software"), to deal in the Software without
   *  restriction, including without limitation the rights to use,
   *  copy, modify, merge, publish, distribute, sublicense, and/or sell
   *  copies of the Software, and to permit persons to whom the
   *  Software is furnished to do so, subject to the following
   *  conditions:
   *
   *  The above copyright notice and this permission notice shall be
   *  included in all copies or substantial portions of the Software.
   *
   *  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
   *  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
   *  OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
   *  NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
   *  HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
   *  WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
   *  FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
   *  OTHER DEALINGS IN THE SOFTWARE.
   */
  
  package com.youku.ribut.core.socket.java_websocket;
  
  import com.youku.ribut.core.socket.java_websocket.drafts.Draft;
  import com.youku.ribut.core.socket.java_websocket.drafts.Draft_6455;
  import com.youku.ribut.core.socket.java_websocket.enums.CloseHandshakeType;
  import com.youku.ribut.core.socket.java_websocket.enums.HandshakeState;
  import com.youku.ribut.core.socket.java_websocket.enums.Opcode;
  import com.youku.ribut.core.socket.java_websocket.enums.ReadyState;
  import com.youku.ribut.core.socket.java_websocket.enums.Role;
  import com.youku.ribut.core.socket.java_websocket.exceptions.IncompleteHandshakeException;
  import com.youku.ribut.core.socket.java_websocket.exceptions.InvalidDataException;
  import com.youku.ribut.core.socket.java_websocket.exceptions.InvalidHandshakeException;
  import com.youku.ribut.core.socket.java_websocket.exceptions.LimitExceededException;
  import com.youku.ribut.core.socket.java_websocket.exceptions.WebsocketNotConnectedException;
  import com.youku.ribut.core.socket.java_websocket.framing.CloseFrame;
  import com.youku.ribut.core.socket.java_websocket.framing.Framedata;
  import com.youku.ribut.core.socket.java_websocket.framing.PingFrame;
  import com.youku.ribut.core.socket.java_websocket.handshake.ClientHandshake;
  import com.youku.ribut.core.socket.java_websocket.handshake.ClientHandshakeBuilder;
  import com.youku.ribut.core.socket.java_websocket.handshake.Handshakedata;
  import com.youku.ribut.core.socket.java_websocket.handshake.ServerHandshake;
  import com.youku.ribut.core.socket.java_websocket.handshake.ServerHandshakeBuilder;
  import com.youku.ribut.core.socket.java_websocket.server.WebSocketServer.WebSocketWorker;
  import com.youku.ribut.core.socket.java_websocket.util.Charsetfunctions;
  
  import java.io.IOException;
  import java.net.InetSocketAddress;
  import java.nio.ByteBuffer;
  import java.nio.channels.ByteChannel;
  import java.nio.channels.SelectionKey;
  import java.util.ArrayList;
  import java.util.Collection;
  import java.util.Collections;
  import java.util.List;
  import java.util.concurrent.BlockingQueue;
  import java.util.concurrent.LinkedBlockingQueue;
  
  /**
   * Represents one end (client or server) of a single WebSocketImpl connection.
   * Takes care of the "handshake" phase, then allows for easy sending of
   * text frames, and receiving frames through an event-based model.
   */
  public class WebSocketImpl implements WebSocket {
  
      /**
       * The default port of WebSockets, as defined in the spec. If the nullary
       * constructor is used, DEFAULT_PORT will be the port the WebSocketServer
       * is binded to. Note that ports under 1024 usually require root permissions.
       */
      public static final int DEFAULT_PORT = 80;
  
      /**
       * The default wss port of WebSockets, as defined in the spec. If the nullary
       * constructor is used, DEFAULT_WSS_PORT will be the port the WebSocketServer
       * is binded to. Note that ports under 1024 usually require root permissions.
       */
      public static final int DEFAULT_WSS_PORT = 443;
  
      /**
       * Initial buffer size
       */
      public static final int RCVBUF = 16384;
  
      /**
       * Queue of buffers that need to be sent to the client.
       */
      public final BlockingQueue<ByteBuffer> outQueue;
      /**
       * Queue of buffers that need to be processed
       */
      public final BlockingQueue<ByteBuffer> inQueue;
      /**
       * The listener to notify of WebSocket events.
       */
      private final WebSocketListener wsl;
  
      private SelectionKey key;
  
      /**
       * the possibly wrapped channel object whose selection is controlled by {@link #key}
       */
      private ByteChannel channel;
      /**
       * Helper variable meant to store the thread which ( exclusively ) triggers this objects decode method.
       **/
      private WebSocketWorker workerThread;
      /**
       * When true no further frames may be submitted to be sent
       */
      private boolean flushandclosestate = false;
  
      /**
       * The current state of the connection
       */
      private volatile ReadyState readyState = ReadyState.NOT_YET_CONNECTED;
  
      /**
       * A list of drafts available for this websocket
       */
      private List<Draft> knownDrafts;
  
      /**
       * The draft which is used by this websocket
       */
      private Draft draft = null;
  
      /**
       * The role which this websocket takes in the connection
       */
      private Role role;
  
      /**
       * the bytes of an incomplete received handshake
       */
      private ByteBuffer tmpHandshakeBytes = ByteBuffer.allocate(0);
  
      /**
       * stores the handshake sent by this websocket ( Role.CLIENT only )
       */
      private ClientHandshake handshakerequest = null;
  
      private String closemessage = null;
      private Integer closecode = null;
      private Boolean closedremotely = null;
  
      private String resourceDescriptor = null;
  
      /**
       * Attribute, when the last pong was recieved
       */
      private long lastPong = System.currentTimeMillis();
  
      /**
       * Attribut to synchronize the write
       */
      private final Object synchronizeWriteObject = new Object();
  
      /**
       * Attribute to cache a ping frame
       */
      private PingFrame pingFrame;
  
      /**
       * Attribute to store connection attachment
       *
       * @since 1.3.7
       */
      private Object attachment;
  
      /**
       * Creates a websocket with server role
       *
       * @param listener The listener for this instance
       * @param drafts   The drafts which should be used
       */
      public WebSocketImpl(WebSocketListener listener, List<Draft> drafts) {
          this(listener, (Draft) null);
          this.role = Role.SERVER;
          // draft.copyInstance will be called when the draft is first needed
          if (drafts == null || drafts.isEmpty()) {
              knownDrafts = new ArrayList<Draft>();
              knownDrafts.add(new Draft_6455());
          } else {
              knownDrafts = drafts;
          }
      }
  
      /**
       * creates a websocket with client role
       *
       * @param listener The listener for this instance
       * @param draft    The draft which should be used
       */
      public WebSocketImpl(WebSocketListener listener, Draft draft) {
          if (listener == null || (draft == null && role == Role.SERVER))// socket can be null because we want do be able to create the object without already having a bound channel
              throw new IllegalArgumentException("parameters must not be null");
          this.outQueue = new LinkedBlockingQueue<ByteBuffer>();
          inQueue = new LinkedBlockingQueue<ByteBuffer>();
          this.wsl = listener;
          this.role = Role.CLIENT;
          if (draft != null)
              this.draft = draft.copyInstance();
      }
  
      /**
       * Method to decode the provided ByteBuffer
       *
       * @param socketBuffer the ByteBuffer to decode
       */
      public void decode(ByteBuffer socketBuffer) {
          assert (socketBuffer.hasRemaining());
          if (readyState != ReadyState.NOT_YET_CONNECTED) {
              if (readyState == ReadyState.OPEN) {
                  decodeFrames(socketBuffer);
              }
          } else {
              if (decodeHandshake(socketBuffer) && (!isClosing() && !isClosed())) {
                  assert (tmpHandshakeBytes.hasRemaining() != socketBuffer.hasRemaining() || !socketBuffer.hasRemaining()); // the buffers will never have remaining bytes at the same time
                  if (socketBuffer.hasRemaining()) {
                      decodeFrames(socketBuffer);
                  } else if (tmpHandshakeBytes.hasRemaining()) {
                      decodeFrames(tmpHandshakeBytes);
                  }
              }
          }
      }
  
      /**
       * Returns whether the handshake phase has is completed.
       * In case of a broken handshake this will be never the case.
       **/
      private boolean decodeHandshake(ByteBuffer socketBufferNew) {
          ByteBuffer socketBuffer;
          if (tmpHandshakeBytes.capacity() == 0) {
              socketBuffer = socketBufferNew;
          } else {
              if (tmpHandshakeBytes.remaining() < socketBufferNew.remaining()) {
                  ByteBuffer buf = ByteBuffer.allocate(tmpHandshakeBytes.capacity() + socketBufferNew.remaining());
                  tmpHandshakeBytes.flip();
                  buf.put(tmpHandshakeBytes);
                  tmpHandshakeBytes = buf;
              }
  
              tmpHandshakeBytes.put(socketBufferNew);
              tmpHandshakeBytes.flip();
              socketBuffer = tmpHandshakeBytes;
          }
          socketBuffer.mark();
          try {
              HandshakeState handshakestate;
              try {
                  if (role == Role.SERVER) {
                      if (draft == null) {
                          for (Draft d : knownDrafts) {
                              d = d.copyInstance();
                              try {
                                  d.setParseMode(role);
                                  socketBuffer.reset();
                                  Handshakedata tmphandshake = d.translateHandshake(socketBuffer);
                                  if (!(tmphandshake instanceof ClientHandshake)) {
                                      closeConnectionDueToWrongHandshake(new InvalidDataException(CloseFrame.PROTOCOL_ERROR, "wrong http function"));
                                      return false;
                                  }
                                  ClientHandshake handshake = (ClientHandshake) tmphandshake;
                                  handshakestate = d.acceptHandshakeAsServer(handshake);
                                  if (handshakestate == HandshakeState.MATCHED) {
                                      resourceDescriptor = handshake.getResourceDescriptor();
                                      ServerHandshakeBuilder response;
                                      try {
                                          response = wsl.onWebsocketHandshakeReceivedAsServer(this, d, handshake);
                                      } catch (InvalidDataException e) {
                                          closeConnectionDueToWrongHandshake(e);
                                          return false;
                                      } catch (RuntimeException e) {
                                          wsl.onWebsocketError(this, e);
                                          closeConnectionDueToInternalServerError(e);
                                          return false;
                                      }
                                      write(d.createHandshake(d.postProcessHandshakeResponseAsServer(handshake, response)));
                                      draft = d;
                                      open(handshake);
                                      return true;
                                  }
                              } catch (InvalidHandshakeException e) {
                                  // go on with an other draft
                              }
                          }
                          if (draft == null) {
                              closeConnectionDueToWrongHandshake(new InvalidDataException(CloseFrame.PROTOCOL_ERROR, "no draft matches"));
                          }
                          return false;
                      } else {
                          // special case for multiple step handshakes
                          Handshakedata tmphandshake = draft.translateHandshake(socketBuffer);
                          if (!(tmphandshake instanceof ClientHandshake)) {
                              flushAndClose(CloseFrame.PROTOCOL_ERROR, "wrong http function", false);
                              return false;
                          }
                          ClientHandshake handshake = (ClientHandshake) tmphandshake;
                          handshakestate = draft.acceptHandshakeAsServer(handshake);
  
                          if (handshakestate == HandshakeState.MATCHED) {
                              open(handshake);
                              return true;
                          } else {
                              close(CloseFrame.PROTOCOL_ERROR, "the handshake did finally not match");
                          }
                          return false;
                      }
                  } else if (role == Role.CLIENT) {
                      draft.setParseMode(role);
                      Handshakedata tmphandshake = draft.translateHandshake(socketBuffer);
                      if (!(tmphandshake instanceof ServerHandshake)) {
                          flushAndClose(CloseFrame.PROTOCOL_ERROR, "wrong http function", false);
                          return false;
                      }
                      ServerHandshake handshake = (ServerHandshake) tmphandshake;
                      handshakestate = draft.acceptHandshakeAsClient(handshakerequest, handshake);
                      if (handshakestate == HandshakeState.MATCHED) {
                          try {
                              wsl.onWebsocketHandshakeReceivedAsClient(this, handshakerequest, handshake);
                          } catch (InvalidDataException e) {
                              flushAndClose(e.getCloseCode(), e.getMessage(), false);
                              return false;
                          } catch (RuntimeException e) {
                              wsl.onWebsocketError(this, e);
                              flushAndClose(CloseFrame.NEVER_CONNECTED, e.getMessage(), false);
                              return false;
                          }
                          open(handshake);
                          return true;
                      } else {
                          close(CloseFrame.PROTOCOL_ERROR, "draft " + draft + " refuses handshake");
                      }
                  }
              } catch (InvalidHandshakeException e) {
                  close(e);
              }
          } catch (IncompleteHandshakeException e) {
              if (tmpHandshakeBytes.capacity() == 0) {
                  socketBuffer.reset();
                  int newsize = e.getPreferredSize();
                  if (newsize == 0) {
                      newsize = socketBuffer.capacity() + 16;
                  } else {
                      assert (e.getPreferredSize() >= socketBuffer.remaining());
                  }
                  tmpHandshakeBytes = ByteBuffer.allocate(newsize);
  
                  tmpHandshakeBytes.put(socketBufferNew);
                  // tmpHandshakeBytes.flip();
              } else {
                  tmpHandshakeBytes.position(tmpHandshakeBytes.limit());
                  tmpHandshakeBytes.limit(tmpHandshakeBytes.capacity());
              }
          }
          return false;
      }
  
      private void decodeFrames(ByteBuffer socketBuffer) {
          List<Framedata> frames;
          try {
              frames = draft.translateFrame(socketBuffer);
              for (Framedata f : frames) {
                  draft.processFrame(this, f);
              }
          } catch (LimitExceededException e) {
              if (e.getLimit() == Integer.MAX_VALUE) {
                  wsl.onWebsocketError(this, e);
              }
              close(e);
          } catch (InvalidDataException e) {
              wsl.onWebsocketError(this, e);
              close(e);
          }
      }
  
      /**
       * Close the connection if the received handshake was not correct
       *
       * @param exception the InvalidDataException causing this problem
       */
      private void closeConnectionDueToWrongHandshake(InvalidDataException exception) {
          write(generateHttpResponseDueToError(404));
          flushAndClose(exception.getCloseCode(), exception.getMessage(), false);
      }
  
      /**
       * Close the connection if there was a server error by a RuntimeException
       *
       * @param exception the RuntimeException causing this problem
       */
      private void closeConnectionDueToInternalServerError(RuntimeException exception) {
          write(generateHttpResponseDueToError(500));
          flushAndClose(CloseFrame.NEVER_CONNECTED, exception.getMessage(), false);
      }
  
      /**
       * Generate a simple response for the corresponding endpoint to indicate some error
       *
       * @param errorCode the http error code
       * @return the complete response as ByteBuffer
       */
      private ByteBuffer generateHttpResponseDueToError(int errorCode) {
          String errorCodeDescription;
          switch (errorCode) {
              case 404:
                  errorCodeDescription = "404 WebSocket Upgrade Failure";
                  break;
              case 500:
              default:
                  errorCodeDescription = "500 Internal Server Error";
          }
          return ByteBuffer.wrap(Charsetfunctions.asciiBytes("HTTP/1.1 " + errorCodeDescription + "\r\nContent-Type: text/html\nServer: TooTallNate Java-WebSocket\r\nContent-Length: " + (48 + errorCodeDescription.length()) + "\r\n\r\n<html><head></head><body><h1>" + errorCodeDescription + "</h1></body></html>"));
      }
  
      public synchronized void close(int code, String message, boolean remote) {
          if (readyState != ReadyState.CLOSING && readyState != ReadyState.CLOSED) {
              if (readyState == ReadyState.OPEN) {
                  if (code == CloseFrame.ABNORMAL_CLOSE) {
                      assert (!remote);
                      readyState = ReadyState.CLOSING;
                      flushAndClose(code, message, false);
                      return;
                  }
                  if (draft.getCloseHandshakeType() != CloseHandshakeType.NONE) {
                      try {
                          if (!remote) {
                              try {
                                  wsl.onWebsocketCloseInitiated(this, code, message);
                              } catch (RuntimeException e) {
                                  wsl.onWebsocketError(this, e);
                              }
                          }
                          if (isOpen()) {
                              CloseFrame closeFrame = new CloseFrame();
                              closeFrame.setReason(message);
                              closeFrame.setCode(code);
                              closeFrame.isValid();
                              sendFrame(closeFrame);
                          }
                      } catch (InvalidDataException e) {
                          wsl.onWebsocketError(this, e);
                          flushAndClose(CloseFrame.ABNORMAL_CLOSE, "generated frame is invalid", false);
                      }
                  }
                  flushAndClose(code, message, remote);
              } else if (code == CloseFrame.FLASHPOLICY) {
                  assert (remote);
                  flushAndClose(CloseFrame.FLASHPOLICY, message, true);
              } else if (code == CloseFrame.PROTOCOL_ERROR) { // this endpoint found a PROTOCOL_ERROR
                  flushAndClose(code, message, remote);
              } else {
                  flushAndClose(CloseFrame.NEVER_CONNECTED, message, false);
              }
              readyState = ReadyState.CLOSING;
              tmpHandshakeBytes = null;
              return;
          }
      }
  
      @Override
      public void close(int code, String message) {
          close(code, message, false);
      }
  
      /**
       * This will close the connection immediately without a proper close handshake.
       * The code and the message therefore won't be transfered over the wire also they will be forwarded to onClose/onWebsocketClose.
       *
       * @param code    the closing code
       * @param message the closing message
       * @param remote  Indicates who "generated" <code>code</code>.<br>
       *                <code>true</code> means that this endpoint received the <code>code</code> from the other endpoint.<br>
       *                false means this endpoint decided to send the given code,<br>
       *                <code>remote</code> may also be true if this endpoint started the closing handshake since the other endpoint may not simply echo the <code>code</code> but close the connection the same time this endpoint does do but with an other <code>code</code>. <br>
       **/
      public synchronized void closeConnection(int code, String message, boolean remote) {
          if (readyState == ReadyState.CLOSED) {
              return;
          }
          //Methods like eot() call this method without calling onClose(). Due to that reason we have to adjust the ReadyState manually
          if (readyState == ReadyState.OPEN) {
              if (code == CloseFrame.ABNORMAL_CLOSE) {
                  readyState = ReadyState.CLOSING;
              }
          }
          if (key != null) {
              // key.attach( null ); //see issue #114
              key.cancel();
          }
          if (channel != null) {
              try {
                  channel.close();
              } catch (IOException e) {
                  if (!e.getMessage().equals("Broken pipe")) {
                      wsl.onWebsocketError(this, e);
                  }
              }
          }
          try {
              this.wsl.onWebsocketClose(this, code, message, remote);
          } catch (RuntimeException e) {
  
              wsl.onWebsocketError(this, e);
          }
          if (draft != null)
              draft.reset();
          handshakerequest = null;
          readyState = ReadyState.CLOSED;
      }
  
      protected void closeConnection(int code, boolean remote) {
          closeConnection(code, "", remote);
      }
  
      public void closeConnection() {
          if (closedremotely == null) {
              throw new IllegalStateException("this method must be used in conjunction with flushAndClose");
          }
          closeConnection(closecode, closemessage, closedremotely);
      }
  
      public void closeConnection(int code, String message) {
          closeConnection(code, message, false);
      }
  
      public synchronized void flushAndClose(int code, String message, boolean remote) {
          if (flushandclosestate) {
              return;
          }
          closecode = code;
          closemessage = message;
          closedremotely = remote;
  
          flushandclosestate = true;
  
          wsl.onWriteDemand(this); // ensures that all outgoing frames are flushed before closing the connection
          try {
              wsl.onWebsocketClosing(this, code, message, remote);
          } catch (RuntimeException e) {
              wsl.onWebsocketError(this, e);
          }
          if (draft != null)
              draft.reset();
          handshakerequest = null;
      }
  
      public void eot() {
          if (readyState == ReadyState.NOT_YET_CONNECTED) {
              closeConnection(CloseFrame.NEVER_CONNECTED, true);
          } else if (flushandclosestate) {
              closeConnection(closecode, closemessage, closedremotely);
          } else if (draft.getCloseHandshakeType() == CloseHandshakeType.NONE) {
              closeConnection(CloseFrame.NORMAL, true);
          } else if (draft.getCloseHandshakeType() == CloseHandshakeType.ONEWAY) {
              if (role == Role.SERVER)
                  closeConnection(CloseFrame.ABNORMAL_CLOSE, true);
              else
                  closeConnection(CloseFrame.NORMAL, true);
          } else {
              closeConnection(CloseFrame.ABNORMAL_CLOSE, true);
          }
      }
  
      @Override
      public void close(int code) {
          close(code, "", false);
      }
  
      public void close(InvalidDataException e) {
          close(e.getCloseCode(), e.getMessage(), false);
      }
  
      /**
       * Send Text data to the other end.
       *
       * @throws WebsocketNotConnectedException websocket is not yet connected
       */
      @Override
      public void send(String text) {
          if (text == null)
              throw new IllegalArgumentException("Cannot send 'null' data to a WebSocketImpl.");
          send(draft.createFrames(text, role == Role.CLIENT));
      }
  
      /**
       * Send Binary data (plain bytes) to the other end.
       *
       * @throws IllegalArgumentException       the data is null
       * @throws WebsocketNotConnectedException websocket is not yet connected
       */
      @Override
      public void send(ByteBuffer bytes) {
          if (bytes == null)
              throw new IllegalArgumentException("Cannot send 'null' data to a WebSocketImpl.");
          send(draft.createFrames(bytes, role == Role.CLIENT));
      }
  
      @Override
      public void send(byte[] bytes) {
          send(ByteBuffer.wrap(bytes));
      }
  
      private void send(Collection<Framedata> frames) {
          if (!isOpen()) {
              throw new WebsocketNotConnectedException();
          }
          if (frames == null) {
              throw new IllegalArgumentException();
          }
          ArrayList<ByteBuffer> outgoingFrames = new ArrayList<ByteBuffer>();
          for (Framedata f : frames) {
              outgoingFrames.add(draft.createBinaryFrame(f));
          }
          write(outgoingFrames);
      }
  
      @Override
      public void sendFragmentedFrame(Opcode op, ByteBuffer buffer, boolean fin) {
          send(draft.continuousFrame(op, buffer, fin));
      }
  
      @Override
      public void sendFrame(Collection<Framedata> frames) {
          send(frames);
      }
  
      @Override
      public void sendFrame(Framedata framedata) {
          send(Collections.singletonList(framedata));
      }
  
      public void sendPing() {
          if (pingFrame == null) {
              pingFrame = new PingFrame();
          }
          sendFrame(pingFrame);
      }
  
      @Override
      public boolean hasBufferedData() {
          return !this.outQueue.isEmpty();
      }
  
      public void startHandshake(ClientHandshakeBuilder handshakedata) throws InvalidHandshakeException {
          // Store the Handshake Request we are about to send
          this.handshakerequest = draft.postProcessHandshakeRequestAsClient(handshakedata);
  
          resourceDescriptor = handshakedata.getResourceDescriptor();
          assert (resourceDescriptor != null);
  
          // Notify Listener
          try {
              wsl.onWebsocketHandshakeSentAsClient(this, this.handshakerequest);
          } catch (InvalidDataException e) {
              // Stop if the client code throws an exception
              throw new InvalidHandshakeException("Handshake data rejected by client.");
          } catch (RuntimeException e) {
              wsl.onWebsocketError(this, e);
              throw new InvalidHandshakeException("rejected because of " + e);
          }
  
          // Send
          write(draft.createHandshake(this.handshakerequest));
      }
  
      private void write(ByteBuffer buf) {
          outQueue.add(buf);
          wsl.onWriteDemand(this);
      }
  
      /**
       * Write a list of bytebuffer (frames in binary form) into the outgoing queue
       *
       * @param bufs the list of bytebuffer
       */
      private void write(List<ByteBuffer> bufs) {
          synchronized (synchronizeWriteObject) {
              for (ByteBuffer b : bufs) {
                  write(b);
              }
          }
      }
  
      private void open(Handshakedata d) {
          readyState = ReadyState.OPEN;
          try {
              wsl.onWebsocketOpen(this, d);
          } catch (RuntimeException e) {
              wsl.onWebsocketError(this, e);
          }
      }
  
      @Override
      public boolean isOpen() {
          return readyState == ReadyState.OPEN;
      }
  
      @Override
      public boolean isClosing() {
          return readyState == ReadyState.CLOSING;
      }
  
      @Override
      public boolean isFlushAndClose() {
          return flushandclosestate;
      }
  
      @Override
      public boolean isClosed() {
          return readyState == ReadyState.CLOSED;
      }
  
      @Override
      public ReadyState getReadyState() {
          return readyState;
      }
  
      /**
       * @param key the selection key of this implementation
       */
      public void setSelectionKey(SelectionKey key) {
          this.key = key;
      }
  
      /**
       * @return the selection key of this implementation
       */
      public SelectionKey getSelectionKey() {
          return key;
      }
  
      @Override
      public String toString() {
          return super.toString(); // its nice to be able to set breakpoints here
      }
  
      @Override
      public InetSocketAddress getRemoteSocketAddress() {
          return wsl.getRemoteSocketAddress(this);
      }
  
      @Override
      public InetSocketAddress getLocalSocketAddress() {
          return wsl.getLocalSocketAddress(this);
      }
  
      @Override
      public Draft getDraft() {
          return draft;
      }
  
      @Override
      public void close() {
          close(CloseFrame.NORMAL);
      }
  
      @Override
      public String getResourceDescriptor() {
          return resourceDescriptor;
      }
  
      /**
       * Getter for the last pong recieved
       *
       * @return the timestamp for the last recieved pong
       */
      long getLastPong() {
          return lastPong;
      }
  
      /**
       * Update the timestamp when the last pong was received
       */
      public void updateLastPong() {
          this.lastPong = System.currentTimeMillis();
      }
  
      /**
       * Getter for the websocket listener
       *
       * @return the websocket listener associated with this instance
       */
      public WebSocketListener getWebSocketListener() {
          return wsl;
      }
  
      @Override
      @SuppressWarnings("unchecked")
      public <T> T getAttachment() {
          return (T) attachment;
      }
  
      @Override
      public <T> void setAttachment(T attachment) {
          this.attachment = attachment;
      }
  
      public ByteChannel getChannel() {
          return channel;
      }
  
      public void setChannel(ByteChannel channel) {
          this.channel = channel;
      }
  
      public WebSocketWorker getWorkerThread() {
          return workerThread;
      }
  
      public void setWorkerThread(WebSocketWorker workerThread) {
          this.workerThread = workerThread;
      }
  
  
  }
  