org.jboss.remoting3.remote.InboundMessage Maven / Gradle / Ivy
/*
* JBoss, Home of Professional Open Source.
* Copyright 2017 Red Hat, Inc., and individual contributors
* as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jboss.remoting3.remote;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.function.ToIntFunction;
import org.jboss.remoting3.MessageCancelledException;
import org.jboss.remoting3.MessageInputStream;
import org.xnio.Pooled;
import org.xnio.streams.BufferPipeInputStream;
import static org.jboss.remoting3._private.Messages.conn;
import static org.jboss.remoting3._private.Messages.log;
import static java.lang.Thread.holdsLock;
import static org.xnio.IoUtils.safeClose;
/**
* @author David M. Lloyd
*/
final class InboundMessage {
final short messageId;
final RemoteConnectionChannel channel;
int inboundWindow;
boolean streamClosed;
boolean closeSent;
boolean eofReceived;
boolean cancelled;
long remaining;
static final ToIntFunction INDEXER = InboundMessage::getActualId;
InboundMessage(final short messageId, final RemoteConnectionChannel channel, int inboundWindow, final long maxInboundMessageSize) {
this.messageId = messageId;
this.channel = channel;
this.inboundWindow = inboundWindow;
remaining = maxInboundMessageSize;
}
final BufferPipeInputStream inputStream = new BufferPipeInputStream(new BufferPipeInputStream.InputHandler() {
public void acknowledge(final Pooled acked) throws IOException {
doAcknowledge(acked);
}
public void close() throws IOException {
doClose();
}
});
private int getActualId() {
return messageId & 0xffff;
}
void terminate() {
synchronized (inputStream) {
safeClose(inputStream);
}
}
private void doClose() {
assert holdsLock(inputStream);
if (streamClosed) {
// idempotent
return;
}
streamClosed = true;
// on close, send close message
doSendCloseMessage();
// but keep the mapping around until we receive our EOF
// else just keep discarding data until the EOF comes in.
}
private void doSendCloseMessage() {
assert holdsLock(inputStream);
if (closeSent || ! channel.getConnectionHandler().isMessageClose()) {
// we don't send a MESSAGE_CLOSE because broken versions will simply stop sending packets, and we won't know when the message is really gone.
// the risk is that the remote side could have started a new message in the meantime, and our MESSAGE_CLOSE would kill the wrong message.
// so this behavior is better than the alternative.
return;
}
Pooled pooled = allocate(Protocol.MESSAGE_CLOSE);
boolean ok = false;
try {
ByteBuffer buffer = pooled.getResource();
buffer.flip();
channel.getRemoteConnection().send(pooled);
ok = true;
closeSent = true;
} finally {
if (! ok) pooled.free();
}
}
private void doAcknowledge(final Pooled acked) {
assert holdsLock(inputStream);
if (eofReceived) {
// no ack needed; also a best-effort to work around broken peers
return;
}
final boolean badMsgSize = channel.getConnectionHandler().isFaultyMessageSize();
int consumed = acked.getResource().position();
if (! badMsgSize) consumed -= 8; // position minus header length (not including framing size)
inboundWindow += consumed;
Pooled pooled = allocate(Protocol.MESSAGE_WINDOW_OPEN);
boolean ok = false;
try {
ByteBuffer buffer = pooled.getResource();
buffer.putInt(consumed); // Open window by buffer size
buffer.flip();
channel.getRemoteConnection().send(pooled);
ok = true;
} finally {
if (! ok) pooled.free();
}
}
final MessageInputStream messageInputStream = new MessageInputStream() {
public int read() throws IOException {
synchronized (inputStream) {
if (cancelled) {
throw new MessageCancelledException();
}
return inputStream.read();
}
}
public int read(final byte[] bytes, final int offs, final int length) throws IOException {
synchronized (inputStream) {
if (cancelled) {
throw new MessageCancelledException();
}
return inputStream.read(bytes, offs, length);
}
}
public long skip(final long l) throws IOException {
synchronized (inputStream) {
if (cancelled) {
throw new MessageCancelledException();
}
return inputStream.skip(l);
}
}
public int available() throws IOException {
synchronized (inputStream) {
if (cancelled) {
throw new MessageCancelledException();
}
return inputStream.available();
}
}
public void close() throws IOException {
synchronized (inputStream) {
if (! streamClosed) {
inputStream.close();
if (cancelled) {
throw new MessageCancelledException();
}
}
}
}
};
Pooled allocate(byte protoId) {
Pooled pooled = channel.allocate(protoId);
ByteBuffer buffer = pooled.getResource();
buffer.putShort(messageId);
return pooled;
}
void handleIncoming(Pooled pooledBuffer) {
boolean eof;
boolean free = true;
try {
synchronized (inputStream) {
ByteBuffer buffer = pooledBuffer.getResource();
final int bufRemaining = buffer.remaining();
if ((inboundWindow -= bufRemaining) < 0) {
channel.getRemoteConnection().handleException(new IOException("Input overrun"));
return;
}
if (log.isTraceEnabled()) {
log.tracef("Received message (chan %08x msg %04x) (%d-%d=%d remaining)", Integer.valueOf(channel.getChannelId()), Short.valueOf(messageId), Integer.valueOf(inboundWindow + bufRemaining), Integer.valueOf(bufRemaining), Integer.valueOf(inboundWindow));
}
buffer.position(buffer.position() - 1);
byte flags = buffer.get();
eof = (flags & Protocol.MSG_FLAG_EOF) != 0;
boolean cancelled = (flags & Protocol.MSG_FLAG_CANCELLED) != 0;
if (bufRemaining > remaining) {
cancelled = true;
doClose();
}
if (cancelled) {
this.cancelled = true;
// make sure it goes through
inputStream.pushException(new MessageCancelledException());
}
if (streamClosed) {
// ignore, but keep the bits flowing
if (! eof && ! closeSent) {
// we don't need to acknowledge if it's EOF or if we sent a close msg since no more data is coming anyway
buffer.position(buffer.limit()); // "consume" everything
doAcknowledge(pooledBuffer);
}
} else if (! cancelled) {
remaining -= bufRemaining;
free = false;
inputStream.push(pooledBuffer);
}
if (eof) {
eofReceived = true;
if (!streamClosed) {
inputStream.pushEof();
}
channel.freeInboundMessage(messageId);
// if the peer is old, they might reuse the ID now regardless of us; if new, we have to send the close message to acknowledge the remainder
doSendCloseMessage();
}
}
} finally {
if (free) pooledBuffer.free();
}
}
void handleDuplicate() {
// this method is called when the remote side forgot about us. Our mapping will have been already replaced.
// We must not send anything to the peer from here on because things may be in a broken state.
// Though this is a best-effort strategy as everything is screwed up in this case anyway.
conn.duplicateMessageId(messageId, channel.getRemoteConnection().getPeerAddress());
synchronized (inputStream) {
if (! streamClosed) {
eofReceived = true; // it wasn't really, but we should act like it was
closeSent = true; // we didn't really, but we should act like we did
cancelled = true; // just not the usual way...
inputStream.pushException(conn.duplicateMessageIdException());
}
}
}
void dumpState(final StringBuilder b) {
b.append(" ").append(String.format("Inbound message ID %04x, window %d\n", messageId & 0xFFFF, inboundWindow));
b.append(" ").append("* flags: ");
if (cancelled) b.append("cancelled ");
if (closeSent) b.append("close-sent ");
if (streamClosed) b.append("stream-closed ");
if (eofReceived) b.append("eof-received ");
b.append('\n');
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy