Package com.sun.jini.jeri.internal.mux

Source Code of com.sun.jini.jeri.internal.mux.Mux$SessionShutdownTask

/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements.  See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 com.sun.jini.jeri.internal.mux;

import com.sun.jini.jeri.internal.runtime.HexDumpEncoder;
import com.sun.jini.thread.Executor;
import com.sun.jini.thread.GetThreadPoolAction;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.channels.SocketChannel;
import java.nio.charset.CharacterCodingException;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.CharsetEncoder;
import java.security.AccessController;
import java.util.BitSet;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
* Mux is the abstract superclass of both client-side and server-side
* multiplexed connections.
*
* @author Sun Microsystems, Inc.
**/
abstract class Mux {

    static final int CLIENT = 0;
    static final int SERVER = 1;

    static final int MAX_SESSION_ID = 0x7F;
    public static final int MAX_REQUESTS = MAX_SESSION_ID + 1;

    static final int NoOperation  = 0x00// 00000000
    static final int Shutdown    = 0x02; // 00000010
    static final int Ping    = 0x04; // 00000100
    static final int PingAck    = 0x06; // 00000110
    static final int Error    = 0x08; // 00001000
    static final int IncrementRation  = 0x10; // 0001***0
    static final int Abort    = 0x20; // 001000*0
    static final int Close    = 0x30; // 00110000
    static final int Acknowledgment  = 0x40; // 00100000
    static final int Data    = 0x80; // 100****0

    static final int IncrementRation_shift  = 0x0E;
    static final int Abort_partial    = 0x02;
    static final int Data_open      = 0x10;
    static final int Data_close      = 0x08;
    static final int Data_eof      = 0x04;
    static final int Data_ackRequired    = 0x02;

    static final int ClientConnectionHeader_negotiate  = 0x01;

    private static final byte[] magic = {
  (byte) 'J', (byte) 'm', (byte) 'u', (byte) 'x'  // 0x4A6D7578
    };

    private static final int VERSION = 0x01;

    /**
     * pool of threads for executing tasks in system thread group:
     * used for shutting down sessions when a connnection goes down
     */
    private static final Executor systemThreadPool =
  (Executor) AccessController.doPrivileged(
      new GetThreadPoolAction(false));

    /** session shutdown tasks to be executed asynchronously */
    private static final LinkedList sessionShutdownQueue = new LinkedList();

    private static class SessionShutdownTask implements Runnable {
  private final Session[] sessions;
  private final String message;
  private final Throwable cause;

  SessionShutdownTask(Session[] sessions,
          String message,
          Throwable cause)
  {
      this.sessions = sessions;
      this.message = message;
      this.cause = cause;
  }

  public void run() {
      for (int i = 0; i < sessions.length; i++) {
    sessions[i].setDown(message, cause);
      }
  }
    }

    /** mux logger */
    private static final Logger logger =
  Logger.getLogger("net.jini.jeri.connection.mux");

    final int role;
    final int initialInboundRation;
    final int maxFragmentSize;

    private final ConnectionIO connectionIO;
    private final boolean directBuffersUseful;

    /** lock guarding all mutable instance state (below) */
    final Object muxLock = new Object();

    int initialOutboundRation;    // set from remote connection header
    private boolean clientConnectionReady = false; // server header received
    boolean serverConnectionReady = false;     // server header sent

    boolean muxDown = false;
    String muxDownMessage;
    Throwable muxDownCause;

    final BitSet busySessions = new BitSet();
    final Map sessions = new HashMap(5);

    private int expectedPingCookie = -1;

    /**
     * Constructs a new Mux instance for a connection accessible through
     * standard (blocking) I/O streams.
     */
    Mux(OutputStream out, InputStream in,
  int role, int initialInboundRation, int maxFragmentSize)
  throws IOException
    {
  this.role = role;
  if ((initialInboundRation & ~0x00FFFF00) != 0) {
      throw new IllegalArgumentException(
    "illegal initial inbound ration: " +
    toHexString(initialInboundRation));
  }
  this.initialInboundRation = initialInboundRation;
  this.maxFragmentSize = maxFragmentSize;

  this.connectionIO = new StreamConnectionIO(this, out, in);
  directBuffersUseful = false;
    }

    Mux(SocketChannel channel,
  int role, int initialInboundRation, int maxFragmentSize)
  throws IOException
    {
  this.role = role;
  if ((initialInboundRation & ~0x00FFFF00) != 0) {
      throw new IllegalArgumentException(
    "illegal initial inbound ration: " +
    toHexString(initialInboundRation));
  }
  this.initialInboundRation = initialInboundRation;
  this.maxFragmentSize = maxFragmentSize;

  this.connectionIO = new SocketChannelConnectionIO(this, channel);
  directBuffersUseful = true;
    }

