Exemplo n.º 1
0
  private synchronized int flushBuffer() throws IOException {
    if (!_endp.isOpen()) throw new EofException();

    if (_buffer != null && _buffer.hasContent()) return _endp.flush(_buffer);

    return 0;
  }
Exemplo n.º 2
0
 public void receive() {
   EndPoint endPoint = connection.getEndPoint();
   HttpClient client = connection.getHttpClient();
   ByteBufferPool bufferPool = client.getByteBufferPool();
   ByteBuffer buffer = bufferPool.acquire(client.getResponseBufferSize(), true);
   try {
     while (true) {
       // Connection may be closed in a parser callback
       if (connection.isClosed()) {
         LOG.debug("{} closed", connection);
         break;
       } else {
         int read = endPoint.fill(buffer);
         LOG.debug("Read {} bytes from {}", read, connection);
         if (read > 0) {
           parse(buffer);
         } else if (read == 0) {
           fillInterested();
           break;
         } else {
           shutdown();
           break;
         }
       }
     }
   } catch (EofException x) {
     LOG.ignore(x);
     failAndClose(x);
   } catch (Exception x) {
     LOG.debug(x);
     failAndClose(x);
   } finally {
     bufferPool.release(buffer);
   }
 }
 private void replaceConnection() {
   EndPoint endPoint = getEndPoint();
   Connection connection =
       client.getConnectionFactory().newConnection(channel, endPoint, attachment);
   endPoint.getConnection().onClose();
   endPoint.setConnection(connection);
   connection.onOpen();
 }
  private void upgradeConnection(ClientUpgradeResponse response) {
    EndPoint endp = getEndPoint();
    Executor executor = getExecutor();
    WebSocketClientConnection connection = new WebSocketClientConnection(endp, executor, client);

    // Initialize / Negotiate Extensions
    EventDriver websocket = client.getWebSocket();
    WebSocketPolicy policy = client.getPolicy();
    String acceptedSubProtocol = response.getAcceptedSubProtocol();

    WebSocketSession session = new WebSocketSession(request.getRequestURI(), websocket, connection);
    session.setPolicy(policy);
    session.setNegotiatedSubprotocol(acceptedSubProtocol);

    connection.setSession(session);
    List<Extension> extensions = client.getFactory().initExtensions(response.getExtensions());

    // Start with default routing.
    IncomingFrames incoming = session;
    // OutgoingFrames outgoing = connection;

    // Connect extensions
    if (extensions != null) {
      connection.getParser().configureFromExtensions(extensions);
      connection.getGenerator().configureFromExtensions(extensions);

      // FIXME
      // Iterator<Extension> extIter;
      // // Connect outgoings
      // extIter = extensions.iterator();
      // while (extIter.hasNext())
      // {
      // Extension ext = extIter.next();
      // ext.setNextOutgoingFrames(outgoing);
      // outgoing = ext;
      // }
      //
      // // Connect incomings
      // Collections.reverse(extensions);
      // extIter = extensions.iterator();
      // while (extIter.hasNext())
      // {
      // Extension ext = extIter.next();
      // ext.setNextIncomingFrames(incoming);
      // incoming = ext;
      // }
    }

    // configure session for outgoing flows
    // session.setOutgoing(outgoing);
    // configure connection for incoming flows
    connection.getParser().setIncomingFramesHandler(incoming);

    // Now swap out the connection
    endp.setConnection(connection);
    connection.onOpen();
  }
