All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.sf.eBus.client.EUDPConnection Maven / Gradle / Ivy

There is a newer version: 7.6.0
Show newest version
//
// 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