Exemplo n.º 1
0
/**
 * This class should not be constructed directly instead use HttpProxyBuilder to build and configure
 * this class
 *
 * @see ProxyBuilder
 * @author jamesdbloom
 */
public interface Proxy {

  public static final AttributeKey<Proxy> HTTP_PROXY = AttributeKey.valueOf("HTTP_PROXY");
  public static final AttributeKey<LogFilter> LOG_FILTER = AttributeKey.valueOf("PROXY_LOG_FILTER");
  public static final AttributeKey<InetSocketAddress> REMOTE_SOCKET =
      AttributeKey.valueOf("REMOTE_SOCKET");
  public static final AttributeKey<InetSocketAddress> HTTP_CONNECT_SOCKET =
      AttributeKey.valueOf("HTTP_CONNECT_SOCKET");

  public void stop();

  public boolean isRunning();
}
Exemplo n.º 2
0
 @Override
 public void channelInactive(ChannelHandlerContext ctx) throws Exception {
   LOGGER.debug(
       String.format("[%s]\n========关闭连接=======", ctx.channel().localAddress().toString()));
   LOGGER.debug(ctx.channel().remoteAddress().toString());
   LOGGER.debug(ctx.channel().attr(AttributeKey.valueOf("haha")).get().toString());
 }
Exemplo n.º 3
0
  static {
    b = MarkerManager.getMarker("NETWORK_PACKETS", a);
    c = AttributeKey.valueOf("protocol");
    d =
        new class_nw() {
          protected NioEventLoopGroup a() {
            return new NioEventLoopGroup(
                0,
                (new ThreadFactoryBuilder())
                    .setNameFormat("Netty Client IO #%d")
                    .setDaemon(true)
                    .build());
          }

          // $FF: synthetic method
          @Override
          protected Object b() {
            return this.a();
          }
        };
    e =
        new class_nw() {
          protected EpollEventLoopGroup a() {
            return new EpollEventLoopGroup(
                0,
                (new ThreadFactoryBuilder())
                    .setNameFormat("Netty Epoll Client IO #%d")
                    .setDaemon(true)
                    .build());
          }

          // $FF: synthetic method
          @Override
          protected Object b() {
            return this.a();
          }
        };
    f =
        new class_nw() {
          protected LocalEventLoopGroup a() {
            return new LocalEventLoopGroup(
                0,
                (new ThreadFactoryBuilder())
                    .setNameFormat("Netty Local Client IO #%d")
                    .setDaemon(true)
                    .build());
          }

          // $FF: synthetic method
          @Override
          protected Object b() {
            return this.a();
          }
        };
  }
Exemplo n.º 4
0
@Sharable
public class HttpClientHandler extends ChannelDuplexHandler {

  public static final AttributeKey<Boolean> HTTP_CLIENT_HANDLER_TRIGGERED_ERROR =
      AttributeKey.valueOf("__HttpClientHandler_ErrorTriggered");

  private final Logger logger = getLogger(getClass());
  private final HttpClientResponseHandler httpClientResponseHandler;
  private final HttpClientRequestHandler httpClientRequestHandler;

  public HttpClientHandler(
      HttpClientResponseHandler httpClientResponseHandler,
      HttpClientRequestHandler httpClientRequestHandler) {
    this.httpClientResponseHandler = httpClientResponseHandler;
    this.httpClientRequestHandler = httpClientRequestHandler;
  }

  @Override
  public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
    httpClientResponseHandler.handleResponse(ctx, msg);
  }

  @Override
  public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise)
      throws Exception {
    httpClientRequestHandler.handleRequest(ctx, msg, promise);
  }

  @Override
  public void channelInactive(ChannelHandlerContext ctx) throws Exception {
    httpClientResponseHandler.handleChannelInactive(ctx);
    super.channelReadComplete(ctx);
  }

  @Override
  public void exceptionCaught(ChannelHandlerContext ctx, final Throwable cause) {
    logger.trace("Exception on channel " + ctx.channel(), cause);

    ctx.channel().attr(HTTP_CLIENT_HANDLER_TRIGGERED_ERROR).set(true);

    HttpRequestContext httpRequestContext =
        ctx.channel().attr(HttpRequestContext.HTTP_REQUEST_ATTRIBUTE_KEY).get();
    if (httpRequestContext != null) {
      httpRequestContext.getRequestEventBus().triggerEvent(Event.ERROR, httpRequestContext, cause);
    }
  }
}
Exemplo n.º 5
0
 @Override
 public <X> X getAttribute(String key, Class<X> clz) {
   AttributeKey<X> attributeKey = AttributeKey.valueOf(key);
   Attribute<X> attribute = channel.attr(attributeKey);
   return attribute == null ? null : attribute.get();
 }
Exemplo n.º 6
0
 @Override
 public <X, Y extends X> void setAttribute(String key, Class<X> clz, Y value) {
   AttributeKey<X> attributeKey = AttributeKey.valueOf(key);
   channel.attr(attributeKey).set(value);
 }
/**
 * AbstractTrafficShapingHandler allows to limit the global bandwidth (see {@link
 * GlobalTrafficShapingHandler}) or per session bandwidth (see {@link
 * ChannelTrafficShapingHandler}), as traffic shaping. It allows you to implement an almost real
 * time monitoring of the bandwidth using the monitors from {@link TrafficCounter} that will call
 * back every checkInterval the method doAccounting of this handler.<br>
 * <br>
 * If you want for any particular reasons to stop the monitoring (accounting) or to change the
 * read/write limit or the check interval, several methods allow that for you:<br>
 *
 * <ul>
 *   <li><tt>configure</tt> allows you to change read or write limits, or the checkInterval
 *   <li><tt>getTrafficCounter</tt> allows you to have access to the TrafficCounter and so to stop
 *       or start the monitoring, to change the checkInterval directly, or to have access to its
 *       values.
 * </ul>
 */
public abstract class AbstractTrafficShapingHandler extends ChannelDuplexHandler {
  /** Default delay between two checks: 1s */
  public static final long DEFAULT_CHECK_INTERVAL = 1000;

  /** Default minimal time to wait */
  private static final long MINIMAL_WAIT = 10;

  /** Traffic Counter */
  protected TrafficCounter trafficCounter;

  /** Limit in B/s to apply to write */
  private long writeLimit;

  /** Limit in B/s to apply to read */
  private long readLimit;

  /** Delay between two performance snapshots */
  protected long checkInterval = DEFAULT_CHECK_INTERVAL; // default 1 s

  private static final AttributeKey<Boolean> READ_SUSPENDED =
      AttributeKey.valueOf(AbstractTrafficShapingHandler.class.getName() + ".READ_SUSPENDED");
  private static final AttributeKey<Runnable> REOPEN_TASK =
      AttributeKey.valueOf(AbstractTrafficShapingHandler.class.getName() + ".REOPEN_TASK");

  /** @param newTrafficCounter the TrafficCounter to set */
  void setTrafficCounter(TrafficCounter newTrafficCounter) {
    trafficCounter = newTrafficCounter;
  }

  /**
   * @param writeLimit 0 or a limit in bytes/s
   * @param readLimit 0 or a limit in bytes/s
   * @param checkInterval The delay between two computations of performances for channels or 0 if no
   *     stats are to be computed
   */
  protected AbstractTrafficShapingHandler(long writeLimit, long readLimit, long checkInterval) {
    this.writeLimit = writeLimit;
    this.readLimit = readLimit;
    this.checkInterval = checkInterval;
  }

  /**
   * Constructor using default Check Interval
   *
   * @param writeLimit 0 or a limit in bytes/s
   * @param readLimit 0 or a limit in bytes/s
   */
  protected AbstractTrafficShapingHandler(long writeLimit, long readLimit) {
    this(writeLimit, readLimit, DEFAULT_CHECK_INTERVAL);
  }

  /** Constructor using NO LIMIT and default Check Interval */
  protected AbstractTrafficShapingHandler() {
    this(0, 0, DEFAULT_CHECK_INTERVAL);
  }

  /**
   * Constructor using NO LIMIT
   *
   * @param checkInterval The delay between two computations of performances for channels or 0 if no
   *     stats are to be computed
   */
  protected AbstractTrafficShapingHandler(long checkInterval) {
    this(0, 0, checkInterval);
  }

  /**
   * Change the underlying limitations and check interval.
   *
   * @param newWriteLimit The new write limit (in bytes)
   * @param newReadLimit The new read limit (in bytes)
   * @param newCheckInterval The new check interval (in milliseconds)
   */
  public void configure(long newWriteLimit, long newReadLimit, long newCheckInterval) {
    configure(newWriteLimit, newReadLimit);
    configure(newCheckInterval);
  }

  /**
   * Change the underlying limitations.
   *
   * @param newWriteLimit The new write limit (in bytes)
   * @param newReadLimit The new read limit (in bytes)
   */
  public void configure(long newWriteLimit, long newReadLimit) {
    writeLimit = newWriteLimit;
    readLimit = newReadLimit;
    if (trafficCounter != null) {
      trafficCounter.resetAccounting(System.currentTimeMillis() + 1);
    }
  }

  /**
   * Change the check interval.
   *
   * @param newCheckInterval The new check interval (in milliseconds)
   */
  public void configure(long newCheckInterval) {
    checkInterval = newCheckInterval;
    if (trafficCounter != null) {
      trafficCounter.configure(checkInterval);
    }
  }

  /**
   * Called each time the accounting is computed from the TrafficCounters. This method could be used
   * for instance to implement almost real time accounting.
   *
   * @param counter the TrafficCounter that computes its performance
   */
  @SuppressWarnings("unused")
  protected void doAccounting(TrafficCounter counter) {
    // NOOP by default
  }

  /** Class to implement setReadable at fix time */
  private static final class ReopenReadTimerTask implements Runnable {
    final ChannelHandlerContext ctx;

    ReopenReadTimerTask(ChannelHandlerContext ctx) {
      this.ctx = ctx;
    }

    @Override
    public void run() {
      ctx.attr(READ_SUSPENDED).set(false);
      ctx.read();
    }
  }

  /** @return the time that should be necessary to wait to respect limit. Can be negative time */
  private static long getTimeToWait(long limit, long bytes, long lastTime, long curtime) {
    long interval = curtime - lastTime;
    if (interval <= 0) {
      // Time is too short, so just lets continue
      return 0;
    }
    return (bytes * 1000 / limit - interval) / 10 * 10;
  }

  @Override
  public void channelRead(final ChannelHandlerContext ctx, final Object msg) throws Exception {
    long size = calculateSize(msg);
    long curtime = System.currentTimeMillis();

    if (trafficCounter != null) {
      trafficCounter.bytesRecvFlowControl(size);
      if (readLimit == 0) {
        // no action
        ctx.fireChannelRead(msg);

        return;
      }

      // compute the number of ms to wait before reopening the channel
      long wait =
          getTimeToWait(
              readLimit, trafficCounter.currentReadBytes(), trafficCounter.lastTime(), curtime);
      if (wait >= MINIMAL_WAIT) { // At least 10ms seems a minimal
        // time in order to
        // try to limit the traffic
        if (!isSuspended(ctx)) {
          ctx.attr(READ_SUSPENDED).set(true);

          // Create a Runnable to reactive the read if needed. If one was create before it will just
          // be
          // reused to limit object creation
          Attribute<Runnable> attr = ctx.attr(REOPEN_TASK);
          Runnable reopenTask = attr.get();
          if (reopenTask == null) {
            reopenTask = new ReopenReadTimerTask(ctx);
            attr.set(reopenTask);
          }
          ctx.executor().schedule(reopenTask, wait, TimeUnit.MILLISECONDS);
        } else {
          // Create a Runnable to update the next handler in the chain. If one was create before it
          // will
          // just be reused to limit object creation
          Runnable bufferUpdateTask =
              new Runnable() {
                @Override
                public void run() {
                  ctx.fireChannelRead(msg);
                }
              };
          ctx.executor().schedule(bufferUpdateTask, wait, TimeUnit.MILLISECONDS);
          return;
        }
      }
    }
    ctx.fireChannelRead(msg);
  }

  @Override
  public void read(ChannelHandlerContext ctx) {
    if (!isSuspended(ctx)) {
      ctx.read();
    }
  }

  private static boolean isSuspended(ChannelHandlerContext ctx) {
    Boolean suspended = ctx.attr(READ_SUSPENDED).get();
    if (suspended == null || Boolean.FALSE.equals(suspended)) {
      return false;
    }
    return true;
  }

  @Override
  public void write(final ChannelHandlerContext ctx, final Object msg, final ChannelPromise promise)
      throws Exception {
    long curtime = System.currentTimeMillis();
    long size = calculateSize(msg);

    if (size > -1 && trafficCounter != null) {
      trafficCounter.bytesWriteFlowControl(size);
      if (writeLimit == 0) {
        ctx.write(msg, promise);
        return;
      }
      // compute the number of ms to wait before continue with the
      // channel
      long wait =
          getTimeToWait(
              writeLimit, trafficCounter.currentWrittenBytes(), trafficCounter.lastTime(), curtime);
      if (wait >= MINIMAL_WAIT) {
        ctx.executor()
            .schedule(
                new Runnable() {
                  @Override
                  public void run() {
                    ctx.write(msg, promise);
                  }
                },
                wait,
                TimeUnit.MILLISECONDS);
        return;
      }
    }
    ctx.write(msg, promise);
  }

  /** @return the current TrafficCounter (if channel is still connected) */
  public TrafficCounter trafficCounter() {
    return trafficCounter;
  }

  @Override
  public String toString() {
    return "TrafficShaping with Write Limit: "
        + writeLimit
        + " Read Limit: "
        + readLimit
        + " and Counter: "
        + (trafficCounter != null ? trafficCounter.toString() : "none");
  }

  /**
   * Calculate the size of the given {@link Object}.
   *
   * <p>This implementation supports {@link ByteBuf} and {@link ByteBufHolder}. Sub-classes may
   * override this.
   *
   * @param msg the msg for which the size should be calculated
   * @return size the size of the msg or {@code -1} if unknown.
   */
  protected long calculateSize(Object msg) {
    if (msg instanceof ByteBuf) {
      return ((ByteBuf) msg).readableBytes();
    }
    if (msg instanceof ByteBufHolder) {
      return ((ByteBufHolder) msg).content().readableBytes();
    }
    return -1;
  }
}
Exemplo n.º 8
0
@Sharable
public class EncoderHandler extends ChannelOutboundHandlerAdapter {

  private static final byte[] OK = "ok".getBytes(CharsetUtil.UTF_8);

  public static final AttributeKey<String> ORIGIN = AttributeKey.valueOf("origin");
  public static final AttributeKey<String> USER_AGENT = AttributeKey.valueOf("userAgent");
  public static final AttributeKey<Boolean> B64 = AttributeKey.valueOf("b64");
  public static final AttributeKey<Integer> JSONP_INDEX = AttributeKey.valueOf("jsonpIndex");
  public static final AttributeKey<Boolean> WRITE_ONCE = AttributeKey.valueOf("writeOnce");

  private final Logger log = LoggerFactory.getLogger(getClass());

  private final PacketEncoder encoder;

  private String version;
  private Configuration configuration;

  public EncoderHandler(Configuration configuration, PacketEncoder encoder) throws IOException {
    this.encoder = encoder;
    this.configuration = configuration;

    if (configuration.isAddVersionHeader()) {
      readVersion();
    }
  }

  private void readVersion() throws IOException {
    Enumeration<URL> resources = getClass().getClassLoader().getResources("META-INF/MANIFEST.MF");
    while (resources.hasMoreElements()) {
      try {
        Manifest manifest = new Manifest(resources.nextElement().openStream());
        Attributes attrs = manifest.getMainAttributes();
        if (attrs == null) {
          continue;
        }
        String name = attrs.getValue("Bundle-Name");
        if (name != null && name.equals("netty-socketio")) {
          version = name + "/" + attrs.getValue("Bundle-Version");
          break;
        }
      } catch (IOException E) {
        // skip it
      }
    }
  }

  private void write(XHROptionsMessage msg, ChannelHandlerContext ctx, ChannelPromise promise) {
    HttpResponse res = new DefaultHttpResponse(HTTP_1_1, HttpResponseStatus.OK);

    HttpHeaders.addHeader(res, "Set-Cookie", "io=" + msg.getSessionId());
    HttpHeaders.addHeader(res, CONNECTION, KEEP_ALIVE);
    HttpHeaders.addHeader(res, ACCESS_CONTROL_ALLOW_HEADERS, CONTENT_TYPE);
    addOriginHeaders(ctx.channel(), res);

    ByteBuf out = encoder.allocateBuffer(ctx.alloc());
    sendMessage(msg, ctx.channel(), out, res, promise);
  }