Exemplo n.º 5
0
 private int fill(EndPoint endPoint, ByteBuffer buffer) {
   try {
     if (endPoint.isInputShutdown()) return -1;
     return endPoint.fill(buffer);
   } catch (IOException x) {
     endPoint.close();
     throw new RuntimeIOException(x);
   }
 }
 public void disconnect(boolean onlyOutput) {
   EndPoint endPoint = getEndPoint();
   // We need to gently close first, to allow
   // SSL close alerts to be sent by Jetty
   LOG.debug("Shutting down output {}", endPoint);
   endPoint.shutdownOutput();
   if (!onlyOutput) {
     LOG.debug("Closing {}", endPoint);
     endPoint.close();
   }
 }
 @Override
 protected void sendContent(HttpExchange exchange, HttpContent content, Callback callback) {
   try {
     HttpClient client = getHttpChannel().getHttpDestination().getHttpClient();
     ByteBufferPool bufferPool = client.getByteBufferPool();
     ByteBuffer chunk = null;
     while (true) {
       ByteBuffer contentBuffer = content.getByteBuffer();
       boolean lastContent = content.isLast();
       HttpGenerator.Result result =
           generator.generateRequest(null, null, chunk, contentBuffer, lastContent);
       switch (result) {
         case NEED_CHUNK:
           {
             chunk = bufferPool.acquire(HttpGenerator.CHUNK_SIZE, false);
             break;
           }
         case FLUSH:
           {
             EndPoint endPoint = getHttpChannel().getHttpConnection().getEndPoint();
             if (chunk != null)
               endPoint.write(
                   new ByteBufferRecyclerCallback(callback, bufferPool, chunk),
                   chunk,
                   contentBuffer);
             else endPoint.write(callback, contentBuffer);
             return;
           }
         case SHUTDOWN_OUT:
           {
             shutdownOutput();
             break;
           }
         case CONTINUE:
           {
             break;
           }
         case DONE:
           {
             assert generator.isEnd();
             callback.succeeded();
             return;
           }
         default:
           {
             throw new IllegalStateException();
           }
       }
     }
   } catch (Exception x) {
     LOG.debug(x);
     callback.failed(x);
   }
 }
    @Override
    protected void send(HttpExchange exchange) {
      Request request = exchange.getRequest();
      normalizeRequest(request);

      // Save the old idle timeout to restore it
      EndPoint endPoint = getEndPoint();
      idleTimeout = endPoint.getIdleTimeout();
      endPoint.setIdleTimeout(request.getIdleTimeout());

      // One channel per connection, just delegate the send
      if (channel.associate(exchange)) channel.send();
      else channel.release();
    }
Exemplo n.º 9
0
  private synchronized int expelBuffer(long blockFor) throws IOException {
    if (_buffer == null) return 0;
    int result = flushBuffer();
    _buffer.compact();
    if (!_endp.isBlocking()) {
      while (_buffer.space() == 0) {
        boolean ready = _endp.blockWritable(blockFor);
        if (!ready) throw new IOException("Write timeout");

        result += flushBuffer();
        _buffer.compact();
      }
    }
    return result;
  }
  @Override
  public Connection newConnection(Connector connector, EndPoint endPoint) {
    ServerSessionListener listener = newSessionListener(connector, endPoint);

    Generator generator = new Generator(connector.getByteBufferPool(), getMaxHeaderTableSize());
    HTTP2ServerSession session =
        new HTTP2ServerSession(
            connector.getScheduler(),
            endPoint,
            generator,
            listener,
            new HTTP2FlowControl(getInitialStreamWindow()));
    session.setMaxLocalStreams(getMaxConcurrentStreams());
    session.setMaxRemoteStreams(getMaxConcurrentStreams());
    long idleTimeout = endPoint.getIdleTimeout();
    if (idleTimeout > 0) idleTimeout /= 2;
    session.setStreamIdleTimeout(idleTimeout);

    Parser parser = newServerParser(connector.getByteBufferPool(), session);
    HTTP2Connection connection =
        new HTTP2ServerConnection(
            connector.getByteBufferPool(),
            connector.getExecutor(),
            endPoint,
            parser,
            session,
            getInputBufferSize(),
            listener);

    return configure(connection, connector, endPoint);
  }
