@Override protected void channelRead0(final ChannelHandlerContext ctx, final Message responseMessage) throws Exception { MessageID recvMessageID = new MessageID(responseMessage); // Error handling if (responseMessage.type() == Message.Type.UNKNOWN_ID) { String msg = "Message was not delivered successfully, unknown ID (peer may be offline or unknown RPC handler): " + this.message; exceptionCaught(ctx, new PeerException(PeerException.AbortCause.PEER_ABORT, msg)); responseMessage.release(); return; } if (responseMessage.type() == Message.Type.EXCEPTION) { String msg = "Message caused an exception on the other side, handle as peer_abort: " + this.message; exceptionCaught(ctx, new PeerException(PeerException.AbortCause.PEER_ABORT, msg)); responseMessage.release(); return; } if (responseMessage.isRequest()) { ctx.fireChannelRead(responseMessage); return; } if (!sendMessageID.equals(recvMessageID)) { String msg = "Response message [" + responseMessage + "] sent to the node is not the same as we expect. We sent [" + this.message + "]"; exceptionCaught(ctx, new PeerException(PeerException.AbortCause.PEER_ABORT, msg)); responseMessage.release(); return; } // We need to exclude RCON Messages from the sanity check because we // use this RequestHandler for sending a Type.REQUEST_1, // RPC.Commands.RCON message on top of it. Therefore the response // type will never be the same Type as the one the user initially // used (e.g. DIRECT_DATA). /*if (responseMessage.command() != RPC.Commands.RCON.getNr() && message.recipient().isRelayed() != responseMessage.sender().isRelayed()) { String msg = "Response message [" + responseMessage + "] sent has a different relay flag than we sent with request message [" + this.message + "]. Recipient (" + message.recipient().isRelayed() + ") / Sender (" + responseMessage.sender().isRelayed() + ")"; LOG.warn(msg); }*/ // NAT reflection, change it back, as this will be stored in our peer map that may be queried // from other peers if (message.recipientReflected() != null) { responseMessage.sender(message.recipient().withIpv4Socket(message.recipient().ipv4Socket())); } // Stop time measurement of RTT futureResponse.stopRTTMeasurement(); // We got a good answer, let's mark the sender as alive // if its an announce, the peer status will be handled in the RPC if (responseMessage.isOk() || responseMessage.isNotOk()) { LOG.debug("Try adding peer {} to map, {}", responseMessage.sender(), responseMessage); peerBean.notifyPeerFound( responseMessage.sender(), null, null, futureResponse.getRoundTripTime()); } // call this for streaming support if (!responseMessage.isDone()) { LOG.debug("Good message is streaming. {}", responseMessage); return; } // support slow, unreachable devices which cannot respond instantly if (this.message.recipient().relaySize() > 0 && this.message.recipient().slow() && responseMessage.type() == Message.Type.PARTIALLY_OK) { LOG.debug( "Received partially ok by the relay peer. Wait for answer of the unreachable peer."); // wait for the (real) answer of the unreachable peer. connectionBean .dispatcher() .addPendingRequest( message.messageId(), futureResponse, slowResponseTimeoutSeconds, connectionBean.timer()); // close the channel to the relay peer ctx.close(); return; } if (!message.isKeepAlive()) { LOG.debug("Good message {}. Close channel {}.", responseMessage, ctx.channel()); // set the success now, but trigger the notify when we closed the channel. futureResponse.responseLater(responseMessage); // the channel creator adds a listener that sets futureResponse.setResponseNow, when the // channel // is closed ctx.close(); } else { LOG.debug("Good message {}. Leave channel {} open.", responseMessage, ctx.channel()); futureResponse.response(responseMessage); } }