    /**
     * Starts I/O processing.
     *
     * This method should be invoked only after this instance has
     * been completely initialized, so that subclasses will not
     * see uninitialized state.
     */
    public void start() throws IOException {
  if (role == CLIENT) {
      readState = READ_SERVER_CONNECTION_HEADER;
  } else {
      assert role == SERVER;
      readState = READ_CLIENT_CONNECTION_HEADER;
  }

  try {
      connectionIO.start();
  } catch (IOException e) {
      setDown("I/O error starting connection", e);
      throw e;
  }

  if (role == CLIENT) {
      asyncSendClientConnectionHeader();
      synchronized (muxLock) {
    while (!muxDown && !clientConnectionReady) {
        try {
      muxLock.wait();    // REMIND: timeout?
        } catch (InterruptedException e) {
      setDown("interrupt waiting for connection header", e);
        }
    }
    if (muxDown) {
        IOException ioe = new IOException(muxDownMessage);
        ioe.initCause(muxDownCause);
        throw ioe;
    }
      }
  }
    }

    /**
     * Handles indication that this multiplexed connection has
     * gone down, either through normal operation or failure.
     *
     * This method should be overridden by subclasses that want to
     * implement custom behavior when this connection has gone down.
     */
    protected void handleDown() {
    }

    /**
     * This method is invoked internally and is intended to be
     * overridden by subclasses.
     */
    void handleOpen(int sessionID) throws ProtocolException {
  throw new ProtocolException(
      "remote endpoint attempted to open session");
    }

    /**
     *
     * This method is intended to be invoked by subclasses only.
     *
     * This method must ONLY be invoked while synchronized on muxLock
     * and while muxDown is false.
     */
    final void addSession(int sessionID, Session session) {
  assert Thread.holdsLock(muxLock);
  assert !muxDown;
  assert !busySessions.get(sessionID);
  assert sessions.get(new Integer(sessionID)) == null;

  busySessions.set(sessionID);
  sessions.put(new Integer(sessionID), session);
    }

    /**
     *
     * This method is intended to be invoked by this class and
     * subclasses only.
     *
     * This method MAY be invoked while synchronized on muxLock.
     */
    final void setDown(final String message, final Throwable cause) {
  synchronized (muxLock) {
      if (muxDown) {
    return;
      }
      muxDown = true;
      muxDownMessage = message;
      muxDownCause = cause;
      muxLock.notifyAll();
  }

  /*
   * The following should be safe because we just left the
   * synchonized block, and after setting the muxDown latch
   * therein, no other thread should ever touch the "sessions"
   * data structure.
   *
   * Sessions are shut down asynchronously in a separate thread
   * to avoid deadlock, in case our caller holds muxLock,
   * because individual session locks must never be acquired
   * while holding muxLock.
   */
  boolean needWorker = false;
  synchronized (sessionShutdownQueue) {
      if (!sessions.values().isEmpty()) {
    sessionShutdownQueue.add(new SessionShutdownTask(
        (Session[]) sessions.values().toArray(
      new Session[sessions.values().size()]),
        message, cause));
    needWorker = true;
      } else {
    needWorker = !sessionShutdownQueue.isEmpty();
      }
  }
  if (needWorker) {
      try {
    systemThreadPool.execute(new Runnable() {
        public void run() {
      while (true) {
          Runnable task;
          synchronized (sessionShutdownQueue) {
        if (sessionShutdownQueue.isEmpty()) {
            break;
        }
        task = (Runnable)
            sessionShutdownQueue.removeFirst();
          }
          task.run();
      }
        }
    }, "mux session shutdown");
      } catch (OutOfMemoryError e) {  // assume out of threads
    try {
        logger.log(Level.WARNING,
      "could not create thread for session shutdown", e);
    } catch (Throwable t) {
    }
    // absorb exception to proceed with connection shutdown;
    // session shutdown task will remain on queue for later
      }
  }

  handleDown();
    }

    /**
     * Removes the identified session from the session table.
     *
     * This method is intended to be invoked by the associated Session
     * object only.
     */
    final void removeSession(int sessionID) {
  synchronized (muxLock) {
      if (muxDown) {
    return;
      }
      assert busySessions.get(sessionID);
      busySessions.clear(sessionID);
      sessions.remove(new Integer(sessionID));
  }
    }

    /**
     * Returns true if it would be useful to pass direct buffers to
     * this instance's *Send* methods (because the underlying I/O
     * implementation will pass such buffers directly to channel write
     * operations); returns false otherwise.
     */
    final boolean directBuffersUseful() {
  return directBuffersUseful;
    }