Exemplo n.º 11
0
 /**
  * This method is invoked when the idle timeout triggers. We check the close state to act
  * appropriately:
  *
  * <p>* NOT_CLOSED: it's a real idle timeout, we just initiate a close, see {@link #close(int,
  * String, Callback)}.
  *
  * <p>* LOCALLY_CLOSED: we have sent a GO_AWAY and only shutdown the output, but the other peer
  * did not close the connection so we never received the TCP FIN, and therefore we terminate.
  *
  * <p>* REMOTELY_CLOSED: the other peer sent us a GO_AWAY, we should have queued a disconnect, but
  * for some reason it was not processed (for example, queue was stuck because of TCP congestion),
  * therefore we terminate. See {@link #onGoAway(GoAwayFrame)}.
  *
  * @return true if the session should be closed, false otherwise
  * @see #onGoAway(GoAwayFrame)
  * @see #close(int, String, Callback)
  * @see #onShutdown()
  */
 @Override
 public boolean onIdleTimeout() {
   switch (closed.get()) {
     case NOT_CLOSED:
       {
         long elapsed = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - idleTime);
         if (elapsed < endPoint.getIdleTimeout()) return false;
         return notifyIdleTimeout(this);
       }
     case LOCALLY_CLOSED:
     case REMOTELY_CLOSED:
       {
         abort(new TimeoutException("Idle timeout " + endPoint.getIdleTimeout() + " ms"));
         return false;
       }
     default:
       {
         return false;
       }
   }
 }
 /**
  * Read / Parse the waiting read/fill buffer
  *
  * @param buffer the buffer to fill into from the endpoint
  * @return true if there is more to read, false if reading should stop
  */
 private boolean read(ByteBuffer buffer) {
   EndPoint endPoint = getEndPoint();
   try {
     while (true) {
       int filled = endPoint.fill(buffer);
       if (filled == 0) {
         return true;
       } else if (filled < 0) {
         LOG.debug("read - EOF Reached");
         return false;
       } else {
         if (LOG.isDebugEnabled()) {
           LOG.debug("Filled {} bytes - {}", filled, BufferUtil.toDetailString(buffer));
         }
         ClientUpgradeResponse resp = parser.parse(buffer);
         if (resp != null) {
           // Got a response!
           client.setUpgradeResponse(resp);
           validateResponse(resp);
           notifyConnect(resp);
           upgradeConnection(resp);
           return false; // do no more reading
         }
       }
     }
   } catch (IOException e) {
     LOG.warn(e);
     client.failed(e);
     disconnect(false);
     return false;
   } catch (UpgradeException e) {
     LOG.warn(e);
     client.failed(e);
     disconnect(false);
     return false;
   }
 }
Exemplo n.º 13
0
  public synchronized void addFrame(byte flags, byte opcode, byte[] content, int offset, int length)
      throws IOException {
    long blockFor = _endp.getMaxIdleTime();

    if (_buffer == null) _buffer = _buffers.getDirectBuffer();

    if (_buffer.space() == 0) expelBuffer(blockFor);

    bufferPut(opcode, blockFor);

    if (isLengthFrame(opcode)) {
      // Send a length delimited frame

      // How many bytes we need for the length ?
      // We have 7 bits available, so log2(length) / 7 + 1
      // For example, 50000 bytes is 2 8-bytes: 11000011 01010000
      // but we need to write it in 3 7-bytes 0000011 0000110 1010000
      // 65536 == 1 00000000 00000000 => 100 0000000 0000000
      int lengthBytes = new BigInteger(String.valueOf(length)).bitLength() / 7 + 1;
      for (int i = lengthBytes - 1; i > 0; --i) {
        byte lengthByte = (byte) (0x80 | (0x7F & (length >> 7 * i)));
        bufferPut(lengthByte, blockFor);
      }
      bufferPut((byte) (0x7F & length), blockFor);
    }

    int remaining = length;
    while (remaining > 0) {
      int chunk = remaining < _buffer.space() ? remaining : _buffer.space();
      _buffer.put(content, offset + (length - remaining), chunk);
      remaining -= chunk;
      if (_buffer.space() > 0) {
        if (!isLengthFrame(opcode)) _buffer.put((byte) 0xFF);
        // Gently flush the data, issuing a non-blocking write
        flushBuffer();
      } else {
        // Forcibly flush the data, issuing a blocking write
        expelBuffer(blockFor);
        if (remaining == 0) {
          if (!isLengthFrame(opcode)) _buffer.put((byte) 0xFF);
          // Gently flush the data, issuing a non-blocking write
          flushBuffer();
        }
      }
    }
  }
Exemplo n.º 14
0
 public HTTP2Session(
     Scheduler scheduler,
     EndPoint endPoint,
     Generator generator,
     Session.Listener listener,
     FlowControlStrategy flowControl,
     int initialStreamId) {
   this.scheduler = scheduler;
   this.endPoint = endPoint;
   this.generator = generator;
   this.listener = listener;
   this.flowControl = flowControl;
   this.flusher = new HTTP2Flusher(this);
   this.maxLocalStreams = -1;
   this.maxRemoteStreams = -1;
   this.streamIds.set(initialStreamId);
   this.streamIdleTimeout = endPoint.getIdleTimeout();
   this.sendWindow.set(FlowControlStrategy.DEFAULT_WINDOW_SIZE);
   this.recvWindow.set(FlowControlStrategy.DEFAULT_WINDOW_SIZE);
   this.pushEnabled = true; // SPEC: by default, push is enabled.
   this.idleTime = System.nanoTime();
 }