  private void write(XHRPostMessage msg, ChannelHandlerContext ctx, ChannelPromise promise) {
    ByteBuf out = encoder.allocateBuffer(ctx.alloc());
    out.writeBytes(OK);
    sendMessage(msg, ctx.channel(), out, "text/html", promise);
  }

  private void sendMessage(
      HttpMessage msg, Channel channel, ByteBuf out, String type, ChannelPromise promise) {
    HttpResponse res = new DefaultHttpResponse(HTTP_1_1, HttpResponseStatus.OK);

    res.headers()
        .add(CONTENT_TYPE, type)
        .add("Set-Cookie", "io=" + msg.getSessionId())
        .add(CONNECTION, KEEP_ALIVE);

    addOriginHeaders(channel, res);
    HttpHeaders.setContentLength(res, out.readableBytes());

    // prevent XSS warnings on IE
    // https://github.com/LearnBoost/socket.io/pull/1333
    String userAgent = channel.attr(EncoderHandler.USER_AGENT).get();
    if (userAgent != null && (userAgent.contains(";MSIE") || userAgent.contains("Trident/"))) {
      res.headers().add("X-XSS-Protection", "0");
    }

    sendMessage(msg, channel, out, res, promise);
  }

  private void sendMessage(
      HttpMessage msg, Channel channel, ByteBuf out, HttpResponse res, ChannelPromise promise) {
    channel.write(res);

    if (log.isTraceEnabled()) {
      log.trace(
          "Out message: {} - sessionId: {}", out.toString(CharsetUtil.UTF_8), msg.getSessionId());
    }

    if (out.isReadable()) {
      channel.write(out);
    } else {
      out.release();
    }

    channel
        .writeAndFlush(LastHttpContent.EMPTY_LAST_CONTENT, promise)
        .addListener(ChannelFutureListener.CLOSE);
  }

  private void addOriginHeaders(Channel channel, HttpResponse res) {
    if (version != null) {
      res.headers().add(HttpHeaders.Names.SERVER, version);
    }

    if (configuration.getOrigin() != null) {
      HttpHeaders.addHeader(res, ACCESS_CONTROL_ALLOW_ORIGIN, configuration.getOrigin());
      HttpHeaders.addHeader(res, ACCESS_CONTROL_ALLOW_CREDENTIALS, Boolean.TRUE);
    } else {
      String origin = channel.attr(ORIGIN).get();
      if (origin != null) {
        HttpHeaders.addHeader(res, ACCESS_CONTROL_ALLOW_ORIGIN, origin);
        HttpHeaders.addHeader(res, ACCESS_CONTROL_ALLOW_CREDENTIALS, Boolean.TRUE);
      } else {
        HttpHeaders.addHeader(res, ACCESS_CONTROL_ALLOW_ORIGIN, "*");
      }
    }
  }

  @Override
  public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise)
      throws Exception {
    if (!(msg instanceof BaseMessage)) {
      super.write(ctx, msg, promise);
      return;
    }

    if (msg instanceof OutPacketMessage) {
      OutPacketMessage m = (OutPacketMessage) msg;
      if (m.getTransport() == Transport.WEBSOCKET) {
        handleWebsocket((OutPacketMessage) msg, ctx, promise);
      }
      if (m.getTransport() == Transport.POLLING) {
        handleHTTP((OutPacketMessage) msg, ctx, promise);
      }
    } else if (msg instanceof XHROptionsMessage) {
      write((XHROptionsMessage) msg, ctx, promise);
    } else if (msg instanceof XHRPostMessage) {
      write((XHRPostMessage) msg, ctx, promise);
    }
  }

  private void handleWebsocket(
      final OutPacketMessage msg, ChannelHandlerContext ctx, ChannelPromise promise)
      throws IOException {
    while (true) {
      Queue<Packet> queue = msg.getClientHead().getPacketsQueue(msg.getTransport());
      Packet packet = queue.poll();
      if (packet == null) {
        promise.setSuccess();
        break;
      }

      final ByteBuf out = encoder.allocateBuffer(ctx.alloc());
      encoder.encodePacket(packet, out, ctx.alloc(), true);

      WebSocketFrame res = new TextWebSocketFrame(out);
      if (log.isTraceEnabled()) {
        log.trace(
            "Out message: {} sessionId: {}", out.toString(CharsetUtil.UTF_8), msg.getSessionId());
      }

      if (out.isReadable()) {
        ctx.channel().writeAndFlush(res, promise);
      } else {
        promise.setSuccess();
        out.release();
      }

      for (ByteBuf buf : packet.getAttachments()) {
        ByteBuf outBuf = encoder.allocateBuffer(ctx.alloc());
        outBuf.writeByte(4);
        outBuf.writeBytes(buf);
        if (log.isTraceEnabled()) {
          log.trace(
              "Out attachment: {} sessionId: {}", ByteBufUtil.hexDump(outBuf), msg.getSessionId());
        }
        ctx.channel().writeAndFlush(new BinaryWebSocketFrame(outBuf));
      }
    }
  }

  private void handleHTTP(OutPacketMessage msg, ChannelHandlerContext ctx, ChannelPromise promise)
      throws IOException {
    Channel channel = ctx.channel();
    Attribute<Boolean> attr = channel.attr(WRITE_ONCE);

    Queue<Packet> queue = msg.getClientHead().getPacketsQueue(msg.getTransport());

    if (!channel.isActive() || queue.isEmpty() || !attr.compareAndSet(null, true)) {
      promise.setSuccess();
      return;
    }

    ByteBuf out = encoder.allocateBuffer(ctx.alloc());
    Boolean b64 = ctx.channel().attr(EncoderHandler.B64).get();
    if (b64 != null && b64) {
      Integer jsonpIndex = ctx.channel().attr(EncoderHandler.JSONP_INDEX).get();
      encoder.encodeJsonP(jsonpIndex, queue, out, ctx.alloc(), 50);
      String type = "application/javascript";
      if (jsonpIndex == null) {
        type = "text/plain";
      }
      sendMessage(msg, channel, out, type, promise);
    } else {
      encoder.encodePackets(queue, out, ctx.alloc(), 50);
      sendMessage(msg, channel, out, "application/octet-stream", promise);
    }
  }
}
Exemplo n.º 9
0
public class OutboundMessageHandler {
  private static final long FIVE_MINUTES = TimeUnit.MINUTES.toMillis(5);
  private static final AttributeKey<Long> lastMessageAttrKey =
      AttributeKey.valueOf("__last_message");
  private final Logger logger = LoggerFactory.getLogger(OutboundMessageHandler.class);
  private final Map<Long, Boolean> checkedUsers = new HashMap<>();
  private final Map<String, Channel> connections;
  private final Bootstrap outboundBootstrap;
  private final String channel;
  private final AuthenticationManager authenticationManager;
  private final ScheduledExecutorService eventLoopGroup;
  private ScheduledFuture scheduledFuture;

  public OutboundMessageHandler(
      Map<String, Channel> connections,
      String channel,
      AuthenticationManager authenticationManager,
      EventLoopGroup eventLoopGroup) {
    this.connections = connections;
    this.channel = channel;
    this.authenticationManager = authenticationManager;
    this.eventLoopGroup = eventLoopGroup;

    this.outboundBootstrap = new Bootstrap();
    this.outboundBootstrap.group(eventLoopGroup);
    if (Epoll.isAvailable()) {
      this.outboundBootstrap.channel(EpollSocketChannel.class);
    } else {
      this.outboundBootstrap.channel(NioSocketChannel.class);
    }
    this.outboundBootstrap.option(ChannelOption.SO_KEEPALIVE, true);
    this.outboundBootstrap.option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT);
    final ChannelHandler stringEncoder = new StringEncoder(CharsetUtil.UTF_8);
    final ChannelHandler stringDecoder = new StringDecoder(CharsetUtil.UTF_8);
    final ChannelHandler handler = new Handler();
    this.outboundBootstrap.handler(
        new ChannelInitializer<SocketChannel>() {
          @Override
          public void initChannel(SocketChannel channel) throws Exception {
            ChannelPipeline pipeline = channel.pipeline();
            pipeline.addLast(new DelimiterBasedFrameDecoder(8192, Delimiters.lineDelimiter()));
            pipeline.addLast(stringEncoder);
            pipeline.addLast(stringDecoder);
            pipeline.addLast(handler);
          }
        });
  }

  public void shutdown() {
    this.scheduledFuture.cancel(false);
    this.connections.values().forEach(Channel::close);
  }

  public void onMessage(Message message) {
    if (message.getType() == MessageType.MSG && message.get(MessageProperty.ROOM).equals("#main")) {
      long userId = message.get(MessageProperty.USER_ID);
      String name = message.get(MessageProperty.NAME);
      UserCredentials userCredentials = null;
      if (needsFetchingConnectionData(userId)) {
        userCredentials = fetchConnectionDataForUser(name, userId);
      }

      if (userCredentials != null) {
        String id = userCredentials.getId();
        Channel channel = connections.get(id.toLowerCase());
        if (channel == null || !channel.isActive()) {
          try {
            channel = createConnection(id, userCredentials.getToken());
          } catch (InterruptedException e) {
            logger.warn("", e);
          }
        }
        if (channel != null) {
          channel.attr(lastMessageAttrKey).set(System.currentTimeMillis());
          channel.writeAndFlush(
              "PRIVMSG #" + this.channel + " :" + message.get(MessageProperty.TEXT) + "\r\n");
        }
      }
    }
  }

  private boolean needsFetchingConnectionData(long userId) {
    Boolean tmp = checkedUsers.get(userId);
    return tmp == null || tmp;
  }

  private UserCredentials fetchConnectionDataForUser(String userName, long userId) {
    UserCredentials userCredentials = null;
    logger.debug("fetching connection data for user {}", userName);
    boolean r = false;
    UserAuthDto auth = authenticationManager.getAuthDataForUser(userId, "twitch.tv");
    if (auth != null) {
      String token = auth.getAuthenticationKey();
      String extName = auth.getAuthenticationName();
      logger.debug("fetched data for user {} with ext name {}", userName, extName, token);
      if (token != null && extName != null) {
        userCredentials = new UserCredentials(extName, token);
      }
      r = true;
    }
    checkedUsers.put(userId, r);
    return userCredentials;
  }

  private Channel createConnection(String id, String token) throws InterruptedException {
    Channel channel;
    logger.debug("creating connection for {}", id);
    ChannelFuture f = outboundBootstrap.connect("irc.twitch.tv", 6667).sync();
    channel = f.channel();
    channel.write("TWITCHCLIENT 2\r\n");
    channel.write("PASS oauth:" + token + "\r\n");
    channel.write("NICK " + id + "\r\n");
    channel.write("JOIN #" + channel + "\r\n");
    channel.flush();
    connections.put(id.toLowerCase(), channel);
    logger.debug("connection created");
    return channel;
  }

  public void start() {
    this.scheduledFuture =
        eventLoopGroup.scheduleAtFixedRate(new ConnectionCleanupTask(), 10, 5, TimeUnit.MINUTES);
  }

  private static class UserCredentials {
    private final String id;
    private final String token;

    private UserCredentials(String id, String token) {
      this.id = id;
      this.token = token;
    }

    public String getId() {
      return id;
    }

    public String getToken() {
      return token;
    }
  }

  @Sharable
  private class Handler extends SimpleChannelInboundHandler<String> {
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
      logger.trace("received message {}", msg);
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
      if (cause instanceof IOException) {
        logger.debug("exception", cause);
      } else {
        logger.warn("exception", cause);
      }
    }
  }

  private class ConnectionCleanupTask implements Runnable {
    @Override
    public void run() {
      try {
        logger.debug("starting cleanup");
        for (Map.Entry<String, Channel> entry : connections.entrySet()) {
          Channel channel = entry.getValue();
          Long lastMessage = channel.attr(lastMessageAttrKey).get();
          if (System.currentTimeMillis() - lastMessage > FIVE_MINUTES) {
            channel.disconnect();
            connections.remove(entry.getKey());
            logger.debug("connection released for {}", entry.getKey());
          } else if (!channel.isActive()) {
            connections.remove(entry.getKey());
            logger.debug("connection released for {}", entry.getKey());
          }
        }
        logger.debug("cleanup complete");
      } catch (Exception e) {
        logger.error("exception while clean up", e);
      }
    }
  }
}
/** Netty channel handler for routing inbound requests to the proper RequestHandler. */
@Sharable
public class HttpRequestChannelHandler extends SimpleChannelInboundHandler<FullHttpRequest> {

  public static final AttributeKey<PooledServerResponse> ATTR_RESPONSE =
      AttributeKey.<PooledServerResponse>valueOf("response");

  private final HttpServerConfig config;
  private final ServerMessagePool messagePool;

  public HttpRequestChannelHandler(final HttpServerConfig config_) {
    super();
    config = config_;
    messagePool = new ServerMessagePool(config.maxConnections());
  }

  @Override
  public void channelRead0(final ChannelHandlerContext ctx, final FullHttpRequest msg)
      throws Exception {

    final RequestHandlerMapping mapping = config.getRequestMapping(msg.getUri());

    String relativePath = msg.getUri();

    if (mapping != null) {
      relativePath = relativePath.substring(mapping.path().length());
    }

    // Create request/response
    final PooledServerRequest request = messagePool.getRequest();

    // Handle 503 - sanity check, should be caught in acceptor
    if (request == null) {
      sendServerError(ctx, new ServerTooBusyException("Maximum concurrent connections reached"));
      return;
    }

    request.init(ctx.channel(), msg, relativePath);

    final RequestHandler handler = mapping == null ? null : mapping.handler(request);

    final PooledServerResponse response = messagePool.getResponse();
    response.init(ctx, this, handler, request, config.logger());

    if (mapping == null) {
      // No handler found, 404
      response.setStatus(HttpResponseStatus.NOT_FOUND);
    }

    // Store in ChannelHandlerContext for future reference
    ctx.attr(ATTR_RESPONSE).set(response);

    try {

      // MJS: Dispatch an error if not found or authorized
      if (response.getStatus() == HttpResponseStatus.UNAUTHORIZED
          || response.getStatus() == HttpResponseStatus.NOT_FOUND) {
        config.errorHandler().onError(request, response, null);
      } else {
        handler.onRequest(request, response);
      }

    } catch (final Throwable t) {

      // Catch server errors
      response.setStatus(HttpResponseStatus.INTERNAL_SERVER_ERROR);

      try {
        config.errorHandler().onError(request, response, t);
      } catch (final Throwable t2) {
        response.write(
            t.getClass()
                + " was thrown while processing this request.  Additionally, "
                + t2.getClass()
                + " was thrown while handling this exception.");
      }

      config.logger().error(request, response, t);

      // Force request to end on exception, async handlers cannot allow
      // unchecked exceptions and still expect to return data
      if (!response.isFinished()) {
        response.finish();
      }

    } finally {

      // If handler did not request async response, finish request
      if (!response.isFinished() && !response.isSuspended()) {
        response.finish();
      }
    }
  }