    /**
     * Sends the ClientConnectionHeader message for this connection.
     */
    final void asyncSendClientConnectionHeader() {
  assert role == CLIENT;

  ByteBuffer header = ByteBuffer.allocate(8);
  header.put(magic)
        .put((byte) VERSION)
        .putShort((short) (initialInboundRation >> 8))
        .put((byte) 0)
        .flip();
  connectionIO.asyncSend(header);
    }

    /**
     * Sends the ServerConnectionHeader message for this connection.
     */
    final void asyncSendServerConnectionHeader() {
  assert role == SERVER;

  ByteBuffer header = ByteBuffer.allocate(8);
  header.put(magic)
        .put((byte) VERSION)
        .putShort((short) (initialInboundRation >> 8))
        .put((byte) 0)
        .flip();
  connectionIO.asyncSend(header);
    }

    /**
     * Sends a NoOperation message with the contents of the supplied buffer
     * as the data.
     *
     * The "length" of the NoOperation message will be the number of bytes
     * remaining in the buffer, and the data sent will be the contents
     * of the buffer between its current position and its limit.  Or if
     * the buffer argument is null, "length" will simply be zero...
     * REMIND: split into two methods instead?
     *
     * The actual writing to the underlying connection, including access to
     * the buffer's content and other state, is asynchronous with the
     * invocation of this method; therefore, the supplied buffer must not
     * be mutated even after this method has returned.
     */
    final void asyncSendNoOperation(ByteBuffer buffer) {
  ByteBuffer header = ByteBuffer.allocate(4);
  header.put((byte) NoOperation)
        .put((byte) 0);

  if (buffer != null) {
      assert buffer.remaining() <= 0xFFFF;
      header.putShort((short) buffer.remaining())
      .flip();
      connectionIO.asyncSend(header, buffer);
  } else {
      header.putShort((short) 0)
      .flip();
      connectionIO.asyncSend(header);
  }
    }

    /**
     * Sends a Shutdown message with the UTF-8 encoding of the supplied
     * message as the data.  If message is null, then zero bytes of data
     * will be sent with the message header.
     */
    final void asyncSendShutdown(String message) {
  ByteBuffer data = (message != null ?
         getUTF8BufferFromString(message) : null);

  ByteBuffer header = ByteBuffer.allocate(4);
  header.put((byte) Shutdown)
        .put((byte) 0);

  if (data != null) {
      assert data.remaining() <= 0xFFFF;
      header.putShort((short) data.remaining())
      .flip();
      connectionIO.asyncSend(header, data);
  } else {
      header.putShort((short) 0)
      .flip();
      connectionIO.asyncSend(header);
  }
    }

    /**
     * Sends a Ping message with the specified "cookie".
     */
    final void asyncSendPing(int cookie) {
  assert cookie >= 0 && cookie <= 0xFFFF;

  ByteBuffer header = ByteBuffer.allocate(4);
  header.put((byte) Ping)
              .put((byte) 0)
        .putShort((short) cookie)
        .flip();
  connectionIO.asyncSend(header);
    }

    /**
     * Sends a PingAck message with the specified "cookie".
     */
    final void asyncSendPingAck(int cookie) {
  assert cookie >= 0 && cookie <= 0xFFFF;

  ByteBuffer header = ByteBuffer.allocate(4);
  header.put((byte) PingAck)
        .put((byte) 0)
        .putShort((short) cookie)
        .flip();
  connectionIO.asyncSend(header);
    }

    /**
     * Sends an Error message with the UTF-8 encoding of the supplied
     * message as the data.  If message is null, then zero bytes of data
     * will be sent with the message header.
     */
    final void asyncSendError(String message) {
  ByteBuffer data = (message != null ?
         getUTF8BufferFromString(message) : null);

  ByteBuffer header = ByteBuffer.allocate(4);
  header.put((byte) Error)
        .put((byte) 0);

  if (data != null) {
      assert data.remaining() <= 0xFFFF;
      header.putShort((short) data.remaining())
      .flip();
      connectionIO.asyncSend(header, data);
  } else {
      header.putShort((short) 0)
      .flip();
      connectionIO.asyncSend(header);
  }
    }

    /**
     * Sends an Error message with the UTF-8 encoding of the supplied
     * message as the data.  If message is null, then zero bytes of data
     * will be sent with the message header.
     */
    final IOFuture futureSendError(String message) {
  ByteBuffer data = getUTF8BufferFromString(message);

  ByteBuffer header = ByteBuffer.allocate(4);
  header.put((byte) Error)
        .put((byte) 0);

  assert data.remaining() <= 0xFFFF;
  header.putShort((short) data.remaining())
        .flip();
  return connectionIO.futureSend(header, data);
    }