Exemplo n.º 15
0
 private void tunnelSucceeded() {
   try {
     // Replace the promise back with the original
     context.put(HttpClientTransport.HTTP_CONNECTION_PROMISE_CONTEXT_KEY, promise);
     HttpDestination destination =
         (HttpDestination) context.get(HttpClientTransport.HTTP_DESTINATION_CONTEXT_KEY);
     HttpClient client = destination.getHttpClient();
     ClientConnectionFactory sslConnectionFactory =
         new SslClientConnectionFactory(
             client.getSslContextFactory(),
             client.getByteBufferPool(),
             client.getExecutor(),
             connectionFactory);
     org.eclipse.jetty.io.Connection oldConnection = endPoint.getConnection();
     org.eclipse.jetty.io.Connection newConnection =
         sslConnectionFactory.newConnection(endPoint, context);
     Helper.replaceConnection(oldConnection, newConnection);
     LOG.debug("HTTP tunnel established: {} over {}", oldConnection, newConnection);
   } catch (Throwable x) {
     tunnelFailed(x);
   }
 }
Exemplo n.º 16
0
 @Override
 public void write(ByteBuffer buffer, final Callback callback) {
   EndPoint endPoint = getEndPoint();
   endPoint.write(callback, buffer);
 }
Exemplo n.º 17
0
 public void disconnect() {
   if (LOG.isDebugEnabled()) LOG.debug("Disconnecting {}", this);
   endPoint.close();
 }
Exemplo n.º 18
0
 public InetSocketAddress getLocalAddress() {
   return _endPoint.getLocalAddress();
 }
  @Test
  public void testMaxIdleWithRequest11NoClientClose() throws Exception {
    final Exchanger<EndPoint> exchanger = new Exchanger<>();
    configureServer(
        new EchoHandler() {
          @Override
          public void handle(
              String target,
              Request baseRequest,
              HttpServletRequest request,
              HttpServletResponse response)
              throws IOException, ServletException {
            try {
              exchanger.exchange(baseRequest.getHttpChannel().getEndPoint());
            } catch (Exception e) {
              e.printStackTrace();
            }
            super.handle(target, baseRequest, request, response);
          }
        });
    Socket client = newSocket(_serverURI.getHost(), _serverURI.getPort());
    client.setSoTimeout(10000);

    assertFalse(client.isClosed());

    OutputStream os = client.getOutputStream();
    InputStream is = client.getInputStream();

    String content = "Wibble";
    byte[] contentB = content.getBytes("utf-8");
    os.write(
        ("POST /echo HTTP/1.1\r\n"
                + "host: "
                + _serverURI.getHost()
                + ":"
                + _serverURI.getPort()
                + "\r\n"
                + "content-type: text/plain; charset=utf-8\r\n"
                + "content-length: "
                + contentB.length
                + "\r\n"
                + "connection: close\r\n"
                + "\r\n")
            .getBytes("utf-8"));
    os.write(contentB);
    os.flush();

    // Get the server side endpoint
    EndPoint endPoint = exchanger.exchange(null, 10, TimeUnit.SECONDS);

    // read the response
    IO.toString(is);

    // check client reads EOF
    assertEquals(-1, is.read());

    TimeUnit.MILLISECONDS.sleep(3 * MAX_IDLE_TIME);

    // further writes will get broken pipe or similar
    try {
      for (int i = 0; i < 1000; i++) {
        os.write(
            ("GET / HTTP/1.0\r\n"
                    + "host: "
                    + _serverURI.getHost()
                    + ":"
                    + _serverURI.getPort()
                    + "\r\n"
                    + "connection: keep-alive\r\n"
                    + "\r\n")
                .getBytes("utf-8"));
        os.flush();
      }
      Assert.fail("half close should have timed out");
    } catch (SocketException e) {
      // expected
    }

    // check the server side is closed
    Assert.assertFalse(endPoint.isOpen());
  }
