/* * Copyright 2014-2017 Real Logic Ltd. * * 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 io.aeron.driver.media; import io.aeron.driver.*; import io.aeron.driver.exceptions.ConfigurationException; import io.aeron.driver.status.ChannelEndpointStatus; import io.aeron.protocol.*; import org.agrona.LangUtil; import org.agrona.collections.Int2IntCounterMap; import org.agrona.concurrent.status.AtomicCounter; import org.agrona.concurrent.UnsafeBuffer; import java.io.IOException; import java.net.InetSocketAddress; import java.net.StandardSocketOptions; import java.nio.ByteBuffer; import static io.aeron.driver.status.ChannelEndpointStatus.status; import static io.aeron.driver.status.SystemCounterDescriptor.*; import static io.aeron.protocol.StatusMessageFlyweight.SEND_SETUP_FLAG; /** * Aggregator of multiple subscriptions onto a single transport channel for receiving of data and setup frames * plus sending status and NAK frames. */ @EventLog public class ReceiveChannelEndpoint extends UdpChannelTransport { private final DataPacketDispatcher dispatcher; private final ByteBuffer smBuffer; private final StatusMessageFlyweight statusMessageFlyweight; private final ByteBuffer nakBuffer; private final NakFlyweight nakFlyweight; private final ByteBuffer rttMeasurementBuffer; private final RttMeasurementFlyweight rttMeasurementFlyweight; private final AtomicCounter shortSends; private final AtomicCounter possibleTtlAsymmetry; private final AtomicCounter statusIndicator; private final Int2IntCounterMap refCountByStreamIdMap = new Int2IntCounterMap(0); private final long receiverId; private int soRcvBufLength; private boolean isClosed = false; public ReceiveChannelEndpoint( final UdpChannel udpChannel, final DataPacketDispatcher dispatcher, final AtomicCounter statusIndicator, final MediaDriver.Context context) { super( udpChannel, udpChannel.remoteData(), udpChannel.remoteData(), null, context.errorLog(), context.systemCounters().get(INVALID_PACKETS)); this.dispatcher = dispatcher; this.statusIndicator = statusIndicator; shortSends = context.systemCounters().get(SHORT_SENDS); possibleTtlAsymmetry = context.systemCounters().get(POSSIBLE_TTL_ASYMMETRY); final ReceiveChannelEndpointThreadLocals threadLocals = context.receiveChannelEndpointThreadLocals(); smBuffer = threadLocals.smBuffer(); statusMessageFlyweight = threadLocals.statusMessageFlyweight(); nakBuffer = threadLocals.nakBuffer(); nakFlyweight = threadLocals.nakFlyweight(); rttMeasurementBuffer = threadLocals.rttMeasurementBuffer(); rttMeasurementFlyweight = threadLocals.rttMeasurementFlyweight(); receiverId = threadLocals.receiverId(); } /** * Send contents of {@link java.nio.ByteBuffer} to remote address * * @param buffer to send * @param remoteAddress to send to * @return number of bytes sent */ public int sendTo(final ByteBuffer buffer, final InetSocketAddress remoteAddress) { int bytesSent = 0; try { bytesSent = sendDatagramChannel.send(buffer, remoteAddress); } catch (final IOException ex) { LangUtil.rethrowUnchecked(ex); } return bytesSent; } public String originalUriString() { return udpChannel().originalUriString(); } public void indicateActive() { final long currentStatus = statusIndicator.get(); if (currentStatus != ChannelEndpointStatus.INITIALIZING) { throw new IllegalStateException( "Channel cannot be registered unless INITIALISING: status=" + status(currentStatus)); } statusIndicator.setOrdered(ChannelEndpointStatus.ACTIVE); } public void closeStatusIndicator() { if (!statusIndicator.isClosed()) { statusIndicator.setOrdered(ChannelEndpointStatus.CLOSING); statusIndicator.close(); } } public void close() { super.close(); isClosed = true; } public void openChannel() { openDatagramChannel(statusIndicator); soRcvBufLength = getOption(StandardSocketOptions.SO_RCVBUF); } public void possibleTtlAsymmetryEncountered() { possibleTtlAsymmetry.orderedIncrement(); } public int incRefToStream(final int streamId) { return refCountByStreamIdMap.incrementAndGet(streamId); } public int decRefToStream(final int streamId) { final int count = refCountByStreamIdMap.decrementAndGet(streamId); if (-1 == count) { refCountByStreamIdMap.remove(streamId); throw new IllegalStateException("Could not find stream Id to decrement: " + streamId); } return count; } public int streamCount() { return refCountByStreamIdMap.size(); } public boolean shouldBeClosed() { return refCountByStreamIdMap.isEmpty() && !statusIndicator.isClosed(); } public boolean hasExplicitControl() { return udpChannel.hasExplicitControl(); } public InetSocketAddress explicitControlAddress() { return udpChannel.hasExplicitControl() ? udpChannel.localControl() : null; } public int onDataPacket( final DataHeaderFlyweight header, final UnsafeBuffer buffer, final int length, final InetSocketAddress srcAddress) { return dispatcher.onDataPacket(this, header, buffer, length, srcAddress); } public void onSetupMessage( final SetupFlyweight header, final UnsafeBuffer buffer, final int length, final InetSocketAddress srcAddress) { dispatcher.onSetupMessage(this, header, buffer, srcAddress); } public void onRttMeasurement( final RttMeasurementFlyweight header, final UnsafeBuffer buffer, final int length, final InetSocketAddress srcAddress) { if (header.receiverId() == receiverId || header.receiverId() == 0) { dispatcher.onRttMeasurement(this, header, srcAddress); } } public void sendSetupElicitingStatusMessage( final InetSocketAddress controlAddress, final int sessionId, final int streamId) { sendStatusMessage(controlAddress, sessionId, streamId, 0, 0, 0, SEND_SETUP_FLAG); } public void validateWindowMaxLength(final int windowMaxLength) { if (windowMaxLength > soRcvBufLength) { throw new ConfigurationException(String.format( "Max Window length greater than socket SO_RCVBUF, " + "increase '%s' to match window: windowMaxLength=%d, SO_RCVBUF=%d", Configuration.INITIAL_WINDOW_LENGTH_PROP_NAME, windowMaxLength, soRcvBufLength)); } } public void validateSenderMtuLength(final int senderMtuLength) { Configuration.validateMtuLength(senderMtuLength); if (senderMtuLength > soRcvBufLength) { throw new ConfigurationException(String.format( "Sender MTU greater than socket SO_RCVBUF, " + "increase '%s' to match MTU: senderMtuLength=%d, SO_RCVBUF=%d", Configuration.SOCKET_RCVBUF_LENGTH_PROP_NAME, senderMtuLength, soRcvBufLength)); } } public void sendStatusMessage( final InetSocketAddress controlAddress, final int sessionId, final int streamId, final int termId, final int termOffset, final int window, final short flags) { if (!isClosed) { smBuffer.clear(); statusMessageFlyweight .sessionId(sessionId) .streamId(streamId) .consumptionTermId(termId) .consumptionTermOffset(termOffset) .receiverWindowLength(window) .flags(flags); final int bytesSent = sendTo(smBuffer, controlAddress); if (StatusMessageFlyweight.HEADER_LENGTH != bytesSent) { shortSends.increment(); } } } public void sendNakMessage( final InetSocketAddress controlAddress, final int sessionId, final int streamId, final int termId, final int termOffset, final int length) { if (!isClosed) { nakBuffer.clear(); nakFlyweight .streamId(streamId) .sessionId(sessionId) .termId(termId) .termOffset(termOffset) .length(length); final int bytesSent = sendTo(nakBuffer, controlAddress); if (NakFlyweight.HEADER_LENGTH != bytesSent) { shortSends.increment(); } } } public void sendRttMeasurement( final InetSocketAddress controlAddress, final int sessionId, final int streamId, final long echoTimestampNs, final long receptionDelta, final boolean isReply) { if (!isClosed) { rttMeasurementFlyweight .sessionId(sessionId) .streamId(streamId) .receiverId(receiverId) .echoTimestampNs(echoTimestampNs) .receptionDelta(receptionDelta) .flags(isReply ? RttMeasurementFlyweight.REPLY_FLAG : 0); final int bytesSent = sendTo(rttMeasurementBuffer, controlAddress); if (RttMeasurementFlyweight.HEADER_LENGTH != bytesSent) { shortSends.increment(); } } } public void removePendingSetup(final int sessionId, final int streamId) { dispatcher.removePendingSetup(sessionId, streamId); } public void removePublicationImage(final PublicationImage publicationImage) { dispatcher.removePublicationImage(publicationImage); } public void addSubscription(final int streamId) { dispatcher.addSubscription(streamId); } public void removeSubscription(final int streamId) { dispatcher.removeSubscription(streamId); } public void addPublicationImage(final PublicationImage image) { dispatcher.addPublicationImage(image); } public void removeCoolDown(final int sessionId, final int streamId) { dispatcher.removeCoolDown(sessionId, streamId); } public boolean shouldElicitSetupMessage() { return dispatcher.shouldElicitSetupMessage(); } }