    /**
     * Sends an IncrementRation message for the specified "sessionID" and
     * the specified "increment".
     */
    final void asyncSendIncrementRation(/*****int op, *****/int sessionID,
          int increment)
    {
  final int op = IncrementRation;
//  assert (op & 0xF1) == IncrementRation;  // validate operation code
//  assert (op & 0xE0) == 0;    // NYI: support use of shift
  assert sessionID >= 0 && sessionID <= MAX_SESSION_ID;
  assert increment >= 0 && increment <= 0xFFFF;

  ByteBuffer header = ByteBuffer.allocate(4);
  header.put((byte) op)
        .put((byte) sessionID)
        .putShort((short) increment)
        .flip();
  connectionIO.asyncSend(header);
    }

    /**
     * Sends an Abort message for the specified "sessionID" with the contents
     * of the specified buffer as the data.
     *
     * The "length" of the Abort message will be the number of bytes
     * remaining in the buffer, and the data sent will be the contents
     * of the buffer between its current position and its limit.  Or if
     * the buffer argument is null, "length" will simply be zero...
     * REMIND: split into two methods instead?
     *
     * For efficiency, the caller is responsible for pre-computing the first
     * byte of the message, including any control flags if appropriate.
     */
    final void asyncSendAbort(int op, int sessionID, ByteBuffer data) {
  assert (op & 0xFD) == Abort;    // validate operation code
  assert sessionID >= 0 && sessionID <= MAX_SESSION_ID;

  ByteBuffer header = ByteBuffer.allocate(4);
  header.put((byte) op)
        .put((byte) sessionID);

  if (data != null) {
      assert data.remaining() <= 0xFFFF;
      header.putShort((short) data.remaining())
      .flip();
      connectionIO.asyncSend(header, data);
  } else {
      header.putShort((short) 0)
      .flip();
      connectionIO.asyncSend(header);
  }
    }

    /**
     * Sends a Close message for the specified "sessionID".
     */
    final void asyncSendClose(int sessionID) {
  assert sessionID >= 0 && sessionID <= MAX_SESSION_ID;

  ByteBuffer header = ByteBuffer.allocate(4);
  header.put((byte) Close)
        .put((byte) sessionID)
        .putShort((short) 0)
        .flip();
  connectionIO.asyncSend(header);
    }

    /**
     * Sends an Acknowledgment message for the specified "sessionID".
     */
    final void asyncSendAcknowledgment(int sessionID) {
  assert sessionID >= 0 && sessionID <= MAX_SESSION_ID;

  ByteBuffer header = ByteBuffer.allocate(4);
  header.put((byte) Acknowledgment)
        .put((byte) sessionID)
        .putShort((short) 0)
        .flip();
  connectionIO.asyncSend(header);
    }

    /**
     * Sends a Data message for the specified "sessionID" with the contents
     * of the supplied buffer as the data.
     *
     * The "length" of the Data message will be the number of bytes
     * remaining in the buffer, and the data sent will be the contents
     * of the buffer between its current position and its limit.  Or if
     * the buffer argument is null, "length" will simply be zero...
     * REMIND: split into two methods instead?
     *
     * For efficiency, the caller is responsible for pre-computing the first
     * byte of the Data message, including any control flags if appropriate.
     *
     * The actual writing to the underlying connection, including access to
     * the buffer's content and other state, is asynchronous with the
     * invocation of this method; therefore, the supplied buffer must not
     * be mutated even after this method has returned.
     */
    final void asyncSendData(int op, int sessionID, ByteBuffer data) {
  assert (op & 0xE1) == Data;  // validate operation code
  assert (op & Data_eof) != 0 ||  // close and ackRequired require eof
      (op & Data_close & Data_ackRequired) == 0;
  assert sessionID >= 0 && sessionID <= MAX_SESSION_ID;

  ByteBuffer header = ByteBuffer.allocate(4);
  header.put((byte) op)
        .put((byte) sessionID);

  if (data != null) {
      assert data.remaining() <= 0xFFFF;
      header.putShort((short) data.remaining())
      .flip();
      connectionIO.asyncSend(header, data);
  } else {
      header.putShort((short) 0)
      .flip();
      connectionIO.asyncSend(header);
  }
    }