Exemplo n.º 20
0
 private void tunnelFailed(Throwable failure) {
   endPoint.close();
   failed(failure);
 }
Exemplo n.º 21
0
 public InetSocketAddress getRemoteAddress() {
   return _endPoint.getRemoteAddress();
 }
  @Override
  protected void sendHeaders(HttpExchange exchange, HttpContent content, Callback callback) {
    Request request = exchange.getRequest();
    ContentProvider requestContent = request.getContent();
    long contentLength = requestContent == null ? -1 : requestContent.getLength();
    String path = request.getPath();
    String query = request.getQuery();
    if (query != null) path += "?" + query;
    HttpGenerator.RequestInfo requestInfo =
        new HttpGenerator.RequestInfo(
            request.getVersion(), request.getHeaders(), contentLength, request.getMethod(), path);

    try {
      HttpClient client = getHttpChannel().getHttpDestination().getHttpClient();
      ByteBufferPool bufferPool = client.getByteBufferPool();
      ByteBuffer header = bufferPool.acquire(client.getRequestBufferSize(), false);
      ByteBuffer chunk = null;

      ByteBuffer contentBuffer = null;
      boolean lastContent = false;
      if (!expects100Continue(request)) {
        content.advance();
        contentBuffer = content.getByteBuffer();
        lastContent = content.isLast();
      }
      while (true) {
        HttpGenerator.Result result =
            generator.generateRequest(requestInfo, header, chunk, contentBuffer, lastContent);
        switch (result) {
          case NEED_CHUNK:
            {
              chunk = bufferPool.acquire(HttpGenerator.CHUNK_SIZE, false);
              break;
            }
          case FLUSH:
            {
              int size = 1;
              boolean hasChunk = chunk != null;
              if (hasChunk) ++size;
              boolean hasContent = contentBuffer != null;
              if (hasContent) ++size;
              ByteBuffer[] toWrite = new ByteBuffer[size];
              ByteBuffer[] toRecycle = new ByteBuffer[hasChunk ? 2 : 1];
              toWrite[0] = header;
              toRecycle[0] = header;
              if (hasChunk) {
                toWrite[1] = chunk;
                toRecycle[1] = chunk;
              }
              if (hasContent) toWrite[toWrite.length - 1] = contentBuffer;
              EndPoint endPoint = getHttpChannel().getHttpConnection().getEndPoint();
              endPoint.write(
                  new ByteBufferRecyclerCallback(callback, bufferPool, toRecycle), toWrite);
              return;
            }
          default:
            {
              throw new IllegalStateException();
            }
        }
      }
    } catch (Exception x) {
      LOG.debug(x);
      callback.failed(x);
    }
  }
Exemplo n.º 23
0
 public boolean isDisconnected() {
   return !endPoint.isOpen();
 }
Exemplo n.º 24
0
 @Override
 public InetSocketAddress getLocalAddress() {
   return endPoint.getLocalAddress();
 }
  @Test
  public void testMaxIdleWithRequest10ClientIgnoresClose() throws Exception {
    final Exchanger<EndPoint> exchanger = new Exchanger<>();
    configureServer(
        new HelloWorldHandler() {
          @Override
          public void handle(
              String target,
              Request baseRequest,
              HttpServletRequest request,
              HttpServletResponse response)
              throws IOException, ServletException {
            try {
              exchanger.exchange(baseRequest.getHttpChannel().getEndPoint());
            } catch (Exception e) {
              e.printStackTrace();
            }
            super.handle(target, baseRequest, request, response);
          }
        });
    Socket client = newSocket(_serverURI.getHost(), _serverURI.getPort());
    client.setSoTimeout(10000);

    assertFalse(client.isClosed());

    OutputStream os = client.getOutputStream();
    InputStream is = client.getInputStream();

    os.write(
        ("GET / HTTP/1.0\r\n"
                + "host: "
                + _serverURI.getHost()
                + ":"
                + _serverURI.getPort()
                + "\r\n"
                + "connection: close\r\n"
                + "\r\n")
            .getBytes("utf-8"));
    os.flush();

    // Get the server side endpoint
    EndPoint endPoint = exchanger.exchange(null, 10, TimeUnit.SECONDS);
    if (endPoint instanceof SslConnection.DecryptedEndPoint)
      endPoint = endPoint.getConnection().getEndPoint();

    // read the response
    String result = IO.toString(is);
    Assert.assertThat("OK", result, containsString("200 OK"));

    // check client reads EOF
    assertEquals(-1, is.read());
    assertTrue(endPoint.isOutputShutdown());

    Thread.sleep(2 * MAX_IDLE_TIME);

    // further writes will get broken pipe or similar
    try {
      long end = System.currentTimeMillis() + MAX_IDLE_TIME + 3000;
      while (System.currentTimeMillis() < end) {
        os.write("THIS DATA SHOULD NOT BE PARSED!\n\n".getBytes("utf-8"));
        os.flush();
        Thread.sleep(100);
      }
      Assert.fail("half close should have timed out");
    } catch (SocketException e) {
      // expected

      // Give the SSL onClose time to act
      Thread.sleep(100);
    }

    // check the server side is closed
    Assert.assertFalse(endPoint.isOpen());
  }