  private void sendServerError(final ChannelHandlerContext ctx, final ServerException cause)
      throws Exception {

    if (ctx.channel().isActive()) {

      final ByteBuf content = Unpooled.buffer();

      content.writeBytes(
          (cause.getStatus().code()
                  + " "
                  + cause.getStatus().reasonPhrase()
                  + " - "
                  + cause.getMessage())
              .getBytes());

      final FullHttpResponse response =
          new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, cause.getStatus());

      response.headers().set(HttpHeaders.Names.CONTENT_LENGTH, content.readableBytes());

      response.content().writeBytes(content);

      ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
    }
  }

  @Override
  public void channelInactive(final ChannelHandlerContext ctx) {

    final PooledServerResponse response = ctx.attr(ATTR_RESPONSE).get();

    if (response != null) {

      try {

        if (!response.isFinished()) {

          response.close();

          final RequestHandler handler = response.handler();

          if (handler != null) {
            handler.onAbort(response.request(), response);
          }
        }

      } finally {

        freeHandlers(ctx);
      }
    }
  }

  @Override
  public void exceptionCaught(final ChannelHandlerContext ctx, final Throwable exception)
      throws Exception {

    final PooledServerResponse response = ctx.attr(ATTR_RESPONSE).get();

    if (response != null) {

      try {

        try {

          if (!response.isFinished()) {

            response.setStatus(HttpResponseStatus.INTERNAL_SERVER_ERROR);

            config.errorHandler().onError(response.request(), response, exception);

            response.close();

            final RequestHandler handler = response.handler();

            if (handler != null) {
              handler.onException(response.request(), response, exception);
            }
          }

        } finally {

          config.logger().error(response.request(), response, exception);
        }

      } finally {

        freeHandlers(ctx);
      }
    }
  }

  /** Free any request/response handlers related to the current channel handler context. */
  public void freeHandlers(final ChannelHandlerContext ctx) {

    final PooledServerResponse response = ctx.attr(ATTR_RESPONSE).getAndRemove();

    if (response != null) {

      try {

        final RequestHandler handler = response.handler();

        if (handler != null) {
          handler.onComplete(response.request(), response);
        }

      } finally {

        response.request().release();
        messagePool.makeAvailable(response.request());

        response.close();

        messagePool.makeAvailable(response);
      }
    }
  }
}
Exemplo n.º 11
0
/**
 * Channel handler deals with the switch connection and dispatches messages to the higher orders of
 * control.
 *
 * @author Jason Parraga <*****@*****.**>
 */
class OFChannelHandler extends SimpleChannelInboundHandler<Iterable<OFMessage>> {

  private static final Logger log = LoggerFactory.getLogger(OFChannelHandler.class);

  public static final AttributeKey<OFChannelInfo> ATTR_CHANNEL_INFO =
      AttributeKey.valueOf("channelInfo");

  private final ChannelPipeline pipeline;
  private final INewOFConnectionListener newConnectionListener;
  private final SwitchManagerCounters counters;
  private Channel channel;
  private final Timer timer;
  private volatile OFChannelState state;
  private OFFactory factory;
  private OFFeaturesReply featuresReply;
  private volatile OFConnection connection;
  private final IDebugCounterService debugCounters;
  private final List<U32> ofBitmaps;

  /**
   * transaction Ids to use during handshake. Since only one thread calls into the OFChannelHandler
   * we don't need atomic. We will count down
   */
  private long handshakeTransactionIds = 0x00FFFFFFFFL;

  private volatile long echoSendTime;
  private volatile long featuresLatency;

  /**
   * Default implementation for message handlers in any OFChannelState.
   *
   * <p>Individual states must override these if they want a behavior that differs from the default.
   */
  public abstract class OFChannelState {

    void processOFHello(OFHello m) throws IOException {
      // we only expect hello in the WAIT_HELLO state
      illegalMessageReceived(m);
    }

    void processOFEchoRequest(OFEchoRequest m) throws IOException {
      sendEchoReply(m);
    }

    void processOFEchoReply(OFEchoReply m) throws IOException {
      /* Update the latency -- halve it for one-way time */
      updateLatency(U64.of((System.currentTimeMillis() - echoSendTime) / 2));
    }

    void processOFError(OFErrorMsg m) {
      logErrorDisconnect(m);
    }

    void processOFExperimenter(OFExperimenter m) {
      unhandledMessageReceived(m);
    }

    void processOFFeaturesReply(OFFeaturesReply m) throws IOException {
      // we only expect features reply in the WAIT_FEATURES_REPLY state
      illegalMessageReceived(m);
    }

    void processOFPortStatus(OFPortStatus m) {
      unhandledMessageReceived(m);
    }

    private final boolean channelHandshakeComplete;

    OFChannelState(boolean handshakeComplete) {
      this.channelHandshakeComplete = handshakeComplete;
    }

    void logState() {
      log.debug(
          "{} OFConnection Handshake - enter state {}",
          getConnectionInfoString(),
          this.getClass().getSimpleName());
    }

    /**
     * enter this state. Can initialize the handler, send the necessary messages, etc.
     *
     * @throws IOException
     */
    void enterState() throws IOException {
      // Do Nothing
    }

    /**
     * Get a string specifying the switch connection, state, and message received. To be used as
     * message for SwitchStateException or log messages
     *
     * @param h The channel handler (to get switch information_
     * @param m The OFMessage that has just been received
     * @param details A string giving more details about the exact nature of the problem.
     * @return
     */
    // needs to be protected because enum members are acutally subclasses
    protected String getSwitchStateMessage(OFMessage m, String details) {
      return String.format(
          "Switch: [%s], State: [%s], received: [%s]" + ", details: %s",
          getConnectionInfoString(), this.toString(), m.getType().toString(), details);
    }

    /**
     * We have an OFMessage we didn't expect given the current state and we want to treat this as an
     * error. We currently throw an exception that will terminate the connection However, we could
     * be more forgiving
     *
     * @param h the channel handler that received the message
     * @param m the message
     * @throws SwitchStateExeption we always through the execption
     */
    // needs to be protected because enum members are acutally subclasses
    protected void illegalMessageReceived(OFMessage m) {
      String msg =
          getSwitchStateMessage(m, "Switch should never send this message in the current state");
      throw new SwitchStateException(msg);
    }

    /**
     * We have an OFMessage we didn't expect given the current state and we want to ignore the
     * message
     *
     * @param h the channel handler the received the message
     * @param m the message
     */
    protected void unhandledMessageReceived(OFMessage m) {
      counters.unhandledMessage.increment();
      if (log.isDebugEnabled()) {
        String msg = getSwitchStateMessage(m, "Ignoring unexpected message");
        log.debug(msg);
      }
    }

    /**
     * Log an OpenFlow error message from a switch
     *
     * @param sw The switch that sent the error
     * @param error The error message
     */
    protected void logError(OFErrorMsg error) {
      log.error(
          "{} from switch {} in state {}",
          new Object[] {error.toString(), getConnectionInfoString(), this.toString()});
    }

    /**
     * Log an OpenFlow error message from a switch and disconnect the channel
     *
     * @param sw The switch that sent the error
     * @param error The error message
     */
    protected void logErrorDisconnect(OFErrorMsg error) {
      logError(error);
      channel.disconnect();
    }

    /**
     * Process an OF message received on the channel and update state accordingly.
     *
     * <p>The main "event" of the state machine. Process the received message, send follow up
     * message if required and update state if required.
     *
     * <p>Switches on the message type and calls more specific event handlers for each individual OF
     * message type. If we receive a message that is supposed to be sent from a controller to a
     * switch we throw a SwitchStateExeption.
     *
     * <p>The more specific handlers can also throw SwitchStateExceptions
     *
     * @param h The OFChannelHandler that received the message
     * @param m The message we received.
     * @throws SwitchStateException
     * @throws IOException
     */
    void processOFMessage(OFMessage m) throws IOException {
      // Handle Channel Handshake
      if (!state.channelHandshakeComplete) {
        switch (m.getType()) {
          case HELLO:
            processOFHello((OFHello) m);
            break;
          case ERROR:
            processOFError((OFErrorMsg) m);
            break;
          case FEATURES_REPLY:
            processOFFeaturesReply((OFFeaturesReply) m);
            break;
          case EXPERIMENTER:
            processOFExperimenter((OFExperimenter) m);
            break;
            /* echos can be sent at any time */
          case ECHO_REPLY:
            processOFEchoReply((OFEchoReply) m);
            break;
          case ECHO_REQUEST:
            processOFEchoRequest((OFEchoRequest) m);
            break;
          case PORT_STATUS:
            processOFPortStatus((OFPortStatus) m);
            break;
          default:
            illegalMessageReceived(m);
            break;
        }
      } else {
        switch (m.getType()) {
          case ECHO_REPLY:
            processOFEchoReply((OFEchoReply) m);
            break;
          case ECHO_REQUEST:
            processOFEchoRequest((OFEchoRequest) m);
            break;
            // Send to SwitchManager and thus higher orders of control
          default:
            sendMessageToConnection(m);
            break;
        }
      }
    }
  }

  /** Initial state before channel is connected. */
  class InitState extends OFChannelState {

    InitState() {
      super(false);
    }
  }

  /**
   * We send a HELLO to the switch and wait for a reply. Once we receive the reply we send an
   * OFFeaturesRequest Next state is WaitFeaturesReplyState
   */
  class WaitHelloState extends OFChannelState {

    WaitHelloState() {
      super(false);
    }

    @Override
    void processOFHello(OFHello m) throws IOException {
      OFVersion theirVersion = m.getVersion();
      OFVersion commonVersion = null;
      /* First, check if there's a version bitmap supplied. WE WILL ALWAYS HAVE a controller-provided version bitmap. */
      if (theirVersion.compareTo(OFVersion.OF_13) >= 0 && !m.getElements().isEmpty()) {
        List<U32> bitmaps = new ArrayList<U32>();
        List<OFHelloElem> elements = m.getElements();
        /* Grab all bitmaps supplied */
        for (OFHelloElem e : elements) {
          if (e instanceof OFHelloElemVersionbitmap) {
            bitmaps.addAll(((OFHelloElemVersionbitmap) e).getBitmaps());
          } else {
            log.warn("Unhandled OFHelloElem {}", e);
          }
        }
        /* Lookup highest, common supported OpenFlow version */
        commonVersion = computeOFVersionFromBitmap(bitmaps);
        if (commonVersion == null) {
          log.error(
              "Could not negotiate common OpenFlow version for {} with greatest version bitmap algorithm.",
              channel.remoteAddress());
          channel.disconnect();
          return;
        } else {
          log.info(
              "Negotiated OpenFlow version of {} for {} with greatest version bitmap algorithm.",
              commonVersion.toString(),
              channel.remoteAddress());
          factory = OFFactories.getFactory(commonVersion);
          OFMessageDecoder decoder = pipeline.get(OFMessageDecoder.class);
          decoder.setVersion(commonVersion);
        }
      }
      /* If there's not a bitmap present, choose the lower of the two supported versions. */
      else if (theirVersion.compareTo(factory.getVersion()) < 0) {
        log.info(
            "Negotiated down to switch OpenFlow version of {} for {} using lesser hello header algorithm.",
            theirVersion.toString(),
            channel.remoteAddress());
        factory = OFFactories.getFactory(theirVersion);
        OFMessageDecoder decoder = pipeline.get(OFMessageDecoder.class);
        decoder.setVersion(theirVersion);
      } /* else The controller's version is < or = the switch's, so keep original controller factory. */ else if (theirVersion
          .equals(factory.getVersion())) {
        log.info(
            "Negotiated equal OpenFlow version of {} for {} using lesser hello header algorithm.",
            factory.getVersion().toString(),
            channel.remoteAddress());
      } else {
        log.info(
            "Negotiated down to controller OpenFlow version of {} for {} using lesser hello header algorithm.",
            factory.getVersion().toString(),
            channel.remoteAddress());
      }

      setState(new WaitFeaturesReplyState());
    }

    @Override
    void enterState() throws IOException {
      sendHelloMessage();
    }
  }

  /**
   * We are waiting for a features reply message. Once we receive it we send capture the features
   * reply. Next state is CompleteState
   */
  class WaitFeaturesReplyState extends OFChannelState {
    WaitFeaturesReplyState() {
      super(false);
    }

    @Override
    void processOFFeaturesReply(OFFeaturesReply m) throws IOException {
      featuresReply = m;

      featuresLatency = (System.currentTimeMillis() - featuresLatency) / 2;

      // Mark handshake as completed
      setState(new CompleteState());
    }

    @Override
    void processOFHello(OFHello m) throws IOException {
      /*
       * Brocade switches send a second hello after
       * the controller responds with its hello. This
       * might be to confirm the protocol version used,
       * but isn't defined in the OF specification.
       *
       * We will ignore such hello messages assuming
       * the version of the hello is correct according
       * to the algorithm in the spec.
       *
       * TODO Brocade also sets the XID of this second
       * hello as the same XID the controller used.
       * Checking for this might help to assure we're
       * really dealing with the situation we think
       * we are.
       */
      if (m.getVersion().equals(factory.getVersion())) {
        log.warn(
            "Ignoring second hello from {} in state {}. Might be a Brocade.",
            channel.remoteAddress(),
            state.toString());
      } else {
        super.processOFHello(m); /* Versions don't match as they should; abort */
      }
    }

    @Override
    void processOFPortStatus(OFPortStatus m) {
      log.warn(
          "Ignoring PORT_STATUS message from {} during OpenFlow channel establishment. Ports will be explicitly queried in a later state.",
          channel.remoteAddress());
    }

    @Override
    void enterState() throws IOException {
      sendFeaturesRequest();
      featuresLatency = System.currentTimeMillis();
    }

    @Override
    void processOFMessage(OFMessage m) throws IOException {
      if (m.getType().equals(OFType.PACKET_IN)) {
        log.warn(
            "Ignoring PACKET_IN message from {} during OpenFlow channel establishment.",
            channel.remoteAddress());
      } else {
        super.processOFMessage(m);
      }
    }
  };

  /**
   * This state denotes that the channel handshaking is complete. An OF connection is generated and
   * passed to the switch manager for handling.
   */
  class CompleteState extends OFChannelState {

    CompleteState() {
      super(true);
    }

    @Override
    void enterState() throws IOException {

      setSwitchHandshakeTimeout();

      // Handle non 1.3 connections
      if (featuresReply.getVersion().compareTo(OFVersion.OF_13) < 0) {
        connection =
            new OFConnection(
                featuresReply.getDatapathId(),
                factory,
                channel,
                OFAuxId.MAIN,
                debugCounters,
                timer);
      }
      // Handle 1.3 connections
      else {
        connection =
            new OFConnection(
                featuresReply.getDatapathId(),
                factory,
                channel,
                featuresReply.getAuxiliaryId(),
                debugCounters,
                timer);

        // If this is an aux connection, we set a longer echo idle time
        if (!featuresReply.getAuxiliaryId().equals(OFAuxId.MAIN)) {
          setAuxChannelIdle();
        }
      }

      connection.updateLatency(U64.of(featuresLatency));
      echoSendTime = 0;

      // Notify the connection broker
      notifyConnectionOpened(connection);
    }
  };

  /**
   * Creates a handler for interacting with the switch channel
   *
   * @param controller the controller
   * @param newConnectionListener the class that listens for new OF connections (switchManager)
   * @param pipeline the channel pipeline
   * @param threadPool the thread pool
   * @param idleTimer the hash wheeled timer used to send idle messages (echo). passed to
   *     constructor to modify in case of aux connection.
   * @param debugCounters
   */
  OFChannelHandler(
      @Nonnull IOFSwitchManager switchManager,
      @Nonnull INewOFConnectionListener newConnectionListener,
      @Nonnull ChannelPipeline pipeline,
      @Nonnull IDebugCounterService debugCounters,
      @Nonnull Timer timer,
      @Nonnull List<U32> ofBitmaps,
      @Nonnull OFFactory defaultFactory) {

    Preconditions.checkNotNull(switchManager, "switchManager");
    Preconditions.checkNotNull(newConnectionListener, "connectionOpenedListener");
    Preconditions.checkNotNull(pipeline, "pipeline");
    Preconditions.checkNotNull(timer, "timer");
    Preconditions.checkNotNull(debugCounters, "debugCounters");

    this.pipeline = pipeline;
    this.debugCounters = debugCounters;
    this.newConnectionListener = newConnectionListener;
    this.counters = switchManager.getCounters();
    this.state = new InitState();
    this.timer = timer;
    this.ofBitmaps = ofBitmaps;
    this.factory = defaultFactory;

    log.debug(
        "constructor on OFChannelHandler {}", String.format("%08x", System.identityHashCode(this)));
  }

  /**
   * Determine the highest supported version of OpenFlow in common between both our OFVersion bitmap
   * and the switch's.
   *
   * @param theirs, the version bitmaps of the switch
   * @return the highest OFVersion in common b/t the two
   */
  private OFVersion computeOFVersionFromBitmap(List<U32> theirs) {
    Iterator<U32> theirsItr = theirs.iterator();
    Iterator<U32> oursItr = ofBitmaps.iterator();
    OFVersion version = null;
    int pos = 0;
    int size = 32;
    while (theirsItr.hasNext() && oursItr.hasNext()) {
      int t = theirsItr.next().getRaw();
      int o = oursItr.next().getRaw();

      int common = t & o; /* Narrow down the results to the common bits */
      for (int i = 0; i < size; i++) {
          /* Iterate over and locate the 1's */
        int tmp = common & (1 << i); /* Select the bit of interest, 0-31 */
        if (tmp != 0) {
            /* Is the version of this bit in common? */
          for (OFVersion v : OFVersion.values()) {
              /* Which version does this bit represent? */
            if (v.getWireVersion() == i + (size * pos)) {
              version = v;
            }
          }
        }
      }
      pos++; /* OFVersion position. 1-31 = 1, 32 - 63 = 2, etc. Inc at end so it starts at 0. */
    }
    return version;
  }