    /**
     * Sends a Data message for the specified sessionID with the contents
     * of the supplied buffer as the data.
     *
     * The "length" of the Data message will be the number of bytes
     * remaining in the buffer, and the data sent will be the contents
     * of the buffer between its current position and its limit.
     *
     * For efficiency, the caller is responsible for pre-computing the first
     * byte of the Data message, including any control flags if appropriate.
     *
     * The actual writing to the underlying connection, including access to
     * the buffer's content and other state, is asynchronous with the
     * invocation of this method; therefore, the supplied buffer must not
     * be mutated even after this method has returned, until it is guaranteed
     * that use of the buffer has completed.
     *
     * The returned IOFuture object can be used to wait until the write has
     * definitely completed (or will definitely not complete due to some
     * failure).  After the write has completed, the buffer's position will
     * have been incremented to its limit (which will not have changed).
     */
    final IOFuture futureSendData(int op, int sessionID, ByteBuffer data) {
  assert (op & 0xE1) == Data;  // verify operation code
  assert (op & Data_eof) != 0 ||  // close and ackRequired require eof
      (op & Data_close & Data_ackRequired) == 0;
  assert sessionID >= 0 && sessionID <= MAX_SESSION_ID;
  assert data.remaining() <= 0xFFFF;

  ByteBuffer header = ByteBuffer.allocate(4);
  header.put((byte) op)
        .put((byte) sessionID)
        .putShort((short) data.remaining())
        .flip();
  return connectionIO.futureSend(header, data);
    }

    /*
     * read states
     */
    private static final int READ_CLIENT_CONNECTION_HEADER  = 0;
    private static final int READ_SERVER_CONNECTION_HEADER  = 1;
    private static final int READ_MESSAGE_HEADER    = 2;
    private static final int READ_MESSAGE_BODY      = 3;

    /*
     * current read state lock and variables
     */
    private final Object readStateLock = new Object();
    private int readState;
    private int currentOp;
    private int currentSessionID;
    private int currentLengthRemaining;
    private ByteBuffer currentDataBuffer = null;

    void processIncomingData(ByteBuffer buffer) throws ProtocolException {
  buffer.flip()// process data that has been read into buffer
  assert buffer.hasRemaining();

  synchronized (readStateLock) {
    stateLoop:
      do {
    switch (readState) {
      case READ_CLIENT_CONNECTION_HEADER:
        if (!readClientConnectionHeader(buffer)) {
      break stateLoop;
        }
        break;

      case READ_SERVER_CONNECTION_HEADER:
        if (!readServerConnectionHeader(buffer)) {
      break stateLoop;
        }
        break;

      case READ_MESSAGE_HEADER:
        if (!readMessageHeader(buffer)) {
      break stateLoop;
        }
        break;

      case READ_MESSAGE_BODY:
        if (!readMessageBody(buffer)) {
      break stateLoop;
        }
        break;

      default:
        throw new AssertionError();
    }
      } while (buffer.hasRemaining());
  }

  buffer.compact();
    }

    private boolean readClientConnectionHeader(ByteBuffer buffer)
  throws ProtocolException
    {
  assert role == SERVER;

  validatePartialMagicNumber(buffer);
  if (buffer.remaining() < 8) {
      return false;    // wait for complete header to arrive
  }
  int headerPosition = buffer.position();
  buffer.position(headerPosition + 4)// skip header already checked
  int version = (buffer.get() & 0xFF);
  int ration = (buffer.getShort() & 0xFFFF) << 8;
  int flags = (buffer.get() & 0xFF);
  boolean negotiate = (flags & ClientConnectionHeader_negotiate) != 0;

  synchronized (muxLock) {
      initialOutboundRation = ration;
      asyncSendServerConnectionHeader();

      if (version == 0) {
    throw new ProtocolException(
        "bad protocol version: " + version);
      }
      if (version > VERSION) {
    if (!negotiate) {
        setDown("unsupported protocol version: " + version, null);
        throw new ProtocolException(
      "unsupported protocol version: " + version);
    }
      }

      serverConnectionReady = true;
  }

  readState = READ_MESSAGE_HEADER;
  return true;
    }

    private boolean readServerConnectionHeader(ByteBuffer buffer)
  throws ProtocolException
    {
  assert role == CLIENT;

  validatePartialMagicNumber(buffer);

  if (buffer.remaining() < 8) {
      return false;
  }
  int headerPosition = buffer.position();
  buffer.position(headerPosition + 4)// skip header already checked
  int version = (buffer.get() & 0xFF);
  int ration = (buffer.getShort() & 0xFFFF) << 8;
  int flags = (buffer.get() & 0xFF);

  synchronized (muxLock) {
      initialOutboundRation = ration;

      if (version == 0) {
    throw new ProtocolException(
        "bad protocol version: " + version);
      }
      if (version > VERSION) {
    throw new ProtocolException(
        "unexpected protocol version: " + version);
      }

      clientConnectionReady = true;
      muxLock.notifyAll();
  }

  readState = READ_MESSAGE_HEADER;
  return true;
    }

    private void validatePartialMagicNumber(ByteBuffer buffer)
  throws ProtocolException
    {
  if (buffer.remaining() > 0) {
      byte[] temp = new byte[Math.min(buffer.remaining(), magic.length)];
      buffer.mark();
      buffer.get(temp);
      buffer.reset();
      for (int i = 0; i < temp.length; i++) {
    if (temp[i] != magic[i]) {
        setDown((role == CLIENT ? "server" : "client") +
      " sent bad magic number: " + toHexString(temp), null);
        throw new ProtocolException("bad magic number: " +
            toHexString(temp));
    }
      }
  }
    }

    private boolean readMessageHeader(ByteBuffer buffer)
  throws ProtocolException
    {
  if (buffer.remaining() < 4) {
      return false;    // wait for complete header to arrive
  }
  int headerPosition = buffer.position();
  if (logger.isLoggable(Level.FINEST)) {
      logger.log(Level.FINEST,
           "message header: " +
           toHexString(buffer.getInt(headerPosition)));
  }

  int op = (buffer.get() & 0xFF);
  if ((op & 0xE1) == Data) {
      int sessionID = (buffer.get() & 0xFF);
      if (sessionID > MAX_SESSION_ID) {
    throw new ProtocolException("bad message header: " +
        toHexString(buffer.getInt(headerPosition)));
      }
      currentOp = op;
      currentSessionID = sessionID;
      currentLengthRemaining = (buffer.getShort() & 0xFFFF);
      if (currentLengthRemaining > 0) {
    currentDataBuffer =
        ByteBuffer.allocate(currentLengthRemaining);
    readState = READ_MESSAGE_BODY;
      } else {
    dispatchCurrentMessage();
      }
      return true;

  } else if ((op & 0xF1) == IncrementRation) {
      int sessionID = (buffer.get() & 0xFF);
      if (sessionID > MAX_SESSION_ID) {
    throw new ProtocolException("bad message header: " +
        toHexString(buffer.getInt(headerPosition)));
      }
      int increment = (buffer.getShort() & 0xFFFF);
      int shift = op & IncrementRation_shift;
      increment <<= shift;
      handleIncrementRation(sessionID, increment);
      return true;

  } else if ((op & 0xFD) == Abort) {
      int sessionID = (buffer.get() & 0xFF);
      if (sessionID > MAX_SESSION_ID) {
    throw new ProtocolException("bad message header: " +
        toHexString(buffer.getInt(headerPosition)));
      }
      currentOp = op;
      currentSessionID = sessionID;
      currentLengthRemaining = (buffer.getShort() & 0xFFFF);
      if (currentLengthRemaining > 0) {
    currentDataBuffer =
        ByteBuffer.allocate(currentLengthRemaining);
    readState = READ_MESSAGE_BODY;
      } else {
    dispatchCurrentMessage();
      }
      return true;

  }
  switch (op) {
    case NoOperation: {
      if (buffer.get() != 0) {  // ignore sign extension
    throw new ProtocolException("bad message header: " +
        toHexString(buffer.getInt(headerPosition)));
      }
      currentOp = op;
      currentLengthRemaining = (buffer.getShort() & 0xFFFF);
      currentDataBuffer = null// ignore data for NoOperation
      if (currentLengthRemaining > 0) {
    readState = READ_MESSAGE_BODY;
      } else {
    dispatchCurrentMessage();
      }
      return true;
    }

    case Shutdown: {
      if (buffer.get() != 0) {  // ignore sign extension
    throw new ProtocolException("bad message header: " +
        toHexString(buffer.getInt(headerPosition)));
      }
      currentOp = op;
      currentLengthRemaining = (buffer.getShort() & 0xFFFF);
      if (currentLengthRemaining > 0) {
    currentDataBuffer =
        ByteBuffer.allocate(currentLengthRemaining);
    readState = READ_MESSAGE_BODY;
      } else {
    dispatchCurrentMessage();
      }
      return true;
    }

    case Ping: {
      if (buffer.get() != 0) {  // ignore sign extension
    throw new ProtocolException("bad message header: " +
        toHexString(buffer.getInt(headerPosition)));
      }
      int cookie = (buffer.getShort() & 0xFFFF);
      handlePing(cookie);
      return true;
    }

    case PingAck: {
      if (buffer.get() != 0) {  // ignore sign extension
    throw new ProtocolException("bad message header: " +
        toHexString(buffer.getInt(headerPosition)));
      }
      int cookie = (buffer.getShort() & 0xFFFF);
      handlePingAck(cookie);
      return true;
    }

    case Error: {
      if (buffer.get() != 0) {  // ignore sign extension
    throw new ProtocolException("bad message header: " +
        toHexString(buffer.getInt(headerPosition)));
      }
      currentOp = op;
      currentLengthRemaining = (buffer.getShort() & 0xFFFF);
      if (currentLengthRemaining > 0) {
    currentDataBuffer =
        ByteBuffer.allocate(currentLengthRemaining);
    readState = READ_MESSAGE_BODY;
      } else {
    dispatchCurrentMessage();
      }
      return true;
    }

    case Close: {
      int sessionID = (buffer.get() & 0xFF);
      if (sessionID > MAX_SESSION_ID ||
    buffer.getShort() != 0)    // ignore sign extension
      {
    throw new ProtocolException("bad message header: " +
        toHexString(buffer.getInt(headerPosition)));
      }
      handleClose(sessionID);
      return true;
    }

    case Acknowledgment: {
      int sessionID = (buffer.get() & 0xFF);
      if (sessionID > MAX_SESSION_ID ||
    buffer.getShort() != 0)    // ignore sign extension
      {
    throw new ProtocolException("bad message header: " +
        toHexString(buffer.getInt(headerPosition)));
      }
      handleAcknowledgment(sessionID);
      return true;
    }

    default:
      throw new ProtocolException("bad message header: " +
    toHexString(buffer.getInt(headerPosition)));
  }
    }

