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

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

The newest version!
//
// Copyright 2020 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 com.google.common.base.Strings;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.net.StandardProtocolFamily;
import java.nio.BufferUnderflowException;
import java.nio.ByteBuffer;
import java.nio.channels.DatagramChannel;
import java.nio.channels.SelectableChannel;
import static net.sf.eBus.client.EAbstractConnection.ANY_PORT;
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.config.EConfigure;
import net.sf.eBus.config.EConfigure.Service;
import net.sf.eBus.messages.EMessage;
import net.sf.eBus.messages.EMessageHeader;
import net.sf.eBus.messages.EReplyMessage.ReplyStatus;
import net.sf.eBus.messages.ESystemMessage;
import net.sf.eBus.messages.type.DataType;
import net.sf.eBus.messages.type.MessageType;
import net.sf.eBus.net.AbstractAsyncDatagramSocket;
import net.sf.eBus.net.AsyncDatagramSocket;
import net.sf.eBus.net.DatagramBufferWriter;
import net.sf.eBus.net.DatagramListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * This UDP datagram socket "accepts" a new UDP datagram socket
 * from a remote eBus application. While UDP is a connection-less
 * protocol eBus is connection-based. So a UDP server socket is
 * used to accept a new connection via when it receives a
 * {@link UdpConnectRequest} message. UDP server is created in
 * two ways: via the
 * {@link net.sf.eBus.config.EConfigure.ServerBuilder} API and
 * passing the {@link Service} instance to
 * {@link EUDPServer#EUDPServer(EConfigure.Service)} or at
 * application start using a properties or JSON configuration
 * file. See {@link net.sf.eBus.config.EConfigure} about using
 * properties or configuration files.
 *
 * @see ETCPServer
 * @see EUDPConnection
 *
 * @author Charles W. Rapp
 */

public class EUDPServer
    extends EServer
    implements DatagramListener
{
//---------------------------------------------------------------
// Member data.
//

    //-----------------------------------------------------------
    // Constants.
    //

    /**
     * UDP connect request key identifier.
     */
    protected static final int UDP_CONNECT_REQUEST_KEY_ID =
        (SystemMessageType.UDP_CONNECT_REQUEST).keyId();

    /**
     * UDP connect reply key identifier.
     */
    protected static final int UDP_CONNECT_REPLY_KEY_ID =
        (SystemMessageType.UDP_CONNECT_REPLY).keyId();

    //-----------------------------------------------------------
    // Statics.
    //

    /**
     * Message type for de-serializing UDP connect request
     * messages.
     */
    private static final MessageType sRequestType =
        (MessageType) DataType.findType(UdpConnectRequest.class);

    /**
     * Message type for serializing UDP connect reply.
     */
    protected static final MessageType sReplyType =
        (MessageType) DataType.findType(UdpConnectReply.class);

    /**
     * The logging subsystem interface.
     */
    private static final Logger sLogger =
        LoggerFactory.getLogger(EUDPServer.class);

    //-----------------------------------------------------------
    // Locals.
    //

    /**
     * Encodes a {@link UdpConnectReply} message to the UDP
     * socket.
     */
    private final DatagramBufferWriter mWriter;

    /**
     * UDP datagram socket used to accept UDP connect requests.
     */
    private AsyncDatagramSocket mSocket;

    /**
     * The most recent UDP connect request came from this address.
     * Stored temporarily during the connection process and
     * dropped once the connect reply is sent.
     */
    private SocketAddress mSourceAddress;

//---------------------------------------------------------------
// Member methods.
//

    //-----------------------------------------------------------
    // Constructors.
    //

    /**
     * Creates a new, closed UDP server instance.
     * @param config UDP server configuration.
     */
    public EUDPServer(final Service config)
    {
        super (config);

        mWriter = new UDPMessageWriter();
    } // end of EUDPServer(Service)

    /**
     * Creates a new, closed UDP server instance for the given
     * configuration and message writer.
     * @param config UDP server configuration.
     * @param writer server message writer.
     */
    protected EUDPServer(final Service config,
                         final UDPMessageWriter writer)
    {
        super (config);

        mWriter = writer;
    } // end of EUDPServer(Service, UDPMessageWriter)

    //
    // end of Constructors.
    //-----------------------------------------------------------

    //-----------------------------------------------------------
    // Abstract Method Implementations.
    //

    /**
     * Creates a new {@code AsyncDatagramSocket} and opens on the
     * given server address.
     * @throws IOException
     * if there is an I/O failure creating and opening the server
     * UDP socket.
     */
    @Override
    protected void doOpen()
        throws IOException
    {
        if (mSocket == null)
        {
            final AsyncDatagramSocket.DatagramBuilder builder =
                AsyncDatagramSocket.builder();

            mSocket =
                builder.byteOrder(mConfiguration.byteOrder())
                       .inputBufferSize(mConfiguration.inputBufferSize())
                       .outputBufferSize(mConfiguration.outputBufferSize())
                       .selector(mConfiguration.serviceSelector())
                       .listener(this)
                       .build();
        }

        if (!mSocket.isOpen())
        {
            mSocket.open(mAddress);
        }
    } // end of doOpen()

    /**
     * Closes and drops the open UDP server socket.
     */
    @Override
    protected void doClose()
    {
        final AsyncDatagramSocket asyncSocket = mSocket;

        if (asyncSocket != null && asyncSocket.isOpen())
        {
            mSocket = null;
            asyncSocket.close();
        }
    } // end of doClose()

    /**
     * Closes the accepted UDP connection and sends a failed
     * UDP connect reply.
     * @param text explains the connect attempt failure.
     * @param channel accepted UDP connection.
     */
    @Override
    protected void doAcceptFailed(final String text,
                                  final SelectableChannel channel)
    {
        // Close the accepted channel first and then send the
        // UDP connect reply via the server UDP socket.
        try
        {
            channel.close();
        }
        catch (IOException ioex)
        {
            // Ignore exception when closing.
        }

        sendReply(ReplyStatus.ERROR, text, -1, mSourceAddress);
    } // end of doAcceptFailed(String, SelectableChannel)

    //
    // end of Abstract Method Implementations.
    //-----------------------------------------------------------

    //-----------------------------------------------------------
    // DatagramListener Interface Implementation.
    //

    /**
     * Creates a {@link ERemoteApp} to handle this new
     * connection if the connection passes the positive filter.
     * If not, the accepted client connection is immediately
     * closed.
     * @param buffer contains encoded eBus message.
     * @param address message received from this address.
     * @param socket message received on this UDP socket.
     */
    // java:S2095 - Resources should be closed.
    // Since this is an async channel the underlying socket is
    // closed at a later time.
    @SuppressWarnings({"java:S2095"})
    @Override
    public void handleInput(final ByteBuffer buffer,
                            final InetSocketAddress address,
                            final AbstractAsyncDatagramSocket socket)
    {
        final UdpConnectRequest request = decode(buffer);

        // Was a valid UDP connect request sent?
        if (request != null)
        {
            // Yes. Create a UDP channel to go along with it.
            try
            {
                final SocketAddress bindAddr =
                    new InetSocketAddress(ANY_PORT);
                final DatagramChannel channel =
                    DatagramChannel.open(
                        StandardProtocolFamily.INET6);

                // Have the datagram channel bind to any
                // available ephemeral port and wildcard address.
                channel.bind(bindAddr);
                channel.configureBlocking(false);

                sLogger.debug(
                    "UDP server {}: accepted connection from {}, new local UDP channel bound to {}.",
                    mAddress,
                    address,
                    channel.getLocalAddress());

                mSourceAddress = address;
                acceptConnection(address, channel);
            }
            catch (IOException ioex)
            {
                String message = ioex.getMessage();

                if (Strings.isNullOrEmpty(message))
                {
                    message = "(no reason given)";
                }

                sLogger.warn(
                    "Failed to open DatagramChannel: {}",
                    message,
                    ioex);

                sendReply(ReplyStatus.ERROR,
                          message,
                          -1,
                          address);
            }
        }
    } // end of handleInput(...)

    /**
     * Logs the given {@code Throwable} as a warning.
     * @param t error occurring on {@code socket}.
     * @param socket UDP datagram socket on which the error
     * occurred.
     */
    @Override
    public void handleError(final Throwable t,
                            final AbstractAsyncDatagramSocket socket)
    {
        sLogger.warn("{}: error on UDP server socket.",
                     socket.localSocketAddress(),
                     t);
    } // end of handleError(...)

    //
    // end of DatagramListener Interface Implementation.
    //-----------------------------------------------------------

    /**
     * Creates a {@link UdpConnectReply} from the parameters and
     * sends that message to {@code address}.
     * @param status connect attempt status.
     * @param text text explaining {@code status}.
     * @param address send the message to this address.
     */
    /* package */ void sendReply(final ReplyStatus status,
                                 final String text,
                                 final int localPort,
                                 final SocketAddress address)
    {
        final UdpConnectReply.Builder builder =
            UdpConnectReply.builder();
        final UdpConnectReply reply =
            builder.status(status)
                   .text(text)
                   .port(localPort)
                   .build();

        sLogger.trace("UDP Server {}: sending message to {}:\n{}",
                      mAddress,
                      address,
                      reply);

        ((UDPMessageWriter) mWriter).setTarget(reply, address);

        try
        {
            mSocket.send(mWriter);
        }
        catch (IOException ioex)
        {
            sLogger.warn(
                "UDP server {}: failed to send message:\n{}",
                mAddress,
                reply,
                ioex);
        }
    } // end of sendReply(...)

    /**
     * Reads in eBus message header from and to identifiers.
     * @param buffer extract eBus message header from this
     * buffer.
     */
    protected void decodeHeader(final ByteBuffer buffer)
    {
        // Skip the from and to identifiers.
        buffer.getInt();
        buffer.getInt();
    } // end of decodeHeader(ByteBuffer)

    /**
     * Returns the UDP connect request decoded from the given
     * buffer. Returns {@code null} if the message is not a UDP
     * connect request or the decode failed.
     * @param buffer contains encoded eBus message.
     * @param address message came from this remote eBus
     * application.
     * @return decoded eBus message.
     * @throws BufferUnderflowException
     * if {@code buffer} contains an incomplete UDP connect
     * request message.
     */
    private UdpConnectRequest decode(final ByteBuffer buffer)
    {
        final int size = buffer.getInt();
        final int keyId = (size < 0 ? -1: buffer.getInt());
        UdpConnectRequest retval = null;

        sLogger.trace("{}: received message, size={}, key ID={}.",
                      mSourceAddress,
                      size,
                      keyId);

        // Ignore heartbeat messages and non-UDP connect request
        // messages.
        if (keyId == UDP_CONNECT_REQUEST_KEY_ID)
        {
            // Skip the from and to identifiers.
            decodeHeader(buffer);

            retval =
                (UdpConnectRequest) sRequestType.deserialize(
                    ESystemMessage.SYSTEM_SUBJECT, buffer);

            if (sLogger.isTraceEnabled())
            {
                sLogger.trace(
                    "{}: handling UDPConnectRequest message:\n{}",
                    mSocket.localSocketAddress(),
                    retval);
            }
            else
            {
                sLogger.debug(
                    "{}: handling UDPConnectRequest message.",
                    mSocket.localSocketAddress());
            }
        }

        return (retval);
    } // end of decode(ByteBuffer)

//---------------------------------------------------------------
// Inner classes.
//

    /**
     * Used to encode a {@code UDPConnectReply} message directly
     * to a datagram output buffer.
     */
    protected static class UDPMessageWriter
        implements DatagramBufferWriter
    {
    //-----------------------------------------------------------
    // Member data.
    //

        //-------------------------------------------------------
        // Locals.
        //

        /**
         * UDP connect reply message to be serialized.
         */
        private EMessage mMessage;

        /**
         * Contains the destination address of the next outbound
         * UDP connect reply message. Set just prior to sending
         * the message.
         */
        private SocketAddress mDestination;

    //-----------------------------------------------------------
    // Member methods.
    //

        //-------------------------------------------------------
        // Constructors.
        //

        protected UDPMessageWriter()
        {}

        //
        // end of Constructors.
        //-------------------------------------------------------

        //-------------------------------------------------------
        // DatagramBufferWriter Interface Implementation.
        //

        /**
         * Encodes {@link #mMessage} to {@code buffer} including
         * the eBus message header.
         * @param buffer encode the message to this buffer.
         * @return the UDP packet destination.
         */
        @Override
        public SocketAddress fill(final ByteBuffer buffer)
        {
            // Skip over the size byte position and fill in last.
            buffer.position(EAbstractConnection.MESSAGE_SIZE_SIZE);
            fillHeader(buffer);
            sReplyType.serialize(mMessage, null, buffer);

            // Now write out the message size.
            buffer.putInt(0, buffer.position());

            return (mDestination);
        } // end of fill(ByteBuffer)

        //
        // end of DatagramBufferWriter Interface Implementation.
        //-------------------------------------------------------

        //-------------------------------------------------------
        // Set Methods.
        //

        /**
         * Sets the message and destination used by the
         * {@code fill(ByteBuffer)} method.
         * @param msg encode this message.
         * @param destination send the encoded message to this
         * destination.
         */
        private void setTarget(final EMessage msg,
                               final SocketAddress destination)
        {
            mMessage = msg;
            mDestination = destination;
        } // end of setTarget(EMessage, SocketAddress)

        //
        // end of Set Methdos.
        //-------------------------------------------------------

        /**
         * Fills in the message header. Note that the buffer
         * has already been positioned after the message size
         * field.
         * @param buffer serialize message header to this buffer.
         */
        protected void fillHeader(final ByteBuffer buffer)
        {
            buffer.putInt(UDP_CONNECT_REPLY_KEY_ID)
                  .putInt(EMessageHeader.NO_ID)
                  .putInt(EMessageHeader.NO_ID);
        } // end of fillHeader(ByteBuffer)
    } // end of class UDPMessageReader
} // end of class EUDPServer




© 2015 - 2024 Weber Informatics LLC | Privacy Policy