  /**
   * Determines if the entire switch handshake is complete (channel+switch). If the channel
   * handshake is complete the call is forwarded to the connection listener/switch manager to be
   * handled by the appropriate switch handshake handler.
   *
   * @return whether or not complete switch handshake is complete
   */
  public boolean isSwitchHandshakeComplete() {
    if (this.state.channelHandshakeComplete) {
      return connection.getListener().isSwitchHandshakeComplete(connection);
    } else {
      return false;
    }
  }

  /** Notifies the channel listener that we have a valid baseline connection */
  private final void notifyConnectionOpened(OFConnection connection) {
    this.connection = connection;
    this.newConnectionListener.connectionOpened(connection, featuresReply);
  }

  /** Notifies the channel listener that we our connection has been closed */
  private final void notifyConnectionClosed(OFConnection connection) {
    connection.getListener().connectionClosed(connection);
  }

  /** Notifies the channel listener that we have a valid baseline connection */
  private final void sendMessageToConnection(OFMessage m) {
    connection.messageReceived(m);
  }

  @Override
  public void channelActive(ChannelHandlerContext ctx) throws Exception {
    log.debug(
        "channelConnected on OFChannelHandler {}",
        String.format("%08x", System.identityHashCode(this)));
    counters.switchConnected.increment();
    channel = ctx.channel();
    log.info("New switch connection from {}", channel.remoteAddress());
    setState(new WaitHelloState());
  }

  @Override
  public void channelInactive(ChannelHandlerContext ctx) {
    // Only handle cleanup connection is even known
    if (this.connection != null) {
      // Alert the connection object that the channel has been disconnected
      this.connection.disconnected();
      // Punt the cleanup to the Switch Manager
      notifyConnectionClosed(this.connection);
    }
    log.info("[{}] Disconnected connection", getConnectionInfoString());
  }

  @Override
  public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
    if (cause instanceof ReadTimeoutException) {

      if (featuresReply.getVersion().compareTo(OFVersion.OF_13) < 0) {
        log.error(
            "Disconnecting switch {} due to read timeout on main cxn.", getConnectionInfoString());
        ctx.channel().close();
      } else {
        if (featuresReply.getAuxiliaryId().equals(OFAuxId.MAIN)) {
          log.error(
              "Disconnecting switch {} due to read timeout on main cxn.",
              getConnectionInfoString());
          ctx.channel().close();
        } else {
          // We only don't disconnect on aux connections
          log.warn("Switch {} encountered read timeout on aux cxn.", getConnectionInfoString());
        }
      }
      // Increment counters
      counters.switchDisconnectReadTimeout.increment();

    } else if (cause instanceof HandshakeTimeoutException) {
      log.error(
          "Disconnecting switch {}: failed to complete handshake. Channel handshake complete : {}",
          getConnectionInfoString(),
          this.state.channelHandshakeComplete);
      counters.switchDisconnectHandshakeTimeout.increment();
      ctx.channel().close();
    } else if (cause instanceof ClosedChannelException) {
      log.debug("Channel for sw {} already closed", getConnectionInfoString());
    } else if (cause instanceof IOException) {
      log.error(
          "Disconnecting switch {} due to IO Error: {}",
          getConnectionInfoString(),
          cause.getMessage());
      if (log.isDebugEnabled()) {
        // still print stack trace if debug is enabled
        log.debug("StackTrace for previous Exception: ", cause);
      }
      counters.switchDisconnectIOError.increment();
      ctx.channel().close();
    } else if (cause instanceof SwitchStateException) {
      log.error(
          "Disconnecting switch {} due to switch state error: {}",
          getConnectionInfoString(),
          cause.getMessage());
      if (log.isDebugEnabled()) {
        // still print stack trace if debug is enabled
        log.debug("StackTrace for previous Exception: ", cause);
      }
      counters.switchDisconnectSwitchStateException.increment();
      ctx.channel().close();
    } else if (cause instanceof OFAuxException) {
      log.error(
          "Disconnecting switch {} due to OF Aux error: {}",
          getConnectionInfoString(),
          cause.getMessage());
      if (log.isDebugEnabled()) {
        // still print stack trace if debug is enabled
        log.debug("StackTrace for previous Exception: ", cause);
      }
      counters.switchDisconnectSwitchStateException.increment();
      ctx.channel().close();
    } else if (cause instanceof OFParseError) {
      log.error(
          "Disconnecting switch " + getConnectionInfoString() + " due to message parse failure",
          cause);
      counters.switchDisconnectParseError.increment();
      ctx.channel().close();
    } else if (cause instanceof RejectedExecutionException) {
      log.warn("Could not process message: queue full");
      counters.rejectedExecutionException.increment();
    } else if (cause instanceof IllegalArgumentException) {
      log.error("Illegal argument exception with switch {}. {}", getConnectionInfoString(), cause);
      counters.switchSslConfigurationError.increment();
      ctx.channel().close();
    } else {
      log.error(
          "Error while processing message from switch "
              + getConnectionInfoString()
              + "state "
              + this.state,
          cause);
      counters.switchDisconnectOtherException.increment();
      ctx.channel().close();
    }
  }

  @Override
  public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
    log.debug(
        "channelIdle on OFChannelHandler {}", String.format("%08x", System.identityHashCode(this)));
    OFChannelHandler handler = ctx.pipeline().get(OFChannelHandler.class);
    handler.sendEchoRequest();
  }

  @Override
  public void channelRead0(ChannelHandlerContext ctx, Iterable<OFMessage> msgList)
      throws Exception {
    for (OFMessage ofm : msgList) {
      try {
        // Do the actual packet processing
        state.processOFMessage(ofm);
      } catch (Exception ex) {
        // We are the last handler in the stream, so run the
        // exception through the channel again by passing in
        // ctx.getChannel().
        ctx.fireExceptionCaught(ex);
      }
    }
  }

  /**
   * Sets the channel pipeline's idle (Echo) timeouts to a longer interval. This is specifically for
   * aux channels.
   */
  private void setAuxChannelIdle() {
    IdleStateHandler idleHandler =
        new IdleStateHandler(PipelineIdleReadTimeout.AUX, PipelineIdleWriteTimeout.AUX, 0);
    pipeline.replace(PipelineHandler.MAIN_IDLE, PipelineHandler.AUX_IDLE, idleHandler);
  }

  /**
   * Sets the channel pipeline's handshake timeout to a more appropriate value for the remaining
   * part of the switch handshake.
   */
  private void setSwitchHandshakeTimeout() {

    HandshakeTimeoutHandler handler =
        new HandshakeTimeoutHandler(this, this.timer, PipelineHandshakeTimeout.SWITCH);

    pipeline.replace(
        PipelineHandler.CHANNEL_HANDSHAKE_TIMEOUT,
        PipelineHandler.SWITCH_HANDSHAKE_TIMEOUT,
        handler);
  }

  /**
   * Return a string describing this switch based on the already available information (DPID and/or
   * remote socket)
   *
   * @return
   */
  private String getConnectionInfoString() {

    String channelString;
    if (channel == null || channel.remoteAddress() == null) {
      channelString = "?";
    } else {
      channelString = channel.remoteAddress().toString();
      if (channelString.startsWith("/")) channelString = channelString.substring(1);
    }
    String dpidString;
    if (featuresReply == null) {
      dpidString = "?";
    } else {
      StringBuilder b = new StringBuilder();
      b.append(featuresReply.getDatapathId());
      if (featuresReply.getVersion().compareTo(OFVersion.OF_13) >= 0) {
        b.append("(").append(featuresReply.getAuxiliaryId()).append(")");
      }
      dpidString = b.toString();
    }
    return String.format("[%s from %s]", dpidString, channelString);
  }

  /**
   * Update the channels state. Only called from the state machine.
   *
   * @param state
   * @throws IOException
   */
  private void setState(OFChannelState state) throws IOException {
    this.state = state;
    state.logState();
    state.enterState();
  }

  /**
   * Send a features request message to the switch using the handshake transactions ids.
   *
   * @throws IOException
   */
  private void sendFeaturesRequest() throws IOException {
    // Send initial Features Request
    OFFeaturesRequest m = factory.buildFeaturesRequest().setXid(handshakeTransactionIds--).build();
    write(m);
  }

  /**
   * Send a hello message to the switch using the handshake transactions ids.
   *
   * @throws IOException
   */
  private void sendHelloMessage() throws IOException {
    // Send initial hello message

    OFHello.Builder builder = factory.buildHello();

    /* Our highest-configured OFVersion does support version bitmaps, so include it */
    if (factory.getVersion().compareTo(OFVersion.OF_13) >= 0) {
      List<OFHelloElem> he = new ArrayList<OFHelloElem>();
      he.add(factory.buildHelloElemVersionbitmap().setBitmaps(ofBitmaps).build());
      builder.setElements(he);
    }

    OFHello m = builder.setXid(handshakeTransactionIds--).build();

    write(m);
    log.debug("Send hello: {}", m);
  }

  private void sendEchoRequest() {
    OFEchoRequest request = factory.buildEchoRequest().setXid(handshakeTransactionIds--).build();
    /* Record for latency calculation */
    echoSendTime = System.currentTimeMillis();
    write(request);
  }

  private void sendEchoReply(OFEchoRequest request) {
    OFEchoReply reply =
        factory.buildEchoReply().setXid(request.getXid()).setData(request.getData()).build();
    write(reply);
  }

  private void write(OFMessage m) {
    channel.writeAndFlush(Collections.singletonList(m));
  }

  OFChannelState getStateForTesting() {
    return state;
  }

  IOFConnectionBackend getConnectionForTesting() {
    return connection;
  }

  ChannelPipeline getPipelineForTesting() {
    return this.pipeline;
  }

  private void updateLatency(U64 latency) {
    if (connection != null) {
      connection.updateLatency(latency);
    }
  }
}
Exemplo n.º 12
0
public class Decoder {

  public static final AttributeKey<InetSocketAddress> INET_ADDRESS_KEY =
      AttributeKey.valueOf("inet-addr");
  public static final AttributeKey<PeerAddress> PEER_ADDRESS_KEY =
      AttributeKey.valueOf("peer-addr");

  private static final Logger LOG = LoggerFactory.getLogger(Decoder.class);

  private final Queue<Content> contentTypes = new LinkedList<Message.Content>();

  // private Message2 result = null;

  // current state - needs to be deleted if we want to reuse
  private Message message = null;
  private boolean headerDone = false;
  private Signature signature = null;

  private int neighborSize = -1;
  private NeighborSet neighborSet = null;

  private int peerSocketAddressSize = -1;
  private List<PeerSocketAddress> peerSocketAddresses = null;

  private int keyCollectionSize = -1;
  private KeyCollection keyCollection = null;

  private int mapSize = -1;
  private DataMap dataMap = null;
  private Data data = null;
  private Number640 key = null;

  private int keyMap640KeysSize = -1;
  private KeyMap640Keys keyMap640Keys = null;

  private int keyMapByteSize = -1;
  private KeyMapByte keyMapByte = null;

  private int bufferSize = -1;
  private int bufferTransferred = 0;
  private DataBuffer buffer = null;

  private int trackerDataSize = -1;
  private TrackerData trackerData = null;
  private Data currentTrackerData = null;

  private Content lastContent = null;

  private final SignatureFactory signatureFactory;

  public Decoder(SignatureFactory signatureFactory) {
    this.signatureFactory = signatureFactory;
  }

  public boolean decode(
      ChannelHandlerContext ctx,
      final ByteBuf buf,
      InetSocketAddress recipient,
      final InetSocketAddress sender) {

    LOG.debug("Decoding of TomP2P starts now. Readable: {}.", buf.readableBytes());

    try {
      final int readerBefore = buf.readerIndex();
      // set the sender of this message for handling timeout
      final Attribute<InetSocketAddress> attributeInet = ctx.attr(INET_ADDRESS_KEY);
      attributeInet.set(sender);

      if (message == null && !headerDone) {
        headerDone = decodeHeader(buf, recipient, sender);
        if (headerDone) {
          // store the sender as an attribute
          final Attribute<PeerAddress> attributePeerAddress = ctx.attr(PEER_ADDRESS_KEY);
          attributePeerAddress.set(message.sender());
          message.udp(ctx.channel() instanceof DatagramChannel);
          if (message.isFireAndForget() && message.isUdp()) {
            TimeoutFactory.removeTimeout(ctx);
          }
        } else {
          return false;
        }
      }

      final boolean donePayload = decodePayload(buf);
      decodeSignature(buf, readerBefore, donePayload);

      if (donePayload) {
        boolean isRelay = message.sender().isRelayed();
        if (isRelay && !message.peerSocketAddresses().isEmpty()) {
          PeerAddress tmpSender =
              message.sender().changePeerSocketAddresses(message.peerSocketAddresses());
          message.sender(tmpSender);
        }
      }

      // see https://github.com/netty/netty/issues/1976
      buf.discardSomeReadBytes();
      return donePayload;

    } catch (Exception e) {
      ctx.fireExceptionCaught(e);
      e.printStackTrace();
      return true;
    }
  }

  public void decodeSignature(final ByteBuf buf, final int readerBefore, final boolean donePayload)
      throws InvalidKeyException, SignatureException {
    final int readerAfter = buf.readerIndex();
    final int len = readerAfter - readerBefore;
    if (len > 0) {
      verifySignature(buf, readerBefore, len, donePayload);
    }
  }

  private void verifySignature(
      final ByteBuf buf, final int readerBefore, final int len, final boolean donePayload)
      throws SignatureException, InvalidKeyException {

    if (!message.isSign()) {
      return;
    }
    // if we read the complete data, we also read the signature
    // for the verification, we should not use this for the signature
    final int length = donePayload ? len - signatureFactory.signatureSize() : len;
    ByteBuffer[] byteBuffers = buf.nioBuffers(readerBefore, length);
    if (signature == null) {
      signature = signatureFactory.update(message.publicKey(0), byteBuffers);
    } else {
      for (int i = 0; i < byteBuffers.length; i++) {
        signature.update(byteBuffers[i]);
      }
    }

    if (donePayload) {
      byte[] signatureReceived = message.receivedSignature().encode();
      LOG.debug("Verifying received signature: {}", Arrays.toString(signatureReceived));
      if (signature.verify(signatureReceived)) {
        // set public key only if signature is correct
        message.setVerified();
        LOG.debug("Signature check OK.");
      } else {
        LOG.warn("Signature check NOT OK. Message: {}.", message);
      }
    }
  }

  public boolean decodeHeader(
      final ByteBuf buf, InetSocketAddress recipient, final InetSocketAddress sender) {
    if (message == null) {
      if (buf.readableBytes() < MessageHeaderCodec.HEADER_SIZE) {
        // we don't have the header yet, we need the full header first
        // wait for more data
        return false;
      }
      message = MessageHeaderCodec.decodeHeader(buf, recipient, sender);
    }

    if (message.sender().isNet4Private()
        && buf.readableBytes() < MessageHeaderCodec.HEADER_PRIVATE_ADDRESS_SIZE) {
      return false;
    } else if (message.sender().isNet4Private()
        && buf.readableBytes() >= MessageHeaderCodec.HEADER_PRIVATE_ADDRESS_SIZE) {
      PeerSocketAddress internalPeerSocketAddress = PeerSocketAddress.create(buf, true);
      message.sender(message.sender().changeInternalPeerSocketAddress(internalPeerSocketAddress));
    }
    // we have set the content types already
    message.presetContentTypes(true);
    for (Content content : message.contentTypes()) {
      if (content == Content.EMPTY) {
        break;
      }
      if (content == Content.PUBLIC_KEY_SIGNATURE) {
        message.setHintSign();
      }
      contentTypes.offer(content);
    }
    LOG.debug("Parsed message {}.", message);
    return true;
  }