    private boolean readMessageBody(ByteBuffer buffer)
  throws ProtocolException
    {
  assert currentLengthRemaining > 0;
  assert currentDataBuffer == null ||
      currentDataBuffer.remaining() == currentLengthRemaining;

  if (buffer.remaining() > currentLengthRemaining) {
      int origLimit = buffer.limit();
      buffer.limit(buffer.position() + currentLengthRemaining);
      if (currentDataBuffer != null) {
    currentDataBuffer.put(buffer);
      } else {
    buffer.position(buffer.position() + currentLengthRemaining);
      }
      currentLengthRemaining = 0;
      buffer.limit(origLimit);
  } else {
      currentLengthRemaining -= buffer.remaining();
      if (currentDataBuffer != null) {
    currentDataBuffer.put(buffer);
      } else {
    buffer.position(buffer.limit());
      }
  }

  if (currentLengthRemaining > 0) {
      return false;
  } else {
      currentDataBuffer.flip();
      dispatchCurrentMessage();
      currentDataBuffer = null;    // don't let this linger
      readState = READ_MESSAGE_HEADER;
      return true;
  }
    }

    private void dispatchCurrentMessage() throws ProtocolException {
  assert currentDataBuffer == null || currentDataBuffer.hasRemaining();

  int op = currentOp;
  if ((op & 0xE1) == Data) {
      boolean open  = (op & Data_open) != 0;
      boolean close  = (op & Data_close) != 0;
      boolean eof    = (op & Data_eof) != 0;
      boolean ackRequired  = (op & Data_ackRequired) != 0;
      handleData(currentSessionID, open, close, eof, ackRequired,
           (currentDataBuffer != null ?
      currentDataBuffer : ByteBuffer.allocate(0)));
      return;

  } else if ((op & 0xFD) == Abort) {
      boolean partial = (op & Abort_partial) != 0;
      handleAbort(currentSessionID, partial,
      (currentDataBuffer != null ?
       getStringFromUTF8Buffer(currentDataBuffer) : ""));
      return;

  }
  switch (op) {
    case NoOperation:
      handleNoOperation();
      return;

    case Shutdown:
      handleShutdown(currentDataBuffer != null ?
         getStringFromUTF8Buffer(currentDataBuffer) : "");
      return;

    case Error:
      handleError(currentDataBuffer != null ?
      getStringFromUTF8Buffer(currentDataBuffer) : "");
      return;

    default:
      throw new AssertionError(Integer.toHexString((byte) op));
  }
    }

    private void handleNoOperation() throws ProtocolException {
  if (logger.isLoggable(Level.FINEST)) {
      logger.log(Level.FINEST, "NoOperation");
  }

  // do nothing
    }

    private void handleShutdown(String message) throws ProtocolException {
  if (logger.isLoggable(Level.FINEST)) {
      logger.log(Level.FINEST, "Shutdown");
  }

  if (role != CLIENT) {
      throw new ProtocolException("Shutdown sent by client");
  }
  setDown("mux connection shut down gracefully", null);
  throw new ProtocolException("received Shutdown message");
    }

    private void handlePing(int cookie) throws ProtocolException {
  if (logger.isLoggable(Level.FINEST)) {
      logger.log(Level.FINEST, "Ping: cookie=" + cookie);
  }

  asyncSendPingAck(cookie);
    }

    private void handlePingAck(int cookie) throws ProtocolException {
  if (logger.isLoggable(Level.FINEST)) {
      logger.log(Level.FINEST, "PingAck: cookie=" + cookie);
  }

  synchronized (muxLock) {
      if (cookie != expectedPingCookie) {
    throw new ProtocolException(
        "unexpected ping cookie: " + cookie);
      } else {
    expectedPingCookie = -1;
    // NYI: rest of ping machinery
      }
  }
    }

