/*
* 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.Configuration;
import io.aeron.driver.status.ChannelEndpointStatus;
import io.aeron.protocol.HeaderFlyweight;
import org.agrona.LangUtil;
import org.agrona.concurrent.UnsafeBuffer;
import org.agrona.concurrent.errors.DistinctErrorLog;
import org.agrona.concurrent.status.AtomicCounter;
import java.io.IOException;
import java.net.*;
import java.nio.ByteBuffer;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.DatagramChannel;
import java.nio.channels.SelectionKey;
import static io.aeron.logbuffer.FrameDescriptor.frameVersion;
import static java.net.StandardSocketOptions.*;
public abstract class UdpChannelTransport implements AutoCloseable
{
protected final UdpChannel udpChannel;
protected final AtomicCounter invalidPackets;
protected final DistinctErrorLog errorLog;
protected UdpTransportPoller transportPoller;
protected SelectionKey selectionKey;
protected InetSocketAddress bindAddress;
protected InetSocketAddress endPointAddress;
protected InetSocketAddress connectAddress;
protected DatagramChannel sendDatagramChannel;
protected DatagramChannel receiveDatagramChannel;
protected int multicastTtl = 0;
public UdpChannelTransport(
final UdpChannel udpChannel,
final InetSocketAddress endPointAddress,
final InetSocketAddress bindAddress,
final InetSocketAddress connectAddress,
final DistinctErrorLog errorLog,
final AtomicCounter invalidPackets)
{
this.udpChannel = udpChannel;
this.errorLog = errorLog;
this.endPointAddress = endPointAddress;
this.bindAddress = bindAddress;
this.connectAddress = connectAddress;
this.invalidPackets = invalidPackets;
}
/**
* Create the underlying channel for reading and writing.
*
* @param statusIndicator to set for status
*/
public void openDatagramChannel(final AtomicCounter statusIndicator)
{
try
{
sendDatagramChannel = DatagramChannel.open(udpChannel.protocolFamily());
receiveDatagramChannel = sendDatagramChannel;
if (udpChannel.isMulticast())
{
final NetworkInterface localInterface = udpChannel.localInterface();
if (null != connectAddress)
{
receiveDatagramChannel = DatagramChannel.open(udpChannel.protocolFamily());
}
receiveDatagramChannel.setOption(StandardSocketOptions.SO_REUSEADDR, true);
receiveDatagramChannel.bind(new InetSocketAddress(endPointAddress.getPort()));
receiveDatagramChannel.join(endPointAddress.getAddress(), localInterface);
sendDatagramChannel.setOption(StandardSocketOptions.IP_MULTICAST_IF, localInterface);
if (0 != udpChannel.multicastTtl())
{
sendDatagramChannel.setOption(StandardSocketOptions.IP_MULTICAST_TTL, udpChannel.multicastTtl());
multicastTtl = getOption(StandardSocketOptions.IP_MULTICAST_TTL);
}
if (null != connectAddress)
{
sendDatagramChannel.connect(connectAddress);
}
}
else
{
sendDatagramChannel.bind(bindAddress);
if (null != connectAddress)
{
sendDatagramChannel.connect(connectAddress);
}
}
if (0 != Configuration.SOCKET_SNDBUF_LENGTH)
{
sendDatagramChannel.setOption(SO_SNDBUF, Configuration.SOCKET_SNDBUF_LENGTH);
}
if (0 != Configuration.SOCKET_RCVBUF_LENGTH)
{
receiveDatagramChannel.setOption(SO_RCVBUF, Configuration.SOCKET_RCVBUF_LENGTH);
}
sendDatagramChannel.configureBlocking(false);
receiveDatagramChannel.configureBlocking(false);
}
catch (final IOException ex)
{
statusIndicator.setOrdered(ChannelEndpointStatus.ERRORED);
throw new RuntimeException("Erroneous channel: " + udpChannel.originalUriString(), ex);
}
}
/**
* Register this transport for reading from a {@link UdpTransportPoller}.
*
* @param transportPoller to register read with
*/
public void registerForRead(final UdpTransportPoller transportPoller)
{
this.transportPoller = transportPoller;
selectionKey = transportPoller.registerForRead(this);
}
/**
* Return underlying {@link UdpChannel}
*
* @return underlying channel
*/
public UdpChannel udpChannel()
{
return udpChannel;
}
/**
* The {@link DatagramChannel} for this transport channel.
*
* @return {@link DatagramChannel} for this transport channel.
*/
public DatagramChannel receiveDatagramChannel()
{
return receiveDatagramChannel;
}
/**
* Get the multicast TTL value for sending datagrams on the channel.
*
* @return the multicast TTL value for sending datagrams on the channel.
*/
public int multicastTtl()
{
return multicastTtl;
}
/**
* Close transport, canceling any pending read operations and closing channel
*/
public void close()
{
try
{
if (null != selectionKey)
{
selectionKey.cancel();
}
if (null != transportPoller)
{
transportPoller.cancelRead(this);
transportPoller.selectNowWithoutProcessing();
}
if (null != sendDatagramChannel)
{
sendDatagramChannel.close();
}
if (receiveDatagramChannel != sendDatagramChannel && null != receiveDatagramChannel)
{
receiveDatagramChannel.close();
}
}
catch (final IOException ex)
{
errorLog.record(ex);
}
}
/**
* Is transport representing a multicast media or unicast
*
* @return if transport is multicast media
*/
public boolean isMulticast()
{
return udpChannel.isMulticast();
}
/**
* Is the received frame valid. This method will do some basic checks on the header and can be
* overridden in a subclass for further validation.
*
* @param buffer containing the frame.
* @param length of the frame.
* @return true if the frame is believed valid otherwise false.
*/
public boolean isValidFrame(final UnsafeBuffer buffer, final int length)
{
boolean isFrameValid = true;
if (frameVersion(buffer, 0) != HeaderFlyweight.CURRENT_VERSION)
{
isFrameValid = false;
invalidPackets.increment();
}
else if (length < HeaderFlyweight.HEADER_LENGTH)
{
isFrameValid = false;
invalidPackets.increment();
}
return isFrameValid;
}
/**
* Receive a datagram from the media layer.
*
* @param buffer into which the datagram will be received.
* @return the source address of the datagram if one is available otherwise false.
*/
public InetSocketAddress receive(final ByteBuffer buffer)
{
buffer.clear();
InetSocketAddress address = null;
try
{
address = (InetSocketAddress)receiveDatagramChannel.receive(buffer);
}
catch (final PortUnreachableException | ClosedChannelException ignored)
{
// do nothing
}
catch (final Exception ex)
{
LangUtil.rethrowUnchecked(ex);
}
return address;
}
/**
* Return socket option value
*
* @param socketOption of the socket option
* @param <T> type of option
* @return option value
*/
protected <T> T getOption(final SocketOption<T> socketOption)
{
T option = null;
try
{
option = sendDatagramChannel.getOption(socketOption);
}
catch (final IOException ex)
{
LangUtil.rethrowUnchecked(ex);
}
return option;
}
}