  public boolean decodePayload(final ByteBuf buf)
      throws NoSuchAlgorithmException, InvalidKeySpecException, InvalidKeyException {
    LOG.debug(
        "About to pass message {} to {}. Buffer to read: {}.",
        message,
        message.senderSocket(),
        buf.readableBytes());
    if (!message.hasContent()) {
      return true;
    }

    // payload comes here
    int size;
    PublicKey receivedPublicKey;
    while (contentTypes.size() > 0) {
      Content content = contentTypes.peek();
      LOG.debug("Parse content: {} in message {}", content, message);
      switch (content) {
        case INTEGER:
          if (buf.readableBytes() < Utils.INTEGER_BYTE_SIZE) {
            return false;
          }
          message.intValue(buf.readInt());
          lastContent = contentTypes.poll();
          break;
        case LONG:
          if (buf.readableBytes() < Utils.LONG_BYTE_SIZE) {
            return false;
          }
          message.longValue(buf.readLong());
          lastContent = contentTypes.poll();
          break;
        case KEY:
          if (buf.readableBytes() < Number160.BYTE_ARRAY_SIZE) {
            return false;
          }
          byte[] me = new byte[Number160.BYTE_ARRAY_SIZE];
          buf.readBytes(me);
          message.key(new Number160(me));
          lastContent = contentTypes.poll();
          break;
        case BLOOM_FILTER:
          if (buf.readableBytes() < Utils.SHORT_BYTE_SIZE) {
            return false;
          }
          size = buf.getUnsignedShort(buf.readerIndex());
          if (buf.readableBytes() < size) {
            return false;
          }
          message.bloomFilter(new SimpleBloomFilter<Number160>(buf));
          lastContent = contentTypes.poll();
          break;
        case SET_NEIGHBORS:
          if (neighborSize == -1 && buf.readableBytes() < Utils.BYTE_BYTE_SIZE) {
            return false;
          }
          if (neighborSize == -1) {
            neighborSize = buf.readUnsignedByte();
          }
          if (neighborSet == null) {
            neighborSet = new NeighborSet(-1, new ArrayList<PeerAddress>(neighborSize));
          }
          for (int i = neighborSet.size(); i < neighborSize; i++) {
            if (buf.readableBytes() < Utils.SHORT_BYTE_SIZE) {
              return false;
            }
            int header = buf.getUnsignedShort(buf.readerIndex());
            size = PeerAddress.size(header);
            if (buf.readableBytes() < size) {
              return false;
            }
            PeerAddress pa = new PeerAddress(buf);
            neighborSet.add(pa);
          }
          message.neighborsSet(neighborSet);
          lastContent = contentTypes.poll();
          neighborSize = -1;
          neighborSet = null;
          break;
        case SET_PEER_SOCKET:
          if (peerSocketAddressSize == -1 && buf.readableBytes() < Utils.BYTE_BYTE_SIZE) {
            return false;
          }
          if (peerSocketAddressSize == -1) {
            peerSocketAddressSize = buf.readUnsignedByte();
          }
          if (peerSocketAddresses == null) {
            peerSocketAddresses = new ArrayList<PeerSocketAddress>(peerSocketAddressSize);
          }
          for (int i = peerSocketAddresses.size(); i < peerSocketAddressSize; i++) {
            if (buf.readableBytes() < Utils.BYTE_BYTE_SIZE) {
              return false;
            }
            int header = buf.getUnsignedByte(buf.readerIndex());
            boolean isIPv4 = header == 0;
            size = PeerSocketAddress.size(isIPv4);
            if (buf.readableBytes() < size + Utils.BYTE_BYTE_SIZE) {
              return false;
            }
            // skip the ipv4/ipv6 header
            buf.skipBytes(1);
            peerSocketAddresses.add(PeerSocketAddress.create(buf, isIPv4));
          }
          message.peerSocketAddresses(peerSocketAddresses);
          lastContent = contentTypes.poll();
          peerSocketAddressSize = -1;
          peerSocketAddresses = null;
          break;
        case SET_KEY640:
          if (keyCollectionSize == -1 && buf.readableBytes() < Utils.INTEGER_BYTE_SIZE) {
            return false;
          }
          if (keyCollectionSize == -1) {
            keyCollectionSize = buf.readInt();
          }
          if (keyCollection == null) {
            keyCollection = new KeyCollection(new ArrayList<Number640>(keyCollectionSize));
          }
          for (int i = keyCollection.size(); i < keyCollectionSize; i++) {
            if (buf.readableBytes()
                < Number160.BYTE_ARRAY_SIZE
                    + Number160.BYTE_ARRAY_SIZE
                    + Number160.BYTE_ARRAY_SIZE
                    + Number160.BYTE_ARRAY_SIZE) {
              return false;
            }
            byte[] me2 = new byte[Number160.BYTE_ARRAY_SIZE];
            buf.readBytes(me2);
            Number160 locationKey = new Number160(me2);
            buf.readBytes(me2);
            Number160 domainKey = new Number160(me2);
            buf.readBytes(me2);
            Number160 contentKey = new Number160(me2);
            buf.readBytes(me2);
            Number160 versionKey = new Number160(me2);
            keyCollection.add(new Number640(locationKey, domainKey, contentKey, versionKey));
          }
          message.keyCollection(keyCollection);
          lastContent = contentTypes.poll();
          keyCollectionSize = -1;
          keyCollection = null;
          break;
        case MAP_KEY640_DATA:
          if (mapSize == -1 && buf.readableBytes() < Utils.INTEGER_BYTE_SIZE) {
            return false;
          }
          if (mapSize == -1) {
            mapSize = buf.readInt();
          }
          if (dataMap == null) {
            dataMap = new DataMap(new TreeMap<Number640, Data>());
          }
          if (data != null) {
            if (!data.decodeBuffer(buf)) {
              return false;
            }
            if (!data.decodeDone(buf, message.publicKey(0), signatureFactory)) {
              return false;
            }
            data = null;
            key = null;
          }
          for (int i = dataMap.size(); i < mapSize; i++) {
            if (key == null) {
              if (buf.readableBytes()
                  < Number160.BYTE_ARRAY_SIZE
                      + Number160.BYTE_ARRAY_SIZE
                      + Number160.BYTE_ARRAY_SIZE
                      + Number160.BYTE_ARRAY_SIZE) {
                return false;
              }
              byte[] me3 = new byte[Number160.BYTE_ARRAY_SIZE];
              buf.readBytes(me3);
              Number160 locationKey = new Number160(me3);
              buf.readBytes(me3);
              Number160 domainKey = new Number160(me3);
              buf.readBytes(me3);
              Number160 contentKey = new Number160(me3);
              buf.readBytes(me3);
              Number160 versionKey = new Number160(me3);
              key = new Number640(locationKey, domainKey, contentKey, versionKey);
            }
            LOG.debug("Key decoded in message {}, remaining {}", message, buf.readableBytes());
            data = Data.decodeHeader(buf, signatureFactory);
            if (data == null) {
              return false;
            }
            LOG.debug("Header decoded in message {}, remaining {}", message, buf.readableBytes());
            dataMap.dataMap().put(key, data);

            if (!data.decodeBuffer(buf)) {
              return false;
            }
            LOG.debug("Buffer decoded in message {}", message);
            if (!data.decodeDone(buf, message.publicKey(0), signatureFactory)) {
              return false;
            }
            LOG.debug("Done decoded in message {}", message);
            // if we have signed the message, set the public key anyway, but only if we indicated so
            inheritPublicKey(message, data);
            data = null;
            key = null;
          }

          message.setDataMap(dataMap);
          lastContent = contentTypes.poll();
          mapSize = -1;
          dataMap = null;
          break;
        case MAP_KEY640_KEYS:
          if (keyMap640KeysSize == -1 && buf.readableBytes() < Utils.INTEGER_BYTE_SIZE) {
            return false;
          }
          if (keyMap640KeysSize == -1) {
            keyMap640KeysSize = buf.readInt();
          }
          if (keyMap640Keys == null) {
            keyMap640Keys = new KeyMap640Keys(new TreeMap<Number640, Collection<Number160>>());
          }

          final int meta =
              Number160.BYTE_ARRAY_SIZE
                  + Number160.BYTE_ARRAY_SIZE
                  + Number160.BYTE_ARRAY_SIZE
                  + Number160.BYTE_ARRAY_SIZE;

          for (int i = keyMap640Keys.size(); i < keyMap640KeysSize; i++) {
            if (buf.readableBytes() < meta + Utils.BYTE_BYTE_SIZE) {
              return false;
            }
            size = buf.getUnsignedByte(buf.readerIndex() + meta);

            if (buf.readableBytes()
                < meta + Utils.BYTE_BYTE_SIZE + (size * Number160.BYTE_ARRAY_SIZE)) {
              return false;
            }
            byte[] me3 = new byte[Number160.BYTE_ARRAY_SIZE];
            buf.readBytes(me3);
            Number160 locationKey = new Number160(me3);
            buf.readBytes(me3);
            Number160 domainKey = new Number160(me3);
            buf.readBytes(me3);
            Number160 contentKey = new Number160(me3);
            buf.readBytes(me3);
            Number160 versionKey = new Number160(me3);

            int numBasedOn = buf.readByte();
            Set<Number160> value = new HashSet<Number160>(numBasedOn);
            for (int j = 0; j < numBasedOn; j++) {
              buf.readBytes(me3);
              Number160 basedOnKey = new Number160(me3);
              value.add(basedOnKey);
            }

            keyMap640Keys.put(new Number640(locationKey, domainKey, contentKey, versionKey), value);
          }

          message.keyMap640Keys(keyMap640Keys);
          lastContent = contentTypes.poll();
          keyMap640KeysSize = -1;
          keyMap640Keys = null;
          break;
        case MAP_KEY640_BYTE:
          if (keyMapByteSize == -1 && buf.readableBytes() < Utils.INTEGER_BYTE_SIZE) {
            return false;
          }
          if (keyMapByteSize == -1) {
            keyMapByteSize = buf.readInt();
          }
          if (keyMapByte == null) {
            keyMapByte = new KeyMapByte(new HashMap<Number640, Byte>(2 * keyMapByteSize));
          }

          for (int i = keyMapByte.size(); i < keyMapByteSize; i++) {
            if (buf.readableBytes()
                < Number160.BYTE_ARRAY_SIZE
                    + Number160.BYTE_ARRAY_SIZE
                    + Number160.BYTE_ARRAY_SIZE
                    + Number160.BYTE_ARRAY_SIZE
                    + 1) {
              return false;
            }
            byte[] me3 = new byte[Number160.BYTE_ARRAY_SIZE];
            buf.readBytes(me3);
            Number160 locationKey = new Number160(me3);
            buf.readBytes(me3);
            Number160 domainKey = new Number160(me3);
            buf.readBytes(me3);
            Number160 contentKey = new Number160(me3);
            buf.readBytes(me3);
            Number160 versionKey = new Number160(me3);
            byte value = buf.readByte();
            keyMapByte.put(new Number640(locationKey, domainKey, contentKey, versionKey), value);
          }

          message.keyMapByte(keyMapByte);
          lastContent = contentTypes.poll();
          keyMapByteSize = -1;
          keyMapByte = null;
          break;
        case BYTE_BUFFER:
          if (bufferSize == -1 && buf.readableBytes() < Utils.INTEGER_BYTE_SIZE) {
            return false;
          }
          if (bufferSize == -1) {
            bufferSize = buf.readInt();
          }
          if (buffer == null) {
            buffer = new DataBuffer();
          }

          final int remaining = bufferSize - bufferTransferred;
          bufferTransferred += buffer.transferFrom(buf, remaining);

          if (bufferTransferred < bufferSize) {
            LOG.debug(
                "Still looking for data. Indicating that its not finished yet. Already Transferred = {}, Size = {}.",
                bufferTransferred,
                bufferSize);
            return false;
          }

          message.buffer(new Buffer(buffer.toByteBuf(), bufferSize));
          lastContent = contentTypes.poll();
          bufferSize = -1;
          bufferTransferred = 0;
          buffer = null;
          break;
        case SET_TRACKER_DATA:
          if (trackerDataSize == -1 && buf.readableBytes() < Utils.BYTE_BYTE_SIZE) {
            return false;
          }
          if (trackerDataSize == -1) {
            trackerDataSize = buf.readUnsignedByte();
          }
          if (trackerData == null) {
            trackerData = new TrackerData(new HashMap<PeerAddress, Data>(2 * trackerDataSize));
          }
          if (currentTrackerData != null) {
            if (!currentTrackerData.decodeBuffer(buf)) {
              return false;
            }
            if (!currentTrackerData.decodeDone(buf, message.publicKey(0), signatureFactory)) {
              return false;
            }
            currentTrackerData = null;
          }
          for (int i = trackerData.size(); i < trackerDataSize; i++) {

            if (buf.readableBytes() < Utils.SHORT_BYTE_SIZE) {
              return false;
            }

            int header = buf.getUnsignedShort(buf.readerIndex());

            size = PeerAddress.size(header);

            if (buf.readableBytes() < size) {
              return false;
            }
            PeerAddress pa = new PeerAddress(buf);

            currentTrackerData = Data.decodeHeader(buf, signatureFactory);
            if (currentTrackerData == null) {
              return false;
            }
            trackerData.peerAddresses().put(pa, currentTrackerData);
            if (message.isSign()) {
              currentTrackerData.publicKey(message.publicKey(0));
            }

            if (!currentTrackerData.decodeBuffer(buf)) {
              return false;
            }
            if (!currentTrackerData.decodeDone(buf, message.publicKey(0), signatureFactory)) {
              return false;
            }
            currentTrackerData = null;
          }

          message.trackerData(trackerData);
          lastContent = contentTypes.poll();
          trackerDataSize = -1;
          trackerData = null;
          break;
        case PUBLIC_KEY: // fall-through
        case PUBLIC_KEY_SIGNATURE:
          receivedPublicKey = signatureFactory.decodePublicKey(buf);
          if (content == Content.PUBLIC_KEY_SIGNATURE) {
            if (receivedPublicKey == PeerBuilder.EMPTY_PUBLIC_KEY) {
              throw new InvalidKeyException("The public key cannot be empty.");
            }
          }
          if (receivedPublicKey == null) {
            return false;
          }

          message.publicKey(receivedPublicKey);
          lastContent = contentTypes.poll();
          break;
        default:
          break;
      }
    }
    LOG.debug("Parsed content in message {}", message);
    if (message.isSign()) {
      size = signatureFactory.signatureSize();
      if (buf.readableBytes() < size) {
        return false;
      }

      SignatureCodec signatureEncode = signatureFactory.signatureCodec(buf);
      message.receivedSignature(signatureEncode);
    }
    return true;
  }

  public Message prepareFinish() {
    Message ret = message;
    message.setDone();
    contentTypes.clear();
    message = null;
    headerDone = false;
    neighborSize = -1;
    neighborSet = null;
    keyCollectionSize = -1;
    keyCollection = null;
    mapSize = -1;
    dataMap = null;
    data = null;
    keyMap640KeysSize = -1;
    keyMap640Keys = null;
    bufferSize = -1;
    bufferTransferred = 0;
    buffer = null;
    signature = null;
    return ret;
  }

  public Message message() {
    return message;
  }

  public Content lastContent() {
    return lastContent;
  }

  public static void inheritPublicKey(Message message, Data data) {
    if (message.isSign()
        && message.publicKey(0) != null
        && data.hasPublicKey()
        && (data.publicKey() == null || data.publicKey() == PeerBuilder.EMPTY_PUBLIC_KEY)) {
      data.publicKey(message.publicKey(0));
    }
  }

  public void release() {
    if (message != null) {
      message.release();
    }
    // release partial data
    if (data != null) {
      data.release();
    }
    if (dataMap != null) {
      for (Data data : dataMap.dataMap().values()) {
        data.release();
      }
    }
    if (buffer != null) {
      buffer.release();
    }

    if (currentTrackerData != null) {
      currentTrackerData.release();
    }
    if (trackerData != null) {
      for (Data data : trackerData.peerAddresses().values()) {
        data.release();
      }
    }
  }
}
Exemplo n.º 13
0
public final class SessionManager implements ISessionManager {
  public static final AttributeKey<Session> SESSION_ATTR = AttributeKey.valueOf("Session.attr");
  public static final AttributeKey<Integer> CHANNEL_ID_ATTR =
      AttributeKey.valueOf("ChannelId.attr");

  private final AtomicInteger idGenerator = new AtomicInteger();
  private final Map<Integer, BaseSession> sessions = new ConcurrentHashMap<>();

  private final ChannelGroup channelGroup = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);

  public static boolean isLocked = false;

  public SessionManager() {
    SessionManagerAccessor.getInstance().setSessionManager(this);
  }

  public boolean add(ChannelHandlerContext channel) {
    Session session = new Session(channel);

    //        if(PlayerManager.getInstance().getPlayerCountByIpAddress(session.getIpAddress()) >
    // CometSettings.maxConnectionsPerIpAddress) {
    //            return false;
    //        }

    this.channelGroup.add(channel.channel());
    channel.attr(CHANNEL_ID_ATTR).set(this.idGenerator.incrementAndGet());

    return (this.sessions.putIfAbsent(channel.attr(CHANNEL_ID_ATTR).get(), session) == null);
  }

  public boolean remove(ChannelHandlerContext channel) {
    if (this.sessions.containsKey(channel.attr(CHANNEL_ID_ATTR).get())) {
      this.channelGroup.remove(channel.channel());
      this.sessions.remove(channel.attr(CHANNEL_ID_ATTR).get());

      return true;
    }

    return false;
  }

  public boolean disconnectByPlayerId(int id) {
    if (PlayerManager.getInstance().getSessionIdByPlayerId(id) == -1) {
      return false;
    }

    int sessionId = PlayerManager.getInstance().getSessionIdByPlayerId(id);
    Session session = (Session) sessions.get(sessionId);

    if (session != null) {
      session.disconnect();
      return true;
    }

    return false;
  }

  public Session getByPlayerId(int id) {
    if (PlayerManager.getInstance().getSessionIdByPlayerId(id) != -1) {
      int sessionId = PlayerManager.getInstance().getSessionIdByPlayerId(id);

      return (Session) sessions.get(sessionId);
    }

    return null;
  }

  public Set<BaseSession> getByPlayerPermission(String permission) {
    // TODO: Optimize this
    Set<BaseSession> sessions = new HashSet<>();

    //        int rank =
    // PermissionsManager.getInstance().getPermissions().get(permission).getRank();
    //
    //        for (Map.Entry<Integer, BaseSession> session : this.sessions.entrySet()) {
    //            if (session.getValue().getPlayer() != null) {
    //                if (((Session) session.getValue()).getPlayer().getData().getRank() >= rank) {
    //                    sessions.add(session.getValue());
    //                }
    //            }
    //        }

    return sessions;
  }

  public Session getByPlayerUsername(String username) {
    int playerId = PlayerManager.getInstance().getPlayerIdByUsername(username);

    if (playerId == -1) return null;

    int sessionId = PlayerManager.getInstance().getSessionIdByPlayerId(playerId);

    if (sessionId == -1) return null;

    if (this.sessions.containsKey(sessionId)) return (Session) this.sessions.get(sessionId);

    return null;
  }

  public int getUsersOnlineCount() {
    return PlayerManager.getInstance().size();
  }

  public Map<Integer, BaseSession> getSessions() {
    return this.sessions;
  }

  public void broadcast(IMessageComposer msg) {
    this.getChannelGroup().writeAndFlush(msg);
    //
    //        for (Session client : sessions.values()) {
    //            client.getChannel().write(msg);
    //        }
  }

  public ChannelGroup getChannelGroup() {
    return channelGroup;
  }

  public void broadcastToModerators(IMessageComposer messageComposer) {
    for (BaseSession session : this.sessions.values()) {
      if (session.getPlayer() != null
          && session.getPlayer().getPermissions() != null
          && session.getPlayer().getPermissions().getRank().modTool()) {
        session.send(messageComposer);
      }
    }
  }

  @Override
  public void parseCommand(String[] message, ChannelHandlerContext ctx) {
    String password = message[0];

    if (password.equals("cometServer")) {
      String command = message[1];

      switch (command) {
        default:
          {
            ctx.channel().writeAndFlush("response||You're connected!");
            break;
          }

        case "stats":
          {
            ctx.channel()
                .writeAndFlush("response||" + JsonFactory.getInstance().toJson(CometStats.get()));
            break;
          }
      }
    } else {
      ctx.disconnect();
    }
  }
}
/**
 * A simple handler that serves incoming HTTP requests to send their respective HTTP responses. It
 * also implements {@code 'If-Modified-Since'} header to take advantage of browser cache, as
 * described in <a href="http://tools.ietf.org/html/rfc2616#section-14.25">RFC 2616</a>.
 *
 * <p>
 *
 * <h3>How Browser Caching Works</h3>
 *
 * <p>Web browser caching works with HTTP headers as illustrated by the following sample:
 *
 * <ol>
 *   <li>Request #1 returns the content of {@code /file1.txt}.
 *   <li>Contents of {@code /file1.txt} is cached by the browser.
 *   <li>Request #2 for {@code /file1.txt} does return the contents of the file again. Rather, a 304
 *       Not Modified is returned. This tells the browser to use the contents stored in its cache.
 *   <li>The server knows the file has not been modified because the {@code If-Modified-Since} date
 *       is the same as the file's last modified date.
 * </ol>
 *
 * <p>
 *
 * <pre>
 * Request #1 Headers
 * ===================
 * GET /file1.txt HTTP/1.1
 *
 * Response #1 Headers
 * ===================
 * HTTP/1.1 200 OK
 * Date:               Tue, 01 Mar 2011 22:44:26 GMT
 * Last-Modified:      Wed, 30 Jun 2010 21:36:48 GMT
 * Expires:            Tue, 01 Mar 2012 22:44:26 GMT
 * Cache-Control:      private, max-age=31536000
 *
 * Request #2 Headers
 * ===================
 * GET /file1.txt HTTP/1.1
 * If-Modified-Since:  Wed, 30 Jun 2010 21:36:48 GMT
 *
 * Response #2 Headers
 * ===================
 * HTTP/1.1 304 Not Modified
 * Date:               Tue, 01 Mar 2011 22:44:28 GMT
 *
 * </pre>
 */
public class HttpStaticFileServerHandler extends SimpleChannelInboundHandler<FullHttpRequest> {
  private static final Logger logger = LoggerFactory.getLogger(HttpStaticFileServerHandler.class);

  public static final String HTTP_DATE_FORMAT = "EEE, dd MMM yyyy HH:mm:ss zzz";
  public static final String HTTP_DATE_GMT_TIMEZONE = "GMT";
  public static final int HTTP_CACHE_SECONDS = 60;

  public static final AttributeKey<Object> ATTACHMENT =
      AttributeKey.valueOf("HttpStaticFileServerHandler.attachment");

  public static final String STATIC_MAPPING =
      SimpleChannelInboundHandler.class.getName() + ".staticMapping";
  public static final String SERVICED = SimpleChannelInboundHandler.class.getName() + ".serviced";
  private final List<String> paths;
  private String defaultContentType = "text/html";

  public HttpStaticFileServerHandler(List<String> paths) {
    this.paths = paths;
  }

  @Override
  public void channelRead0(ChannelHandlerContext ctx, FullHttpRequest request) throws Exception {
    if (!request.getDecoderResult().isSuccess()) {
      sendError(ctx, BAD_REQUEST, request);
      return;
    }

    if (request.getMethod() != GET) {
      sendError(ctx, METHOD_NOT_ALLOWED, request);
      return;
    }

    File file = null;
    RandomAccessFile raf = null;
    boolean found = true;
    for (String p : paths) {
      String path = p + sanitizeUri(request.getUri());

      if (path == null) {
        path = "/index.html";
      }

      if (path.endsWith("/") || path.endsWith(File.separator)) {
        path += "index.html";
      }

      if (path.endsWith("/favicon.ico") || path.endsWith(File.separator)) {
        request.headers().add(SERVICED, "true");
        found = false;
        continue;
      }

      file = new File(path);
      if (file.isHidden() || !file.exists()) {
        found = false;
        continue;
      }

      //            if (file.isDirectory()) {
      //                if (uri.endsWith("/")) {
      //                    sendListing(ctx, file);
      //                } else {
      //                    sendRedirect(ctx, uri + '/');
      //                }
      //                return;
      //            }

      if (!file.isFile()) {
        found = false;
        continue;
      }

      try {
        raf = new RandomAccessFile(file, "r");
        found = true;
        break;
      } catch (FileNotFoundException ignore) {
        sendError(ctx, NOT_FOUND, request);
        return;
      }
    }

    if (!found) {
      sendError(ctx, NOT_FOUND, request);
      return;
    }
    request.headers().add(SERVICED, "true");

    // Cache Validation
    String ifModifiedSince = request.headers().get(IF_MODIFIED_SINCE);
    if (ifModifiedSince != null && !ifModifiedSince.isEmpty()) {
      SimpleDateFormat dateFormatter = new SimpleDateFormat(HTTP_DATE_FORMAT, Locale.US);
      Date ifModifiedSinceDate = dateFormatter.parse(ifModifiedSince);

      // Only compare up to the second because the datetime format we send to the client
      // does not have milliseconds
      long ifModifiedSinceDateSeconds = ifModifiedSinceDate.getTime() / 1000;
      long fileLastModifiedSeconds = file.lastModified() / 1000;
      if (ifModifiedSinceDateSeconds == fileLastModifiedSeconds) {
        sendNotModified(ctx);
        return;
      }
    }

    long fileLength = raf.length();

    ctx.pipeline().addBefore(BridgeRuntime.class.getName(), "encoder", new HttpResponseEncoder());
    HttpResponse response = new DefaultHttpResponse(HTTP_1_1, OK);
    HttpHeaders.setContentLength(response, fileLength);
    contentType(request, response, file);
    setDateAndCacheHeaders(response, file);
    //        if (HttpHeaders.isKeepAlive(request)) {
    //            response.headers().set(CONNECTION, HttpHeaders.Values.KEEP_ALIVE);
    //        }

    // Write the initial line and the header.
    ctx.write(response);

    // Write the content.
    ChannelFuture sendFileFuture;
    ChannelFuture lastContentFuture;
    if (ctx.pipeline().get(SslHandler.class) == null) {
      sendFileFuture =
          ctx.write(
              new DefaultFileRegion(raf.getChannel(), 0, fileLength), ctx.newProgressivePromise());
      // Write the end marker.
      lastContentFuture = ctx.writeAndFlush(LastHttpContent.EMPTY_LAST_CONTENT);
    } else {
      sendFileFuture =
          ctx.writeAndFlush(
              new HttpChunkedInput(new ChunkedFile(raf, 0, fileLength, 8192)),
              ctx.newProgressivePromise());
      // HttpChunkedInput will write the end marker (LastHttpContent) for us.
      lastContentFuture = sendFileFuture;
    }

    sendFileFuture.addListener(
        new ChannelProgressiveFutureListener() {
          @Override
          public void operationProgressed(
              ChannelProgressiveFuture future, long progress, long total) {
            if (total < 0) { // total unknown
              logger.trace(future.channel() + " Transfer progress: " + progress);
            } else {
              logger.trace(future.channel() + " Transfer progress: " + progress + " / " + total);
            }
          }

          @Override
          public void operationComplete(ChannelProgressiveFuture future) {
            logger.trace(future.channel() + " Transfer complete.");
          }
        });

    // Close the connection when the whole content is written out.
    lastContentFuture.addListener(ChannelFutureListener.CLOSE);
  }

  @Override
  public void exceptionCaught(ChannelHandlerContext ctx, Throwable t) {

    Channel ch = ctx.channel();

    // Prevent recursion when the client close the connection during a write operation. In that
    // scenario the sendError will be invoked, but will fail since the channel has already been
    // closed
    // For an unknown reason,
    if (ch.attr(ATTACHMENT) != null
        && Error.class.isAssignableFrom(ch.attr(ATTACHMENT).get().getClass())) {
      return;
    }

    Throwable cause = t.getCause();
    if (cause instanceof TooLongFrameException) {
      sendError(ctx, BAD_REQUEST, null);
      return;
    }

    ch.attr(ATTACHMENT).set(new Error());
    if (ch.isOpen()) {
      sendError(ctx, INTERNAL_SERVER_ERROR, null);
    }

    if (ctx.channel().isActive()) {
      sendError(ctx, INTERNAL_SERVER_ERROR, null);
    }
  }

  private static final Pattern INSECURE_URI = Pattern.compile(".*[<>&\"].*");

  protected String sanitizeUri(String uri) {
    try {
      uri = URLDecoder.decode(uri, "UTF-8");
    } catch (UnsupportedEncodingException e) {
      try {
        uri = URLDecoder.decode(uri, "ISO-8859-1");
      } catch (UnsupportedEncodingException e1) {
        throw new Error();
      }
    }

    uri = uri.replace('/', File.separatorChar);

    if (uri.contains(File.separator + ".")
        || uri.contains("." + File.separator)
        || uri.startsWith(".")
        || uri.endsWith(".")) {
      return null;
    }

    int pos = uri.indexOf("?");
    if (pos != -1) {
      uri = uri.substring(0, pos);
    }
    return uri;
  }

  public static void sendRedirect(ChannelHandlerContext ctx, String newUri) {
    FullHttpResponse response = new DefaultFullHttpResponse(HTTP_1_1, FOUND);
    response.headers().set(LOCATION, newUri);

    // Close the connection as soon as the error message is sent.
    ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
  }

  public void sendError(
      ChannelHandlerContext ctx, HttpResponseStatus status, FullHttpRequest request) {
    FullHttpResponse response =
        new DefaultFullHttpResponse(
            HTTP_1_1,
            status,
            Unpooled.copiedBuffer("Failure: " + status + "\r\n", CharsetUtil.UTF_8));
    response.headers().set(CONTENT_TYPE, "text/plain; charset=UTF-8");

    // Close the connection as soon as the error message is sent.
    ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
  }

  /**
   * When file timestamp is the same as what the browser is sending up, send a "304 Not Modified"
   *
   * @param ctx Context
   */
  public static void sendNotModified(ChannelHandlerContext ctx) {
    FullHttpResponse response = new DefaultFullHttpResponse(HTTP_1_1, NOT_MODIFIED);
    setDateHeader(response);

    // Close the connection as soon as the error message is sent.
    ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
  }

  /**
   * Sets the Date header for the HTTP response
   *
   * @param response HTTP response
   */
  public static void setDateHeader(FullHttpResponse response) {
    SimpleDateFormat dateFormatter = new SimpleDateFormat(HTTP_DATE_FORMAT, Locale.US);
    dateFormatter.setTimeZone(TimeZone.getTimeZone(HTTP_DATE_GMT_TIMEZONE));

    Calendar time = new GregorianCalendar();
    response.headers().set(DATE, dateFormatter.format(time.getTime()));
  }

  /**
   * Sets the Date and Cache headers for the HTTP Response
   *
   * @param response HTTP response
   * @param fileToCache file to extract content type
   */
  public static void setDateAndCacheHeaders(HttpResponse response, File fileToCache) {
    SimpleDateFormat dateFormatter = new SimpleDateFormat(HTTP_DATE_FORMAT, Locale.US);
    dateFormatter.setTimeZone(TimeZone.getTimeZone(HTTP_DATE_GMT_TIMEZONE));

    // Date header
    Calendar time = new GregorianCalendar();
    response.headers().set(DATE, dateFormatter.format(time.getTime()));

    // Add cache headers
    time.add(Calendar.SECOND, HTTP_CACHE_SECONDS);
    response.headers().set(EXPIRES, dateFormatter.format(time.getTime()));
    response.headers().set(CACHE_CONTROL, "private, max-age=" + HTTP_CACHE_SECONDS);
    response
        .headers()
        .set(LAST_MODIFIED, dateFormatter.format(new Date(fileToCache.lastModified())));
  }

