
net.sf.eBus.client.EUDPConnection Maven / Gradle / Ivy
//
// Copyright 2019 Charles W. Rapp
//
// 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 net.sf.eBus.client;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.ProtocolException;
import java.net.SocketAddress;
import java.nio.BufferUnderflowException;
import java.nio.ByteBuffer;
import java.nio.channels.DatagramChannel;
import java.nio.channels.NetworkChannel;
import java.nio.channels.SelectableChannel;
import javax.net.ssl.SSLContext;
import static net.sf.eBus.client.EAbstractConnection.HEARTBEAT;
import static net.sf.eBus.client.EAbstractConnection.HEARTBEAT_REPLY;
import static net.sf.eBus.client.EAbstractConnection.MAX_MESSAGE_SIZE;
import static net.sf.eBus.client.EAbstractConnection.MESSAGE_HEADER_SIZE;
import net.sf.eBus.client.sysmessages.SystemMessageType;
import net.sf.eBus.client.sysmessages.UdpConnectReply;
import net.sf.eBus.client.sysmessages.UdpConnectRequest;
import net.sf.eBus.client.sysmessages.UdpDisconnectReply;
import net.sf.eBus.client.sysmessages.UdpDisconnectRequest;
import net.sf.eBus.config.EConfigure.AbstractConfig;
import net.sf.eBus.config.EConfigure.ConnectionRole;
import net.sf.eBus.config.EConfigure.ConnectionType;
import net.sf.eBus.config.EConfigure.RemoteConnection;
import net.sf.eBus.config.EConfigure.Service;
import net.sf.eBus.messages.EMessageHeader;
import net.sf.eBus.messages.EReplyMessage.ReplyStatus;
import net.sf.eBus.messages.InvalidMessageException;
import net.sf.eBus.messages.UnknownMessageException;
import net.sf.eBus.messages.type.DataType;
import net.sf.eBus.net.AbstractAsyncDatagramSocket;
import net.sf.eBus.net.AsyncChannel;
import static net.sf.eBus.net.AsyncChannel.LOCALHOST;
import net.sf.eBus.net.AsyncDatagramSocket;
import net.sf.eBus.net.AsyncDatagramSocket.DatagramBuilder;
import net.sf.eBus.net.AsyncSecureDatagramSocket;
import net.sf.eBus.net.DatagramBufferWriter;
import net.sf.eBus.net.SecureDatagramListener;
import net.sf.eBus.util.HexDump;
import net.sf.eBus.util.LazyString;
import net.sf.eBus.util.ValidationException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Used to either make a "connection" to a remote eBus
* application or "accept a connect" from a remote application.
* While UDP is a connection-less protocol, eBus is
* connection-based. This requires UDP communication to
* establish a connection to the remote application as by the
* means of handshaking with {@link UdpConnectRequest} and
* {@link UdpConnectReply} system messages. The connection is
* taken done by a similar {@link UdpDisconnectRequest} and
* {@link UdpDisconnectReply} handshake.
*
* Because {@link ERemoteApp} interacts with connections via
* {@link EAbstractConnection}, there is no difference between
* TCP and UDP connections in {@code ERemoteApp}. This class
* implements the code to establish a connection and
* encode/decode messages to and from UDP datagrams.
*
*
* @author Charles W. Rapp
*/
/* package */ class EUDPConnection
extends EAbstractConnection
implements SecureDatagramListener
{
//---------------------------------------------------------------
// Member data.
//
//-----------------------------------------------------------
// Constants.
//
private static final int CONNECT_REPLY_ID =
(SystemMessageType.UDP_CONNECT_REPLY).keyId();
private static final int DISCONNECT_REQUEST_ID =
(SystemMessageType.UDP_DISCONNECT_REQUEST).keyId();
private static final int DISCONNECT_REPLY_ID =
(SystemMessageType.UDP_DISCONNECT_REPLY).keyId();
//-----------------------------------------------------------
// Statics.
//
/**
* Access point for logging.
*/
private static final Logger sLogger =
LoggerFactory.getLogger(EUDPConnection.class);
//-----------------------------------------------------------
// Locals.
//
/**
* Secure context used to create a DTLS engine. Will be
* {@code null} if connection type is for a clear text
* UDP socket.
*/
private final SSLContext mDTLSContext;
/**
* Post the message to this address. This is not final
* because the address is not set until the UDP connection
* is opened.
*/
protected SocketAddress mTarget;
//---------------------------------------------------------------
// Member methods.
//
//-----------------------------------------------------------
// Constructors.
//
/**
* Creates a new EUDPConnection instance of the given
* connection type.
* @param connType UDP connection type.
* @param connRole either accepted or initiated.
* @param dtlsContext DTLS context used for a secure UDP
* connection type.
* @param remoteApp eBus remote application instance owning
* this connection.
*/
protected EUDPConnection(final ConnectionType connType,
final ConnectionRole connRole,
final SSLContext dtlsContext,
final ERemoteApp remoteApp)
{
super (connType, connRole, remoteApp, true);
mDTLSContext = dtlsContext;
} // end of EUDPConnection(...)
//
// end of Constructors.
//-----------------------------------------------------------
//-----------------------------------------------------------
// Abstract Method Implementations.
//
/**
* Creates the UDP connection datagram buffer writer.
* The buffer writer is initially targeted to the UDP
* "server" address.
* @param config UDP connection configuration.
*/
@Override
protected void doInitialize(final RemoteConnection config)
{
switch (config.connectionType())
{
case UDP:
case RELIABLE_UDP:
mAsocket = createClearTextUDP(config);
break;
case SECURE_UDP:
case SECURE_RELIABLE_UDP:
mAsocket = createSecureUDP(config);
break;
default:
}
} // end of initialize(RemoteConnection)
/**
* Creates a UDP connection for an "accepted" connection.
* In this case it means that a UDP "server" received a
* connect request from {@code address}.
* @param config eBus service configuration.
*/
@Override
protected void doInitialize(final Service config)
{
switch (config.connectionType())
{
case UDP:
case RELIABLE_UDP:
mAsocket = createClearTextUDP(config);
break;
case SECURE_UDP:
case SECURE_RELIABLE_UDP:
mAsocket = createSecureUDP(config);
break;
default:
}
} // end of doInitialize(Service)
/**
* Returns the output writer for this connection type.
* @param config remote connection configuration.
* @return connection output writer.
*/
@Override
protected AbstractMessageWriter createWriter(final AbstractConfig config)
{
return (new MessageWriter(config, this));
} // end of createWriter(AbstractConfig)
/**
* Closes the eBus connection if currently open. There is no
* difference between this method and {@link #closeNow} since
* UDP sends packets immediately. There are no pending
* packet transmits to drain prior to closing the datagram
* channel.
*
* @see #closeNow
*/
@Override
protected final void doClose()
{
// Make sure the datagram channel exists and is open
// before closing.
if (mAsocket != null && mAsocket.isOpen())
{
sLogger.debug("{}: closing connection.",
mRemoteAddress);
// Send the disconnect request to the far-end and
// wait for a reply before closing.
try
{
send(
new EMessageHeader(
DISCONNECT_REPLY_ID,
ERemoteApp.NO_ID,
ERemoteApp.NO_ID,
(UdpDisconnectReply.builder()).build()));
}
catch (IOException | ValidationException jex)
{
// Ignore.
}
}
} // end of doClose()
/**
* Closes the eBus connection immediately if currently open.
*
* @see #close
*/
@Override
protected final void doCloseNow()
{
// Make sure the datagram channel exists and is open
// before closing.
if (mAsocket != null && mAsocket.isOpen())
{
sLogger.debug("{}: closing connection now.",
mRemoteAddress);
// Send the disconnect request to the far-end but do
// do wait for a reply before closing.
try
{
send(
new EMessageHeader(
DISCONNECT_REPLY_ID,
ERemoteApp.NO_ID,
ERemoteApp.NO_ID,
(UdpDisconnectReply.builder()).build()));
}
catch (IOException | ValidationException jex)
{
// Ignore.
}
mAsocket.closeNow();
}
} // end of doCloseNow()
/**
* Establishes a UDP connection to the host and port stored
* in {@code config.address()}.
* @param address connect to this remote address.
* @param bindAddress bind the local address to this address.
* @return {@code false} since UDP connections cannot be
* established immediately.
* @throws IOException
* if an I/O failure occurs when sending the UDP connect
* request.
*/
@Override
protected final boolean doOpen(final SocketAddress address,
final SocketAddress bindAddress)
throws IOException
{
sLogger.debug("{}: connecting.", address);
// Open the datagram channel and immediately send the
// open UDP session message letting the far-end know
// that you want to connect.
((AsyncDatagramSocket) mAsocket).open(bindAddress);
// Target the output writer to the UDP "server".
mTarget = address;
// Send a connect request to that far-end.
send(
new EMessageHeader(
(SystemMessageType.UDP_CONNECT_REQUEST).keyId(),
ERemoteApp.NO_ID,
ERemoteApp.NO_ID,
(UdpConnectRequest.builder()).build()));
// UDP "connection" is not up immediately, so return
// false to let the caller know.
return (false);
} // end of doOpen(SocketAddress, int)
/**
* Encapsulates an already "connected" UDP socket created by
* an {@link EUDPServer} instance. Will not
* reconnect if the connection is lost due to heartbeat
* failure.
* @param socket accepted UDP channel.
* @return {@code true} if this is a clear text channel and
* {@code false} if a secure channel.
* @throws IOException
* if {@code socket} is closed.
*/
@Override
protected final boolean doOpen(final SelectableChannel socket)
throws IOException
{
final EUDPServer server =
(EUDPServer) mRemoteApp.acceptingServer();
final InetSocketAddress localAddress =
(InetSocketAddress)
((NetworkChannel) socket).getLocalAddress();
final boolean isSecure = mConnectionType.isSecure();
((AsyncDatagramSocket) mAsocket).open(
(DatagramChannel) socket);
// Send OK UDP connection reply containing this socket's
// local address. This must be done *before* connecting
// a secure datagram socket since the DTLS opening
// handshake bytes will be interleaved with the eBus
// message.
server.sendReply(ReplyStatus.OK_FINAL,
null,
localAddress.getPort(),
mRemoteAddress);
// Is this a secure datagram socket?
if (!isSecure)
{
// No. Use a vanilla UDP connection which immediately
// takes effect.
sLogger.debug("{}: connecting to peer.",
mRemoteAddress);
((AsyncDatagramSocket) mAsocket).connect(mTarget);
}
else
{
// Yes. Start the server-side DTLS opening handshake.
sLogger.debug(
"{}: starting DTLS opening service handshake, context {}.",
mRemoteAddress,
(mDTLSContext == null ?
"(not set)" :
mDTLSContext.getProtocol()));
setState(ConnectState.UDP_SECURE_CONNECTING);
((AsyncSecureDatagramSocket) mAsocket).connect(
mRemoteAddress, false, mDTLSContext);
}
// Now that this UDP socket is "connected" to its peer,
// set the target address to null because MessageWriter
// now needs to return null. Otherwise
// MessageWriter.fill() will cause an exception.
mTarget = null;
sLogger.info("{}: sending OK connect reply.",
mRemoteAddress);
return (!isSecure);
} // end of doOpen(SelectableChannel)
/**
* If this is a secure connection, then resets the connection
* state to {@link ConnectState#UDP_SECURE_CONNECTING} and
* begins the process of establishing a secure UDP connection
* to peer. Otherwise does nothing since the underlying
* socket is not connected.
* @throws IOException
* if an I/O exception occurs setting up the UDP connection.
*/
@Override
protected final void doConnected()
throws IOException
{
// Clear text or secure?
if (!mConnectionType.isSecure())
{
// No, clear text. Use a vanilla UDP connection which
// immediately takes effect.
sLogger.debug("{}: connecting to peer.",
mRemoteAddress);
((AsyncDatagramSocket) mAsocket).connect(mTarget);
}
else
{
sLogger.debug(
"{}: starting DTLS opening client handshake.",
mRemoteAddress);
setState(ConnectState.UDP_SECURE_CONNECTING);
((AsyncSecureDatagramSocket) mAsocket).connect(
mTarget, true, mDTLSContext);
}
// Now that this UDP socket is "connected" to its peer,
// set the target address to null because MessageWriter
// now needs to return null. Otherwise
// MessageWriter.fill() will cause an exception.
mTarget = null;
} // end of doConnected()
/**
* Returns {@code true} if the connection state is connected
* or connecting.
* @return {@code true} if clear to transmit a message.
*/
@Override
protected final boolean maySend()
{
final ConnectState state = mState.get();
return (state == ConnectState.CONNECTED ||
state == ConnectState.CONNECTING ||
state == ConnectState.CLOSING);
} // end of maySend()
/**
* Sends message stored in {@code writer} to remote eBus
* application. Since there is no delay in UDP packet
* transmits, the transmit will fail fast. Also the UDP
* outbound byte buffer will not overflow unless the message
* exceeds the buffer size. In that case it will be
* impossible to transmit the message.
* @param writer outbound message is stored in this writer.
* @throws IOException
*/
@Override
protected void doSend(final AbstractMessageWriter writer)
throws IOException
{
((AbstractAsyncDatagramSocket) mAsocket).send(
(DatagramBufferWriter) writer);
} // end of doSend(AbstractMessageWriter)
/**
* Sends heartbeat bytes to far-end via a UDP datagram.
* @throws IOException
* if an I/O failure occurs in transmitting the heartbeat
* bytes.
*/
@Override
protected final void doSendHeartbeat()
throws IOException
{
((AsyncDatagramSocket) mAsocket).send(mHeartbeatData, 0, 0);
} // end of doSendHeartbeat()
//
// end of Abstract Method Implementations.
//-----------------------------------------------------------
//-----------------------------------------------------------
// SecureDatagramListener Interface Implementation
//
/**
* Handles situation where a secure datagram socket
* is connected to its peer and DTLS secure handshake
* successfully completed. No messages may be transmitted
* to peer until this happens. Mark this socket as connected
* and start the heartbeat timer.
* @param socket now securely connected datagram socket.
*/
@Override
public void handleConnect(final AsyncSecureDatagramSocket socket)
{
sLogger.debug(
"{}: DTLS handshake successfully completed.",
mRemoteAddress);
mState.set(ConnectState.CONNECTED);
// Is this an accepted or initiaed connection?
if (mConnectRole == ConnectionRole.INITIATOR)
{
// Initiated Then call fullyConnected which completes
// the initiator connection process.
fullyConnected();
}
// Accepted. Then simply start the heartbeat timer.
else
{
startHeartbeatTimer();
}
} // end of handleConnect(AsyncSecureDatagramSocket)
/**
* Reports an unexpected disconnect on the secure UDP
* socket. Start the reconnect process.
* @param t reason for connection loss, possibly
* {@code null}.
* @param socket disconnect occurred on this UDP socket.
*/
@Override
public void handleDisconnect(final Throwable t,
final AsyncSecureDatagramSocket socket)
{
disconnected(t);
} // end of handleDisconnect(Throwable, AsyncSecureDatagramSocket)
/**
* Processes the UDP datagram contained in {@code buffer} if
* this UDP connection is either connecting or fully
* connecting. If in the connecting state then the buffer
* should contain a UDP connection message.
* @param buffer contains UDP datagram.
* @param address datagram received from this address.
* @param socket datagram received on this socket.
*/
@Override
public void handleInput(final ByteBuffer buffer,
final InetSocketAddress address,
final AbstractAsyncDatagramSocket socket)
{
final ConnectState state = mState.get();
sLogger.trace(
"{}: received {} bytes from {}, state={}.",
mRemoteAddress,
buffer.limit(),
address,
mState);
// Are we still connected or connecting?
if (state == ConnectState.CONNECTED ||
state == ConnectState.CONNECTING)
{
// Yes, carry on.
processInput(buffer, address);
}
// No, ignore the input but do clear it out by setting
// the buffer position to its limit.
else
{
buffer.position(buffer.limit());
}
} // end of handleInput(...
/**
* Logs the given socket exception.
* @param t socket exception.
* @param socket exception occurred on this UDP socket.
*/
@Override
public void handleError(final Throwable t,
final AbstractAsyncDatagramSocket socket)
{
sLogger.warn("UDP socket error.", t);
} // end of handleError(
//
// end of SecureDatagramListener Interface Implementation
//-----------------------------------------------------------
/**
* Returns a new, closed eBus UDP connection configured
* according to {@code config}.
* @param config remote connection configuration.
* @param remoteApp pass messages to this eBus remote
* application interface.
* @return a closed eBus UDP connection.
*
* @see #create(InetSocketAddress, Service, ERemoteApp)
* @see #doOpen(RemoteConnection)
*/
/* package */ static EUDPConnection create(final RemoteConnection config,
final ERemoteApp remoteApp)
{
final EUDPConnection retval =
new EUDPConnection(config.connectionType(),
ConnectionRole.INITIATOR,
config.sslContext(),
remoteApp);
// Initialize the socket, message writer, and message
// readers map.
retval.initialize(config);
return (retval);
} // end of create(RemoteConnection, ERemoteApp)
/**
* Creates a UDP connection for the "accepted" connection
* from {@code address}. This means that the UDP "server"
* received a connect request from a UDP connection at that
* address and port.
* @param address far end UDP connection.
* @param config eBus service configuration.
* @param remoteApp connection belongs to this remote
* application proxy.
* @return UDP connection.
*/
/* package */ static EUDPConnection create(final InetSocketAddress address,
final Service config,
final ERemoteApp remoteApp)
{
final EUDPConnection retval =
new EUDPConnection(config.connectionType(),
ConnectionRole.ACCEPTOR,
config.sslContext(),
remoteApp);
// Initialize the socket, message writer, and message
// readers map.
retval.initialize(config);
retval.mTarget = address;
return (retval);
} // end of create(InetSocketAddress, Service, ERemoteApp)
/**
* Provides the connection heartbeat implementation and
* top-level message serialization validation. The actual
* message decoding is performed in
* {@link #decode(ByteBuffer, InetSocketAddress)}.
* @param buffer contains message from far-end.
* @param address far-end host and port.
*
* @see #decode(ByteBuffer, InetSocketAddress)
*/
protected void processInput(final ByteBuffer buffer,
final InetSocketAddress address)
{
// Stop whichever heart timer is running while processing
// input.
stopHeartbeatTimer();
// The fact that we received input of any kind serves
// purpose of a heartbeat reply. We know that the
// connection is still alive. So turn off the heartbeat
// reply flag.
mHeartbeatReplyFlag = false;
if (sLogger.isTraceEnabled())
{
final int rem = buffer.remaining();
final int pos = buffer.position();
final int ms = buffer.getInt(pos);
final byte[] data = new byte[rem];
buffer.mark();
buffer.get(data);
buffer.reset();
sLogger.trace(
"{}: {} bytes available (start={}, msg size={}):\n{}",
address,
rem,
pos,
ms,
new LazyString(
() -> HexDump.dump(data, 0, rem, " ")));
}
else
{
sLogger.debug("{}: {} bytes available.",
address,
buffer.remaining());
}
final int messageSize = buffer.getInt();
boolean hbReplyFlag = false;
// Heartbeat and heartbeat reply messages are
// simply two-byte integers that are < 0.
if (messageSize == HEARTBEAT)
{
hbReplyFlag = true;
}
else if (messageSize == HEARTBEAT_REPLY)
{
// That's nice. A reply to our heartbeat.
// But the heartbeat flag is already turned off.
// So there is nothing else to do.
}
// The message size must be at least big enough for
// the message header or is within the maximum
// allowed message size. If not, disconnect; there is
// not way to recover from this protocol error.
else if (messageSize < MESSAGE_HEADER_SIZE ||
messageSize > MAX_MESSAGE_SIZE)
{
logError(
new ProtocolException(
"invalid message size - " +
Integer.toString(messageSize)));
}
else
{
// Have the message reader parse the message and
// forward it to the remote application interface.
decode(buffer, address);
}
// Do we need to send a heartbeat reply?
if (hbReplyFlag)
{
// Yes, send the reply on its way.
try
{
sLogger.trace("{}: sending heartbeat reply.",
mRemoteAddress);
((AsyncDatagramSocket) mAsocket).send(
mHeartbeatReplyData,
0,
mHeartbeatReplyData.length);
}
catch (IOException bufex)
{
// Ignore.
// Why?
// Because a full output buffer means that we
// have output waiting to be sent and that output
// will serve as a heartbeat reply. See the top
// of this method.
// On the other hand, if the other side is
// sending us a heartbeat while our output buffer
// is full up, then something is desparately
// wrong. We're pitching bytes and they keep
// missing them. If max output message queue is
// set, we will likely hit that max and doClose
// the connection. If not set, then this JVM will
// run out of heap and crash.
}
}
// Now that we have finished processing input,
// start the appropriate heartbeat timer.
startHeartbeatTimer();
} // end of processInput(ByteBuffer, InetSocketAddress)
/**
* Decodes the message and processes it as per the message
* key identifier. Checks if this is a UDP connection system
* message or an application message. Connection messages are
* consumed by this UDP connection; application messages are
* forwarded to {@link ERemoteApp}.
* @param buffer buffer containing input from far-end.
* @param address far-end address.
*/
@SuppressWarnings({"java:S3457"})
protected final void decode(final ByteBuffer buffer,
final InetSocketAddress address)
{
int keyId = Integer.MIN_VALUE;
try
{
keyId = buffer.getInt();
final MessageReader reader =
mInputReaders.get(keyId);
final EMessageHeader header =
reader.extractMessage(buffer, address);
processMessage(keyId, address, header, reader);
}
catch (NullPointerException nullex)
{
sLogger.warn("received unsupported key ID {}",
keyId,
nullex);
}
catch (BufferUnderflowException |
InvalidMessageException |
UnknownMessageException jex)
{
logError(jex);
}
} // end of decode(ByteBuffer, InetSocketAddress)
/**
* Handles UDP-specific system messages internally. Otherwise
* passes message to
* {@link MessageReader#forwardMessage(EMessageHeader, ERemoteApp)}.
* @param keyId message key identifier extracted from message
* header.
* @param address far-end address.
* @param header decoded message header.
* @param reader message reader used to decode message.
*/
protected void processMessage(final int keyId,
final InetSocketAddress address,
final EMessageHeader header,
final MessageReader reader)
{
// Did the far-end reply to a connect request?
if (keyId == CONNECT_REPLY_ID)
{
// Yes.
connectReply(
(UdpConnectReply) header.message(), address);
}
// Does the far-end wish to disconnect?
else if (keyId == DISCONNECT_REQUEST_ID)
{
// Yes.
disconnectRequest();
}
// Is the far-end replying to a disconnect request?
else if (keyId == DISCONNECT_REPLY_ID)
{
// Yes.
disconnectReply();
}
// Neither. Forward this message to the encapsulating
// remote app.
else
{
reader.forwardMessage(header, mRemoteApp);
}
} // end of processMessage(...)
/**
* Completes the UDP connection process as per the reply
* status.
* @param reply UDP connection reply.
* @param address reply came from this address.
*/
private void connectReply(final UdpConnectReply reply,
final InetSocketAddress address)
{
// Did the far-end accept or reject the connect request?
if (reply.status == ReplyStatus.OK_FINAL)
{
// Accepted. Reset the target address to the
// given address.
mTarget =
new InetSocketAddress(
((InetSocketAddress) mTarget).getAddress(),
reply.port);
connected();
}
// REJECTED!
else
{
connectFailed(address, LOCALHOST);
}
} // end of connectReply(UdpConnectReply, InetSocketAddress)
/**
* Handles the disconnect request by first sending back a
* disconnect reply and then disconnecting this end of the
* UDP connection.
*/
private void disconnectRequest()
{
// Send a disconnect reply back first and then doClose the
// channel, and finally let upstream know about this.
try
{
send(
new EMessageHeader(
DISCONNECT_REPLY_ID,
ERemoteApp.NO_ID,
ERemoteApp.NO_ID,
(UdpDisconnectReply.builder()).build()));
}
catch (IOException | ValidationException jex)
{
// Ignore.
}
doCloseNow();
disconnected(null);
} // end of disconnectRequest()
/**
* Handles the disconnect reply by immediately disconnecting
* this end of the UDP connection.
*/
private void disconnectReply()
{
// Now doClose the connection and let ERemoteApp know about
// it.
doCloseNow();
disconnected(null);
} // end of disconnectReply()
/**
* Returns a plain text UDP connection based on the given
* remote connection configuration.
* @param config remote connection configuration.
* @return un-secure UDP client connection.
*/
private AsyncChannel createClearTextUDP(final RemoteConnection config)
{
final DatagramBuilder builder =
AsyncDatagramSocket.builder();
sLogger.debug(
"Creating clear text UDP client connection");
return (builder.inputBufferSize(config.inputBufferSize())
.outputBufferSize(config.outputBufferSize())
.byteOrder(config.byteOrder())
.selector(config.selector())
.listener(this)
.build());
} // end of createClearTextUDP(RemoteConnection)
/**
* Returns a DTLS secure UDP connection based on the given
* remote connection configuration.
* @param config remote connection configuration.
* @return secure UDP client connection.
*/
private AsyncChannel createSecureUDP(final RemoteConnection config)
{
final AsyncSecureDatagramSocket.SecureDgramSocketBuilder builder =
AsyncSecureDatagramSocket.secureBuilder();
sLogger.debug("Creating secure UDP client connection");
return (builder.inputBufferSize(config.inputBufferSize())
.outputBufferSize(config.outputBufferSize())
.byteOrder(config.byteOrder())
.selector(config.selector())
.listener(this)
.build());
} // end of createSecureUDP(RemoteConnection)
/**
* Returns a clear text UDP connection based on the given
* local service configuration.
* @param config local service configuration.
* @return un-secure UDP connection.
*/
// This method is used by doInitialize(Service)
@SuppressWarnings({"java:S1144"})
private AsyncChannel createClearTextUDP(final Service config)
{
final DatagramBuilder builder =
AsyncDatagramSocket.builder();
sLogger.debug(
"Creating clear text UDP accepted connection.");
return (builder.inputBufferSize(config.inputBufferSize())
.outputBufferSize(config.outputBufferSize())
.byteOrder(config.byteOrder())
.selector(config.connectionSelector())
.listener(this)
.build());
} // end of createClearTextUDP(Service)
/**
* Returns a DTLS secure UDP connection based on the given
* local service configuration.
* @param config remote connection configuration.
* @return secure UDP client connection.
*/
// This method is used by doInitialize(Service)
@SuppressWarnings({"java:S1144"})
private AsyncChannel createSecureUDP(final Service config)
{
final AsyncSecureDatagramSocket.SecureDgramSocketBuilder builder =
AsyncSecureDatagramSocket.secureBuilder();
sLogger.debug("Creating secure UDP accepted connection");
return (builder.inputBufferSize(config.inputBufferSize())
.outputBufferSize(config.outputBufferSize())
.byteOrder(config.byteOrder())
.selector(config.connectionSelector())
.listener(this)
.build());
} // end of createSecureUDP(Service)
//---------------------------------------------------------------
// Inner classes.
//
/**
* Responsible for encoding outbound eBus messages to the
* underlying {@link AsyncDatagramSocket} outbound buffer.
*/
private final class MessageWriter
extends AbstractMessageWriter
implements DatagramBufferWriter
{
//-----------------------------------------------------------
// Member data.
//
//-----------------------------------------------------------
// Member methods.
//
//-------------------------------------------------------
// Constructors.
//
/**
* Creates the datagram buffer message writer for the
* given UDP connection.
* @param config connection configuration.
* @param connection UDP connection.
*/
private MessageWriter(final AbstractConfig config,
final EUDPConnection connection)
{
super (config, connection);
} // end of MessageWriter(EUDPConnection)
//
// end of Constructors.
//-------------------------------------------------------
//-------------------------------------------------------
// DatagramBufferWriter Interface Implementation.
//
/**
* Encodes the first message in the queue to the given
* byte buffer. Returns message destination address if
* targeted and {@code null} if datagram socket is
* connected.
* @param buffer place encoded message into this buffer.
* @return destination address.
*/
@Override
public SocketAddress fill(final ByteBuffer buffer)
{
final EMessageHeader header = mTransmitQueue.poll();
final DataType msgType = header.dataType();
final int sizePosition = buffer.position();
int queueSize;
// Move past the message size bytes and fill in the
// rest of the header.
// Note: buffer.position() returns Buffer and not
// ByteBuffer.
(buffer.position(sizePosition + MESSAGE_SIZE_SIZE))
.putInt(header.classId())
.putInt(header.fromFeedId())
.putInt(header.toFeedId());
msgType.serialize(header.message(), null, buffer);
// Now write out the message size.
buffer.putInt(sizePosition,
(buffer.position() - sizePosition));
// Decrement the transmit queue size
// if-and-only-if the message was *not* system.
if (header.isSystemMessage())
{
queueSize = mTransmitQueueSize.get();
}
else
{
queueSize =
mTransmitQueueSize.decrementAndGet();
}
++mTransmitCount;
sLogger.trace(
"{}: queued message sent to {} (size={}, transmited={}, discarded={}).",
mConnection.remoteSocketAddress(),
(mTarget == null ?
"connected" :
mTarget),
queueSize,
mTransmitCount,
mDiscardCount);
return (mTarget);
} // end of fill(ByteBuffer)
//
// end of DatagramBufferWriter Interface Implementation.
//-------------------------------------------------------
} // end of class MessageWriter
} // end of class EUDPConnection
© 2015 - 2025 Weber Informatics LLC | Privacy Policy