Exemplo n.º 26
0
 @Override
 public InetSocketAddress getRemoteAddress() {
   return endPoint.getRemoteAddress();
 }
Exemplo n.º 27
0
 @Override
 protected Connection onSwitchProtocol(EndPoint endp) throws IOException {
   _log.debug("onSwitchProtocol: host {}", (endp == null) ? null : endp.getRemoteHost());
   return super.onSwitchProtocol(endp);
 }
Exemplo n.º 28
0
  public synchronized void addFrame(byte flags, byte opcode, byte[] content, int offset, int length)
      throws IOException {
    // System.err.printf("<< %s %s
    // %s\n",TypeUtil.toHexString(flags),TypeUtil.toHexString(opcode),length);

    long blockFor = _endp.getMaxIdleTime();

    if (_buffer == null)
      _buffer = (_maskGen != null) ? _buffers.getBuffer() : _buffers.getDirectBuffer();

    boolean last = WebSocketConnectionD06.isLastFrame(flags);
    opcode = (byte) (((0xf & flags) << 4) + 0xf & opcode);

    int space = (_maskGen != null) ? 14 : 10;

    do {
      opcode = _opsent ? WebSocketConnectionD06.OP_CONTINUATION : opcode;
      _opsent = true;

      int payload = length;
      if (payload + space > _buffer.capacity()) {
        // We must fragement, so clear FIN bit
        opcode &= (byte) 0x7F; // Clear the FIN bit
        payload = _buffer.capacity() - space;
      } else if (last) opcode |= (byte) 0x80; // Set the FIN bit

      // ensure there is space for header
      if (_buffer.space() <= space) expelBuffer(blockFor);

      // write mask
      if ((_maskGen != null)) {
        _maskGen.genMask(_mask);
        _m = 0;
        _buffer.put(_mask);
      }

      // write the opcode and length
      if (payload > 0xffff) {
        bufferPut(
            new byte[] {
              opcode,
              (byte) 0x7f,
              (byte) 0,
              (byte) 0,
              (byte) 0,
              (byte) 0,
              (byte) ((payload >> 24) & 0xff),
              (byte) ((payload >> 16) & 0xff),
              (byte) ((payload >> 8) & 0xff),
              (byte) (payload & 0xff)
            });
      } else if (payload >= 0x7e) {
        bufferPut(new byte[] {opcode, (byte) 0x7e, (byte) (payload >> 8), (byte) (payload & 0xff)});
      } else {
        bufferPut(opcode);
        bufferPut((byte) payload);
      }

      // write payload
      int remaining = payload;
      while (remaining > 0) {
        _buffer.compact();
        int chunk = remaining < _buffer.space() ? remaining : _buffer.space();

        if ((_maskGen != null)) {
          for (int i = 0; i < chunk; i++) bufferPut(content[offset + (payload - remaining) + i]);
        } else _buffer.put(content, offset + (payload - remaining), chunk);

        remaining -= chunk;
        if (_buffer.space() > 0) {
          // Gently flush the data, issuing a non-blocking write
          flushBuffer();
        } else {
          // Forcibly flush the data, issuing a blocking write
          expelBuffer(blockFor);
          if (remaining == 0) {
            // Gently flush the data, issuing a non-blocking write
            flushBuffer();
          }
        }
      }
      offset += payload;
      length -= payload;
    } while (length > 0);
    _opsent = !last;
  }