  protected void contentType(FullHttpRequest request, HttpResponse response, File resource) {
    String substr;
    String uri = request.getUri();
    int dot = uri.lastIndexOf(".");
    if (dot < 0) {
      substr = resource.toString();
      dot = substr.lastIndexOf(".");
    } else {
      substr = uri;
    }

    if (dot > 0) {
      String ext = substr.substring(dot + 1);
      int queryString = ext.indexOf("?");
      if (queryString > 0) {
        ext.substring(0, queryString);
      }
      String contentType = MimeType.get(ext, defaultContentType);
      response.headers().add(HttpHeaders.Names.CONTENT_TYPE, contentType);
    } else {
      response.headers().add(HttpHeaders.Names.CONTENT_TYPE, defaultContentType);
    }
  }
}
Exemplo n.º 15
0
public class NetworkDispatcher extends SimpleChannelInboundHandler<Packet>
    implements ChannelOutboundHandler {
  private static boolean DEBUG_HANDSHAKE =
      Boolean.parseBoolean(System.getProperty("fml.debugNetworkHandshake", "false"));

  private static enum ConnectionState {
    OPENING,
    AWAITING_HANDSHAKE,
    HANDSHAKING,
    HANDSHAKECOMPLETE,
    FINALIZING,
    CONNECTED;
  }

  private static enum ConnectionType {
    MODDED,
    BUKKIT,
    VANILLA;
  }

  public static NetworkDispatcher get(NetworkManager manager) {
    return manager.channel().attr(FML_DISPATCHER).get();
  }

  public static NetworkDispatcher allocAndSet(NetworkManager manager) {
    NetworkDispatcher net = new NetworkDispatcher(manager);
    manager.channel().attr(FML_DISPATCHER).getAndSet(net);
    return net;
  }

  public static NetworkDispatcher allocAndSet(
      NetworkManager manager, ServerConfigurationManager scm) {
    NetworkDispatcher net = new NetworkDispatcher(manager, scm);
    manager.channel().attr(FML_DISPATCHER).getAndSet(net);
    return net;
  }

  public static final AttributeKey<NetworkDispatcher> FML_DISPATCHER =
      AttributeKey.valueOf("fml:dispatcher");
  public static final AttributeKey<Boolean> IS_LOCAL = AttributeKey.valueOf("fml:isLocal");
  public static final AttributeKey<GameData.GameDataSnapshot> FML_GAMEDATA_SNAPSHOT =
      AttributeKey.valueOf("fml:gameDataSnapshot");
  public final NetworkManager manager;
  private final ServerConfigurationManager scm;
  private EntityPlayerMP player;
  private ConnectionState state;
  private ConnectionType connectionType;
  private final Side side;
  private final EmbeddedChannel handshakeChannel;
  private NetHandlerPlayServer serverHandler;
  private INetHandler netHandler;
  private Map<String, String> modList;
  private int overrideLoginDim;

  public NetworkDispatcher(NetworkManager manager) {
    super(Packet.class, false);
    this.manager = manager;
    this.scm = null;
    this.side = Side.CLIENT;
    this.handshakeChannel =
        new EmbeddedChannel(
            new HandshakeInjector(this),
            new ChannelRegistrationHandler(),
            new FMLHandshakeCodec(),
            new HandshakeMessageHandler<FMLHandshakeClientState>(FMLHandshakeClientState.class));
    this.handshakeChannel.attr(FML_DISPATCHER).set(this);
    this.handshakeChannel.attr(NetworkRegistry.CHANNEL_SOURCE).set(Side.SERVER);
    this.handshakeChannel.attr(NetworkRegistry.FML_CHANNEL).set("FML|HS");
    this.handshakeChannel.attr(IS_LOCAL).set(manager.isLocalChannel());
    if (DEBUG_HANDSHAKE) PacketLoggingHandler.register(manager);
  }

  public NetworkDispatcher(NetworkManager manager, ServerConfigurationManager scm) {
    super(Packet.class, false);
    this.manager = manager;
    this.scm = scm;
    this.side = Side.SERVER;
    this.handshakeChannel =
        new EmbeddedChannel(
            new HandshakeInjector(this),
            new ChannelRegistrationHandler(),
            new FMLHandshakeCodec(),
            new HandshakeMessageHandler<FMLHandshakeServerState>(FMLHandshakeServerState.class));
    this.handshakeChannel.attr(FML_DISPATCHER).set(this);
    this.handshakeChannel.attr(NetworkRegistry.CHANNEL_SOURCE).set(Side.CLIENT);
    this.handshakeChannel.attr(NetworkRegistry.FML_CHANNEL).set("FML|HS");
    this.handshakeChannel.attr(IS_LOCAL).set(manager.isLocalChannel());
    if (DEBUG_HANDSHAKE) PacketLoggingHandler.register(manager);
  }

  public void serverToClientHandshake(EntityPlayerMP player) {
    this.player = player;
    insertIntoChannel();
    Boolean fml = this.manager.channel().attr(NetworkRegistry.FML_MARKER).get();
    if (fml != null && fml.booleanValue()) {
      // FML on client, send server hello
      // TODO: Make this cleaner as it uses netty magic 0.o
    } else {
      serverInitiateHandshake();
      FMLLog.info("Connection received without FML marker, assuming vanilla.");
      this.completeServerSideConnection(ConnectionType.VANILLA);
    }
  }

  protected void setModList(Map<String, String> modList) {
    this.modList = modList;
  }

  private void insertIntoChannel() {
    this.manager.channel().config().setAutoRead(false);
    // Insert ourselves into the pipeline
    this.manager.channel().pipeline().addBefore("packet_handler", "fml:packet_handler", this);
  }

  public void clientToServerHandshake() {
    insertIntoChannel();
  }

  @Override
  public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
    if (this.state != null) {
      FMLLog.getLogger()
          .log(
              Level.INFO,
              "Opening channel which already seems to have a state set. This is a vanilla connection. Handshake handler will stop now");
      return;
    }
    FMLLog.getLogger().log(Level.TRACE, "Handshake channel activating");
    this.state = ConnectionState.OPENING;
    // send ourselves as a user event, to kick the pipeline active
    this.handshakeChannel.pipeline().fireUserEventTriggered(this);
    this.manager.channel().config().setAutoRead(true);
  }

  int serverInitiateHandshake() {
    // Send mod salutation to the client
    // This will be ignored by vanilla clients
    this.state = ConnectionState.AWAITING_HANDSHAKE;
    // Need to start the handler here, so we can send custompayload packets
    serverHandler =
        new NetHandlerPlayServer(scm.getServerInstance(), manager, player) {
          /** Updates the JList with a new model. */
          @Override
          public void update() {
            if (NetworkDispatcher.this.state == ConnectionState.FINALIZING) {
              completeServerSideConnection(ConnectionType.MODDED);
            }
            super.update();
          }
        };
    this.netHandler = serverHandler;
    // NULL the play server here - we restore it further on. If not, there are packets sent before
    // the login
    player.playerNetServerHandler = null;
    // manually for the manager into the PLAY state, so we can send packets later
    this.manager.setConnectionState(EnumConnectionState.PLAY);

    // Return the dimension the player is in, so it can be pre-sent to the client in the ServerHello
    // v2 packet
    // Requires some hackery to the serverconfigmanager and stuff for this to work
    NBTTagCompound playerNBT = scm.getPlayerNBT(player);
    if (playerNBT != null) {
      return playerNBT.getInteger("Dimension");
    } else {
      return 0;
    }
  }

  void clientListenForServerHandshake() {
    manager.setConnectionState(EnumConnectionState.PLAY);
    // FMLCommonHandler.instance().waitForPlayClient();
    this.netHandler = FMLCommonHandler.instance().getClientPlayHandler();
    this.state = ConnectionState.AWAITING_HANDSHAKE;
  }

  private void completeClientSideConnection(ConnectionType type) {
    this.connectionType = type;
    FMLLog.info(
        "[%s] Client side %s connection established",
        Thread.currentThread().getName(), this.connectionType.name().toLowerCase(Locale.ENGLISH));
    this.state = ConnectionState.CONNECTED;
    FMLCommonHandler.instance()
        .bus()
        .post(
            new FMLNetworkEvent.ClientConnectedToServerEvent(manager, this.connectionType.name()));
  }

  private synchronized void completeServerSideConnection(ConnectionType type) {
    this.connectionType = type;
    FMLLog.info(
        "[%s] Server side %s connection established",
        Thread.currentThread().getName(), this.connectionType.name().toLowerCase(Locale.ENGLISH));
    this.state = ConnectionState.CONNECTED;
    FMLCommonHandler.instance()
        .bus()
        .post(new FMLNetworkEvent.ServerConnectionFromClientEvent(manager));
    if (DEBUG_HANDSHAKE)
      manager.closeChannel(
          new ChatComponentText("Handshake Complete review log file for details."));
    scm.initializeConnectionToPlayer(manager, player, serverHandler);
  }

  @Override
  protected void channelRead0(ChannelHandlerContext ctx, Packet msg) throws Exception {
    boolean handled = false;
    if (msg instanceof C17PacketCustomPayload) {
      handled = handleServerSideCustomPacket((C17PacketCustomPayload) msg, ctx);
    } else if (msg instanceof S3FPacketCustomPayload) {
      handled = handleClientSideCustomPacket((S3FPacketCustomPayload) msg, ctx);
    } else if (state != ConnectionState.CONNECTED && state != ConnectionState.HANDSHAKECOMPLETE) {
      handled = handleVanilla(msg);
    }
    if (!handled) {
      ctx.fireChannelRead(msg);
    }
  }

  private boolean handleVanilla(Packet msg) {
    if (state == ConnectionState.AWAITING_HANDSHAKE && msg instanceof S01PacketJoinGame) {
      handshakeChannel.pipeline().fireUserEventTriggered(msg);
    } else {
      FMLLog.info(
          "Unexpected packet during modded negotiation - assuming vanilla or keepalives : %s",
          msg.getClass().getName());
    }
    return false;
  }

  public INetHandler getNetHandler() {
    return netHandler;
  }

  /**
   * The mod list returned by this method is in no way reliable because it is provided by the client
   *
   * @return a map that will contain String keys and values listing all mods and their versions
   */
  public Map<String, String> getModList() {
    return Collections.unmodifiableMap(modList);
  }

  @Override
  public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
    if (evt instanceof ConnectionType && side == Side.SERVER) {
      FMLLog.info("Timeout occurred, assuming a vanilla client");
      kickVanilla();
    }
  }

  private void kickVanilla() {
    kickWithMessage("This is modded. No modded response received. Bye!");
  }

  private void kickWithMessage(String message) {
    final ChatComponentText chatcomponenttext = new ChatComponentText(message);
    if (side == Side.CLIENT) {
      manager.closeChannel(chatcomponenttext);
    } else {
      manager.sendPacket(
          new S40PacketDisconnect(chatcomponenttext),
          new GenericFutureListener<Future<?>>() {
            @Override
            public void operationComplete(Future<?> result) {
              manager.closeChannel(chatcomponenttext);
            }
          });
    }
    manager.channel().config().setAutoRead(false);
  }

  private MultiPartCustomPayload multipart = null;

  private boolean handleClientSideCustomPacket(
      S3FPacketCustomPayload msg, ChannelHandlerContext context) {
    String channelName = msg.getChannelName();
    if ("FML|MP".equals(channelName)) {
      try {
        if (multipart == null) {
          multipart = new MultiPartCustomPayload(msg.getBufferData());
        } else {
          multipart.processPart(msg.getBufferData());
        }
      } catch (IOException e) {
        this.kickWithMessage(e.getMessage());
        multipart = null;
        return true;
      }

      if (multipart.isComplete()) {
        msg = multipart;
        channelName = msg.getChannelName();
        multipart = null;
      } else {
        return true; // Haven't received all so return till we have.
      }
    }
    if ("FML|HS".equals(channelName)
        || "REGISTER".equals(channelName)
        || "UNREGISTER".equals(channelName)) {
      FMLProxyPacket proxy = new FMLProxyPacket(msg);
      proxy.setDispatcher(this);
      handshakeChannel.writeInbound(proxy);
      // forward any messages into the regular channel
      for (Object push : handshakeChannel.inboundMessages()) {
        List<FMLProxyPacket> messageResult =
            FMLNetworkHandler.forwardHandshake(
                (FMLMessage.CompleteHandshake) push, this, Side.CLIENT);
        for (FMLProxyPacket result : messageResult) {
          result.setTarget(Side.CLIENT);
          result.payload().resetReaderIndex();
          context.fireChannelRead(result);
        }
      }
      handshakeChannel.inboundMessages().clear();
      return true;
    } else if (NetworkRegistry.INSTANCE.hasChannel(channelName, Side.CLIENT)) {
      FMLProxyPacket proxy = new FMLProxyPacket(msg);
      proxy.setDispatcher(this);
      context.fireChannelRead(proxy);
      return true;
    }
    return false;
  }

  private boolean handleServerSideCustomPacket(
      C17PacketCustomPayload msg, ChannelHandlerContext context) {
    if (state == ConnectionState.AWAITING_HANDSHAKE) {
      synchronized (this) { // guard from other threads changing the state on us
        if (state == ConnectionState.AWAITING_HANDSHAKE) {
          state = ConnectionState.HANDSHAKING;
        }
      }
    }
    String channelName = msg.getChannelName();
    if ("FML|HS".equals(channelName)
        || "REGISTER".equals(channelName)
        || "UNREGISTER".equals(channelName)) {
      FMLProxyPacket proxy = new FMLProxyPacket(msg);
      proxy.setDispatcher(this);
      handshakeChannel.writeInbound(proxy);
      for (Object push : handshakeChannel.inboundMessages()) {
        List<FMLProxyPacket> messageResult =
            FMLNetworkHandler.forwardHandshake(
                (FMLMessage.CompleteHandshake) push, this, Side.SERVER);
        for (FMLProxyPacket result : messageResult) {
          result.setTarget(Side.SERVER);
          result.payload().resetReaderIndex();
          context.fireChannelRead(result);
        }
      }
      handshakeChannel.inboundMessages().clear();
      return true;
    } else if (NetworkRegistry.INSTANCE.hasChannel(channelName, Side.SERVER)) {
      FMLProxyPacket proxy = new FMLProxyPacket(msg);
      proxy.setDispatcher(this);
      context.fireChannelRead(proxy);
      return true;
    }
    return false;
  }

  public void sendProxy(FMLProxyPacket msg) {
    manager.sendPacket(msg);
  }

  public void rejectHandshake(String result) {
    kickWithMessage(result);
  }

  @Override
  public void bind(ChannelHandlerContext ctx, SocketAddress localAddress, ChannelPromise promise)
      throws Exception {
    ctx.bind(localAddress, promise);
  }

  @Override
  public void connect(
      ChannelHandlerContext ctx,
      SocketAddress remoteAddress,
      SocketAddress localAddress,
      ChannelPromise promise)
      throws Exception {
    ctx.connect(remoteAddress, localAddress, promise);
  }

  @Override
  public void disconnect(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception {
    if (side == Side.CLIENT) {
      FMLCommonHandler.instance()
          .bus()
          .post(new FMLNetworkEvent.ClientDisconnectionFromServerEvent(manager));
    } else {
      FMLCommonHandler.instance()
          .bus()
          .post(new FMLNetworkEvent.ServerDisconnectionFromClientEvent(manager));
    }
    cleanAttributes(ctx);
    ctx.disconnect(promise);
  }

  @Override
  public void close(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception {
    if (side == Side.CLIENT) {
      FMLCommonHandler.instance()
          .bus()
          .post(new FMLNetworkEvent.ClientDisconnectionFromServerEvent(manager));
    } else {
      FMLCommonHandler.instance()
          .bus()
          .post(new FMLNetworkEvent.ServerDisconnectionFromClientEvent(manager));
    }
    cleanAttributes(ctx);
    ctx.close(promise);
  }

  @Override
  @Deprecated
  public void deregister(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception {
    ctx.deregister(promise);
  }

  @Override
  public void read(ChannelHandlerContext ctx) throws Exception {
    ctx.read();
  }

  @Override
  public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise)
      throws Exception {
    if (msg instanceof FMLProxyPacket) {
      if (side == Side.CLIENT) {
        // Client to server large packets are not supported to prevent client being bad.
        ctx.write(((FMLProxyPacket) msg).toC17Packet(), promise);
      } else {
        List<Packet> parts = ((FMLProxyPacket) msg).toS3FPackets();
        for (Packet pkt : parts) {
          ctx.write(pkt, promise);
        }
      }
    } else {
      ctx.write(msg, promise);
    }
  }

  @Override
  public void flush(ChannelHandlerContext ctx) throws Exception {
    ctx.flush();
  }

  public void completeHandshake(Side target) {
    if (state == ConnectionState.CONNECTED) {
      FMLLog.severe("Attempt to double complete the network connection!");
      throw new FMLNetworkException("Attempt to double complete!");
    }
    if (side == Side.CLIENT) {
      completeClientSideConnection(ConnectionType.MODDED);
    } else {
      this.state = ConnectionState.FINALIZING; // Delay and finalize in the world tick loop.
    }
  }

  public void completeClientHandshake() {
    state = ConnectionState.HANDSHAKECOMPLETE;
  }

  public void abortClientHandshake(String type) {
    FMLLog.log(Level.INFO, "Aborting client handshake \"%s\"", type);
    // FMLCommonHandler.instance().waitForPlayClient();
    completeClientSideConnection(ConnectionType.valueOf(type));
  }

  @Override
  public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
    // Stop the epic channel closed spam at close
    if (!(cause instanceof ClosedChannelException)) {
      FMLLog.log(Level.ERROR, cause, "NetworkDispatcher exception");
    }
    super.exceptionCaught(ctx, cause);
  }

  // if we add any attributes, we should force removal of them here so that
  // they do not hold references to the world and causes it to leak.
  private void cleanAttributes(ChannelHandlerContext ctx) {
    ctx.channel().attr(FMLOutboundHandler.FML_MESSAGETARGETARGS).remove();
    ctx.channel().attr(NetworkRegistry.NET_HANDLER).remove();
    ctx.channel().attr(NetworkDispatcher.FML_DISPATCHER).remove();
    this.handshakeChannel.attr(FML_DISPATCHER).remove();
    this.manager.channel().attr(FML_DISPATCHER).remove();
  }

  public void setOverrideDimension(int overrideDim) {
    this.overrideLoginDim = overrideDim;
    FMLLog.fine("Received override dimension %d", overrideDim);
  }

  public int getOverrideDimension(S01PacketJoinGame packetIn) {
    FMLLog.fine("Overriding dimension: using %d", this.overrideLoginDim);
    return this.overrideLoginDim != 0 ? this.overrideLoginDim : packetIn.getDimension();
  }

  private class MultiPartCustomPayload extends S3FPacketCustomPayload {
    private String channel;
    private byte[] data;
    private PacketBuffer data_buf = null;
    private int part_count = 0;
    private int part_expected = 0;
    private int offset = 0;

    private MultiPartCustomPayload(PacketBuffer preamble) throws IOException {
      channel = preamble.readStringFromBuffer(20);
      part_count = preamble.readUnsignedByte();
      int length = preamble.readInt();
      if (length <= 0 || length >= FMLProxyPacket.MAX_LENGTH) {
        throw new IOException(
            "The received FML MultiPart packet outside of valid length bounds, Max: "
                + FMLProxyPacket.MAX_LENGTH
                + ", Received: "
                + length);
      }
      data = new byte[length];
      data_buf = new PacketBuffer(Unpooled.wrappedBuffer(data));
    }

    public void processPart(PacketBuffer input) throws IOException {
      int part = (int) (input.readByte() & 0xFF);
      if (part != part_expected) {
        throw new IOException(
            "Received FML MultiPart packet out of order, Expected "
                + part_expected
                + " Got "
                + part);
      }
      int len = input.readableBytes() - 1;
      input.readBytes(data, offset, len);
      part_expected++;
      offset += len;
    }

    public boolean isComplete() {
      return part_expected == part_count;
    }

    @Override
    public String getChannelName() // getChannel
        {
      return this.channel;
    }

    @Override
    public PacketBuffer getBufferData() // getData
        {
      return this.data_buf;
    }
  }
}
Exemplo n.º 16
0
public class NetworkManager extends SimpleChannelInboundHandler {
  private static final Logger logger = LogManager.getLogger();
  public static final Marker logMarkerNetwork = MarkerManager.getMarker("NETWORK");
  public static final Marker logMarkerPackets =
      MarkerManager.getMarker("NETWORK_PACKETS", logMarkerNetwork);
  public static final AttributeKey attrKeyConnectionState = AttributeKey.valueOf("protocol");
  public static final LazyLoadBase CLIENT_NIO_EVENTLOOP =
      new LazyLoadBase() {

        protected NioEventLoopGroup genericLoad() {
          return new NioEventLoopGroup(
              0,
              (new ThreadFactoryBuilder())
                  .setNameFormat("Netty Client IO #%d")
                  .setDaemon(true)
                  .build());
        }

        protected Object load() {
          return this.genericLoad();
        }
      };
  public static final LazyLoadBase CLIENT_LOCAL_EVENTLOOP =
      new LazyLoadBase() {

        protected LocalEventLoopGroup genericLoad() {
          return new LocalEventLoopGroup(
              0,
              (new ThreadFactoryBuilder())
                  .setNameFormat("Netty Local Client IO #%d")
                  .setDaemon(true)
                  .build());
        }

        protected Object load() {
          return this.genericLoad();
        }
      };
  private final EnumPacketDirection direction;