    private void handleError(String message) throws ProtocolException {
  if (logger.isLoggable(Level.FINEST)) {
      logger.log(Level.FINEST, "Error");
  }

  setDown((role == CLIENT ? "server" : "client") +
    " reported protocol error: " + message, null);
  throw new ProtocolException("received Error message");
    }

    private void handleIncrementRation(int sessionID, int increment)
  throws ProtocolException
    {
  if (logger.isLoggable(Level.FINEST)) {
      logger.log(Level.FINEST,
    "IncrementRation: sessionID=" + sessionID +
    ",increment=" + increment);
  }

  getSession(sessionID).handleIncrementRation(increment);
    }

    private void handleAbort(int sessionID, boolean partial, String message)
  throws ProtocolException
    {
  if (logger.isLoggable(Level.FINEST)) {
      logger.log(Level.FINEST,
    "Abort: sessionID=" + sessionID +
    ",partial=" + partial);
  }

  getSession(sessionID).handleAbort(partial);
    }

    private void handleClose(int sessionID) throws ProtocolException {
  if (logger.isLoggable(Level.FINEST)) {
      logger.log(Level.FINEST, "Close: sessionID=" + sessionID);
  }

  getSession(sessionID).handleClose();
    }

    private void handleAcknowledgment(int sessionID) throws ProtocolException {
  if (logger.isLoggable(Level.FINEST)) {
      logger.log(Level.FINEST, "Acknowledgment: sessionID=" + sessionID);
  }

  getSession(sessionID).handleAcknowledgment();
    }

    private void handleData(int sessionID, boolean open, boolean close,
          boolean eof, boolean ackRequired, ByteBuffer data)
  throws ProtocolException
    {
  if (logger.isLoggable(Level.FINEST)) {
      int length = data.remaining();
      HexDumpEncoder encoder = new HexDumpEncoder();
      byte[] bytes = new byte[data.remaining()];
      data.mark();
      data.get(bytes);
      data.reset();
      logger.log(Level.FINEST,
    "Data: sessionID=" + sessionID +
    (open ? ",open" : "") +
    (close ? ",close" : "") +
    (eof ? ",eof" : "") +
    (ackRequired ? ",ackRequired" : "") +
    ",length=" + length +
    (length > 0 ? ",data=\n" + encoder.encode(bytes) : ""));
  }

  if (!eof && (close || ackRequired)) {
      throw new ProtocolException("Data: eof=" + eof +
          ",close=" + close +
          ",ackRequired=" + ackRequired);
  }

  if (open) {
      handleOpen(sessionID);
  }

  getSession(sessionID).handleData(data, eof, close, ackRequired);
    }

    private Session getSession(int sessionID) throws ProtocolException {
  synchronized (muxLock) {
      if (!busySessions.get(sessionID)) {
    throw new ProtocolException(
        "inactive sessionID: " + sessionID);
      }
      return (Session) sessions.get(new Integer(sessionID));
  }
    }

    private static ByteBuffer getUTF8BufferFromString(String s) {
  CharsetEncoder encoder = Charset.forName("UTF-8").newEncoder();
  try {
      return encoder.encode(CharBuffer.wrap(s));
  } catch (CharacterCodingException e) {
      return null;
  }
    }

    private static String getStringFromUTF8Buffer(ByteBuffer buffer) {
  CharsetDecoder decoder = Charset.forName("UTF-8").newDecoder();
  try {
      return decoder.decode(buffer).toString();
  } catch (CharacterCodingException e) {
      return "(error decoding UTF-8 message: " + e.toString() + ")";
  }
    }

    private static String toHexString(byte x) {
  char[] buf = new char[2];
  buf[0] = toHexChar((x >> 4) & 0xF);
  buf[1] = toHexChar(x & 0xF);
  return new String(buf);
    }

    private static String toHexString(int x) {
  char[] buf = new char[8];
  for (int i = 0; i < 8; i++) {
      buf[i] = toHexChar((x >> ((7 - i) * 4)) & 0xF);
  }
  return new String(buf);
    }

    private static String toHexString(byte[] b) {
  char[] buf = new char[b.length * 2];
  int j = 0;
  for (int i = 0; i < b.length; i++) {
      buf[j++] = toHexChar((b[i] >> 4) & 0xF);
      buf[j++] = toHexChar(b[i] & 0xF);
  }
  return new String(buf);
    }

    private static char toHexChar(int x) {
  return x < 10 ? (char) ('0' + x) : (char) ('A' - 10 + x);
    }
}
TOP

Related Classes of com.sun.jini.jeri.internal.mux.Mux$SessionShutdownTask

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.