  /** The queue for packets that require transmission */
  private final Queue outboundPacketsQueue = Queues.newConcurrentLinkedQueue();

  /** The active channel */
  private Channel channel;

  /** The address of the remote party */
  private SocketAddress socketAddress;

  /** The INetHandler instance responsible for processing received packets */
  private INetHandler packetListener;

  /** A String indicating why the network has shutdown. */
  private IChatComponent terminationReason;

  private boolean isEncrypted;
  private boolean disconnected;

  public NetworkManager(EnumPacketDirection packetDirection) {
    this.direction = packetDirection;
  }

  public void channelActive(ChannelHandlerContext p_channelActive_1_) throws Exception {
    super.channelActive(p_channelActive_1_);
    this.channel = p_channelActive_1_.channel();
    this.socketAddress = this.channel.remoteAddress();

    try {
      this.setConnectionState(EnumConnectionState.HANDSHAKING);
    } catch (Throwable var3) {
      logger.fatal(var3);
    }
  }

  /** Sets the new connection state and registers which packets this channel may send and receive */
  public void setConnectionState(EnumConnectionState newState) {
    this.channel.attr(attrKeyConnectionState).set(newState);
    this.channel.config().setAutoRead(true);
    logger.debug("Enabled auto read");
  }

  public void channelInactive(ChannelHandlerContext p_channelInactive_1_) {
    this.closeChannel(new ChatComponentTranslation("disconnect.endOfStream", new Object[0]));
  }

  public void exceptionCaught(
      ChannelHandlerContext p_exceptionCaught_1_, Throwable p_exceptionCaught_2_) {
    logger.debug("Disconnecting " + this.getRemoteAddress(), p_exceptionCaught_2_);
    this.closeChannel(
        new ChatComponentTranslation(
            "disconnect.genericReason",
            new Object[] {"Internal Exception: " + p_exceptionCaught_2_}));
  }

  protected void channelRead0(ChannelHandlerContext p_channelRead0_1_, Packet p_channelRead0_2_) {
    ReadPacketEvent event = new ReadPacketEvent(p_channelRead0_2_);
    XIV.getInstance().getListenerManager().call(event);
    if (event.isCancelled()) return;
    p_channelRead0_2_ = event.getPacket();
    if (this.channel.isOpen()) {
      try {
        p_channelRead0_2_.processPacket(this.packetListener);
      } catch (ThreadQuickExitException var4) {;
      }
    }
  }

  /**
   * Sets the NetHandler for this NetworkManager, no checks are made if this handler is suitable for
   * the particular connection state (protocol)
   */
  public void setNetHandler(INetHandler handler) {
    Validate.notNull(handler, "packetListener", new Object[0]);
    logger.debug("Set listener of {} to {}", new Object[] {this, handler});
    this.packetListener = handler;
  }

  public void sendPacket(Packet packetIn) {
    if (this.channel != null && this.channel.isOpen()) {
      this.flushOutboundQueue();
      this.dispatchPacket(packetIn, (GenericFutureListener[]) null);
    } else {
      this.outboundPacketsQueue.add(
          new NetworkManager.InboundHandlerTuplePacketListener(
              packetIn, (GenericFutureListener[]) null));
    }
  }

  public void sendPacket(
      Packet packetIn, GenericFutureListener listener, GenericFutureListener... listeners) {
    if (this.channel != null && this.channel.isOpen()) {
      this.flushOutboundQueue();
      this.dispatchPacket(
          packetIn, (GenericFutureListener[]) ArrayUtils.add(listeners, 0, listener));
    } else {
      this.outboundPacketsQueue.add(
          new NetworkManager.InboundHandlerTuplePacketListener(
              packetIn, (GenericFutureListener[]) ArrayUtils.add(listeners, 0, listener)));
    }
  }

  /**
   * Will commit the packet to the channel. If the current thread 'owns' the channel it will write
   * and flush the packet, otherwise it will add a task for the channel eventloop thread to do that.
   */
  private void dispatchPacket(
      final Packet inPacket, final GenericFutureListener[] futureListeners) {
    final EnumConnectionState var3 = EnumConnectionState.getFromPacket(inPacket);
    final EnumConnectionState var4 =
        (EnumConnectionState) this.channel.attr(attrKeyConnectionState).get();

    if (var4 != var3) {
      logger.debug("Disabled auto read");
      this.channel.config().setAutoRead(false);
    }

    if (this.channel.eventLoop().inEventLoop()) {
      if (var3 != var4) {
        this.setConnectionState(var3);
      }

      ChannelFuture var5 = this.channel.writeAndFlush(inPacket);

      if (futureListeners != null) {
        var5.addListeners(futureListeners);
      }

      var5.addListener(ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE);
    } else {
      this.channel
          .eventLoop()
          .execute(
              new Runnable() {

                public void run() {
                  if (var3 != var4) {
                    NetworkManager.this.setConnectionState(var3);
                  }

                  ChannelFuture var1 = NetworkManager.this.channel.writeAndFlush(inPacket);

                  if (futureListeners != null) {
                    var1.addListeners(futureListeners);
                  }

                  var1.addListener(ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE);
                }
              });
    }
  }

  /** Will iterate through the outboundPacketQueue and dispatch all Packets */
  private void flushOutboundQueue() {
    if (this.channel != null && this.channel.isOpen()) {
      while (!this.outboundPacketsQueue.isEmpty()) {
        NetworkManager.InboundHandlerTuplePacketListener var1 =
            (NetworkManager.InboundHandlerTuplePacketListener) this.outboundPacketsQueue.poll();
        this.dispatchPacket(var1.packet, var1.futureListeners);
      }
    }
  }

  /** Checks timeouts and processes all packets received */
  public void processReceivedPackets() {
    this.flushOutboundQueue();

    if (this.packetListener instanceof IUpdatePlayerListBox) {
      ((IUpdatePlayerListBox) this.packetListener).update();
    }

    this.channel.flush();
  }

  /** Returns the socket address of the remote side. Server-only. */
  public SocketAddress getRemoteAddress() {
    return this.socketAddress;
  }

  /**
   * Closes the channel, the parameter can be used for an exit message (not certain how it gets
   * sent)
   */
  public void closeChannel(IChatComponent message) {
    if (this.channel.isOpen()) {
      this.channel.close().awaitUninterruptibly();
      this.terminationReason = message;
    }
  }

  /**
   * True if this NetworkManager uses a memory connection (single player game). False may imply both
   * an active TCP connection or simply no active connection at all
   */
  public boolean isLocalChannel() {
    return this.channel instanceof LocalChannel || this.channel instanceof LocalServerChannel;
  }

  /**
   * Prepares a clientside NetworkManager: establishes a connection to the address and port supplied
   * and configures the channel pipeline. Returns the newly created instance.
   */
  public static NetworkManager provideLanClient(InetAddress p_150726_0_, int p_150726_1_) {
    final NetworkManager var2 = new NetworkManager(EnumPacketDirection.CLIENTBOUND);
    ((Bootstrap)
            ((Bootstrap)
                    ((Bootstrap)
                            (new Bootstrap())
                                .group((EventLoopGroup) CLIENT_NIO_EVENTLOOP.getValue()))
                        .handler(
                            new ChannelInitializer() {

                              protected void initChannel(Channel p_initChannel_1_) {
                                try {
                                  p_initChannel_1_
                                      .config()
                                      .setOption(ChannelOption.IP_TOS, Integer.valueOf(24));
                                } catch (ChannelException var4) {;
                                }

                                try {
                                  p_initChannel_1_
                                      .config()
                                      .setOption(ChannelOption.TCP_NODELAY, Boolean.valueOf(false));
                                } catch (ChannelException var3) {;
                                }

                                p_initChannel_1_
                                    .pipeline()
                                    .addLast("timeout", new ReadTimeoutHandler(20))
                                    .addLast("splitter", new MessageDeserializer2())
                                    .addLast(
                                        "decoder",
                                        new MessageDeserializer(EnumPacketDirection.CLIENTBOUND))
                                    .addLast("prepender", new MessageSerializer2())
                                    .addLast(
                                        "encoder",
                                        new MessageSerializer(EnumPacketDirection.SERVERBOUND))
                                    .addLast("packet_handler", var2);
                              }
                            }))
                .channel(NioSocketChannel.class))
        .connect(p_150726_0_, p_150726_1_)
        .syncUninterruptibly();
    return var2;
  }

  /**
   * Prepares a clientside NetworkManager: establishes a connection to the socket supplied and
   * configures the channel pipeline. Returns the newly created instance.
   */
  public static NetworkManager provideLocalClient(SocketAddress p_150722_0_) {
    final NetworkManager var1 = new NetworkManager(EnumPacketDirection.CLIENTBOUND);
    ((Bootstrap)
            ((Bootstrap)
                    ((Bootstrap)
                            (new Bootstrap())
                                .group((EventLoopGroup) CLIENT_LOCAL_EVENTLOOP.getValue()))
                        .handler(
                            new ChannelInitializer() {

                              protected void initChannel(Channel p_initChannel_1_) {
                                p_initChannel_1_.pipeline().addLast("packet_handler", var1);
                              }
                            }))
                .channel(LocalChannel.class))
        .connect(p_150722_0_)
        .syncUninterruptibly();
    return var1;
  }

  /**
   * Adds an encoder+decoder to the channel pipeline. The parameter is the secret key used for
   * encrypted communication
   */
  public void enableEncryption(SecretKey key) {
    this.isEncrypted = true;
    this.channel
        .pipeline()
        .addBefore(
            "splitter", "decrypt", new NettyEncryptingDecoder(CryptManager.func_151229_a(2, key)));
    this.channel
        .pipeline()
        .addBefore(
            "prepender", "encrypt", new NettyEncryptingEncoder(CryptManager.func_151229_a(1, key)));
  }

  public boolean func_179292_f() {
    return this.isEncrypted;
  }

  /** Returns true if this NetworkManager has an active channel, false otherwise */
  public boolean isChannelOpen() {
    return this.channel != null && this.channel.isOpen();
  }

  public boolean hasNoChannel() {
    return this.channel == null;
  }

  /** Gets the current handler for processing packets */
  public INetHandler getNetHandler() {
    return this.packetListener;
  }

  /** If this channel is closed, returns the exit message, null otherwise. */
  public IChatComponent getExitMessage() {
    return this.terminationReason;
  }

  /** Switches the channel to manual reading modus */
  public void disableAutoRead() {
    this.channel.config().setAutoRead(false);
  }

  public void setCompressionTreshold(int treshold) {
    if (treshold >= 0) {
      if (this.channel.pipeline().get("decompress") instanceof NettyCompressionDecoder) {
        ((NettyCompressionDecoder) this.channel.pipeline().get("decompress"))
            .setCompressionTreshold(treshold);
      } else {
        this.channel
            .pipeline()
            .addBefore("decoder", "decompress", new NettyCompressionDecoder(treshold));
      }

      if (this.channel.pipeline().get("compress") instanceof NettyCompressionEncoder) {
        ((NettyCompressionEncoder) this.channel.pipeline().get("decompress"))
            .setCompressionTreshold(treshold);
      } else {
        this.channel
            .pipeline()
            .addBefore("encoder", "compress", new NettyCompressionEncoder(treshold));
      }
    } else {
      if (this.channel.pipeline().get("decompress") instanceof NettyCompressionDecoder) {
        this.channel.pipeline().remove("decompress");
      }

      if (this.channel.pipeline().get("compress") instanceof NettyCompressionEncoder) {
        this.channel.pipeline().remove("compress");
      }
    }
  }

  public void checkDisconnected() {
    if (!this.hasNoChannel() && !this.isChannelOpen() && !this.disconnected) {
      this.disconnected = true;

      if (this.getExitMessage() != null) {
        this.getNetHandler().onDisconnect(this.getExitMessage());
      } else if (this.getNetHandler() != null) {
        this.getNetHandler().onDisconnect(new ChatComponentText("Disconnected"));
      }
    }
  }

  protected void channelRead0(ChannelHandlerContext p_channelRead0_1_, Object p_channelRead0_2_) {
    this.channelRead0(p_channelRead0_1_, (Packet) p_channelRead0_2_);
  }

  static class InboundHandlerTuplePacketListener {
    private final Packet packet;
    private final GenericFutureListener[] futureListeners;

    public InboundHandlerTuplePacketListener(
        Packet inPacket, GenericFutureListener... inFutureListeners) {
      this.packet = inPacket;
      this.futureListeners = inFutureListeners;
    }
  }
}